Digital Multiple Clock Timer With PIC16F628 Microcontroller

This circuit uses the PIC16F628 microcontroller to provide 4 timed outputs that can be programmed from 1 minute to 1 week. An extra 8 bit shift register (74HC164) is used for the 4 timer outputs and to display four additional indicator lights.
On bootup, the display will show current time of 2:57 AM and all timer settings will be set to zero. One toggle and four momentary switches are used to enter and display data. A SPST toggle switch (A/B) is used to select the setup or Run mode and the other four switches can be momentary push buttons used to enter data or manually toggle timer outputs on and off.

Switch functions are as follows:


Run/Setup (A/B) - Selects run or setup mode. (A = Run)
Time (C) - Displays current time, active in both
setup and run modes.
Hours (E) - Increments Hours data. Active in Setup mode.
Minutes (F) - Increments Minutes data. Active in Setup mode.
Snooze Timer (E) - Toggles timer #1 on for selected time. Active in
run mode.
Manual select (F) - Toggles selected timers on or off. Active in
run mode.
Advance (D) - Advances display to next data. Active in
both setup and run modes.

There are 19 displays. The first 3 indicate current time, AMPM and day of week, and options and seconds. The remaining 16 displays indicate start and stop times, AMPM and day of week for the 4 timers. AMPM is indicated with a 1 or 0 in the hours digit (1=PM) and day of week is indicated in the minutes digit (1-7) (1 = Sunday). The current display is indicated by the 4 LEDs attached to the lower 4 bits of the 74HC164 shift register (bits 0 to 3). This makes it a little easier to figure out what is being displayed rather than counting from 1 to 19. The timer outputs are controlled by the upper 4 bits of the register (bits 4 to 7). The current display is the binary value of the 4 LEDs so that 0000 is the start time for timer 1 and 0001 is the AMPM and day of week setting for the start time of timer 1. The last display will be 1111 which indicates AMPM and day of week for the stop time of timer 4. However, since there are 19 displays, the first 4 are indicated by binary 0000. The sequence goes as follows:

0000  - Current Time
0000 - Current AMPM and day of week
0000 - Options and seconds
0000 - Start time for timer 1
0001 - AMPM and day of week for start time of timer 1
0010 - Stop time for timer 1
0011 - AMPM and day of week for stop time of timer 1
0100 - Start time for timer 2
0101 - AMPM and day of week for start time of timer 2
0110 - Stop time for timer 2
0111 - AMPM and day of week for stop time of timer 2
1000 - Start time for timer 3
1001 - AMPM and day of week for start time of timer 3
1010 - Stop time for timer 3
1011 - AMPM and day of week for stop time of timer 3
1100 - Start time for timer 4
1101 - AMPM and day of week for start time of timer 4
1110 - Stop time for timer 4
1111 - AMPM and day of week for stop time of timer 4

Some examples are listed here:

To set timer 2 for 7:00AM to 8:00AM every day, first set the setup/run switch to the setup mode. Then press the time switch to get to the top of the stack. Next press the advance switch until 0100 is displayed. Next use the hours and minutes switches to set the time to 7:00. Then press the advance switch once so the display is 0101 and set the display for 0:00 which means AM and every day of the week. If you wanted PM, the hours digit would be set to '1' and if you wanted the event to occur only on Mondays, the minutes digit would be set to '2' (Sunday=1)

Parallel Mode:

In the parallel mode, all events are sent to timer #1 so that four timed intervals can be obtained from timer 1. So, if you set timer 2 to start at 8:00AM, both timer 1 and 2 will start at 8:00AM. The parallel mode is activated by entering a '1' in the options field, which is the hours digit of the third display (0000). This same display shows the current seconds in the minutes digit.

Snooze function:

The snooze feature allows timer 1 to be set for periods of 5 to 75 minutes in 5 minute steps. To use it, first select the desired time by advancing the LED display to the binary value divided by 5. So, if you want 5 minutes, set the display to 0001. 10 minutes is 0010 and so forth. When the correct value is displayed, press the hours switch to begin. Note that using a settimg of 0000 will turn on timer 1 indefinetly until a stop time is encountered or the timer is manually switched off with the minutes key.

Manual control:

The state of all 4 timers can be set manually by advancing the LED display to the desired state and then pressing the minutes key. For example, to engage timer 1 and 3, the LED display will be set to 0101. To reset the timers press the time key so the LED display is 0000 and then press the minutes key.

----------------------Program Listing ---------------------------


;--------------------- Clock2.asm 09/19/07 -------------------

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 ; Current Time
MINUTES equ 22h
AMPM equ 23h
WEEKDAY equ 24h
OPTIONS equ 25h
SECONDS equ 26h

HOURS_START1 equ 27h ; Timer 1 0000
MINUTES_START1 equ 28h
AMPM_START1 equ 29h ; 0001
WEEKDAY_START1 equ 2ah
HOURS_STOP1 equ 2bh ; 0010
MINUTES_STOP1 equ 2ch
AMPM_STOP1 equ 2dh ; 0011
WEEKDAY_STOP1 equ 2eh

HOURS_START2 equ 2fh ; Timer 2 0100
MINUTES_START2 equ 30h
AMPM_START2 equ 31h ; 0101
WEEKDAY_START2 equ 32h
HOURS_STOP2 equ 33h ; 0110
MINUTES_STOP2 equ 34h
AMPM_STOP2 equ 35h ; 0111
WEEKDAY_STOP2 equ 36h

HOURS_START3 equ 37h ; Timer 3 1000
MINUTES_START3 equ 38h
AMPM_START3 equ 39h ; 1001
WEEKDAY_START3 equ 3ah
HOURS_STOP3 equ 3bh ; 1010
MINUTES_STOP3 equ 3ch
AMPM_STOP3 equ 3dh ; 1011
WEEKDAY_STOP3 equ 3eh

HOURS_START4 equ 3fh ; Timer 4 1100
MINUTES_START4 equ 40h
AMPM_START4 equ 41h ; 1101
WEEKDAY_START4 equ 42h
HOURS_STOP4 equ 43h ; 1110
MINUTES_STOP4 equ 44h
AMPM_STOP4 equ 45h ; 1111
WEEKDAY_STOP4 equ 46h

TEMP equ 47h ; Value passed to Digits routine
TENS equ 48h ; Value returned from Digits routine
TEMPW equ 49h ; Used in interrupt to save w
SWITCH equ 4ah ; Value returned from switches

STATUS_SAVE equ 4bh ; Interrupt (save status)
TEMP1 equ 4ch ; Part of delay routine
ALARM equ 4dh ; Alarm on/off (bit 7 set =on)
TEMP_SAVE equ 4eh ; Saves a copy of TEMP
TIMER equ 4fh
AMPM_LED equ 50h
FLAG equ 51h
PORTC equ 52h ; Current state of 74HC164 register
PORTC_OUT equ 53h ; Copy of PORTC to output
COUNTER1 equ 54h
TEMP2 equ 55h
STEPS equ 56h ; Current state of the 4 LED indicators
FLAG1 equ 57h

;--------------------- 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 Check_events ; Check events where minutes are not 0

call Snooze_Timer
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 Check_events ; Check events where minutes = 0

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

WeekDay
incf WEEKDAY,f
movlw d'8'
xorwf WEEKDAY,0
btfss STATUS,2 ; Check for new week
goto Done
clrf WEEKDAY
incf WEEKDAY,f ; Set weekday to 1 = Sunday
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 h'22'
movwf FSR

Load_Defaults ; Set all values to 0

movlw d'0'
incf FSR,f ; Increment Pointer
movwf INDF
movlw h'5a'
xorwf FSR,0
btfss STATUS,2
goto Load_Defaults
movlw h'21'
movwf FSR ; Set Pointer to Time display

movlw d'2'
movwf HOURS
movlw d'57'
movwf MINUTES
call Write_PortC ; Clear shift register
goto Main

Timer_Table ; Set or Reset 1 bit of portC
call Parallel
bsf FLAG,1 ; Write port C if flag set
movfw COUNTER1
addwf PC,1
bsf PORTC,4
return
bcf PORTC,4
return
bsf PORTC,5
return
bcf PORTC,5
return
bsf PORTC,6
return
bcf PORTC,6
return
bsf PORTC,7
return
bcf PORTC,7
return

Parallel ; Set or reset bit 4 of portC if parallel option selected

movlw d'0'
xorwf OPTIONS,0
btfsc STATUS,2
return
movfw COUNTER1
addwf PC,1
bsf PORTC,4
return
bcf PORTC,4
return
bsf PORTC,4
return
bcf PORTC,4
return
bsf PORTC,4
return
bcf PORTC,4
return
bsf PORTC,4
return
bcf PORTC,4
return

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"

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
clrf STEPS
clrf FLAG1
call Update_PortC
call Wait

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
clrf STEPS
clrf FLAG1
call Update_PortC
call Wait

Increment_Display

movlw d'13' ; Check for advance key pressed in run mode
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' ; Ckeck for advance key pressed in setup mode
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' ; Check for hours key pressed
xorwf SWITCH,0
btfss STATUS,2
goto Increment_10s
call Wait

incf INDF,f
movlw d'13' ; Rollover to 0 at 13
xorwf INDF,0
btfsc STATUS,2
clrf INDF

Increment_10s ; RA5 + alarm off = 39

movlw d'39' ; Check for minutes key pressed
xorwf SWITCH,0
btfss STATUS,2
goto Timer_Toggle
call Wait
incf FSR,f
incf INDF,f
movlw d'60' ; Rollover to 0 at 60
xorwf INDF,0
btfsc STATUS,2
clrf INDF
decf FSR,f

Timer_Toggle ; Copy LED states to timer output

movlw d'7'
xorwf SWITCH,0
btfss STATUS,2
goto Snooze
movfw STEPS
movwf TEMP
rlf TEMP,f
rlf TEMP,f
rlf TEMP,f
rlf TEMP,f
clrf PORTC
movfw TEMP
iorwf PORTC,f
call Update_PortC
call Wait

Snooze ; Set snooze timer for 5 to 75 minutes
movlw d'11'
xorwf SWITCH,0
btfss STATUS,2
goto Main
bsf PORTC,4
call Update_PortC
movfw STEPS
clrf TIMER
movwf COUNTER
movlw d'5'
Add
addwf TIMER,f ; Multiply timer by counter
decfsz COUNTER,f
goto Add
Repeat
movlw d'11'
xorwf SWITCH,0
btfss STATUS,2
goto Main
movfw TIMER
movwf TEMP
call Digits

movfw TENS
call Output ; Display snooze timer setting (tens digit)
movlw d'11'
movwf PORTA
call Delay

movfw TEMP
call Output ; Display snooze timer setting (ones digit)
movlw d'7'
movwf PORTA
call Delay
call Read_Port ; Look for switch open
goto Repeat ; Continue to display until switch open


;-------------------------- End of Main Loop ------------------------


Snooze_Timer ; Decrement timer from preset value
movlw d'0'
xorwf TIMER,0
btfsc STATUS,2
return
decf TIMER,f
movlw d'0'
xorwf TIMER,0
btfss STATUS,2
goto $ +3
bcf PORTC,4
call Update_PortC
return



Output ; Write data to port B
call Array
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
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

Check_events ;Check data for timers 1 to 8 --- Start at address h27

movfw FSR
movwf TEMP2 ; Save pointer
clrf COUNTER1
bcf FLAG,0
movlw h'27'
movwf FSR

Timer_Loop ; Check current time against all events

movfw HOURS
xorwf INDF,0
btfss STATUS,2
bsf FLAG,0 ; No Good

incf FSR,f
movfw MINUTES
xorwf INDF,0
btfss STATUS,2
bsf FLAG,0

incf FSR,f
movfw AMPM
xorwf INDF,0
btfss STATUS,2
bsf FLAG,0

incf FSR,f ; Weekday
movlw d'0'
xorwf INDF,0
btfsc STATUS,2
goto $ +5 ; skip test if weekday =0

movfw WEEKDAY
xorwf INDF,0
btfss STATUS,2
bsf FLAG,0

btfss FLAG,0 ; data no good, jump
call Timer_Table
incf COUNTER1,f
incf COUNTER1,f
incf FSR,f
bcf FLAG,0
movlw d'16'
xorwf COUNTER1,0
btfss STATUS,2
goto Timer_Loop
movfw PORTC
movwf PORTC_OUT
clrf COUNTER1
btfss FLAG,1
goto $ +2
call Write_PortC

movfw TEMP2
movwf FSR ; Restore pointer
clrf FLAG
return

Write_PortC ; RB7 = clock RA7 = Data

btfsc PORTC_OUT,0
bsf PORTA,7
btfss PORTC_OUT,0
bcf PORTA,7
rrf PORTC_OUT,f
bsf PORTB,7 ; Move clock up
bcf PORTB,7 ; Move clock down
incf COUNTER1,f
movlw d'8'
xorwf COUNTER1,0
btfss STATUS,2
goto Write_PortC ; Next bit of 8
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)
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
call Step
movlw h'47'
xorwf FSR,0
btfss STATUS,2
return
movlw h'21'
movwf FSR ; Set Pointer to Time display
return

Step ; Increment step for LED display (range 0 to 15)

movlw h'29'
subwf FSR,0
btfsc STATUS,2
bsf FLAG1,0
btfss FLAG1,0
return
incf STEPS,f
movlw h'47'
xorwf FSR,0
btfsc STATUS,2
goto $ +2
goto Update_PortC
clrf STEPS
clrf FLAG1

Update_PortC ; Load LED display data into lower 4 bits of portc

clrf COUNTER1
bcf PORTC,0
bcf PORTC,1
bcf PORTC,2
bcf PORTC,3
movfw STEPS
iorwf PORTC,f ; add timer data (upper 4 bits)
movfw PORTC
movwf PORTC_OUT ; Move portc to output byte
call Write_PortC
return

end

--------------------- Compiled Hex code -----------------------

:020000003328A3
:08000800C900030ECB008312B6
:10001000A60A3C302606031D2D28A601A20A2A2185
:10002000F2203C302206031D2D28A201A10A2A211C
:100030000D302106031D1E28A101A10A0C30210646
:10004000031D2D28A30AA31023182D28A40A083065
:100050002406031D2D28A401A40A0B114B0E8300B6
:10006000C90E490E09008316031300308600703054
:100070008500811681110110831203100311831072
:100080008B160B118B1707309F0022308400003035
:10009000840A80005A300406031D4728213084005A
:1000A0000230A1003930A2005B218B286A20D114D4
:1000B000540882075216080052120800D21608008F
:1000C000D21208005217080052130800D217080075
:1000D000D2130800003025060319080054088207CF
:1000E0005216080052120800521608005212080058
:1000F0005216080052120800521608005212080048
:1001000082074034793424343034193412340234C0
:10011000783400341034682113210E304A06031D50
:10012000972821308400D601D701B52192212E30A5
:100130004A06031DA12821308400D601D701B5212C
:1001400092210D304A06031DA72892219D212D30B2
:100150004A06031DAD2892219D212B304A06031D1E
:10016000B7289221800A0D3000060319800127303C
:100170004A06031DC3289221840A800A3C300006E7
:1001800003198001840307304A06031DD22856084C
:10019000C700C70DC70DC70DC70DD2014708D20450
:1001A000B52192210B304A06031D8B285216B5212A
:1001B0005608CF01A0000530CF07A00BDC280B307C
:1001C0004A06031D8B284F08C70009214808FE2056
:1001D0000B30850001214708FE20073085000121F2
:1001E0001321DF2800304F0603190800CF03003029
:1001F0004F06031DFD285212B521080080208600FD
:1002000008001930CC00A000A00B0429CC0B032956
:100210000800C8010A30C80AC702031C10290B29AC
:10022000C803C70708007F308500860083167F302B
:1002300085003F308500831205130A30022105082E
:10024000CA0083167F3085007030850083122F30FE
:10025000CA0508000408D500D401511027308400D5
:1002600021080006031D5114840A22080006031DFC
:100270005114840A23080006031D5114840A003017
:1002800000060319472924080006031D5114511CB8
:100290005620D40AD40A840A511010305406031D83
:1002A00030295208D300D401D11C57295B215508AD
:1002B0008400D101080053188517531C8513D30CF3
:1002C00086178613D40A08305406031D5B290800DC
:1002D000D00121300406031D6F292318D017000810
:1002E000C7000921481C7A294808FE200E30500416
:1002F000850001214708FE200D30500485000121B2
:10030000840A0008C70009214808FE200B30500469
:10031000850001214708FE20073050048500012197
:1003200084030800682113210F304A0603190800CE
:100330002F304A06031908009229840A840AA7214B
:1003400047300406031D08002130840008002930CE
:10035000040203195714571C0800D60A4730040634
:100360000319B329B529D601D701D4015210D210EF
:100370005211D2115608D2045208D3005B21080052
:02400E00103F61
:00000001FF