PIC Digital Clock Timer

Note: There is a bug in this program that prevents the alarm from working if the minutes are set to zero. So, an alarm setting of 8:59 or 6:01 will work but 6:00 will not. The bug can be fixed by adding a program line to call the alarm routine after the hours are incremented.
Current listing in the interrupt section is:

           call        Alarm
           movlw       d'60'
           xorwf       MINUTES,0
           btfss       STATUS,2        ; Check for 60 minutes
           goto        Done            ; Jump out if not 60
           clrf        MINUTES
           incf        HOURS,f


New listing should be:

           call        Alarm
           movlw       d'60'
           xorwf       MINUTES,0
           btfss       STATUS,2        ; Check for 60 minutes
           goto        Done            ; Jump out if not 60
           clrf        MINUTES
           incf        HOURS,f
           call        Alarm           ; New line added here
This clock timer uses a PIC16F628 microcontroller to display 3 and 1/2 digit time and control an external load. The clock includes a calendar with leap year and optional daylight savings adjustments. The timer output can be set from 1 to 59 minutes and manually switched on and off. The clock also has a correction feature that allows an additional second to be added every so many hours to compensate for a slightly slow running oscillator. The oscillator uses a common 32.768 KHz watch crystal and the frequency can be adjusted slightly with the 24pF capacitor on the right side of the crystal.

On bootup, the display should read 2:56 AM and other data can be displayed by toggling the advance switch (D). Each time the (D) switch is closed and opened, the display will advance to the next data. The order of displayed data and bootup values is as follows:

Time       --------------------------------------------- (2:56)
Alarm      --------------------------------------------- (6:30)
Calendar   --------------------------------------------- (3:07)
Weekday (Sunday=1), Seconds ---------------------------- (1:Seconds)
AM/PM (Alarm)  (AM=0/PM=1), Timer Duration ------------- (0:45)
AM/PM (Time)   (AM=0/PM=1), Daylight Savings Disabled -- (0:00)
Year  (1 to 4)            , Error Correction  ---------- (2:18)

There are 7 displays that advance each time the 'D' switch is toggled. To make adjustments, set the RA5 switch to the "B" position and then toggle the E and F switches to advance the data in the hours or minutes digits. Then toggle the "D" switch to move to the next data. After the 7th display, it will go back to the top and display the current time. Or, just press the time switch 'C' to get to the top at anytime. When done setting everything up, set the RA5 switch to the "A" position so the data cannot be accendentally changed. You can still view everything with the "D" advance key, but the E an F switches will just turn on or off the alarm at RB7. I use it with an external transistor to switch on and off a radio. The 'Daylight savings' setting (in the 6th display in the minutes digits) is used to enable daylight savings time adjustments, one hour ahead on the 2nd sunday in March, and one hour behind on the first sunday in November. The entry will be either 0, 1, or 3. 0 = Daylight savings time disabled (default). 1 = Savings time enabled and current time is standard time. 3 = Savings time enabled and current time is daylight savings time. The last 2 entries on the list (Year and Correction) is for the current year (1 to 4) (4 = Leapyear) so today's setting (2006) will be 2 since leapyear will be on year 4 which is 2 years from now. The correction setting will add a second every so many hours for fine adjustment to the oscillator frequency. My setting is 18 which adds a second every 18 hours. It's pretty accurate and only loses 3 seconds a month. You probably want to run it for a couple weeks to figure out what correction is needed for the crystal you have. Switch functions: RA0 (C switch) = Display Time RA1 (D switch) = Advance to next data (alarm, calendar, etc) RA2, RA3 (E and F switch) = Advance hours and minutes (in setup mode). RA2, RA3 (E and F switch) = Toggle alarm output on/off (in run mode) RA5 in the 'B' position (open) = Setup Mode

;------------Program Listing, Clock.asm - REV 1 - 11/08/06 --------- LIST P=16F628 ; Device number (PIC16F628) ERRORLEVEL -224 ; suppress annoying message because of tris ERRORLEVEL -302 ; suppress message because of page change ;--------------------- Configuration --------------------------------- _BODEN_OFF equ H'3FBF' ; Brown out detection off _CP_OFF equ H'3FFF' ; Code protection off _PWRTE_ON equ H'3FF7' ; Power-on reset enabled _WDT_OFF equ H'3FFB' ; Watch dog timer off _LVP_OFF equ H'3F7F' ; Low Voltage programming off _INTRC_OSC_NOCLKOUT equ H'3FFC' ; Use Internal RC Oscillator _MCLRE_OFF equ H'3FDF' ; Use RA5 as functional input __CONFIG _CP_OFF & _WDT_OFF & _INTRC_OSC_NOCLKOUT & _PWRTE_ON & _LVP_OFF & _BODEN_OFF & _MCLRE_OFF ;--------------------- Define Variables ------------------------------- INDF equ 00h FSR equ 04h CMCON equ 1Fh ; Comparator Control Address INTCON equ 0Bh ; Interrupt control register OPTION_REG equ 81h ; Option register STATUS equ 03h ; Status register TRISA equ 85h ; I/O control for port A TRISB equ 86h ; I/O control for port B PORTB equ 06h ; Address of port B PORTA equ 05h ; Address of port A PC equ 02h ; Program counter COUNTER equ 20h ; Addresses 20H-7FH = general RAM HOURS equ 21h ; These 20 addresses for display MINUTES equ 22h HOURS_A equ 23h MINUTES_A equ 24h MONTH equ 25h DAYS equ 26h WEEKDAY equ 27h SECONDS equ 28h AMPM_A equ 29h TIMER_LIMIT equ 2ah AMPM equ 2bh DAYLIGHT equ 2ch YEAR equ 2dh CORRECTION equ 2eh TEMP equ 35h ; Value passed to Digits routine TENS equ 36h ; Value returned from Digits routine TEMPW equ 37h ; Used in interrupt to save w SWITCH equ 38h ; Value returned from switches STATUS_SAVE equ 39h ; Interrupt (save status) TEMP1 equ 3ah ; Part of delay routine ALARM equ 3bh ; Alarm on/off (bit 7 set =on) ; BLANK equ 3ch ; Not used LIMIT equ 3dh ; Increments every hour to (correction) TEMP_SAVE equ 3eh ; Saves a copy of TEMP TIMER equ 3fh AMPM_LED equ 40h ;--------------------- Program Starts here -------------------------- goto INIT ;--------------------- Interrupt routine to update time ------------- org 0x04 movwf TEMPW ; Save w swapf STATUS,0 ; Get status register into w movwf STATUS_SAVE ; Save status register bcf STATUS,5 ; Go to bank 0 (00) incf SECONDS,f ; Advance seconds movlw d'60' xorwf SECONDS,0 btfss STATUS,2 ; Check for 60 seconds goto Done ; Jump out if not 60 clrf SECONDS incf MINUTES,f call Alarm movlw d'60' xorwf MINUTES,0 btfss STATUS,2 ; Check for 60 minutes goto Done ; Jump out if not 60 clrf MINUTES incf HOURS,f call Daylight call Add_Second ; Compensate for slow oscillator movlw d'13' xorwf HOURS,0 btfss STATUS,2 ; Check for 13 hours goto Noon ; Jump out if not 13 clrf HOURS incf HOURS,f ; Set hours to 1:00 Noon movlw d'12' xorwf HOURS,0 btfss STATUS,2 ; Check for 12 hours goto Done ; Jump out if not 12 incf AMPM,f bcf AMPM,1 ; Clear Bit 1 to stop overflow btfsc AMPM,0 ; AM = Bit 0 clear Goto Done incf DAYS,f movfw MONTH call Table xorwf DAYS,0 ; Check for Days = Limit btfss STATUS,2 goto WeekDay clrf DAYS incf DAYS,f incf MONTH,f movlw d'13' xorwf MONTH,0 btfss STATUS,2 ; Check for new year goto WeekDay clrf MONTH incf MONTH,f incf YEAR,f movlw d'5' xorwf YEAR,0 btfss STATUS,2 goto WeekDay clrf YEAR incf YEAR,f WeekDay incf WEEKDAY,f movlw d'8' xorwf WEEKDAY,0 btfss STATUS,2 ; Check for new week goto Leap clrf WEEKDAY incf WEEKDAY,f ; Set weekday to 1 = Sunday Leap movlw d'2' xorwf MONTH,0 btfss STATUS,2 goto Done movlw d'29' xorwf DAYS,0 btfss STATUS,2 goto Done movlw d'4' xorwf YEAR,0 btfsc STATUS,2 goto Done movlw d'3' movwf MONTH clrf DAYS incf DAYS,f Done bcf INTCON,2 swapf STATUS_SAVE,0 movwf STATUS swapf TEMPW,f swapf TEMPW,0 retfie ;--------------------- End Interrupt Procedure ---------------------- INIT ; Initialize variables bsf STATUS,5 ; Select memory bank 1 (01) bcf STATUS,6 ; Select memory bank 1 (01) movlw b'00000000' movwf TRISB ; Set port B as output movlw b'01110000' ; movwf TRISA ; Set port A as output, RA4,5,6=Input bsf OPTION_REG,5 ; Select Timer0 (TOCS=1) bcf OPTION_REG,3 ; Assign prescaler to timer0 bcf OPTION_REG,0 ; Set prescaler to 128 bcf STATUS,5 ; Reset to bank 0 bcf STATUS,0 ; Clear carry bit bcf STATUS,2 ; Clear zero flag bcf STATUS,1 ; bsf INTCON,5 ; Enable timer0 interrupt bcf INTCON,2 ; Clear interrupt flag bsf INTCON,7 ; Enable global interrupt movlw 07h movwf CMCON ; Comparators off movlw d'2' movwf HOURS ; Initialize hours to 2 movlw d'56' movwf MINUTES ; Inititlize minutes to 56 movlw d'6' movwf HOURS_A ; Initialize alarm hours to 6 movlw d'30' movwf MINUTES_A movlw d'3' movwf MONTH ; Initialize Month to March, 7 movlw d'7' movwf DAYS movlw d'1' movwf WEEKDAY ; Initialize weekday to Sunday (1) clrf SECONDS clrf AMPM ; Initialize AMPM to AM movlw d'45' movwf TIMER_LIMIT ; Initialize alarm timer to 45 clrf AMPM_A clrf DAYLIGHT ; Turn off daylight savings time movlw d'2' movwf YEAR ; Set year to 2 (Leap year=4) movlw d'18' movwf CORRECTION ; Add 1 second every 18 hours clrf ALARM ; Turn off alarm clrf TIMER clrf LIMIT clrf AMPM_LED movlw h'21' movwf FSR ; Address pointer points to Hours movlw d'15' movwf SWITCH goto Main Array ; Data for 7 segment digits addwf PC,1 retlw b'01000000' ; "0" retlw b'01111001' ; "1" retlw b'00100100' ; "2" retlw b'00110000' ; "3" retlw b'00011001' ; "4" retlw b'00010010' ; "5" retlw b'00000010' ; "6" retlw b'01111000' ; "7" retlw b'00000000' ; "8" retlw b'00010000' ; "9" Table ; Days per month plus 1 addwf PC,1 retlw d'00' ; Unused line retlw d'32' ; Jan retlw d'30' retlw d'32' retlw d'31' retlw d'32' ; May retlw d'31' retlw d'32' retlw d'32' retlw d'31' retlw d'32' retlw d'31' retlw d'32' ; December Main ; ------------ Main Loop ---------------------- call Display ; Display data call Read_Port ; Check for switch closed movlw d'14' ; Check for time switch closed xorwf SWITCH,0 btfss STATUS,2 goto Set_Time movlw h'21' movwf FSR Set_Time movlw d'46' ; Check for time switch and RA5 closed xorwf SWITCH,0 btfss STATUS,2 goto Increment_Display movlw h'21' movwf FSR Increment_Display movlw d'13' xorwf SWITCH,0 btfss STATUS,2 goto Function ; Function key not hit (13) call Wait ; Wait for switch to open call Increment_Pointer Function movlw d'45' xorwf SWITCH,0 btfss STATUS,2 goto Increment_100s ; Function key not hit (13) call Wait ; Wait for switch to open call Increment_Pointer Increment_100s ; On plus RA5 = 32 + 11 = 43 movlw d'43' xorwf SWITCH,0 btfss STATUS,2 goto Increment_10s call Wait incf INDF,f movlw d'13' ; Rollover at 12 xorwf INDF,0 btfsc STATUS,2 clrf INDF Increment_10s ; RA5 + alarm off = 39 movlw d'39' xorwf SWITCH,0 btfss STATUS,2 goto Alarm_Toggle call Wait incf FSR,f incf INDF,f movlw d'60' ; Rollover at 60 xorwf INDF,0 btfsc STATUS,2 clrf INDF movlw h'28' ; Check for Seconds display xorwf FSR,0 btfsc STATUS,2 clrf SECONDS ; Zero seconds decf FSR,f Alarm_Toggle movlw d'7' ; Alarm Off xorwf SWITCH,0 btfsc STATUS,2 bcf ALARM,7 movlw d'11' ; Alarm On xorwf SWITCH,0 btfss STATUS,2 goto Main bsf ALARM,7 clrf TIMER goto Main ;--------------------- End of Main Loop ------------------------------ Output ; Write data to port B call Array iorwf ALARM,0 movwf PORTB return Delay ;------------------------ Delay ---- about 600 uS ------------ movlw d'25' Delay_0 movwf TEMP1 Delay_1 movwf COUNTER Delay_2 decfsz COUNTER,f goto Delay_2 decfsz TEMP1,f goto Delay_1 return Digits ; Converts value in TEMP to 2 single digits - TENS and TEMP clrf TENS movlw d'10' Loop incf TENS,f subwf TEMP,f btfss STATUS,0 goto Ones goto Loop Ones decf TENS,f addwf TEMP,f return Read_Port ; Look to see if switch is closed movlw d'127' movwf PORTA iorwf ALARM,0 ; add alarm bit movwf PORTB ; Set port B to high level bsf STATUS,5 ; Select bank 1 (01) movlw b'01111111' movwf TRISA ; Set port A as input, RA7=output movlw b'00111111' movwf TRISA ; Set RA6 to output bcf STATUS,5 ; Return to bank 0 (00) bcf PORTA,6 ; Low level on RA6 movlw d'10' call Delay_0 ; Wait movfw PORTA ; Read the pins movwf SWITCH bsf STATUS,5 ; Select Bank 1 movlw b'01111111' movwf TRISA ; Set port A to input movlw b'01110000' movwf TRISA ; Set porta,0,1,2,3 to output bcf STATUS,5 ; Return to Bank 0 movlw b'00101111' ; RA5 is normally 0 andwf SWITCH,f ; Switch returns value 0 to 47 return Alarm incf TIMER,f movfw TIMER_LIMIT ; Default is 45 minutes xorwf TIMER,0 btfsc STATUS,2 bcf ALARM,7 movfw HOURS xorwf HOURS_A,0 btfss STATUS,2 return movfw MINUTES xorwf MINUTES_A,0 btfss STATUS,2 return movfw AMPM xorwf AMPM_A,0 btfss STATUS,2 return bsf ALARM,7 clrf TIMER return Add_Second incf LIMIT,f movfw CORRECTION xorwf LIMIT,0 btfss STATUS,2 return incf SECONDS,f clrf LIMIT return Daylight ;----------------------- Daylight savings adjustment btfss DAYLIGHT,0 ; Bit 0 set = Daylight enabled return movlw d'1' ; Check for Sunday xorwf WEEKDAY,0 btfss STATUS,2 return movlw d'3' ; Adjust daylight at 3AM xorwf HOURS,0 btfss STATUS,2 return btfsc AMPM,0 ; Adjust daylight if AM return movlw d'3' xorwf MONTH,0 btfss STATUS,2 goto MinusHour btfss DAYS,3 ; Bit 3 must be set for 2nd Sunday return btfsc DAYLIGHT,1 ; Bit 1 set = Correction done (March) return incf HOURS,f bsf DAYLIGHT,1 ; Correction done return MinusHour ;---------- Subtract 1 hour on 1st Sunday in November movlw d'11' xorwf MONTH,0 btfss STATUS,2 return btfss DAYLIGHT,1 ; Bit 1 set = Do Correction return decf HOURS,f bcf DAYLIGHT,1 ; Bit 1 clear = Correction done return Display ; -------------------- Display Data ----------------------- clrf AMPM_LED ; AMPM off movlw h'21' xorwf FSR,0 btfss STATUS,2 goto $ +3 btfsc AMPM,0 bsf AMPM_LED,7 ; Add AMPM light (time) movlw h'23' xorwf FSR,0 btfss STATUS,2 goto $ +3 btfsc AMPM_A,0 bsf AMPM_LED,7 ; Add AMPM light (alarm) movfw INDF ; Get 100s data movwf TEMP call Digits btfss TENS,0 goto Ones_Hours movfw TENS ; Light 10s Hours LED call Output movlw d'14' iorwf AMPM_LED,0 ; Add AMPM light if time or alarm movwf PORTA call Delay Ones_Hours movfw TEMP call Output movlw d'13' iorwf AMPM_LED,0 ; Add AMPM light if time or alarm movwf PORTA call Delay incf FSR,f movfw INDF movwf TEMP call Digits movfw TENS call Output movlw d'11' iorwf AMPM_LED,0 ; Add AMPM light if time or alarm movwf PORTA call Delay movfw TEMP call Output movlw d'7' iorwf AMPM_LED,0 ; Add AMPM light if time or alarm movwf PORTA call Delay decf FSR,f return Wait ; Wait until switches are open call Display call Read_Port movlw d'15' ; Switches open in run mode xorwf SWITCH,0 btfsc STATUS,2 return movlw d'47' ; Switches open in program mode xorwf SWITCH,0 btfsc STATUS,2 return goto Wait Increment_Pointer incf FSR,f ; Increment Pointer 2 steps incf FSR,f movlw h'2f' xorwf FSR,0 btfss STATUS,2 return movlw h'21' movwf FSR ; Set Pointer to Time display return end -------------------------Compiled HEX code -------------------------- :0200000059287D :08000800B700030EB9008312DA :10001000A80A3C302806031D5328A801A20A122171 :100020003C302206031D5328A201A10A2E212621BD :100030000D302106031D1E28A101A10A0C30210646 :10004000031D5328AB0AAB102B185328A60A25080A :1000500097202606031D3C28A601A60AA50A0D30F6 :100060002506031D3C28A501A50AAD0A05302D066D :10007000031D3C28AD01AD0AA70A08302706031D61 :100080004328A701A70A02302506031D53281D3067 :100090002606031D532804302D0603195328033068 :1000A000A500A601A60A0B11390E8300B70E370E64 :1000B0000900831603130030860070308500811616 :1000C0008111011083120310031183108B160B1181 :1000D0008B1707309F000230A1003830A200063095 :1000E000A3001E30A4000330A5000730A600013095 :1000F000A700A801AB012D30AA00A901AC01023074 :10010000AD001230AE00BB01BF01BD01C001213006 :1001100084000F30B800A528820740347934243495 :1001200030341934123402347834003410348207F5 :10013000003420341E3420341F3420341F34203443 :1001400020341F3420341F3420344E21FA200E3046 :100150003806031DAD28213084002E303806031DDB :10016000B328213084000D303806031DB9287E21C4 :1001700089212D303806031DBF287E2189212B308F :100180003806031DC9287E21800A0D300006031998 :10019000800127303806031DD9287E21840A800A71 :1001A0003C30000603198001283004060319A80119 :1001B0008403073038060319BB130B303806031DC0 :1001C000A528BB17BF01A5288C203B04860008008A :1001D0001930BA00A000A00BEB28BA0BEA280800DF :1001E000B6010A30B60AB502031CF728F228B60396 :1001F000B50708007F3085003B04860083167F30FA :1002000085003F308500831205130A30E920050878 :10021000B80083167F3085007030850083122F3040 :10022000B8050800BF0A2A083F060319BB132108B6 :100230002306031D080022082406031D08002B08BE :100240002906031D0800BB17BF010800BD0A2E08C0 :100250003D06031D0800A80ABD0108002C1C08006B :1002600001302706031D080003302106031D080086 :100270002B18080003302506031D4529A61D08007C :10028000AC180800A10AAC1408000B302506031DA9 :100290000800AC1C0800A103AC100800C00121300C :1002A0000406031D55292B18C01723300406031D0F :1002B0005B292918C0170008B500F020361C6629F4 :1002C0003608E4200E3040048500E8203508E4209C :1002D0000D3040048500E820840A0008B500F020B5 :1002E0003608E4200B3040048500E8203508E4207F :1002F000073040048500E820840308004E21FA20DE :100300000F303806031908002F303806031908008B :100310007E29840A840A2F300406031D0800213038 :04032000840008004D :02400E00103F61 :00000001FF

Menu