;-----------------------------------------------------------------------;
; 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
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.
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?
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 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'.
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
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:
Why use a mask to check for a button release?James Newton replies: Using the mask allows the program to pass the button to be checked as a variable. The bit test commands must have the bit number hard coded. DT is a shorthand for multiple retlw commands.
Why not test directly with "btfss SETPB" or "btfsc SETPB", as applicable?
Also, in the table defining the limits for each digit, what is the "dt" command? I would have expected a "retlw" in front of each value. Is this short hand?
Think about the largest number that can be displayed for each digit on a clock display.
Meh, sorry... i just noticed something terribly wrong.. of course it'd need to be lit only when the button is pressed, otherwise it'd be too difficult to read the clock, as understanding wether it's showing hours or minutes atm would be rather difficult.
(yes, i am a newbie with PICs)
Interesting.. How would i go if i wanted it to begin at 00:00, use rb0, rb1 and rb2 for ten hours and ten minutes, rb3, rb4, rb5 and rb6 for hours and minutes. The leds would be lit 24/7... Any help would be appreciated.