 TITLE  "Digital voltmeter"   	  
 LIST P=PIC12F508, R=DEC	
 INCLUDE "p12f508.inc"

 __CONFIG  _CP_OFF & _MCLRE_OFF & _WDT_OFF & _IntRC_OSC

#define	ENA 		GPIO,0		; MAX7221 interface: CS
#define	SDT			GPIO,1		;         data in
#define	SCK 		GPIO,2		; 		  clock	
#define SCL			5			; I2C clock pin setup
#define SDA			4			; I2C data pin setup
#define	SDA_in 		b'011000'	; I2C data line setup for input	
#define	SDA_out 	b'001000'	; I2C data line setup for output
#define	DP			0			; decimal point bit
#define ch2			5			; MCP3422 channel selection bit

 CBLOCK 0x07       				; data segment 
 del, cnt, tmp:2				; temporary variables   
 n:2, nn:2 						; storage for ADC output of MCP3422
 dat:2, dataIO					; data interface for MAX7221 and MCP3422 
 ACK, channel					
 thousands1, hundreds1, tens1, units1	; storage for BCD of positive supply
 thousands2, hundreds2, tens2, units2	; storage for BCD of negative supply
 ENDC					

 ORG 0							; code segment
	clrf	GPIO				; initialize the I/O port
	bsf		ENA					; disable writing to MAX7221
	bsf		GPIO, SCL			; set I2C clock line high
	movlw	SDA_in				; enable input on SDA pin
	tris	GPIO				; save changes in TRISIO
	movlw	b'11011111'			; enable GPIO:2 for output
	option						; TMR0 will be then clocked by FOSC
	call	init_MAX7221		; initialize the LED driver 
	goto	loop				; proceed with the main program loop

;//////////////// procedures
delay							; delay for the number of msec in del
	movlw	200					; set up delay counter
	movwf	cnt
	goto	$+1					; this inner loop takes 1us*5*200 = 1ms
	decfsz	cnt, f				; for PIC @ 4 MHz
	 goto 	$-2
	decfsz	del, f				; the outer loop counter		
	 goto	delay
	retlw	0

encode7seg ;  bdafgcep
	andlw	0x0F				; make sure that W < 16
	addwf	PCL, f
	retlw	b'11110110'			; code for 0
	retlw	b'10000100'			; code for 1
	retlw	b'11101010'			; code for 2
	retlw	b'11101100'			; code for 3
	retlw	b'10011100'			; code for 4
	retlw	b'01111100'			; code for 5
	retlw	b'01111110'			; code for 6
	retlw	b'10100100'			; code for 7
	retlw	b'11111110'			; code for 8
	retlw	b'11111100'			; code for 9
	retlw	b'00001000'			; code for -
	retlw	b'01011110'			; code for b
	retlw	b'01110010'			; code for C
	retlw	b'11001110'			; code for d
	retlw	b'01111010'			; code for E
	retlw	b'00111010'			; code for F

;;;;;;;;;;;;;;;;;;;;;;;;;;; interface with MAX7221
init_MAX7221
	clrf	tmp
	incf	tmp, f				; digits clear loop
	movf	tmp, w
	movwf	dat+1				; digit address
	clrf	dat
	call	write_MAX7221
	btfss	tmp, 3
	 goto	$-6

	movlw	0x09
	movwf	dat+1				; decode register
	clrf	dat					; disable decoding for all digits
	call	write_MAX7221
 
	movlw	0x0A
	movwf	dat+1				; intensity register
	movlw	15
	movwf	dat					; set max intensity 15/16
	call	write_MAX7221

	movlw	0x0B
	movwf	dat+1				; scan limit register
	movlw	7
	movwf	dat					; scan 8 digits
	call	write_MAX7221
	
	movlw	0x0C
	movwf	dat+1				; config register
	movlw	1					; normal operation
	movwf	dat					
	call	write_MAX7221

	movlw	0x0F
	movwf	dat+1				; display test register
	clrf	dat					; normal operation
	call	write_MAX7221
	retlw	0

write_MAX7221					; write a 16-bit word; first byte is reg address
	movlw	16					; bits counter
	movwf	cnt
	bcf		SCK					; make sure that SCK=0
	bcf		ENA					; enable the device
write_loop	
	rlf		dat, f
	rlf		dat+1, f			; shift out MSB to 
	btfsc	STATUS, C
	 bsf	SDT
	btfss	STATUS, C
	 bcf	SDT					; the data line is set up now
	bsf		SCK					; clock up
	nop							; short delay
	bcf		SCK					; ... and down
	decfsz	cnt, f				; all bits sent?
	 goto	write_loop			; not yet
	bsf		ENA					; yes - latch in the data and exit
	retlw	0	

;;;;;;;;;;;;;;;;;;;;;;; interface with MCP3422
requestData			
	call	start_I2C			; start communication
	movlw	b'11010000'			; MCP3422 address and write
	movwf	dataIO
	call	write_I2C			; write adress byte
	movlw	b'10000100'			; single conversion request
	addwf	channel, w			; select channel
	movwf	dataIO				; 14-bit, PGA=1
	call	write_I2C			; write configuration byte
	call	stop_I2C			; terminate communication
	retlw 	0

getData
	call	start_I2C			; start communication
	movlw	b'11010001'			; MCP3422 address and read
	movwf	dataIO
	call	write_I2C
	clrf	ACK					; setup for ACK signal
	call	read_I2C			; get MSB
	movf	dataIO, w
	btfss	channel, ch2
	 movwf	nn+1				; save it in nn+1
	btfsc	channel, ch2
	 movwf	n+1
	incf	ACK, f				; setup for NACK signal
	call	read_I2C			; get MSB
	movf	dataIO, w
	btfss	channel, ch2
	 movwf	nn					; save it in nn
	btfsc	channel, ch2
	 movwf	n
	call	stop_I2C			; terminate communication
	retlw	0 

;;;;;;;;;;;;;;;;;;;;;;; implementation of the I2C interface
;       ___        
; DATA:    |_______
;       ______    
; SCK :       |___
start_I2C						; start setup for I2C interface
	movlw	SDA_in
	tris	GPIO				; pull high SDA
	bsf		GPIO, SCL			; pull high SCL
	nop							; delay slot
	bcf		GPIO, SDA
	movlw	SDA_out
	tris	GPIO				; set data low
	goto	$+1					; wait for slave to detect it
	bcf		GPIO, SCL			; set clock low
	retlw	0
;              ___        
; DATA: ______|   
;           ______    
; SCK : ___|
stop_I2C						; stop setup for I2C interface
	bcf		GPIO, SCL			; make sure that clock and data
	bcf		GPIO, SDA
	movlw	SDA_out
	tris	GPIO				; are low
	goto	$+1
	bsf		GPIO, SCL			; release clock line
	nop
	movlw	SDA_in
	tris	GPIO				; release data line
	retlw	0

write_I2C						; write a byte to the I2C bus
	bcf		GPIO, SCL			; make sure that clock is low
	movlw	SDA_out
	tris	GPIO				; configure SDA line for output
	movlw	8
	movwf	cnt					; bits counter

w_loop
	bcf		GPIO, SDA			; preset 0 on data line
	rlf		dataIO, f			; get the rightmost data bit
	btfsc	STATUS, C
	 movlw	SDA_in				; preset data line high (data=1)
	btfss	STATUS, C
	 movlw	SDA_out				; preset data line low  (data=0)
	tris	GPIO				; configure the data pin
	bsf		GPIO, SCL			; raise clock up
	nop
	bcf		GPIO, SCL			; ... and down
	decfsz	cnt, f
	 goto	w_loop				; repeat this 8 times

	movlw	SDA_in				; release the data line
	tris	GPIO				; get ACK from slave		
	goto	$+1					; let slave respond
	bsf		GPIO, SCL			; clock up
	nop							; slave responds OK (hopefully)
	bcf		GPIO, SCL			; clock down
	retlw	0

read_I2C						; read byte from I2C bus
	bcf		GPIO, SCL			; make sure that clock is low		
	movlw	8					; we assume that data line is released
	movwf	cnt					; bits counter	

r_loop
	bsf		GPIO, SCL			; clock up
	bcf		STATUS, C
	btfsc	GPIO, SDA			; read data pin
	 bsf	STATUS, C			; C = data bit
	bcf		GPIO, SCL			; clock down
	rlf		dataIO, f			; insert it into the data byte
	decfsz	cnt, f
	 goto	r_loop				; repeat this 8 times

	movf	ACK, f				; set Z flag
	bcf		GPIO, SDA
	movlw	SDA_in
	btfsc	STATUS, Z
	 movlw	SDA_out				; ACK signal
	tris	GPIO
	bsf		GPIO, SCL			; clock up
	goto	$+1
	goto	$+1
	nop
	bcf		GPIO, SCL			; clock down
	movlw	SDA_in
	tris	GPIO				; release the data line
	retlw 	0

;//////////////// MAIN LOOP
loop
	movlw	200
	movwf	del
	call	delay				; 0.2 sec delay

	clrf	channel
	call	requestData			; request data from channel 1
	movlw	25
	movwf	del
	call	delay				; 25 msec ADC delay
	call	getData				; get ADC code for negative voltages 
	btfss	nn+1, 7				; ADC code must not be negative
	 goto	$+3					
	clrf	nn					; clear nn (2 bytes) if it is
	clrf	nn+1				; when no voltage is attached

	bsf		channel, ch2
	call	requestData			; request data from channel 2
	movlw	25
	movwf	del
	call	delay				; 25 msec ADC delay
	call	getData				; get ADC code for positive voltages
	btfss	n+1, 7
	 goto	$+3
	clrf	n					; set n=0 for negative n, e.g. for input
	clrf	n+1					; voltages close to 0V

	movlw	LOW 7562			; checking the range	
	subwf	nn, w				; ADC code for negative voltages should not
	movlw	HIGH 7562			; exceed 7261 (treated as unsigned)	
	movwf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	nn+1, w
	btfss	STATUS, C
	 goto	bin2BCD
	movlw	LOW	7561			; replace nn with its maximum value
	movwf	nn					; if needed, e.g. if no negative voltage is
	movlw	HIGH 7561			; connected
	movwf	nn+1				; in this case 0 will be shown on display

bin2BCD
	movf	n, w				; preprocessing the positive voltage code n
	movwf	tmp					; n is at most 13-bit value
	movf	n+1, w
	movwf	tmp+1				; copy n in tmp

	bcf		STATUS, C
	rlf		n, f
	rlf		n+1, f				; n = 2*n

	movf	tmp, w
	addwf	n, f
	movf	tmp+1, w
	btfsc	STATUS, C
	 incf	tmp+1, w
	addwf	n+1, f				; n = 3*n

	movlw	5					; add rounding constant
	addwf	n, f
	btfsc	STATUS, C
	 incf	n+1, f				; n is ready for BCD conversion

	movf	nn, w				; preprocessing the negative voltage code nn
	movwf	tmp					; nn is at most 13-bit value
	movf	nn+1, w
	movwf	tmp+1				; copy nn in tmp

	bcf		STATUS, C
	rlf		nn, f
	rlf		nn+1, f				; nn = 2*nn

	movf	tmp, w
	addwf	nn, f
	movf	tmp+1, w
	btfsc	STATUS, C
	 incf	tmp+1, w
	addwf	nn+1, f				; nn = 3*nn

	bcf		STATUS, C
	rrf		tmp+1, f
	rrf		tmp, f
	bcf		STATUS, C
	rrf		tmp+1, f
	rrf		tmp, f				; tmp = [nn / 4]
	btfss	STATUS, C			
	 goto	$+4
	incf	tmp, f				; rounding off
	btfsc	STATUS, Z
	 incf	tmp+1, f

	movf	nn, w				; computing tmp += nn
	addwf	tmp, f
	movf	nn+1, w
	btfsc	STATUS, C
	 incf	nn+1, w
	addwf	tmp+1, f			; tmp = 13*nn/4	

	movlw	LOW 24576			
	movwf	nn
	movlw	HIGH 24576
	movwf	nn+1				; nn = 24576

	movf 	tmp, w				; computing nn = nn - tmp
	subwf	nn, f
	movf	tmp+1, w
	btfss	STATUS, C
	 incf	tmp+1, w
	subwf	nn+1, f

	movlw	5					; add rounding constant
	addwf	nn, f
	btfsc	STATUS, C
	 incf	nn+1, f				; nn is ready for BCD conversion

	movlw	0xFF
	movwf	thousands1
	movwf	thousands2
	movwf	hundreds1
	movwf	hundreds2
	movwf	tens1
	movwf	tens2
	movwf	units1
	movwf	units2

l1	movlw	LOW 10000			; 16-bit subtract
	subwf	n, f		
	movlw	HIGH 10000	
	movwf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+1, f
	incf	thousands1, f
	btfsc	STATUS, C
	 goto	l1

	movlw	LOW 10000			; restore n
	addwf	n, f
	movlw	HIGH 10000
	btfsc	STATUS, C
	 movlw	(HIGH 10000) + 1
	addwf	n+1, f

l2	movlw	LOW 1000			; 16-bit subtract
	subwf	n, f		
	movlw	HIGH 1000	
	movwf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+1, f
	incf	hundreds1, f
	btfsc	STATUS, C
	 goto	l2

	movlw	LOW 1000			; restore n
	addwf	n, f
	movlw	HIGH 1000
	btfsc	STATUS, C
	 movlw	(HIGH 1000) + 1
	addwf	n+1, f

l3	movlw	100					; 16-bit subtract
	subwf	n, f
	clrw		
	clrf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+1, f
	incf	tens1, f
	btfsc	STATUS, C
	 goto	l3

	movlw	100					; restore n
	addwf	n, f

l4	movlw	10
	subwf	n, f
	incf	units1, f
	btfsc	STATUS, C
	 goto	l4

l5	movlw	LOW 10000			; processing negative voltages
	subwf	nn, f		
	movlw	HIGH 10000	
	movwf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	nn+1, f
	incf	thousands2, f
	btfsc	STATUS, C
	 goto	l5

	movlw	LOW 10000			; restore nn
	addwf	nn, f
	movlw	HIGH 10000
	btfsc	STATUS, C
	 movlw	(HIGH 10000) + 1
	addwf	nn+1, f

l6	movlw	LOW 1000			; 16-bit subtract
	subwf	nn, f		
	movlw	HIGH 1000	
	movwf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	nn+1, f
	incf	hundreds2, f
	btfsc	STATUS, C
	 goto	l6

	movlw	LOW 1000			; restore nn
	addwf	nn, f
	movlw	HIGH 1000
	btfsc	STATUS, C
	 movlw	(HIGH 1000) + 1
	addwf	nn+1, f

l7	movlw	100					; 16-bit subtract
	subwf	nn, f
	clrw		
	clrf	tmp
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	nn+1, f
	incf	tens2, f
	btfsc	STATUS, C
	 goto	l7

	movlw	100					; restore nn
	addwf	nn, f

l8	movlw	10
	subwf	nn, f
	incf	units2, f
	btfsc	STATUS, C
	 goto	l8

display							; display ADC data in HEX
	movlw	1					; digit address
	movwf	dat+1				; (negative voltages)
	movf	thousands2, w
;	btfss	STATUS, Z			; drop leading 0 
;	 call	encode7seg
	btfsc	STATUS, Z			; display "-" sign
	 movlw	10
	call	encode7seg
	movwf	dat
	call	write_MAX7221

	movlw	3					; digit address
	movwf	dat+1
	movf	hundreds2, w
	call	encode7seg
	movwf	dat
	bsf		dat, DP
	call	write_MAX7221

	movlw	8					; digit address
	movwf	dat+1
	movf	tens2, w
	call	encode7seg
	movwf	dat
	call	write_MAX7221

	movlw	6					; digit address
	movwf	dat+1
	movf	units2, w
	call	encode7seg
	movwf	dat
	call	write_MAX7221

	movlw	2					; SECOND NUMBER digit address
	movwf	dat+1				; (positive voltages)
	movf	thousands1, w
	btfss	STATUS, Z
	 call	encode7seg
	movwf	dat
	call	write_MAX7221

	movlw	4					; digit address
	movwf	dat+1
	movf	hundreds1, w
	call	encode7seg
	movwf	dat
	bsf		dat, DP
	call	write_MAX7221

	movlw	7					; digit address
	movwf	dat+1
	movf	tens1, w
	call	encode7seg
	movwf	dat
	call	write_MAX7221

	movlw	5					; digit address
	movwf	dat+1
	movf	units1, w			
	call	encode7seg
	movwf	dat
	call	write_MAX7221
  	goto 	loop				; endless loop

 END
