;------------------------------------------------------------------------------------------------------------------------
; Source code for the PIC16F627 based Low Cost CPU Fan Speed Tester.
;------------------------------------------------------------------------------------------------------------------------
;
;	**Fan Tachometer
;	TMR0 is the counter for the fan clock pulses.
;	TMR1 is the timer to time every 0.5 seconds.
;	
;	**Output to CD4511
;	RB0 - RB3 for digits.
;	RB4	- RB6 for 7 segment selector.
;
;	RB4 - 2nd digit from the right. (Numbers in RPM)
;	RB5	- 3rd digit from the right.
;	RB6	- 4th digit from the right.
;	RB7 - 1st digit from the right. (Random number XD)
;
;	Instruction Cycle Time = 1 / (4MHz / 4) = 1us per instruction
;------------------------------------------------------------------------------------------------------------------------

		LIST P=16F627
		INCLUDE "p16f627.inc"   
;		ERRORLEVEL -302        
		__CONFIG _PWRTE_OFF & _HS_OSC & _WDT_OFF & _LVP_OFF & _BODEN_OFF;  configuration switches

		CBlock 0x20
		rmng_num		; Digit breaker registers.
		temp_num
		quotient

		N				; Delay registers.
		FIXDELAY

		digitfan0		; Individual digit registers.
		digitfan1
		digitfan2
		digitfan3
		prevdigitfan0

		multiplier		; Manipulator storage registers.
		adder
		passer
		pulsetotal
		rdmhold
		prevfreq
		prevpulse
		EndC

		org 0x00
		nop
		goto start

		org 0x04
		goto ISR		; Interrupt Vector.

start	call initports
		call initTMRnINT

awaitint	call displayfanspd
			movf TMR1L, w			; Generate random number.
			movwf rdmhold
			movlw 0x0F
			andwf rdmhold, f
			goto awaitint			; Wait for interrupt to happen.
		
;------------------------------------------------------------------------------------------------------------------------
; Subroutine to initialize the PORTs as Inputs or Outputs.
;------------------------------------------------------------------------------------------------------------------------		

initports
			banksel PORTB
			clrf PORTB
		
			banksel TRISB		; Select TRISB bank.
			movlw 0x00			; Define PORTB as Outputs.
			movwf TRISB

			return

;------------------------------------------------------------------------------------------------------------------------
; Initialize Timer and Interrupts Subroutine.
;------------------------------------------------------------------------------------------------------------------------

initTMRnINT
			banksel INTCON		; Enable all unmasked interrupts and peripheral interrupts.
			movlw b'11000000'
			movwf INTCON

			banksel PIE1		; Enable TMR1 overflow interrupt.
			movlw b'00000001'
			movwf PIE1

 			banksel OPTION_REG	; 1:1 Prescaler to TMR0, rising edge of the clock, Clock = RA4 Transition, TMR0 as counter.
			movlw b'11101000'
			movwf OPTION_REG

			banksel T1CON		; 1:8 Prescalar to TMR1, osc off, internal Fosc/4 clock, Enable TMR1.
			movlw b'00110001'
			movwf T1CON

			return

;------------------------------------------------------------------------------------------------------------------------
; Interrupt Service Routine. (ISR)
;------------------------------------------------------------------------------------------------------------------------

ISR
			btfss PIR1, TMR1IF		; Check if TMR1 overflowed.
			goto intdone			; If no, return from interrupt. Else, service interrupts.
beginISR	bcf T1CON, TMR1ON		; Disable TMR1.
			bcf PIR1, TMR1IF		; Clear TMR1 interrupt flag.
			movf TMR0, w			

			movwf prevfreq			; Pass TMR0 value to pulse counters. 
			movwf rmng_num
			movwf pulsetotal

			movf prevpulse, w		; Adds previous fan speed value to current fan speed value and average the two.
			addwf rmng_num, f
			bcf STATUS, C			; Divides summed value by 2.
			rrf rmng_num, f
			
			call breakdigitfanspd	; Break pulse total to individual digits.
			call convertrpm			; Convert Hertz to RPM.
			movf prevfreq, w		; Pass previous counted frequency to prevpulse.
			movwf prevpulse
			clrf TMR1L				; Clear all timers.
			clrf TMR1H
			clrf TMR0
			bsf T1CON, TMR1ON		; Re-enable TMR1.
intdone		retfie

;------------------------------------------------------------------------------------------------------------------------
; N DELAY SUBROUTINE, delay in multiples of 200us up to 200us*255 = 51ms (or more)
;------------------------------------------------------------------------------------------------------------------------

NDELAY
			MOVWF N				; N is delay multiplier
NOTOVER		CALL DELAY200		; Call for 200us
			DECFSZ N, 1			; Decrease N by 1
			GOTO NOTOVER		; The delay isn't done
			RETURN
	
;------------------------------------------------------------------------------------------------------------------------
; FIXED 200us DELAY (Possibly more due to execution time of the DECFSZ instruction.)
;------------------------------------------------------------------------------------------------------------------------

DELAY200	
			MOVLW 0x42			; 66 LOOPS
			MOVWF FIXDELAY		; 200us fixed delay
NOTDONE200	DECFSZ FIXDELAY, 1 	; Decrement of FIXDELAY
			GOTO NOTDONE200		; If 200us isn't up go back to NOTDONE200
			RETURN				; If 200us is up then return to instruction.

;------------------------------------------------------------------------------------------------------------------------
; Breaks down the temperature reading to its individual digits.
;------------------------------------------------------------------------------------------------------------------------

get_dig
			movlw d'10'			; To split the digits, divide them by 10.
			incf quotient, f	; Increment of quotient with each subtraction by 10.
			subwf rmng_num, f	; Subtract the number by 10.
			skpnc				; If already negative, stop division.
			goto get_dig		; Else, continue dividing.
			addwf rmng_num, f	; Restore number.
			decf quotient, f	; Restore quotient.

			movf rmng_num, w	; Move rmng_num to temp_num.
			movwf temp_num
			movf quotient, w	; Move quotient to rmng_num.
			movwf rmng_num
			movf temp_num, w

			return

;------------------------------------------------------------------------------------------------------------------------
; Break down fanspeed to individual digits.
;------------------------------------------------------------------------------------------------------------------------

breakdigitfanspd

			clrf quotient
			call get_dig		; Get 1st digit.
			movwf digitfan0

			clrf quotient	
			call get_dig		; Get 2nd digit.
			movwf digitfan1
		
			clrf quotient
			call get_dig		; Get 3rd digit.
			movwf digitfan2

			return

;------------------------------------------------------------------------------------------------------------------------
; Convert frequency to RPM.
;------------------------------------------------------------------------------------------------------------------------

convertrpm
				banksel multiplier				; Fan Speed in RPM = Fan Speed in Hz * 60
digitfanspd0x6	movlw d'6'
				movwf multiplier
				movf digitfan0, w
				movwf adder

keepmult0		movf adder, w
				addwf digitfan0, f
				decfsz multiplier, f
				goto keepmult0
				subwf digitfan0, f
				movlw d'10'
keeppass0		incf passer, f
				subwf digitfan0, f
				skpnc
				goto keeppass0
				addwf digitfan0, f
				decf passer, f

digitfanspd1x6	movlw d'6'
				movwf multiplier
				movf digitfan1, w
				movwf adder

keepmult1		movf adder, w
				addwf digitfan1, f
				decfsz multiplier, f
				goto keepmult1
				subwf digitfan1, f
				movf passer, w
				addwf digitfan1, f
				clrf passer

				movlw d'10'
keeppass1		incf passer, f
				subwf digitfan1, f
				skpnc
				goto keeppass1
				addwf digitfan1, f
				decf passer, f


digitfanspd2x6	movlw d'6'
				movwf multiplier
				movf digitfan2, w
				movwf adder

keepmult2		movf adder, w
				addwf digitfan2, f
				decfsz multiplier, f
				goto keepmult2
				subwf digitfan2, f
				movf passer, w
				addwf digitfan2, f
				clrf passer
				movlw d'10'
keeppass2		incf passer, f
				subwf digitfan2, f
				skpnc
				goto keeppass2
				addwf digitfan2, f
				decf passer, f

spincheck		movlw d'0'					; Checks if the fan is actually spinning.
				subwf pulsetotal, f
				skpnz
				goto notspinning
				goto spinning 

notspinning		movlw d'0'					; If not spinning then write 0 to the last digit.
				movwf digitfan3
				goto rpmconvdone

spinning		movlw d'10'					; If it is spinning display the generated random number.
				subwf rdmhold, f
				skpc
				addwf rdmhold, f
				movf rdmhold, w
				movwf digitfan3
				goto rpmconvdone

rpmconvdone		clrf passer
				return		

;------------------------------------------------------------------------------------------------------------------------
; Display fan speed on 7 segment displays.
;------------------------------------------------------------------------------------------------------------------------

displayfanspd
			bsf PORTB, 4			; Display digit0.
			bcf PORTB, 5
			bcf PORTB, 6
			bcf PORTB, 7

			movf prevdigitfan0, w	; Obtain average value for digit0 from the current and previous values.
			addwf digitfan0, f
			bcf STATUS, C
			rrf digitfan0, f

			movlw 0xF0				; Strip lower 4 bits.
			andwf PORTB, f
			movf digitfan0, w
			iorwf PORTB, f

			movf digitfan0, w		; Pass current digitfan0 value to previous value.
			movwf prevdigitfan0

			movlw  d'20'			; Small delay for viewing.
			call NDELAY

			bcf PORTB, 4			; Display digit1.
			bsf PORTB, 5
			bcf PORTB, 6
			bcf PORTB, 7

			movlw 0xF0
			andwf PORTB, f
			movf digitfan1, w
			iorwf PORTB, f

			movlw  d'20'			; Small delay for viewing.
			call NDELAY

			bcf PORTB, 4			; Display digit2.
			bcf PORTB, 5
			bsf PORTB, 6
			bcf PORTB, 7

			movlw 0xF0
			andwf PORTB, f
			movf digitfan2, w
			iorwf PORTB, f

			movlw  d'20'			; Small delay for viewing.
			call NDELAY

			bcf PORTB, 4			; Display digit2.
			bcf PORTB, 5
			bcf PORTB, 6
			bsf PORTB, 7

			movlw 0xF0
			andwf PORTB, f
			movf digitfan3, w
			iorwf PORTB, f

			movlw  d'20'			; Small delay for viewing.
			call NDELAY

			return

;------------------------------------------------------------------------------------------------------------------------
; End of programme.
;------------------------------------------------------------------------------------------------------------------------

			end
