;------------------------------------------------------------------------
; Digital Thermometer [TC74 + PIC 16F84] schematic and code
; by Michal Krepa, 2004
;
; email: rtl2gds@opDELETEIT.pl
;
; please remove DELETEIT from e-mail
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; I^2C master send/receive routines
; by Andrew D. Vassallo
;
; email: snurple@hotmail.com
;
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; ZERO-ERROR ONE SECOND TIMER
; Roman Black, 2001
;
; Note! See text: www.ezy.net.au/~fastvid/one_sec.htm
; 
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; BIN2BCD (8 bit binary to BCD conversion subroutine)
; by DH MicroSystems, Inc.
; WWW.DHMICRO.COM
;
;------------------------------------------------------------------------
; 
; Timing set for 4MHz clock
;
;------------------------------------------------------------------------

 		LIST P=16F84A           ;  tells which processor is used
        INCLUDE "p16f84a.inc"   ;  defines various registers etc. Look it over.
        ERRORLEVEL -224        ;  supress annoying message because of tris
        __CONFIG _PWRTE_ON & _XT_OSC & _WDT_OFF   ;  configuration switches

	CBLOCK 0x20
        bres_hi			; hi byte of our 24bit variable
		bres_mid		; mid byte
		bres_lo			; lo byte
						; (we only need 3 bytes for this system)
		status_temp		; used for interrupt servicing
		w_temp			; used for interrupt servicing

		GenCount	; generic counter/temp register (use with caution)
		Mem_Loc		; TC74 command to be executed
		Data_Buf	; byte read from TC74 is stored in this register
		OutputByte	; used for holding byte to be output to TC74
		flags		; flag bit register
		dlycount	; for delay loop usage
		temper		; temperature reading storage, mean of 4 readings
		ONE			; BIN2BCD reg
		TEN			; BIN2BCD reg
		HUND		; BIN2BCD reg
		TE1			; 3rd before actual \
		TE2			; 2nd before actual - temperature readings
		TE3			; 1st before actual /
		Divider
		Dividend
		Result
		Remainder
		Counter

	ENDC 


;------ Define port pins: RA4=SDA=data, RA3=SCL=clock
#define	SDA	TRISA, 4
#define	SCL	TRISA, 3
#define ONESEN PORTA, 0		; ones digit enable in 7seg display [active low]
#define TENSEN PORTA, 1		; tens digit enable in 7seg display [active low]
#define tc_waddr 0x90 ;		; TC74 write address
#define tc_raddr 0x91 ;		; TC74 read address
#define RTR 0x00			; Read Temperature register in TC74
#define RWCR 0x01			; Control Register in TC74
#define standby 7			; standby bit in RWCR
#define dataready 6			; data ready bit in RWCR

	org 0x000
	goto	Main			; goto program startup routine
	org 0x004				; Interrupt vector, int handler code comes next.
;******************************************************************************
;  INTERRUPT HANDLER     (runs this code each timer0 interrupt)
;******************************************************************************
;
;------------------
int_handler				
;------------------

	;-------------------------------------------------
							; first we preserve w and status register
	movwf 	w_temp      	; save off current W register contents
	movf	STATUS,w        ; move status register into W register
	movwf 	status_temp     ; save off contents of STATUS register

	;-------------------------------------------------
	; Note! we get here every 256 instructions, we
	; can now do our special one second timing system.				

	; This consists of three main steps;
	; * subtract 256 counts from our 24bit variable
	; * test if we reached the setpoint
	; * if so, add 1,000,000 counts to 24bit variable and generate event.
	;-------------------------------------------------
						; * optimised 24 bit subtract here 
						; This is done with the minimum instructions.
						; We subtract 256 from the 24bit variable
						; by just decrementing the mid byte.

	tstf bres_mid		; first test for mid==0
	skpnz				; nz = no underflow needed
	decf bres_hi,f		; z, so is underflow, so dec the msb

	decfsz bres_mid,f	; dec the mid byte (subtract 256)

						; now the full 24bit optimised subtract is done!
						; this is about 4 times faster than a "proper"
						; 24bit subtract.

	goto int_exit		; nz, so definitely not one second yet.
						; in most cases the entire 'fake" int takes
						; only 9 instructions.
	;------------------------
						; * test if we have reached one second.
						; only gets here when mid==0, it MAY be one second.
						; only gets to here 1 in every 256 times.
						; (this is our best optimised test)
						; it gets here when bres_mid ==0.

	tstf bres_hi		; test hi for zero too
	skpz				; z = both hi and mid are zero, is one second!
	goto int_exit		; nz, so not one second yet.

	;-------------------------------------------------
	; Only gets to here if we have reached one second.

	; now we can generate our one second event, like add
	; one second to our clock or whatever.
	; (in this example we toggle a led)

	; The other thing we need to do is add 1,000,000 counts
	; to our 24bit variable and start all over again.
	;-------------------------------------------------
						; Add the 1,000,000 counts first.
						; One second = 1,000,000 = 0F 42 40 (in hex)

						; As we know hi==0 and mid==0 this makes it very fast.
						; This is an optimised 24bit add, because we can
						; just load the top two bytes and only need to do
						; a real add on the bottom byte. This is much quicker
						; than a "proper" 24bit add.

	movlw 0x0F			; get msb value 
	movwf bres_hi		; load in msb

	movlw 0x42			; get mid value
	movwf bres_mid		; load in mid

	movlw 0x40			; lsb value to add
	addwf bres_lo,f		; add it to the remainder already in lsb
	skpnc				; nc = no overflow, so mid is still ok

	incf bres_mid,f		; c, so lsb overflowed, so inc mid
						; this is optimised and relies on mid being known
						; and that mid won't overflow from one inc.

						; that's it! Our optimised 24bit add is done,
						; this is roughly twice as quick as a "proper"
						; 24bit add.
	;-------------------------
						; now we do the "event" that we do every one second.

	;movfw	TE2			; T1 = T2
	;movwf	TE1	
	;movfw	TE3			; T2 = T3
	;movwf	TE2			
	;movfw	temper		; T3 = temper
	;movwf	TE3
	
	; temperature read from TC74
	movlw	RTR			; set i2c command to RTR address 
	movwf	Mem_Loc		; store it in Mem_Loc
	call	i2c_read	; i2c read
	movfw	Data_Buf	; data read is in Data_Buf
	movwf	temper		; save temperature reading in temper

	;movfw	TE1			; W = TE1
	;addwf	TE2,0		; W = W+TE2
	;addwf	TE3,0		; W = W+TE3
	;addwf	temper,f	; temper = W+temper
	;rrf		temper,f 	; divide temper by 4	
	;rrf		temper,f
	;movlw	0x3F		; clear 2 msb's if needed
	
	;andwf	temper,f
	;movlw	0x04
	;movwf	Divider
	;movfw	temper
	;movwf	Dividend
	;call	divide
	;movfw	Result
	;movwf	temper		
						
	;-------------------------------------------------
	; now our one second event is all done, we can exit the
	; interrupt handler.
	;-------------------------------------------------
								; finally we restore w and status registers.
								; also clears TMRO int flag now we are finished.
int_exit
	BCF 	INTCON,T0IF			; reset the tmr0 interrupt flag

	movf 	status_temp,w     	; retrieve copy of STATUS register
	movwf 	STATUS            	; restore pre-isr STATUS register contents
	swapf 	w_temp,f
	swapf 	w_temp,w          	; restore pre-isr W register contents
	retfie						; return from interrupt
;------------------------------------------------------------------------------


;------ This routine uses "random addressing.
;------ This code has been optimized for speed at 4MHz, assuming Temperature is between -40 and +85 deg. C.
; address is tc_raddr
; Call with: I2C command in Mem_Loc
; Returns with: byte in Data_Buf
i2c_read
		bcf		STATUS, RP0
		movf	PORTA, 0		; for TC74 operation,
		andlw	0x07			; load zero into RA3 and RA4
		movwf	PORTA			; for passive control of bus
		bsf		STATUS, RP0		; select Bank 1 for TRISA access (passive SCL/SDA control)
		bsf		SDA				; let SDA line get pulled high
		bsf		SCL				; let SCL line get pulled high
		bcf		SDA				; START - data line low
		movlw	tc_waddr		; send TC74 address
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine	; NOTE: MUST USE "RETURN" FROM THERE
		movf	Mem_Loc, 0
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		bcf		SCL				; pull clock line low in preparation for 2nd START bit
		nop
		bsf		SDA				; pull data line high - data transition during clock low
		bsf		SCL				; pull clock line high to begin generating START
		bcf		SDA				; 2nd START - data line low
		movlw	tc_raddr		; request data read from TC74
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
;------ Note that Byte_Out leaves with SDA line freed to allow slave to send data in to master.
		call	Byte_In
		movf	Data_Buf, 0		; put result into W register for returning to CALL
		bcf		SCL				; extra cycle for SDA line to be freed from TC74
		nop
		bcf		SDA				; ensure SDA line low before generating STOP
		bsf		SCL				; pull clock high for STOP
		bsf		SDA				; STOP - data line high
		bcf		STATUS, RP0		; leave with Bank 0 active as default
		return

;------ Save each byte as it's written (not page write mode).
;
; Call with:  EEPROM address [command] in Mem_Loc, byte to be sent in Data_Buf
; Returns with:  nothing returned
i2c_write
		bcf		STATUS, RP0
		movf	PORTA, 0		; for TC74 operation,
		andlw	0x07			; load zero into RA3 and RA4
		movwf	PORTA			; for passive control of bus
		bsf		STATUS, RP0		; select Bank 1 for TRISA access (passive SCL/SDA control)
		bsf		SDA				; ensure SDA line is high
		bsf		SCL				; pull clock high
		bcf		SDA				; START - data line low
		movlw	tc_waddr		; send write  to set address
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine	; NOTE: MUST USE "RETURN" FROM THERE
		movf	Mem_Loc, 0
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		movf	Data_Buf, 0		; move data to be sent to W
		call	Byte_Out
		btfsc	flags, 0
		goto	Error_Routine
		bcf		SCL				; extra cycle for SDA line to be freed from TC74
		nop
		bcf		SDA				; ensure SDA line low before generating STOP
		bsf		SCL				; pull clock high for STOP
		bsf		SDA				; STOP - data line high
		;call	Delay10ms		; 10ms delay max. required for EPROM write cycle
		bcf		STATUS, RP0		; leave with Bank 0 active by default
		return

;------ This routine reads one byte of data from the EPROM into Data_Buf
Byte_In
		clrf	Data_Buf
		movlw	0x08			; 8 bits to receive
		movwf	GenCount
ControlIn
		rlf		Data_Buf, 1		; shift bits into buffer
		bcf		SCL				; pull clock line low
		nop
		bsf		SCL				; pull clock high to read bit
		bcf		STATUS, RP0		; select Bank 0 to read PORTA bits directly!
		btfss	SDA				; test bit from TC74 (if bit=clear, skip because Data_Buf is clear)
		goto	$+3
		bsf		STATUS, RP0		; select Bank 1 to access variables
		bsf		Data_Buf, 0		; read bit into 0 first, then eventually shift to 7
		bsf		STATUS, RP0		; select Bank 1 to access variables
		decfsz	GenCount, 1
		goto	ControlIn
		return

;------ This routine sends out the byte in the W register and then waits for ACK from EPROM (256us timeout period)
Byte_Out
		movwf	OutputByte
		movlw	0x08			; 8 bits to send
		movwf	GenCount
		rrf		OutputByte, 1	; shift right in preparation for next loop
ControlOut
		rlf		OutputByte, 1	; shift bits out of buffer
		bcf		SCL				; pull clock line low
		nop
		btfsc	OutputByte, 7	; send current "bit 7"
		goto	BitHigh
		bcf		SDA
		goto	ClockOut
BitHigh		
		bsf		SDA
ClockOut	
		bsf		SCL				; pull clock high after sending bit
		decfsz	GenCount, 1
		goto	ControlOut
		bcf		SCL				; pull clock low for ACK change
		bsf		SDA				; free up SDA line for slave to generate ACK
		nop
		nop
		nop						; wait for slave to pull down ACK
		bsf		SCL				; pull clock high for ACK read
		clrf	GenCount		; reuse this register as a timeout counter (to 256us) to test for ACK
WaitForACK
		bsf		STATUS, RP0		; select Bank1 for GenCount access
		incf	GenCount, 1		; increase timeout counter each time ACK is not received
		btfsc	STATUS, Z
		goto	No_ACK_Rec
		bcf		STATUS, RP0		; select Bank0 to test SDA PORTA input directly!
		btfsc	SDA				; test pin. If clear, TC74 is pulling SDA low for ACK
		goto	WaitForACK		; ...otherwise, continue to wait
		bsf		STATUS, RP0		; select Bank1 as default during these routines
		bcf		flags, 0		; clear flag bit (ACK received)
		return


;------ No ACK received from slave (must use "return" from here)
;; Typically, set a flag bit to indicate failed write and check for it upon return.
No_ACK_Rec
		bsf		flags, 0		; set flag bit
		return					; returns to Byte_Out routine (Bank 1 selected)


;------ No ACK received from slave.  This is the error handler.
Error_Routine
; Output error message, etc. here
		bcf		STATUS, RP0		; select Bank0 as default before returning home
		return					; returns to INITIAL calling routine

Delay10ms
	movlw		0x0A
	movwf		GenCount
Delay_Start
	nop
	movlw 		0x07			; 249 cycles * 4us per cycle + 5us = 1.000ms
Delay
	addlw 		0x01
	btfss 		STATUS, Z
	goto 		Delay
	decfsz 		GenCount, 1
	goto 		Delay_Start
	return


;-----------------------------------------------------------------------;
;  Here is a subroutine: delays number of millisec in 'W' on entry      ;
;-----------------------------------------------------------------------;
nmsec:
         movwf dlycount       ; save 'W' in a register of our own
dlyloop: 
         nop                  ; each nop is 0.122 milliseconds
         nop
         nop                  ; each total loop is 8 X 0.122 = 0.976 msec
         nop
         nop
         decfsz dlycount, f   ; this is 0.122 msec if skip not taken
         goto dlyloop         ; this is  2 X 0.122 msec  
         return               ; back to calling point

; normal 0-9 digit codes for 7 Segment display
; call with number to display in W reg
bin2seg	
    addwf	PCL, F	; Jump into the lookup table
	retlw	H'C0'	; Return segment code for 0
	retlw	H'F9'	; Return segment code for 1
	retlw	H'A4'	; Return segment code for 2
	retlw	H'B0'	; Return segment code for 3
	retlw	H'99'	; Return segment code for 4
	retlw	H'92'	; Return segment code for 5
	retlw	H'82'	; Return segment code for 6
	retlw	H'F8'	; Return segment code for 7	
	retlw	H'80'	; Return segment code for 8
	retlw	H'90'	; Return segment code for 9

; -7 to 7 digit codes for 7 Segment display
bin2seg7
    addwf	PCL, F	; Jump into the lookup table
	retlw	H'C0'	; Return segment code for 0
	retlw	H'F9'	; Return segment code for 1
	retlw	H'A4'	; Return segment code for 2
	retlw	H'B0'	; Return segment code for 3
	retlw	H'99'	; Return segment code for 4
	retlw	H'92'	; Return segment code for 5
	retlw	H'82'	; Return segment code for 6
	retlw	H'F8'	; Return segment code for 7	
	retlw	b'01000000' ; -0
	retlw	b'01111001' ; -1
	retlw	b'00100100' ; -2
	retlw	b'00110000' ; -3
	retlw	b'00011001' ; -4
	retlw	b'00010010'	; -5
	retlw	b'00000010'	; -6
	retlw	b'01111000'	; -7		

;*****************************************************************
;       BIN2BCD (8 bit binary to BCD conversion subroutine)
;
;       This routine converts the 8 bit number in W to its BCD
;	equivalent with values 0 - 9 stored in HUND, TEN, & ONE
;	registers.
;
;	Usefull when binary data is to be converted into a user
;	readable format (use BCD2ASC to convert BCD values to
;	ASCII characters for display on a LCD or for serial 
;	transmission)
;
;	Input: 8 bit binary number in W register
;	Output: BCD values in HUND, TEN, & ONE registers
;
;	Requires: HUND, TEN, & ONE registers defined. 
;
;	Executed by:
;
;	MOVF	[REGISTER NAME],W
;	CALL	BIN2BCD
;
;	or
;
;	MOVLW	0xNN
;	CALL	BIN2BCD
;
;	DH MicroSystems, Inc.
;	WWW.DHMICRO.COM
;
;*****************************************************************
BIN2BCD	
	MOVWF	ONE
	CLRF	TEN
	CLRF	HUND		; init registers

H1	
	MOVLW	0x64		; 100
	SUBWF	ONE,F		; subtract 100 from number
	BTFSC	STATUS,C	; negative result?
	GOTO	H2			; no? goto H2
	GOTO	H3			; yes? goto H3 (100's done)
H2	INCF	HUND,F		; increment HUND register
	GOTO	H1			; & loop back for another test
H3	MOVLW	0x64		; 100
	ADDWF	ONE,F		; add 100 back to number

T1	MOVLW	0x0A		; 10
	SUBWF	ONE,F		; subtract 10 from number
	BTFSC	STATUS,C	; negative result?
	GOTO	T2			; no? goto T2
	GOTO	T3			; yes? goto T3 (10's done)
T2	INCF	TEN,F		; increment TEN register
	GOTO	T1			; & loop back for another test
T3	MOVLW	0x0A		; 10
	ADDWF	ONE,F		; add 10 back to number

	RETLW	0

; PalPac Software and Electronics
; Written by Louis Christodoulou
; 2001-03-04
; This is an integer (signed) division routine for 2 eight bit numbers and the answer is 
; stored in 8 bits in register Result. The remainder part can be found in the Remainder 
; register. The dividend and the divider are placed in file registers 0x10 and 0x11 
; respectively. The two numbers used here are b'00001010'(10) and b'01100100'(100) which
; should display b'0001010'(10) and in hex 0xA.

divide	
		movlw	8				; load counter parameter
		movwf	Counter			; store it
		clrf	Result			; clear the result
		clrf	Remainder		; clear the remainder
		bcf		STATUS,C		; clear the carry flag
process	
		rlf		Dividend,1		; rotate bit into carry
		rlf 	Remainder,1		; rotate carry into remainder file
		movf	Divider,w		; load the divider
		subwf	Remainder,w		; subtract it from remainder file store in w
		rlf		Result,1		; rotate carry into result (1) pos (0) neg
		btfsc	Result,0		; check if last calculation was positive
		goto 	calc_remainder	; 
countdown	
		decfsz	Counter,1		; coundown 8 bits
		goto 	process			; carry on
forever		
		retlw	0
calc_remainder	
		movwf	Remainder		; store the remainder in Remainder file
		goto	countdown		; carry on


;///////////////// Main routine //////////////////////////////////////////////
Main
    bsf 	STATUS,RP0
    movlw 	0x00
    tris 	PORTB 		; set portb(7:0) as outputs
	tris 	PORTA		; set porta as outputs
	bcf 	STATUS,RP0
						; OPTION setup
	movlw 	b'10001000'	;
		;  x-------		; 7, 0=enable, 1=disable, portb pullups
		;  -x------		; 6, 1=/, int edge select bit
		;  --x-----		; 5, timer0 source, 0=internal clock, 1=ext pin.
		;  ---x----		; 4, timer0 ext edge, 1=\
		;  ----x---		; 3, prescaler assign, 1=wdt, 0=timer0
		;  -----x--		; 2,1,0, timer0 prescaler rate select
		;  ------x-		;   000=2, 001=4, 010=8, 011=16, etc.
		;  -------x		; 
						; Note! We set the prescaler to the wdt, so timer0
						; has NO prescaler and will overflow every 256 
						; instructions and make an interrupt.
						;
	banksel OPTION_REG	; go proper reg bank
	movwf 	OPTION_REG	; load data into OPTION_REG
	banksel 0			; back to normal bank 0

						; INTCON setup
						;
						; for this code example, we enable the timer0
						; overflow interrupt.
						;
						; enable interrupts last
						; interrupt setup
	movlw 	b'10100000'	; GIE=on TOIE=on (timer0 overflow int)
	movwf 	INTCON		; set up.

	;-------------------------------------------------
	; Note! Now the hardware is set up we need to load the
	; first count for one second into our 24bit bres variable.
	;-------------------------------------------------
						; Note! This example uses 4 MHz clock, which is
						; 1,000,000 counts per second.
						;
						; We require a 1 second period, so we must load
						; 1,000,000 counts each time.
						; 1,000,000 = 0F 42 40 (in hex)
						;
						; We also need to add 256 counts for the first time,
						; so we just add 1 to the mid byte.
						; Check mid overflow if needed.

						; here we load the 24bit variable.
	movlw 	0x0F		; get msb value 0x0f
	movwf 	bres_hi		; put in hi

	movlw 	0x42+1		; get mid value (note we added 1 to it)
	movwf 	bres_mid	; put in mid 0x42

	movlw	0x40		; get lsb value
	movwf 	bres_lo		; put in mid 0x40

ReadyOrNot				; read control register and check if TC74 is ready
	movlw	RWCR		; set i2c command to RWCR reg of TC74
    movwf 	Mem_Loc		; save in command variable
	call 	i2c_read	; i2c read
    btfss 	Data_Buf, dataready ; is temperature reading valid?
    goto 	ReadyOrNot	; continue checking
Display					; Display temperature
	MOVFW	temper		; get mean of last four temperature readings
	andlw	0x7F		; clear sign bit (for minus temperatures)
	CALL	BIN2BCD		; convert binary to bcd, result in ONE, TEN

	movfw	ONE			; move ONE value to W
	andlw	0x0F		; clear upper nibble just for sure
	call	bin2seg		; convert ones digit to 7 segment display format [0 to 9]
	movwf	PORTB		; send digit code to display
	bcf		ONESEN		; display ones digit
	movlw	0x01		; wait for 1 ms
	call	nmsec
	bsf		ONESEN		; stop displaying digit

	movfw	TEN			; move TEN value to W
	btfsc	temper,7	; check if it is minus temperature
	addlw	b'00001000'	; if yes light left decimal point on 7SEG tens digit
	andlw	0x0F		; clear upper nibble just for sure
	call	bin2seg7	; convert tens digit to 7 segment display format [-7 to +7]
	movwf	PORTB		; send digit code to display
	bcf		TENSEN		; turn on tens digit
	movlw	0x01		; wait for 1 ms
	call	nmsec
	bsf		TENSEN		; turn off tens digit
	goto	Display		; all again
	
	end
