The electronic hardware for the improved trailer lights was described in the last post. I got the boards in and soldered it together. It works! Now it needs some software. I chose a Microchip PIC12F609 as the brains.
I was surprised how little example PIC code I found that included interrupt handling and I found no usable example code in assembly language. I also got tripped up on the oscillator shutting down during sleep. I know it makes sense to save power, but it makes waiting for a given time period impossible while in sleep mode. Further, since the interrupt returns to the command that was loaded when the interrupt occurs, I can't get away with something simple like
goto $ ; wait for interrupt
commands to do after interrupt
because the commands to do after the interrupt will never occur. I know that isn't the traditional way to use an interrupt, but in this case it isn't just non-traditional--it's impossible. So I put in a tight two command loop that checks for an interrupt to have happened. It feels like a hack, but it works.
The state diagram this program implements is shown in figure 1. Rather than an entirely interrupt driven approach, I could have interrupted only on the button changes and had the timing as part of the main code. I like this approach better.
I always have a bit of trouble explaining code, so here it is. Additional explanation is mixed in with the code in a different font. I am also fighting with Blogger to get it to post code without mangling it. I've got it down to just mangling comments. I'll edit this later if I can figure how to make it mor readable. In the mean time, I'm afraid you'll have to deal with it.
;
; Trailer Boost 3.1
; Bruce McLaren
; nanoDragon LLC
; Bruce.McLaren@nanoDragon.com
; 9 Dec 13 - 5 Feb 14
;
;
; Hardware notes:
; PIC12F609 runnimg at 32kHz using external crystal in LP mode
;
; GP0 (GP0) : Output - Toggles at interrupt TEST NOT USED IN APP
; GP1 (CIN0-) : Analog, POWFB - 20% of Trailer Battery Voltage
; GP2 (GP2) : Output, LIGHT - A high turns LEDs on
; GP3 (GP3) : Input, BUTT - Button, low when button pressed (external pull-up)
; GP4 (OSC2) : OSC, - crystal Oscillator (LP mode)
; GP5 (OSC1) : OSC, - crystal Oscillator (LP mode)
;
#include < p12f609.inc >
; LP oscillator: Low-power crystal on GP4/OSC2/CLKOUT and GP5/OSC1/CLKIN
; WDT disabled and can be enabled by SWDTEN bit of the WDTCON register
; PWRT enabled
; MCLR pin function is digital input, MCLR internally tied to VDD
; Program memory code protection is disabled
; BOR disabled
__CONFIG _FOSC_LP & _IOSCFS_4MHZ & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _BOR_OFF
; errorlevel -302 ; suppress "not in bank0" warning
; Constants
LowBatMax equ .15 ; Number of Low Battery before shutdown: must be <255 comment-255--="">255>
OnTime equ .30 ; Minutes LEDs on: must be <255 comment-255--="">255>I was surprised how little example PIC code I found that included interrupt handling and I found no usable example code in assembly language. I also got tripped up on the oscillator shutting down during sleep. I know it makes sense to save power, but it makes waiting for a given time period impossible while in sleep mode. Further, since the interrupt returns to the command that was loaded when the interrupt occurs, I can't get away with something simple like
goto $ ; wait for interrupt
commands to do after interrupt
because the commands to do after the interrupt will never occur. I know that isn't the traditional way to use an interrupt, but in this case it isn't just non-traditional--it's impossible. So I put in a tight two command loop that checks for an interrupt to have happened. It feels like a hack, but it works.
The state diagram this program implements is shown in figure 1. Rather than an entirely interrupt driven approach, I could have interrupted only on the button changes and had the timing as part of the main code. I like this approach better.
Figure 1. State diagram for trailer lights controller. |
;
; Trailer Boost 3.1
; Bruce McLaren
; nanoDragon LLC
; Bruce.McLaren@nanoDragon.com
; 9 Dec 13 - 5 Feb 14
;
;
; Hardware notes:
; PIC12F609 runnimg at 32kHz using external crystal in LP mode
;
; GP0 (GP0) : Output - Toggles at interrupt TEST NOT USED IN APP
; GP1 (CIN0-) : Analog, POWFB - 20% of Trailer Battery Voltage
; GP2 (GP2) : Output, LIGHT - A high turns LEDs on
; GP3 (GP3) : Input, BUTT - Button, low when button pressed (external pull-up)
; GP4 (OSC2) : OSC, - crystal Oscillator (LP mode)
; GP5 (OSC1) : OSC, - crystal Oscillator (LP mode)
;
#include < p12f609.inc >
; LP oscillator: Low-power crystal on GP4/OSC2/CLKOUT and GP5/OSC1/CLKIN
; WDT disabled and can be enabled by SWDTEN bit of the WDTCON register
; PWRT enabled
; MCLR pin function is digital input, MCLR internally tied to VDD
; Program memory code protection is disabled
; BOR disabled
__CONFIG _FOSC_LP & _IOSCFS_4MHZ & _WDTE_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _BOR_OFF
; errorlevel -302 ; suppress "not in bank0" warning
; Constants
LowBatMax equ .15 ; Number of Low Battery before shutdown: must be <255 comment-255--="">255>
ShortTime equ .9 ; Minutes LEDs on if Low Battery Condition: must be <255 comment-255--="">255>
StateInit equ O'0'
StateWait equ O'1' ; never checked in program
StateLongOff equ O'2' ; never checked in program
StateShortOff equ O'3' ; never checked in program
StatePwrDown equ O'4' ; never checked in program
; BUTTON bits
Pressed equ O'0' ; Set when button pressed, cleared after processing never checked in program
Released equ O'1' ; Set when button released, cleared after processing
; Variables
CBLOCK 0x40 ; 64 bytes RAM in BANK0
INTERRUPT ; FF at end of interrupt routine
STATUS_TEMP ; STATUS save during interrupt
W_TEMP ; W save during interrupt
TENTHS ; tenths of seconds counter
SECONDS ; seconds counter
MINUTES ; minutes counter
LOWBATCNT ; Low Battery Counts - inc when battery<10v dec="" when="">10V.
; Saturates at FF and 00.
BUTTON ; Lower two bits contain button information
STATE ; Used to aid debugging
; 00000001 = Init (STATE, StateInit)
; 00000010 = Wait (STATE, StateWait)
; 00000100 = LongOff (STATE, StateLongOff)
; 00001000 = ShortOff (STATE, StateShortOff)
; 00010000 = PwrDown (STATE, StatePwrDown) Program doesn't set this
ENDC
ORG 0
goto Init ; start
nop ;
nop ;
nop ;
Interrupt:
; Save w, status (including BANKSEL) to STATUS_TEMP
movwf W_TEMP ;
swapf STATUS, W ;
movwf STATUS_TEMP ; 10v>
The W and STATUS register save is copied straight from the PIC12F609 data sheet.
banksel PIR1 ; Bank0
btfss PIR1, TMR1IF ; was this a timer interrupt?
goto ButtCheck ; if not, skip to button check
bcf PIR1, TMR1IF ; clear timer interrupt
; Timer re-seeded with 65536-(32000/10+20)=62316 (F36C)
bcf T1CON, TMR1ON ; TIMER1 disable
movlw H'F3' ;
movwf TMR1H ;
movlw H'6C' ;
movwf TMR1L ;
bsf T1CON, TMR1ON ; TIMER1 enable
I want an interrupt every 1/10 of second. It is a 32kHz crystal. The timer is disabled for five commands, which consume 20 clock cycles. Checking timing on the oscilloscope shows it to be about 2% too slow. It my really be a 32,768kHz crystal or my oscilloscope that hasn't been calibrated in five years may have drifted a bit. An initial timing error of 2% meets the application requirement so I'm not tuning it.
; Increment TENTHS, SECONDS, MINUTES
; If (MINUTES, SECONDS, TENTHS) == (255, 59, 9) Then don't inc (saturate)
movlw .9 ; Load 9 into W
subwf TENTHS, W ; W = TENTHS - W
btfss STATUS, Z ; If Z clear, don't trap
goto IncTime ; TENTHS not 9, handle normal
movlw .59 ; Load 59 into W
subwf SECONDS, W ; Compare W to SECONDS
btfss STATUS, Z ; If Z clear, don't trap
goto IncTime ; SECONDS not 59, handle normal
movlw .255 ; Load 255 into W
subwf MINUTES, W ; Compare W to MINUTES
btfss STATUS, Z ; If Z clear, don't trap
goto IncTime ; MINUTES not 255, handle normal
goto LowBatCheck ; Skip increment time
All that to make sure the time saturates even though it should never get to that point. Call it paranoid. If I was running out of program space, I would delete this code.
IncTime:
incf TENTHS, F ;
movlw .10 ; Load 10 into W
subwf TENTHS, W ; W = TENTHS - W
btfss STATUS, Z ; If TENTHS!=10 (Z clear) move on, otherwise rollover TENTHS
goto LowBatCheck ;
clrf TENTHS ; Rollover TENTHS
incf SECONDS, F ;
movlw .60 ; Load 60 into W (60 seconds in a minute)
subwf SECONDS, W ; Compare W to SECONDS
btfss STATUS, Z ;
goto LowBatCheck ;
clrf SECONDS ; Rollover SECONDS
incf MINUTES, F ;
LowBatCheck: ;
; The battery level is checked on the timer interrupt, i.e., every 0.1 seconds
btfss CMCON0, COUT ; check comparator
goto BattHigh ;
;; bsf GPIO, GP0 ;;; battery low
Uncommenting the bsf GPIO, GP0 line was useful in debugging the battery voltage check. There is a corresponding bcf line below that also gets uncommented and the xorwf GPIO, F that otherwise toggles the pin every interrupt needs to be commented out. The comparator is not handled well in the simulator.
incfsz LOWBATCNT, F ;
goto ClearTime1Int ;
decf LOWBATCNT, F ; If it incremented to 0, decrement will re-saturate
goto ClearTime1Int ;
BattHigh:
movf LOWBATCNT, F ;
;; bcf GPIO, GP0 ;;; battery high
btfss STATUS, Z ; If LOWBATCNT is already 0, don't dec
decf LOWBATCNT, F ;
ClearTime1Int:
bcf PIR1, TMR1IF ; clear timer1 interrupt
ButtCheck:
btfss INTCON, GPIF ; if not a pin interrupt,
goto InterruptFinish ; skip to InterruptFinish
btfsc GPIO, GP3 ;
bsf BUTTON, Pressed ;
btfss GPIO, GP3 ;
bsf BUTTON, Released ;
bcf INTCON, GPIF ; clear pin interrupt
InterruptFinish:
movlw b'00000001' ;
xorwf GPIO, F ; Toggle GP0 each interrupt for testing
Toggling the "unused" output made debugging much easier. It helped both with hardware debugging and simulation debugging.
clrf INTERRUPT ; ensure INTERRUPT clear
comf INTERRUPT, F ; set INTERRUPT to FF
; Restore STATUS and return
swapf STATUS_TEMP, W ; Swap STATUS_TEMP into W
movwf STATUS ; Move W into Status
swapf W_TEMP, F ; Swap W_TEMP
swapf W_TEMP, W ; Swap W_TEMP into W
retfie ; Return from interrupt
nop ;
Init:
banksel STATE ; Bank0
clrf STATE ; clear STATE
bsf STATE, StateInit ; STATE = Init
; GPIO setup
clrf GPIO ; clear outputs
banksel ANSEL ; Bank1
clrf ANSEL ;
bsf ANSEL, GP1 ; GP1 is analog
clrf TRISIO ;
bsf TRISIO, GP1 ; GP1 is an input
bsf TRISIO, GP3 ; GP3 is an input
; clrf WPU ;
; bsf WPU, GP3 ; GP3 has weak pull-up NO, IT CANT
As mentioned in the previous post, I messed this up.GP3 cannot have an internal weak pullup unless it used as the pin is configured as MCLR. It is a pretty clear footnote in the data sheet, I just plain missed it. I had to add an external pull-up to the pin.
clrf IOC ;
bsf IOC, GP3 ; GP3 interrupts on change
banksel INTCON ; Bank0
bsf INTCON, GPIE ; GPIO Change Interrupt Enable
; Timer setup Timer seeded with 65536-(32000/10)=62336 (F380)
movlw H'F3' ;
movwf TMR1H ;
movlw H'80' ;
movwf TMR1L ;
clrf T1CON ; No gate, No prescale, timer1 disable
bsf CMCON1, T1ACS ; FOSC
clrf BUTTON ; clear button
; comparator setup (not enabled until after wakeup)
clrf CMCON0 ; disabled
bsf CMCON0, CMR ; CMVin+ connects to CMVref output
bsf CMCON1, CMHYS ; hysteresis on
clrf VRCON ; Voltage Reference disabled
bsf VRCON, FVREN ; 0.6V Reference Enable
; Interrupt setup
bsf INTCON, GIE ; GPIE already set
InitSleep:
sleep ; wait for pin interrupt
I can use sleep here because this first wait is waiting for the button to be released, there is no timing and no clock. This is the low power wait until someone pushes (and releases) the button state.
nop ;
; Next state logic
btfss BUTTON, Released ;
goto InitSleep ; If button not released, go back to sleep and wait
bsf CMCON0, C1ON ; enable comparator
bsf VRCON, FVREN ; enable voltage reference
bsf T1CON, TMR1ON ; TIMER1 enable
Wait:
banksel STATE ; Bank0
clrf STATE ; clear STATE
bsf STATE, StateWait ; STATE = Wait
clrf TENTHS ; clear time
clrf SECONDS ;
clrf MINUTES ;
bsf GPIO, GP2 ; GP2 high (LEDs on)
bcf BUTTON, Released ; "process" BUTTON, Released
clrf PIR1 ; clear peripheral interrupts including TIMER1 and Comparator
banksel PIE1 ; Bank1
bsf PIE1, TMR1IE ; Enable TIMER1 interrupt
banksel INTCON ; Bank0
bsf INTCON, PEIE ; Enable Periperal interrupts
clrf INTERRUPT ; ensure INTERRUPT clear
WaitLoop:
btfss INTERRUPT, 0 ;
goto WaitLoop
clrf INTERRUPT ; ensure INTERRUPT clear
; Next state logic
; If Released goto LongOff
banksel BUTTON ; Bank0
btfsc BUTTON, Released ;
goto LongOff ;
; If Minutes > OnTime goto LongOff
movlw OnTime ;
subwf MINUTES, W ; W = MINUTES - OnTime
btfsc STATUS, C ; If C==0, W>f
goto LongOff ;
; If LowBatCnt > LowBatMax & Minutes > ShortTime goto ShortOff
movlw ShortTime ;
subwf MINUTES, W ;
btfss STATUS, C ; If C==0, W>f
This check has to be against C instead of Z because the bettery can become too low after the ShortTime is complete but before OnTime is complete. Making this a Carry check ensures the shorter on-time and the quick flash that warns the user the battery is low.
goto WaitLoop ; else repeat
movlw LowBatMax ;
subwf LOWBATCNT, W ;
btfsc STATUS, C ; If C==0, W>f
goto ShortOff ;
goto WaitLoop ; else repeat
LongOff:
banksel STATE ; Bank0
clrf STATE ; clear STATE
bsf STATE, StateLongOff ; STATE = LongOff
bcf BUTTON, Released ; "process" BUTTON, Released
clrf TENTHS ; clear time
clrf SECONDS ;
clrf MINUTES ;
clrf INTERRUPT ; ensure INTERRUPT clear
LongOffLoop:
btfss INTERRUPT, 0 ;
goto LongOffLoop
clrf INTERRUPT ; ensure INTERRUPT clear
bcf GPIO, GP2 ; GP2 low (LEDs off)
btfss SECONDS, 0 ;
bsf GPIO, GP2 ; GP2 high (LEDs on) on even seconds
; Next state logic
; If Released goto Wait
btfsc BUTTON, Released ;
goto Wait ;
; If Seconds > LongFlash goto PowerDown
movlw LongFlash ;
subwf SECONDS, W ;
btfsc STATUS, C ; If C==0, W>f
goto PwrDown ;
goto LongOffLoop ; else repeat
ShortOff:
banksel STATE ; Bank0
clrf STATE ; clear STATE
bsf STATE, StateShortOff ; STATE = ShortOff
bcf BUTTON, Released ; "process" BUTTON, Released
clrf TENTHS ; clear time
clrf SECONDS ;
clrf MINUTES ;
clrf INTERRUPT ; ensure INTERRUPT clear
ShortOffLoop:
btfss INTERRUPT, 0 ; wait for interrupt
goto ShortOffLoop
bcf GPIO, GP2 ; GP2 low (LEDs off)
btfss TENTHS, 0 ;
bsf GPIO, GP2 ; GP2 high (LEDs on) on even tenths
; Next state logic
; If Released goto Wait
btfsc BUTTON, Released ;
goto Wait ;
; If Seconds > ShrtFlash goto PowerDown
movlw ShrtFlash ;
subwf SECONDS, W ;
btfsc STATUS, C ; If C==0, W>f
goto PwrDown ;
goto ShortOffLoop ; else repeat
PwrDown:
banksel INTCON ; Bank0
bcf INTCON, GIE ; disable interrupt
clrf STATE ; clear STATE
bsf STATE, StatePwrDown ; STATE = PowerDown
bcf GPIO, GP2 ; not LIGHT
bcf T1CON, TMR1ON ; disable Timer 1
goto Init ; start over
end
;
So much for my posting every week to get this done quickly. On the other hand, I've been working so I can't complain. The next post will either be the 3d printed case or how to replace the headlight case and rim on a Honda Shadow.
Bruce
No comments:
Post a Comment