Program #5 - A Clock that Displays Time in Binary Numbers

;-----------------------------------------------------------------------;
; BINCLOCK.ASM          A clock that displays in bcd numbers            ;
;-----------------------------------------------------------------------;

        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

            CBLOCK     0CH
                sec             ; seconds digit
                sec10           ; 10's of second digit
                mins            ; minutes digit
                min10           ; 10's of minutes digit
                hr              ; hours digit
                hr10            ; 10's of hours digit
                w_temp          ; holds W during interrupt
                status_temp     ; holds STATUS during interrupt
                fsr_temp        ; holds FSR during interrupt
                button          ; holds mask for pushbuttons
              ENDC
          
;-----------------------------------------------------------------------;
;  Here are some DEFINEs which give 'names' to pushbutton port bits     ;
;-----------------------------------------------------------------------;
            #DEFINE SETPB PORTB, 4
            #DEFINE SELECTPB PORTB, 5
            #DEFINE SHOWPB PORTB, 6

            ORG 0               ; start at location 0

            goto main           ; jump over to main routine       

            ORG 4
             
            goto isr            ; jump to interrupt routine

;-----------------------------------------------------------------------;
;                  High limit + 1 of digits at position W               ;
;-----------------------------------------------------------------------;
sethi:
            addwf PCL, f
         dt H'A',H'6',H'A',H'6',H'A',H'3'


;-----------------------------------------------------------------------;
;                           Delay routines                              ;
;-----------------------------------------------------------------------;
msec250:                       ; enter here to delay for 250 milliseconds
         movlw D'250'          
nmsec:                         ; delay for # msec in W on entry
         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

;-----------------------------------------------------------------------;
;                        Delay for one second                           ;
;-----------------------------------------------------------------------;
onesecond:                     ; a subroutine that delays for 1 seconds
         call msec250
         call msec250
         call msec250
         call msec250
         return

;-----------------------------------------------------------------------;
;                   Put value in W on LEDs for 1 second                 ;
;-----------------------------------------------------------------------;
sendnbr:
         movwf PORTB          ; light LEDs
         call onesecond       ; wait 1 second
         clrf PORTB           ; clear the LEDs
         movlw D'100'         ; pause for 0.1 sec
         call nmsec
         return 

;-----------------------------------------------------------------------;
;                   Send the current time out LEDs                      ;
;-----------------------------------------------------------------------;

disptime:              
             movf hr10, W
             call sendnbr
             movf hr, W
             call sendnbr
             movf min10, W
             call sendnbr
             movf mins, W
             call sendnbr
             return

;-----------------------------------------------------------------------;
;                 Wait until selected button is released                ;
;-----------------------------------------------------------------------;
waitup6:                             ; wait for show pushbutton up
            movlw B'01000000'        ; RB6 mask
            movwf button
            goto wait
waitup5:                             ; wait for select pushbutton up
            movlw B'00100000'        ; RB5 mask
            movwf button
            goto wait
waitup4:                             ; wait for set pushbutton up
            movlw B'00010000'        ; RB4 mask
            movwf button
wait:
            movf button, W           ; mask into W
            andwf PORTB, W
            btfsc STATUS, Z          ; skip if not zero (released)
            goto wait
            movlw D'10'
            call nmsec               ; wait 10 msec for debounce
            movf button, W           ; check for release again
            andwf PORTB, W
            btfsc STATUS, Z          ; skip if selected button released
            goto wait
            return                   ; yes, finished


;-----------------------------------------------------------------------;
;                       Initilization Subroutine                        ;
;-----------------------------------------------------------------------;
init:
            movlw B'0000000'           ; all outputs port A
            tris PORTA                 
            movlw B'01110000'          ; RB4 - RB6 inputs, others outputs
            tris PORTB                 ; on port B
            movlw H'0'                 ; all low (off)
            movlw PORTB
            movlw B'00000100'          ; pull-ups enabled                                    
                                       ; prescaler assigned to TMR0
                                       ; prescaler set to 1:32
                                       ; rolls over each second
            option                
            movlw 0
            movwf hr10
            movlw H'9'                 ; initialize hrs, mins and secs
            movwf hr                   ; Do this before interrupts are
            movlw H'5'                 ; turned on because isr also acts
            movwf min10                  ; on these registers
            movlw H'0'
            movwf mins
            movwf sec10
            movwf sec
            movlw B'10100000'          ; GIE & T0IE set, T0IF cleared
            movwf INTCON
            return

;-----------------------------------------------------------------------;
;  Interrupt routine, increments time by one second  (BCD)              ;
;-----------------------------------------------------------------------;
isr:
            movwf w_temp             ; save W
            swapf STATUS,W           ; save status
            movwf status_temp        ; without changing flags
            swapf FSR,W              ; save FSR
            movwf fsr_temp           ; without changing flags


            movlw sec                ; point at sec register
            movwf FSR
newdigit:   incf INDF, f             ; current digit up one
            movlw sec                ; get difference between sec and FSR
            subwf FSR, W
            call sethi               ; use to get high limit + 1
            subwf INDF, W            ; reached that number yet?
            btfss STATUS, Z          ; skip over if yes
            goto restore             ; else exit isr
            clrf INDF                ; set current digit to 0
            incf FSR, f               ; point at next digit
            btfss hr10, 1            ; has hr10 reached 2?
            goto newdigit            ; no, increment the next digit
            btfss hr, 2              ; has hr reached 4?
            goto newdigit            ; no
            clrf hr                  ; yes, set hour to 00
            clrf hr10                ; and hour 10
restore: 
            swapf status_temp,W      ; get original status back
            movwf STATUS             ; into status register
            swapf fsr_temp,W         ; get original fsr back
            movwf FSR                ; 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

;-----------------------------------------------------------------------;
;           Increment and display digit pointed to by FSR               ;
;-----------------------------------------------------------------------;
updigit:
            incf INDF, f          ; selected digit up one
            movlw mins            ; set up to subtract mins address
            subwf FSR, W          ; from address of current digit
            call sethi            ; get maximum of digit + 1 into W
            subwf INDF, W         ; is it = to current digit value?
            btfsc STATUS, Z       ; gives zero if yes, skip if no
            clrf INDF             ; reset value of digit to zero
            movf INDF, W          ; get current value and ..
            movwf PORTB           ; display it
            call onesecond        ; pause for 1 second
            return

;-----------------------------------------------------------------------;
;          increment selected digit until select pressed                ;
;-----------------------------------------------------------------------;
setdigit:
            movwf PORTB
            btfss SETPB           ; set pressed?
            call updigit          ; yes
            btfsc SELECTPB        ; check if select pressed
            goto $ -3             ; repeat till select pressed again
            call waitup5          ; make sure select released
            incf FSR, f
            return

;-----------------------------------------------------------------------;
;                     Select and increment digits                       ;
;-----------------------------------------------------------------------;
select:
            bcf INTCON, GIE       ; no interrupts while setting time
            movlw mins            ; point at minutes register
            movwf FSR
            call waitup5          ; wait on select pushbutton up
            movlw B'00000001'     ; light right LED (mins)
            call setdigit
            movlw B'00000010'     ; light min10 LED
            call setdigit
            movlw B'00000100'     ; light hr LED
            call setdigit
            movlw B'00001000'     ; hr10 LED on
            call setdigit
            clrf PORTB            ; clear LEDs
            bsf INTCON, GIE       ; enable interrupts again
            return

;-----------------------------------------------------------------------;
;                          The main routine                             ;
;-----------------------------------------------------------------------;

main:      
            call init             ; set up initial conditions
loop:       
            btfss SHOWPB          ; check for show pushbutton
            call disptime         ; display the time
            btfss SELECTPB        ; check for select
            call select
            goto loop             ; do forever

            end

A Binary Clock

This program is a clock. The time will be displayed when the 'show' pushbutton is pressed. The digits of the time are displayed in BCD on the four LEDs. Tens of hours is displayed followed by hours, tens of minutes and then minutes.

If the 'select' pushbutton is pressed the program goes into a mode where the digits can be set. The rightmost LED will come on indicating that the minutes digit is to be set. The minutes digit will be incremented and displayed at one second intervals while the 'set' pushbutton is pressed. Pressing 'select' again will transfer to the next higher digit. After tens of hours is set, the next press of the 'select' button returns you to the time display mode.

Defines

There are three pushbuttons in this program. Each uses a bit in Port B. To make things easier, names have been assigned to these bits. The compiler directive '#DEFINE' is simply a word replacement. Everywhere 'SETPB' is found the the code it will be replaced with 'PORTB, 4'.

A comparison with 'eggtimer.asm'

This program is similar the eggtimer.asm but instead of counting down, it counts up. There are also six registers holding digits, (only 4 displayed). These are updated in an interrupt routine, ('isr'), that is reached by a 'goto' at location 4.

If you look at the interrupt routine in 'eggtimer' you find each of the three digit registers treated in turn. This time there are six registers but you don't see the name of any but 'sec' in 'isr'. The code between saving and restoring of registers is even smaller in the case of 'binclock'. How can this be, more registers but fewer instructions?

Indirect Addressing

The answer lies in the FSR register and what is called 'indirect addressing'. When we have a number of registers in sequence like sec - hr10, we can use another register, FSR, to point to the first, (sec). If we increment FSR, it points to 'sec10'. Increment it again and it points to 'mins' etc. The register INDF, (indirect register), takes the place of whatever register FSR is pointing to. Operations on INDF are actually performed on the register FSR points to. Since the same actions are repeated on each of the six registers in turn, one piece of code containing INDF can operate on each by incrementing FSR.

We use the register 'FSR' in the isr and must save it on entry and restore it on exit of the interrupt subroutine. This is because we also use FSR in the display routines outside the isr.

Setting the time

Indirect addressing and the FSR register are also used in the code that sets the time. This is another place where each register has the same thing done to it in turn. When you push the 'select' button, the program jumps to a routine that allows you to increment a selected register. The selected registers are 'mins' - 'hr10' and a LED lights showing the position of the digit to be incremented.

Setting time involves three subroutines. 'select' calls 'setdigit' which in turn calls 'updigit' Subroutine calls can be listed in any order and the call will find the code. But, I like to list subroutines with those called preceding those doing the calling. This leaves the main routine last.

Notice that interrupts are disabled at the beginning of the time setting routine and enabled on leaving. This is necessary because the interrupt routine works with the same registers as the time setting routine. We can't have both changing the registers 'at the same time'.

Masks and ANDing

Some code space is saved by combining wait routines for three separate pushbuttons. It would be nice to write an instruction like btfsc PORTB, (variable), but that isn't allowed. The bit number has to be a constant. Instead we work with the whole port and what is called a 'mask'.

A mask is like a piece of cardboard with holes in it that you place over the binary representation of a register. Where holes are, the bits come through unchanged; in other positions they will be changed to zeros. The mask has 1's where the holes are, zeros in the other bit positions. We 'AND' the number with a mask to zero certain bits.

Suppose we 'AND' the number B'10110101' with the mask B'00001111'. The result is B'00000101'. We have removed the high four bits, (upper nibble), of the original number. By using a mask with only 1 bit set, we can look at a single bit in a number. This is what is done in the code starting at waitup6.

For more information on 'AND' and other logical bit operations try: http://www.cscene.org/cs9/cs9-02.html

Using a table

A table is used in both the time setting routine and the interrupt subroutine. The table holds 'overflow' numbers for various time digit registers. These are used to reset the digits back to zero. Notice that the table is placed first in program memory. If any part of the table extends beyond the 256th instruction, special instructions are needed to access it.

Now it's your turn

The time setting routine is sufficient but there are a couple of problems. First, the digit zero doesn't appear; instead there is a blank display just the same as between time displays. It should be easy to make a certain pattern appear in place of the zero, say, '1111' or all digits on. Can you make the changes? Two places are necessary; just before 'movwf PORTB' in 'sendnbr' and in 'updigit'.

Secondly, the display indicating which digit is to be set is the same as the display for the digits 1,2,4 and 8. Maybe when indicating which digit is to be set the digit could flash on and off. Can you change the subroutine 'setdigit' to make this happen?

The clock could be turned into an alarm clock. Give some thought to how you would have the current time checked against alarm time to set off the alarm. You will need more registers to hold the alarm time digits. The same time setting routine can be used because of FSR.

You might want the clock to be able to 'memorize' the alarm times, even if the battery failed or was removed. That will be covered in program 6.

This clock program could be combined with routines from 'morsenbr' for an audible clock.

You may want to have the clock display every ten seconds rather than have to press the 'show pushbutton'. Can you make the necessary changes? I hope you are using low current LEDs if you do this.

Interested:

Comments:

Questions: