;-----------------------------------------------------------------------; ; 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.