Sunday, November 12, 2017

A Simple Current Source and An Ode the Resistor


This isn't really a post about using a variable voltage regulator as a current regulator. This is actually a reflection on the lowly resistor's simple but amazing ability to transform a voltage into a current and vice-versa. 

Before I get into the wonders of the resistor, I am going to discuss how to make a voltage regulator into a current regulator. I'll use the venerable and classic LM317 as the example regulator but any three terminal variable regulator will work the same way 

Figure 1. Voltage Regulator.

It was originally meant to be a voltage regulator, says so right in the name. The LM317 adjusts it's output (by internally adjusting a pass transistor -- pretend it sets a resistor between IN and OUT) until the voltage between ADJ and OUT is 1.25V. It uses negative feedback. Essentially, if you stay inside the drop out voltage and other data sheet limitations, VR1 (the voltage across resistor R1) will be 1.25V. 

That makes operation as an adjustable voltage regulator straight forward. The resistor divider formed by R1 and R2 will have 1.25V across its top resistor. You know the math to find the rest. 


Of course the resistor sizes set the current through the divider. The current through the divider is VOUT/(R1+R2). Since we know the voltage difference between VOUT and VADJ is 1.25V and this is the voltage across R1, 1.25V/R1 will give the same result. These equations are approximations since they do not include the current that flows into the ADJ terminal. That is a reason to keep the current in the resistors up -- so this error doesn't become relatively important. Many versions of the 317 insist on at least 10mA(!) of output current. If you have to burn some extra current to meet that criteria it might as well be wasted in the resistor divider.

Figure 2. Current Regulator.

Notice what happened there. The current flowing through R1 keeps flowing through R2 (ignoring a small bit that gets lost into the ADJ pin). That's just Kirchoff's current law. R2 doesn't have anything to do with setting the current. So R2 can be replaced by a circuit that needs to consume that current. An LED is a very common circuit to place there. LEDs have reasonably constant brightness if the current through them is kept constant. This circuit does a decent job of holding the current constant across temperature and voltage. 

Did you see what R1 did in that circuit? As long as 1.25V was across it, a constant current flowed through it. It acted as a voltage to current converter. That function is inherent in the definition of a resistor--Ohm's law. 

V=IR

The voltage put across the resistor generates a predictable current through the resistor. The resistor acts as a voltage-to-current converter. And it works the other direction. 

I=V/R

A current forced through a resistor generates a predictable voltage across the resistor. 


Figure 3. Current to Voltage Converter.

Here is an example of the resistor being used as a current-to-voltage converter. For this to work well, the two current sources need to be well matched. This is a way to get a small floating voltage. The result is that VOUT is 50mV lower than VIN.


Figure 4. Voltage to Current Converter.

Here's an example of the resistor being used as a voltage-to-current converter. The voltage across the resistor is the difference between the power supply and the MOSFET drain (the gate is at the same potential), that is, VDD-VGS. That current gets mirrored over the other MOSFET. Just a different way of thinking about a classic circuit. 


Sunday, February 08, 2015

Fish Lights -- 60W of Green LEDs

Another Project Post

While I was working on the trailer lights, Dan, the scoutmaster, got talking about using green LEDs to attract fish. He had already made a small light using a 9V battery and a resistor.

Dan says, "Can we make this brighter?" So I upped that a bit and soldered together thirty(?) LEDs on a dowel. I had no way to waterproof it, and that is way too many LEDs to run in parallel but it was pretty bright. 

Not my best soldering job.
A bunch of 30mA green LEDs would around a dowel.
Dan says, "Can we make this brighter?" Next was a series/parallel combination of 24 LEDs on a panel. It was pretty bright. The idea was to put several of these together. It was a pain to do all that soldering on a proto board--I soldered two, only one worked. Dan says, "Can we make this brighter?"



Yep, we can make this brighter. Thus is born the Fish Light. LED Engin makes a small 5W four element green LED. TI makes a handy little eight LED driver (TPS61500) that runs from a car battery. Stick on a simple thermal shutdown from Microchip (TC622) and a simple schematic results. 

Fish Light schematic.
Heat dissipation is a big issue with this design. 10W of power is dissipated by the LEDs plus some more by the driver. I made the bottom layer entirely ground so it could sit directly on an aluminum heat sink. The circuit must be placed on a heat sink to operate. It starts overheating (the light output drops) after about 15 seconds of operation with no heat sink. 

Finished and mostly finished panel.
PCB layout
Since the whole thing is supposed to go under water, I used a Bourns Resettable Fuse for a bit of safety in case of a moisture induced short circuit. I found the LEDs impossible to solder by hand. I had to borrow a hot air solderer to attach them. 

A single panel lit in isolation.
This picture was taken under lights, but the green lights wish all that out.
Without heat sinking, the circuit heats up quickly. 
I made a framework from one inch wide, one sixteenth inch thick aluminum strip. The strips were bent so the LEDs would at a 45 degree angle from vertical, which directs their light out and down but not up. To do that much bending, a bending brake is the tool to use. I don't have a bending brake and didn't want to invest the money or shop space in one, so I faked one up using some scrap steal, C-clamps, and a rubber mallet. Pictures below. I drilled the needed holes first so I wouldn't be trying to maneuver the aluminum strip under the drill press when it was bent into a funky shape. 


Bending brake with Al strip ready for bending
Different angle with the strip bent.










All six panels mounted on the aluminum frame.


Lights on! It takes just over 10A at 13.8V to power get this much light.

This is supposed to work under water. I have no idea how to water-proof electronics. I tried painting the whole assembly (except the LED lenses) with 30-minute epoxy. The problem is the epoxy is clear so I can't tell where I missed. Therefore I did an overcoat of green spray paint, laid on thick. I tested this by putting the whole assemblage under water in the kitchen sink. The first time three of the panels failed, so I dried it all out, reapplied epoxy to those three panels and it worked.

Sitting in the sink, waiting for water.
Under water, lit, same lighting as the last picture.

Things I learned
  • I had never used a hot air soldering tool. I have now. I must have improved my soldering in the last few years. I no longer treat an SOIC as a challenge. I did find the HTSSOP package a challenge with its 0.65mm pin spacing leaving only 0.35mm space between pins. 
  • I had never used a bending brake. Now I've made a simple one. 

Things I would change
  • Those particular LED packages were a royal pain. I could find no way to hand solder them. I had to use the hot air soldering tool. 
  • I need to find a better way to waterproof

Each panel has $45 of electronics components on it plus the PCB. That's $270 of electronics, about $100 for PCBs, plus the frame. It was worth doing once as a fun project for a friend, it isn't something I'll be making more of. 

Cool project, time to move on to something else!

Bruce McLaren

Dan sent me this picture of the assembly under water in Lewisville Lake. He says, "This has a 12 ft radius in muddy water."



Sunday, April 06, 2014

IC Design Interview 6: Op-amp Circuits

You must know your op-amp circuits to do analog circuit design! During an interview, you even have to derive the operation equations. Each of these derivations starts with the result in bold (because you must just know that) followed by the derivation.


Inverting Op-Amp

Vout = -(R2/R1)Vin

Vin/R1 = -Vout/R2
-(R2/R1)Vin = Vout


Non-Inverting Op-Amp (Buffer)

Vout = Vin(1+R1/R2) = Vin((R2+R1)/R2)

V- = Vout R2/(R2+R1)  [Voltage Divider]
Assume V- = Vin
Vin = Vout R2/(R2+R1)
(R2+R1)/RVin = Vout
The special case of R2 being open (infinite resistance) creates a Unity Gain Buffer

Difference Amplifier

Vout = R2/R1 (VB-VA)

Assume V- = V+
V+ = VB R2/(R1+R2)   [Voltage Divider]
Since I1=I2                 [Kirchoff's Current Law]
(Vout-V-)/R2 = (V--VA)/R1
Solve for V-
R1Vout+R2VA = V-
      R2+R1
Which is the same as V+, so
R1Vout+R2VA = VB R2
      R2+R1             R1+R2
Which can be solved for Vout
                                                                                         Vout = R2/R1 (VB-VA)

Friday, March 21, 2014

Applied Power Electronics Conference and Exposition (APEC) 2014

One way to learn is to hang out with very smart people and try to keep up.

I recently attended the IEEE APEC in Fort Worth, TX. Since I live close to the convention center, I could attend without having to pay for a hotel or airline flight. Since I am currently between contracts, I had the week free to go. I haven't attended this conference before, but it seemed like a great opportunity. I don't attend many conferences. In fact, the last major conference I attended was ISSCC in 2002. These two conferences were very different. 

Some random comments.

  • They tell you the rooms are often cool so you should bring a sweater or jacket. Boy, are they right. Don't bring a pull-over (the mistake I made Monday) because there will be rooms where you don't want to where it. 
  • They feed you! Lunch was available for purchase on-site Sunday and Monday. Lunch was available for free during the industry exposition and during the poster session. This was cheaper, of course, but it was also incredibly convenient. 
  • There were lots of tracks, sometimes as many as twelve. It was sometimes hard to choose which session to attend. 
  • They kept the speakers on the time schedule. Period. That sometimes meant cutting questions short, occasionally meant cutting a speaker short, and once it meant waiting a few minutes before letting the next speaker begin. Keeping to the strict schedule allowed participants to skip between sessions to catch paper presentations in several tracks. 
  • The conference was very well organized. Presentations were pre-loaded before the sessions. Professional AV people were in every room to adjust the audio and solve problems as they occurred.
  • I was handed a USB stick containing all papers at registration. Fantastic.  
  • It was a very international conference. No real surprise there. 


Compared to the ISSCC 2002, I surprised myself by preferring this conference. 

  • The questions were requests for clarifications not attacks on the paper presented. 
  • Speakers were kept strictly to their times. 
  • APEC was more appropriate for the working engineer
    • There were many industry presented papers.
    • Companies were allowed to make product presentation. They were clearly labeled as such and were often interesting. 
    • There was a large (more than 1,000 booths) industry exposition. 
    • I actually heard the question asked after a paper was presented, "What do you see as the application of this circuit?" This particular paper had some odd input and output goals but the presenter did, in fact, have a real-world application in mind. 

Comments based on presentations

Packaging is a big deal for high speed, high power, and high temperature. SiC and GaN transistors intended for large power supplies are all three. Packaging was discussed a lot. 

It sounds like SiC is really coming into its own. My master's thesis way back in 1993 concentrated on characterizing a particular SiC type (6-H p-type substrate) so I glad that 20 years later that industry is getting some traction. 

Intersil discussed an interesting all digital control loop that eliminated the need for compensation. I haven't managed to get my hands the white paper where it is described in more detail. 

V2 control of DC-DC converter loops was mentioned several times. I need to figure out how that works. Maybe I can make it a future topic for this blog. It looks like it has faster transient response but adds more complexity. 

I am excited by the paper "Capacitor-Less Photovoltaic (PV) Cell-Level Power Balancing Using Diffusion Charge Redistribution". It describes a simple way to keep partial shading on a single cell from significantly lowering the yield from the entire string. I think I could make the controller for less than one dollar per cell. I liked the convergence between solar cells and switch capacitor converter. Some smart work. 

I finally really understand Power Factor Correction (PFC). I knew the circuits, of course, but the point of the exercise was lost on me. Dhaval Dalal made the simple statement, "PFC is meant to control input current not regulate output voltage." That was probably obvious to everyone but me. Now I get it too. 

The linear regulator isn't dead yet. There were no papers presented on them but I recall three times when it was mentioned that a linear regulator wouldn't be much less efficient then a presented buck regulator when the output voltage was close to the output voltage. The Power Supply on Chip guys seem to be holding linear regulators as a real competitor in size and power density. 

I will spare you all of my notes from the 55 sessions I attended. 



The APEC 2014 was very much worth attending. I don't know that I'll make it North Carolina next year. Cost and time may become too much of a problem. 

Bruce McLaren

Wednesday, February 05, 2014

Trailer Lights Version 3.1 Software

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.

Figure 1. State diagram for trailer lights controller.
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--="">
OnTime        equ .30 ; Minutes LEDs on: must be <255 comment-255--="">
ShortTime     equ  .9 ; Minutes LEDs on if Low Battery Condition: must be <255 comment-255--="">
LongFlash     equ .29 ; Seconds LEDs flash for normal off: must be <59 comment-59--="">
ShrtFlash     equ .12 ; Seconds LEDs flash for low battery off: must be <59 comment-59--="">

; STATE bits
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       ; 



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