; TMR2 is used as a way to detect when the rear wheel speed comes to a stop.
; More precisely, when the VSS frequency drops below a certain frequency. TMR2 is
; configured to run at a frequency that will allow it to more quickly detect this
; (more quickly than the watch dog timer, that is). Normally, the Interrupt
; service routine (DoINT) will be clearing the watch dog timer, and the TMR2
; overflow counter. If the rear wheel stops (thus no Interrupts from the VSS)
; then the TMR2 interrupts will be allowed to occur. If more than three TMR2
; interrupts arrive (that's a VSS frequency of just less than about 8 Hz),
; then the TMR2 interrupt routine will disable other interrupts
; and stop the CCP1 module, which in turn halts the output signal to the speedo.
; Shortly there-after, the watchdog timer will expire, and the processor will
; reset, effectively re-initializing the whole routine.
;
; TMR1 is configured to run at 500KHz. The minimum frequency that can be
; supported by the CCP1 module is therefore about 8Hz. In the
; bootstrap portion of the main loop (used to detect initial wheel rotation),
; pre-loading the TMR1 registers with 7530h allows for the maximum amount of time
; (or minimum frequency) to be detected. In other words, the VSS frequency has to
; be at least 8Hz before control is allowed to proceed beyond the "Begin" loop,
; and into the main "Loop".
ERRORLEVEL -302 ;remove message about using proper bank
;***** Declarations and microcontroller configuration *****
PROCESSOR 16f88
#include "p16f88.inc"
__CONFIG _CONFIG1, _CP_OFF&_CCP1_RB3&_DEBUG_OFF&_WRT_PROTECT_OFF&_CPD_OFF&_LVP_OFF&_BODEN_ON&_MCLR_OFF&_PWRTE_ON&_WDT_OFF&_INTRC_IO
__CONFIG _CONFIG2, _IESO_OFF&_FCMEN_OFF
;**********************************************************************************************************
;***** Declaration of variables *****
FAST equ H'20' ; fast or slow correction
SWITCH equ H'21' ; DIP switch reading
CAP1L equ H'22' ; capture 1 timer value
CAP1H equ H'23'
CAP2L equ H'24' ; capture 2 timer value
CAP2H equ H'25'
CAPDIFFL equ H'26' ; capture difference
CAPDIFFH equ H'27'
TEMPDIFFL equ H'28' ; Temp cature difference
TEMPDIFFH equ H'29'
OLDCOMPL equ H'2A' ; old compare timer
OLDCOMPH equ H'2B'
NEXTCOMPL equ H'2C' ; next compare timer
NEXTCOMPH equ H'2D'
COMPL equ H'2E' ; compare timer
COMPH equ H'2F'
NEWCOMPL equ H'30' ; new compare timer
NEWCOMPH equ H'31'
NEWCOMPVALID equ H'32' ; new compare done flag
CAPDIFFUPDOK equ H'33' ; capture difference done flag
TMR2OF equ H'34' ; Timer2 overflow flag
PORTA4 equ H'35' ; Output port on/off flag
; Math variables
AA0 equ H'40' ; Division dividend
AA1 equ H'41'
AA2 equ H'42'
BB0 equ H'43' ; Division divisor
BB1 equ H'44'
CC0 equ H'45' ; 32 bit result from multiply
CC1 equ H'46'
CC2 equ H'47'
CC3 equ H'48'
REMB0 equ H'49' ; Division remainder
REMB1 equ H'4A'
LOOPCOUNT equ H'4B' ; Division loopcount
CFL equ H'4C' ; Correction factor
CFH equ H'4D'
DELAY1 equ H'4E' ; Delay/Loop registers
DELAY2 equ H'4F'
DELAY1_2 equ H'50'
d1 equ H'51'
;Save register area
W_TEMP equ H'70' ; Used to save registers when an interrupt occurs
STATUS_TEMP equ H'71'
;**********************************************************************************************************
;***** Program memory structure *****
ORG 0x00 ; Reset Vector
goto Init ; After reset jump to location
ORG 0x04 ; Interupt vector
goto IntSvc ;
;**********************************************************************************************************
IntSvc
; Save W & STATUS
movwf W_TEMP ;Copy W to W_TEMP register
swapf STATUS,W ;Swap status to be saved into W
;Swaps are used because they do not affect the status bits
movwf STATUS_TEMP ;Save status to bank zero STATUS_TEMP register
;**********************************************************************************************************
IntDispatch
banksel 0
btfsc INTCON,INTF ; Are we handling a INT on RB0? (input)
goto DoINT ; Yes
banksel PIR1
btfsc PIR1,CCP1IF ; Are we handling a CCP1 (compare) interrupt? (output)
goto DoCCP1 ; Yes
btfsc PIR1,TMR2IF ; Are we handling a Timer2 interrupt? (input stopped)
goto DoTMR2 ; Yes
;**********************************************************************************************************
IntSvcExit
; Restore W & STATUS
banksel 0
swapf STATUS_TEMP,W ; Swap STATUS_TEMP register into W
; (sets bank to original state)
movwf STATUS ; Move W into STATUS register
swapf W_TEMP,F ; Swap W_TEMP
swapf W_TEMP,W ; Swap W_TEMP into W
retfie
;**********************************************************************************************************
DoCCP1
bcf PIR1,CCP1IF ; Clear the CCP1IF interrupt flag
banksel 0
btfsc PORTA4,0 ; Check if output port is on
goto $+3 ; jump if yes
bsf PORTA,4 ; switch on (FF)
goto $+2
bcf PORTA,4 ; switch off (00)
comf PORTA4,F ; flip value
; Calculate a new Compare timer value
; CCPR1 = COMP + CCPR1
btfss NEWCOMPVALID,0
goto $+5
movfw NEWCOMPH
movwf COMPH
movfw NEWCOMPL
movwf COMPL
movfw CCPR1H
movwf OLDCOMPH
movfw CCPR1L
movwf OLDCOMPL
movfw OLDCOMPH
addwf COMPH,W
movwf NEXTCOMPH
movfw OLDCOMPL
addwf COMPL,W
movwf NEXTCOMPL
btfsc STATUS,C
incf NEXTCOMPH,F
movfw NEXTCOMPH
movwf CCPR1H
movfw NEXTCOMPL
movwf CCPR1L
goto IntDispatch
;**********************************************************************************************************
DoINT
bcf INTCON,INTF ; Clear the INT interrupt flag
movfw TMR1H
movwf CAP2H
movfw TMR1L
movwf CAP2L
movfw TMR1H
subwf CAP2H,W
btfsc STATUS,Z
goto $+5
movfw TMR1H
movwf CAP2H
movfw TMR1L
movwf CAP2L
btfss CAPDIFFUPDOK,0
goto MoveCAP2toCAP1
; Calculate new capture timer diff.
; CAPDIFF = CAP2 - CAP1
movfw CAP1H
subwf CAP2H,W
movwf CAPDIFFH
movfw CAP1L
subwf CAP2L,W
movwf CAPDIFFL
btfss STATUS,C
decf CAPDIFFH,F
MoveCAP2toCAP1
movfw CAP2H
movwf CAP1H
movfw CAP2L
movwf CAP1L
clrf TMR2OF ; Clear overflow flag and timer
clrf TMR2
clrwdt ; Clear WDT timer to indicate program still running
goto IntDispatch
;**********************************************************************************************************
DoTMR2
bcf PIR1,TMR2IF ; Clear the TMR2IF interrupt flag
banksel 0
incf TMR2OF,F
btfss TMR2OF,2 ; Have we overflowed 2 times yet?
goto IntDispatch ; No
banksel PIE1 ; Shutdown interrupts and let the WDT timeout restart everything
bcf PIE1,CCP1IE
banksel INTCON
bcf INTCON,INTE
clrf CCP1CON ; Turn the CCP1 module off
bcf T1CON,TMR1ON ; Turn Timer1 off
bcf T2CON,TMR2ON ; Turn Timer2 off
bsf PORTA,4 ; Turn on PORTA,4 to switch off Q2
goto IntSvcExit
;**********************************************************************************************************
Init
; Initialize the system clock oscillator frequency
banksel OSCCON
bsf OSCCON,IRCF2 ; IRCF2:IRFC1:IRFC0 = b'111'
bsf OSCCON,IRCF1 ; Configure the internal clock to run at 8MHz
bsf OSCCON,IRCF0
; Initialize the Watch Dog Timer (WDT) period select for approx 1/4 second timeout.
; But leave the WDT disabled, we'll enable it later.
; We configure the period select configuration bits to scale this down to give us an approx
; 1/4 sec timeout. 31,250 Hz / 8192 = 3.81 Hz = 262ms
banksel WDTCON
movlw B'00010000' ; period select = 1:8192, SWDTEN = 0 (off)
movwf WDTCON
banksel OPTION_REG
bcf OPTION_REG,PS2 ; PS2:PS0 = 000 means prescaler 1:1
bcf OPTION_REG,PS1
bcf OPTION_REG,PS0
; Initialize A/D converter
banksel ANSEL
clrf ANSEL ; Make all port pins digital
; Initialize PORTA
banksel PORTA
clrf PORTA
banksel TRISA
movlw B'01000000'
movwf TRISA ; Make PORTA all output except RA6
; Initialize PORTB
banksel PORTB
clrf PORTB
banksel TRISB
movlw B'11111111'
movwf TRISB ; Make PORTB all input
banksel OPTION_REG
bcf OPTION_REG,NOT_RBPU ;Enable PortB pull-ups
; Initialize Timer1
banksel T1CON
bsf T1CON,T1CKPS1 ; Prescaler 1:4 (500Khz)
; Initialize Timer2
banksel PR2
movlw .169 ;169
movwf PR2
banksel TMR2
clrf TMR2
clrf TMR2OF
movlw B'1111111' ; Turn Timer 2 on, Postscaler 1:16, Prescaler 1:16
movwf T2CON
; Initialize CCP1
banksel CCP1CON
movlw B'00000000' ; CCP1 off
movwf CCP1CON
; Initialize RAM variables
banksel 0
clrf PORTA4
clrf CFH
clrf CFL
bsf PORTA,4 ;Turn on PORTA,4 to switch off Q2
call RD_SW
goto Begin
;**********************************************************************************************************
RD_SW ; Read switch settings
TstRet
clrf FAST
clrf SWITCH
btfss PORTA,6 ; Read fast or slow setting
bsf FAST,0 ; if low set bit0 in FAST
movf PORTB,W ; Read PortB
andlw B'11111110' ; Strip bit 0 (RB0 not needed)
movwf SWITCH
bcf STATUS,C
comf SWITCH,f
rrf SWITCH,f ; Rotate remaining bits to the right
PWR
movf SWITCH,w
xorlw H'7F' ; LED test setting
btfsc STATUS,Z ; if equal
goto PWRTest
Freq
movf SWITCH,w
xorlw H'7E' ; 440Hz Output Test
btfsc STATUS,Z ; if equal
goto FREQTest
Freq1
movf SWITCH,w
xorlw H'7D' ; 1.02KHz Output Test
btfsc STATUS,Z ; if equal
goto FREQTest1K
Vss
movf SWITCH,w
xorlw H'7C' ; VSS Input Test
btfsc STATUS,Z ; if equal
goto VSSTest
; Check if fast or slow switch is set
btfss FAST,0 ; Is bit0 set (slower)?
goto $+5 ; yes
; Set correction factor to be faster
movf SWITCH,W
addlw .100 ; Add 100 to switch value
movwf CFL
goto $+4
; Set correction factor to be slower
movf SWITCH,W ; Subtract switch value from 100
sublw .100
movwf CFL
Return
;**********************************************************************************************************
; Check to see if we're moving faster than a crawl
Begin
clrf TMR2
clrf TMR2OF
bcf T1CON,TMR1ON ; Turn Timer1 off
movlw 0x75 ;Prefills timer registers
movwf TMR1H
movwf CAP1H
movlw 0x30
movwf TMR1L
movwf CAP1L
bcf PIR1,TMR1IF ; Clear the Timer1 interrupt flag
btfsc PORTB,0 ; Wait for RB0/INT to go low
goto $-1
btfss PORTB,0 ; Wait for RB0/INT to go high
goto $-1
bsf T1CON,TMR1ON ; Turn Timer1 on
btfsc PORTB,0 ; Wait for RB0/INT to go low
goto $-1
btfss PORTB,0 ; Wait for RB0/INT to go high
goto $-1
btfsc PIR1,TMR1IF ; Did Timer1 overflow?
goto Begin ; Yes, so start over
movfw TMR1L ; Capture Timer1 value
movwf CAP2L
movfw TMR1H
movwf CAP2H
; Calculate first compare timer value.
; CAPDIFF = CAP2 - CAP1
movfw CAP1H
subwf CAP2H,W
movwf CAPDIFFH
movfw CAP1L
subwf CAP2L,W
movwf CAPDIFFL
btfss STATUS,C
decf CAPDIFFH,F
movfw CAPDIFFH
movwf COMPH
movwf CCPR1H
movfw CAPDIFFL
movwf COMPL
movwf CCPR1L
movfw CAP2H
movwf CAP1H
movfw CAP2L
movwf CAP1L
banksel WDTCON
bsf WDTCON,SWDTEN ; Turn the watchdog timer on
; Set up interrupts
banksel INTCON
bcf INTCON,INTF
bcf PIR1,CCP1IF
bcf PIR1,TMR2IF
bsf INTCON,INTE
banksel OPTION_REG
bsf OPTION_REG,INTEDG
banksel PIE1
bsf PIE1,CCP1IE
bsf PIE1,TMR2IE
banksel 0
bsf INTCON,PEIE
bsf INTCON,GIE
movlw B'00001010' ; CCP1 = Compare mode on
movwf CCP1CON
;**********************************************************************************************************
; Main Loop
Loop
call RD_SW
bcf CAPDIFFUPDOK,0 ; Clear ok flag and copy registers
movfw CAPDIFFH
movwf TEMPDIFFH
movfw CAPDIFFL
movwf TEMPDIFFL
bsf CAPDIFFUPDOK,0 ; Set ok flag
call CalcNewCompare
bcf NEWCOMPVALID,0 ; Clear valid flag and copy new values
movfw TEMPDIFFH
movwf NEWCOMPH
movfw TEMPDIFFL
movwf NEWCOMPL
bsf NEWCOMPVALID,0 ; Set valid flag
goto Loop
;**********************************************************************************************************
CalcNewCompare
; Calculate the percentage correction
; The latest capture diff contains the number of Timer1 ticks that occured for one cycle of
; the input frequency.
; Divide 500,000 (Timer1 is running at 500Khz) by the latest of capture diff, giving the
; frequency in Hz.
movlw 0x07 ; 500,000 = 0x07A120
movwf AA0
movlw 0xa1
movwf AA1
movlw 0x20
movwf AA2
movfw TEMPDIFFH
movwf BB0
movfw TEMPDIFFL
movwf BB1
call DIV24x16 ; divide prescaler freq by counted ticks during 1 cycle
movfw AA1
movwf AA0
movfw AA2
movwf AA1
movfw CFH
movwf BB0
movfw CFL
movwf BB1
call MUL16x16 ; Multiply result by correction factor
movfw CC1
movwf AA0
movfw CC2
movwf AA1
movfw CC3
movwf AA2
movlw high .100
movwf BB0
movlw low .100
movwf BB1
call DIV24x16 ; Divide the result by 100
movfw AA1
movwf BB0
movfw AA2
movwf BB1
clrf AA0
movlw 0x07 ; 500,000 = 0x07A120
movwf AA0
movlw 0xa1
movwf AA1
movlw 0x20
movwf AA2
call DIV24x16 ; Answer gives the total time for 1 cycle
movfw AA1
movwf TEMPDIFFH
movfw AA2
movwf TEMPDIFFL
DIV2
; Divide by 2 to give the period for each on and off time for 1 cycle (50% duty)
clrc
rrf TEMPDIFFH, f
rrf TEMPDIFFL, f
skpc
goto no_inc
incf TEMPDIFFL, f
skpnz
incf TEMPDIFFH, f
no_inc
return
;**********************************************************************************************************
; Unsigned 16*16 bit Multiply AA(16) * BB(16), Result (CC) is 32bit
; adapted from code found on
www.piclist.com
;
; params
; AA 0:1 (MSB:LSB) = number to multiply (16bit)
; BB 0:1 (MSB:LSB) = multiplier (16bit)
; CC 0:3 (MSB:LSB) = Product (32bit)
MUL16x16
clrf CC0
clrf CC1
clrf CC2
movlw 0x80
movwf CC3
nextbit
rrf AA0,F
rrf AA1,F
btfss STATUS,C
goto nobit_l
movfw BB1
addwf CC2,F
movfw BB0
btfsc STATUS,C
incfsz BB0,W
addwf CC1,F
btfsc STATUS,C
incf CC0,F
bcf STATUS,C
nobit_l
btfss AA1,7
goto nobit_h
movfw BB1
addwf CC1,F
movfw BB0
btfsc STATUS,C
incfsz BB0,W
addwf CC0,F
nobit_h
rrf CC0,F
rrf CC1,F
rrf CC2,F
rrf CC3,F
btfss STATUS,C
goto nextbit
return
;**********************************************************************************************************
;Divide a 24 bit number by a 16 bit number, resulting in a 16 bit number
;Inputs:
; Dividend - AA0:AA1:AA2 (0 - most significant!)
; Divisor - BB0:BB1
;Temporary:
; Counter - LOOPCOUNT
; Remainder- REMB0:REMB1
;Output:
; Quotient - AA0:AA1:AA2
;
DIV24x16
clrf REMB0
clrf REMB1
movlw .24
movwf LOOPCOUNT
LOOPU2416
rlf AA2,W ;shift dividend left to move next bit to remainder
rlf AA1,F
rlf AA0,F
rlf REMB1,F ;shift carry (next dividend bit) into remainder
rlf REMB0,F
rlf AA2,F ;finish shifting the dividend and save carry in AA2.0,
;since remainder can be 17 bit long in some cases
;(e.g. 0x800000/0xFFFF). This bit will also serve
;as the next result bit.
movf BB1,W ;substract divisor from 16-bit remainder
subwf REMB1,F
movf BB0,W
btfss STATUS,C
incfsz BB0,W
subwf REMB0,F
;here we also need to take into account the 17th bit of remainder, which
;is in AA2.0. If we don't have a borrow after subtracting from lower
;16 bits of remainder, then there is no borrow regardless of 17th bit
;value. But, if we have the borrow, then that will depend on 17th bit
;value. If it is 1, then no final borrow will occur. If it is 0, borrow
;will occur. These values match the borrow flag polarity.
skpnc ;if no borrow after 16 bit subtraction
bsf AA2,0 ;then there is no borrow in result. Overwrite
;AA2.0 with 1 to indicate no borrow.
;if borrow did occur, AA2.0 already
;holds the final borrow value (0-borrow,
;1-no borrow)
btfsc AA2,0 ;if no borrow after 17-bit subtraction
goto UOK46LL ;skip remainder restoration.
addwf REMB0,F ;restore higher byte of remainder.
;(w contains the value subtracted from it previously)
movf BB1,W ;restore lower byte of remainder
addwf REMB1,F
UOK46LL
decfsz LOOPCOUNT,F ;decrement counter
goto LOOPU2416 ;and repeat the loop if not zero.
return
;**********************************************************************************************************
; As short collection of test routines to confirm installation is ok.
PWRTest ; Flashes LED to show its working
movlw 0x5
movwf d1
LEDFlash
bsf PORTA,7 ; turn on LED
call Delay_0
bcf PORTA,7 ; turn off
call Delay_0
decfsz d1,1
goto LEDFlash ; Do it again...
bcf PORTA,7
goto TstRet
Delay_0
movlw 0x08
movwf DELAY1
movlw 0x2F
movwf DELAY2
movlw 0x03
movwf DELAY1_2
Delay_01
decfsz DELAY1, f
goto $+2
decfsz DELAY2, f
goto $+2
decfsz DELAY1_2, f
goto Delay_01
return
;**********************************************************************************************************
FREQTest ; Outputs ~ 400Hz test signal to speedo
movlw 0xFF
movwf d1
bsf PORTA,7 ; Turn on LED to show in test mode
FREQ400
bsf PORTA,4 ; turn on
call Delay_02a
bcf PORTA,4
call Delay_02a
decfsz d1,1
goto FREQ400 ; Do it again...
bcf PORTA,7
goto TstRet
Delay_02a
movlw 0xC2
movwf DELAY1
movlw 0x02
movwf DELAY2
Delay_021a
decfsz DELAY1, f
goto $+2
decfsz DELAY2, f
goto Delay_021a
return
;**********************************************************************************************************
FREQTest1K ; Outputs ~ 1KHz test signal to speedo
movlw 0xFF
movwf d1
bsf PORTA,7 ; Turn on LED to show in test mode
FREQ1K
bsf PORTA,4
call Delay_02
bcf PORTA,4
call Delay_02
decfsz d1,1
goto FREQ1K ; Do it again...
bcf PORTA,4
goto TstRet
Delay_02
movlw 0xc2
movwf DELAY1
movlw 0x01
movwf DELAY2
Delay_021
decfsz DELAY1, f
goto $+2
decfsz DELAY2, f
goto Delay_021
return
;**********************************************************************************************************
; Test mode to detect VSS is working when the wheel is rotated by hand
; LED will flash on each detected pulse from the VSS.
VSSTest
movlw 0x0A ; Needs 10 pulses before return to main prog
movwf d1
Test
btfsc PORTB,0 ; Wait for RB0/INT to go low
goto $-1
bsf PORTA,7 ; Turn LED off
btfss PORTB,0 ; Wait for RB0/INT to go high
goto $-1
bcf PORTA,7 ; Turn LED on
decfsz d1,1
goto Test
bcf PORTA,7
goto TstRet
;**********************************************************************************************************
End
;**********************************************************************************************************