Program #3 - 3 Minute Countdown Timer

;-------------------------------------------------------------------------;
; EGGTIMER.ASM        A 3 minute countdown timer for boiling eggs         ;
;-------------------------------------------------------------------------;

        LIST    P=16F84            ;  tells which processor is used
        INCLUDE "p16f84.inc"       ;  defines various registers etc. Look it over.
        ERRORLEVEL -224            ;  supress annoying message because of tris
        __CONFIG _PWRTE_ON & _LP_OSC & _WDT_OFF   ;  configuration switches

;-------------------------------------------------------------------------;
;            Here we set up the user defined registers                    ;
;-------------------------------------------------------------------------;
        CBLOCK  H'0C'              ; first free register address is 12
                sec                ; keeps track of seconds
                sec10              ; keeps track of tens of seconds
                mins               ; keeps track of minutes
                w_temp             ; holds value of W during interrupt
                status_temp        ; holds value of STATUS during interrupt
                finflag            ; act as a flag to indicate end of countdown
                oldsec10           ; holds last displayed value of sec10
        ENDC

        ORG 0                      ; start a program memory location zero

        goto main                  ; jump over the interrupt routine

        ORG 4

;-------------------------------------------------------------------------;
; here is the interrupt routine, happens every second if GIE is enabled   ;
;-------------------------------------------------------------------------;
        movwf   w_temp             ; save W
        swapf   STATUS,W           ; save status                  
        movwf   status_temp        ; without changing flags

        decf    sec, f             ; decrement seconds register
        movlw   H'FF'              ; check if underflow
        subwf   sec, W             ; will give zero if sec = H'FF'
        btfss   STATUS, Z          ; skip next instruction if underflow
        goto    restore            ; no underflow, leave interrupt routine
        movlw   9                  ; change seconds register to 9
        movwf   sec
        decf    sec10, f           ; now we follow the same procedure ...
        movlw   H'FF'              ; for the sec10 register
        subwf   sec10, W              
        btfss   STATUS, Z          ; skip if underflow
        goto    restore            ; no underflow, leave
        movlw   5                  ; change sec10 register to 5
        movwf   sec10
        decf    mins, f            ; and decrement minutes register
        movlw   H'FF'              ; check if ...
        subwf   mins, W            ; an underflow of minutes ...
        btfss   STATUS, Z          ; yes means the count is finished
        goto    restore            ; no underflow
        incf    finflag, f         ; set the finished flag
restore:  
        swapf   status_temp,W      ; get original status back
        movwf   STATUS             ; into status register
        swapf   w_temp,f           ; old no flags trick again
        swapf   w_temp,W           ; to restore W
        bcf     INTCON,T0IF        ; clear the TMR0 interrupt flag
        retfie                     ; finished reset GIE

;=========================================================================;
;                    This is the main program                             ;
;=========================================================================;
main:

;-------------------------------------------------------------------------;
;           initialize the ports, set up interrupts etc:                  ;
;-------------------------------------------------------------------------;
        movlw   B'00000000'     ; all bits low in W
        tris    PORTA           ; contents of W copied to PORT A ...
        movlw   B'00010000'     ; RB4 input, all other output
        tris    PORTB           ; and PORT B
        movlw   B'00000100'     ; port B pull-ups active
                                ; prescalar assigned to TMR0 and set 1:32
        option                  ; rolls over each second
        movlw   B'00100000'     ; T0IE set, GIE not set yet... 
        movwf   INTCON          ; in the interrupt register
        clrf    PORTB           ; display 0
  
;-------------------------------------------------------------------------;
;                  initialize some other registers:                       ;
;-------------------------------------------------------------------------;
        clrf    sec             ; start with sec = zero
        clrf    sec10           ; and sec10 = zero
        clrf    oldsec10        ; make oldsec10 the same
        movlw   D'3'            ; and minutes = 3
        movwf   mins           
        clrf    finflag         ; clear the finished flag

;-------------------------------------------------------------------------;
;                     wait for pushbutton to start                        ;
;-------------------------------------------------------------------------;
        btfsc   PORTB, 4        ; switch closed, (gives 0)?
        goto    $ -1            ; not yet
                                ; switch has been detected closed
                                ; ( no debounce necessary )
        clrf    TMR0            ; start with timer at zero
        bcf     INTCON, T0IF    ; and make sure the interrupt flag is clear
        bsf     INTCON, GIE     ; enable interrupts, countdown starts

;-------------------------------------------------------------------------;
;   This is the main loop that displays the time and checks if finished   ;
;-------------------------------------------------------------------------;
loop:                           ; now go into a loop displaying registers in..
                                ; sequence each time sec10 changes, (every
                                ; ten seconds), and checking for finished flag
        btfsc   finflag, 0      ; skip next if finflag not set
        goto    finished        ; time up
        movf    oldsec10, W     ; check if sec10 has changed
        subwf   sec10, W        ; zero flag set if sec10 is same as oldsec
        btfsc   STATUS, Z       ; skip over if not the same
        goto    loop            ; else keep checking
        movf    sec10, W        ; replace oldsec10
        movwf   oldsec10        ; making it equal to sec10
        movf    mins, W         ; display minutes
        movwf   PORTB           ; on LEDs
        call    onesecond       ; for one second
        clrf    PORTB           ; blank briefly
        call    msec250
        movf    sec10, W        ; now the same with sec10
        movwf   PORTB           ; show 10's of seconds
        call    onesecond       ; for one second
        clrf    PORTB           ; blank
        goto    loop


;-------------------------------------------------------------------------;
;           We come to this point when the countdown is over              ;
;-------------------------------------------------------------------------;
finished:
        movlw   H'F'            ; turn on all leds indicating finish
        movwf   PORTB
        goto    $               ; go into an endless loop

;-------------------------------------------------------------------------;
;        Four calls to a delay for 250 millisecond = 1 second delay       ;
;-------------------------------------------------------------------------;
onesecond:                     ; a subroutine that delays for 1 seconds
        call    msec250
        call    msec250
        call    msec250
        call    msec250
        return

;-------------------------------------------------------------------------;
;           This subroutine delays for 250 milliseconds                   ;
;-------------------------------------------------------------------------;
msec250:                       ; a subroutine to delay 250 msec
        movlw   D'250'         ; W is changed but no separate register needed
nmsec:                         ; could call it here with # msec in W
        nop                    ; each nop is 0.122 milliseconds
        nop
        nop                    ; each total loop is 8 X 0.122 = 0.976 msec
        nop
        addlw   H'FF'          ; same as subtracting 1 from W
        btfss   STATUS, Z      ; skip if result is zero
        goto    nmsec          ; this is  2 X 0.122 msec  
        return                 ; back to calling point

        end                    ; end of program

3 Minute Coundown Timer

When you run the program the LEDs will be initially blank. When the pushbutton on RB4 is pressed the count will be given every 10 seconds, first by flashing minutes and then tens of seconds. The numbers are in BCD but you should be able to catch them. Seconds are not flashed because they are always zero. If tens of seconds is zero, it will not be seen either. Rather than counting down in binary and then converting to decimal, three registers mins, sec10 and sec are used to handle the numbers directly in decimal. All LEDs are turned on when the count is over.

Interrupts

Program # 2 used an interrupt flag, (T0IF), but didn't use interrupts. This program uses interrupts. When interrupt conditions are satisfied, everything stops, the program jumps to address 4 and execution continues from there. Instructions are executed until a 'retfie' instruction returns to where the interruption happened and carries on as before.

The interrupt routine shouldn't modify any registers or flags used in the main program. That is the reason for the complex set of instructions at the beginning and end of the isr, (interrupt subroutine). Both 'W' and STATUS are likely to be changed in the isr and must be saved. The original values will be restored before exiting the isr.

Saving of these registers must be done with instructions that don't affect any of the flags in STATUS. One of these is 'swapf' which exchanges the high and low 4 bits of a register. Another is 'movwf'. Look over how the registers are saved in swapped form at the beginning and restored with 'swapf' at the end of the interrupt routine.

This program uses a timer zero interrupt. The main program is busy flashing the LEDs to indicate times. At the same time there seems to be a program running in the background that is updating the three time registers every second.

This is done by having the regular program interrupted every time TMR0 rolls over. TMR0 is given a 1:32 prescalar so this happens every second. Three requirements have to be met to generate a TMR0 interrupt.

  1. In INTCON register, T0IE, (Timer 0 Interrupt Enable), has to be set.
  2. In INTCON register, GIE, (General Interrupt Enable), as to be set.
  3. In INTCON register, T0IF, (Timer 0 Interrupt Flag), has to be set, (goes high).

Notice that GIE is not set until the 'button' is pressed to start the countdown. Setting GIE then activates the count. GIE is disabled each time an interrupt occurs and is reset by 'retfie'. T0IF is reset by code in the interrupt routine itself. Notice also that the first instruction is 'goto main' which jumps over the isr at program memory location 4.

Register usage

There are many more user named registers used in this program than previously. Two of these are w_temp and status_temp which are use to save values of W and the STATUS register. Another user defined register is 'finflag' which is set when the coundown is finished. Actually, only one bit, (bit 0), of the register is used. Finflag transfers the fact that the coundown is complete from the interrupt routine where it is detected to the main routine where it is acted upon. The register oldsec10 holds the value of sec10 that was last displayed. Only when sec10 differs from oldsec10 is the display updated.

Updating the Count

The count is initially 3 minutes, 0 seconds. Each second the isr decrements this by one second. Underflow is checked by seeing if the digit has gone to H'FF'. If this happens, the digit is reset to a starting value and the next digit to the left is decremented. When the underflow travels through all three digits, the finished flag is set.

ADDLW

We haven't seen the addlw instruction yet. It obviously adds a literal, (number), to W. But, in this case it is used to subtract 1 from W. You might be tempted to us 'sublw'. I have, and regretted it. 'sublw 3' does not subtract 3 from W, it subtracts W from 3! H'0FF' is the twos compliment value of '-1'. Adding twos compliment is the same as subtracting.The instruction occurs in the nmsec subroutine which introduces a way to provide a delay without using an extra register as we did before. This subroutine can also be entered at two points, msec250 to get a 1/4 second delay or at nmsec with the number of millisecs of delay put in W first.

SUBWF

'subwf (register), W' is a new instruction in the interrupt routine. Notice that W is subtracted from f, not the other way around. In this case we are only looking for a zero so it doesn't make a difference. But, in many cases it might be important. Also, if the destination is W, the register f is not altered.

BCF

BCF, ( clear a bit in the register f), provides a way to clear an individual bit in a register without changing other bits. There is also a BSF instruction to set individual bits.

Now its your turn

a mistake!

If you run the program you find that the 4 LEDs don't come on when the count reaches 0 but 10 seconds later. Can you figure out what is wrong and fix it?

add a speaker

The obvious addition to the program is a speaker to indicate when time is up. I find the easiest way to do this is to put a piezoelectric speaker directly between a port and +4.5V. The ones having a paper cone attached seem louder. A piezo speaker is actually a capacitor so a 100 ohm resistor in series is a good idea to limit the original surge current.

The code to drive the speaker would simply bring the port high, hold it for a millisecond, bring it low, hold it for a millisecond and repeat over and over. See if you can add a speaker at the point where all the LEDs are turned on.

display last 10 digits of coundown

Can you figure a way to have all of the last digits 9-0 displayed?

a variable starting value

A way to change the starting value might be interesting too. Say, if the battery were attached with the button not pressed the starting time would default to 3 minutes. If the button is pressed when power is supplied, the display starts flashing 1,2,3...etc until the button is released. The time at release would be the starting time in minutes. Could you write that program? Remember button debouncing because you would then have to wait for an additional press to start the countdown.

one button, many functions

It is also inconvient to power-down and back up to restart the countdown. How about a press of the button at the end to turn off the LEDs. Yet another press would reload the starting count, (maybe display the minutes). Another press would begin the countdown. Can you make the changes?

See:

Questions:

Interested:

Questions: