1. Abstract 2. Functional specification 3. Clock technical specification 3.1 Registers 3.2 Crystal division 3.3 Initialization 3.4 Usage 3.5 Program flow 4. Calendar technical specification 4.1 Registers 4.2 Leap-year calculation 4.3 Initializations 4.4 Program flow 5. Source code 5.1 Clock 5.2 Calendar
PIC microcontrollers have internal 8-bit TMR0 counter register to count clock pulses or external pulses. The register can be configured to produce interrupt signal when overflowing from
The tiny clock consumes only about 5% of microcontrollers resources. The clock implementation is about 50 lines of assembler code. This takes 50 bytes of MCU's program memory, which is total 1024 bytes. The clock code is executed only when internal TMR0 register produce interrupt signal. This happens depending on clock crystal 10 to 100 times per second. At most of the cases only small part of clock code is executed, the average processing time consuption is 5%. The calendar version is twice as big as the smaller one about 100 bytes of program memory. The calendar update functionality is executed only once per day, so it doesn't load the processor.
Real time clock interrupt program flow
Calendar function program flow
THE FOLLOWING SOURCE CODES ARE PROVIDED AS IS WITHOUT ANY WARANTY.
1. Abstract
In many hardware projects there are needs for real time clock or delay source. Such devices as clocks, timers, etc. are impossible to product without knowledge of exact time. The coal of this project is to create interrupt driven real time clock for Microchip PIC16F84 microcontroller to be used in various applications. The source code can be applied in other PIC MCU version also.
2. Functional specification
There is two version of clock. A tiny 24h clock and a larger version with calendar. The clock base on calculating processors clock pulse. Typical clock crystals used in microcontroller devices have inaccuracy of less than 20 ppm which means less than 10 minutes per year. This is good enough for most of applications. The version with calendar has capability to calculate up to year 25599 taking care of leap-years. Clock has no summer-time winter-time transition functionality.
0xff to 0x00. This register can be prescaled up to 256 to divide the incoming pulses. Every time interrupt signal is generated the interrupt procedure is executed. The procedure simpli increase the value of time counter registers every time it is executed.
3. Clock technical specification
4.1 Registers
For basic clock operations six registers are needed:
Time flag register
msec equ 0x0c ; tens of milliseconds
sec equ 0x0d ; seconds
min equ 0x0e ; minutes
hour equ 0x0f ; hours
timef equ 0x10 ; register for time flags
save equ 0x11 ; save for ACCU
timef contains flags for every registers. These flag signals can be used for delay or timing usage in application program. The flags is rised when corresponding regsiter is increased. Application programs have to care of clearing. save register is needed to store the accumulator value of application program at the time of interrupt procedure execution. The time flags are as follow:
msf equ 0x00 ; millisecond flag
sf equ 0x01 ; second flag
mf equ 0x02 ; minute flag
hf equ 0x03 ; hour flag
df equ 0x04 ; day flag
4.2 Crystal division
There are two constants in the code needed for exact time calculation
The produce 1 Hz clock frequency the crystal frequency is divided as follows:
MSD equ 0x4b ; millisecond divider
PSD equ 0x05 ; prescale divider
The PreScaler division factor is ruled with three most low bits of
1 Hz = Fosc / (4 * PSD * 256 * MSD)
OPTION register, the relation is
Examples:
Bit value TMR0 rate 000 1:2 001 1:4 010 1:8 011 1:16 100 1:32 101 1:64 110 1:128 111 1:256
Crystal PSD MSD sec msec 1.6384 MHz 0x03 0x64 1.000 10.00 2.4576 MHz 0x05 0x4b 1.000 13.33 3.2768 MHz 0x04 0x64 1.000 10.00 4.1943 MHz 0x03 0xff 1.000 3.9 6.5536 MHz 0x05 0x64 1.000 10.00 4.3 Initializations:
Some initializations are needed after power-on to make clock working. TMRO prescale have to set and TMR0 inteerupt must be enabled. Also all time registers need to be initialized as zero. Initial value for time is 0:0:0.
OPTION REGISTER (0x81 bank 1)
PSD = 4*PSD2 + 2*PSD1 + 1*PSD0
Bit 7 6 5 4 3 2 1 0 Signal RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 Init 1 0 0 0 0 PSD2 PSD1 PSD0 INTCON REGISTER (0x0b bank 0)
Bit 7 6 5 4 3 2 1 0 Signal GIE EEIE T0IE INTE RBIE T0IF INTF RBIF Init 1 0 1 0 0 0 0 0 3.4 Usage
The application process can read or write the time registers [hour::msec] anytime also time flags can be read or set anytime. Process writing to time registers have to take care there is no illigal time value in register i.e. the value the second register can not be over 60 or hour register can not be over 24. Interrupt process does not recognize illegal values and the time will de delayd untill the current register value is overflowed to zero. In the case of hour register this may take even 232 hours (almost 10 days!).
3.5 Program flow

4. Calendar technical specifications
The calendar version is capable of calculating days, months and years. The software knows how many days there is in every single month. It cal also calculate the leap-years correctly. Maximum value for year counter is 25599 so there shouldn't be any millenium problems for a while.
4.1 Registers
Calendar functionality need four new register:
The unused bits of time flag register are used:
msec equ 0x0c ; tens of milliseconds
sec equ 0x0d ; seconds
min equ 0x0e ; minutes
hour equ 0x0f ; hours
day equ 0x10 ; days
month equ 0x11 ; months
year equ 0x12 ; years, low 2 digits
century equ 0x13 ; years, high 2 digits
timef equ 0x14 ; time flag register
save equ 0x15 ; save for ACCU
msecf equ 0x00 ; (1000/XD) milliseconds flag
secf equ 0x01 ; second flag
minf equ 0x02 ; minute flag
hourf equ 0x03 ; hour flag
dayf equ 0x04 ; day flag
monthf equ 0x05 ; month flag
yearf equ 0x06 ; year flag
lyf equ 0x07 ; leap-year flag (read-only)
4.2 Leap-year calculation
Every fourth years are leap-years in most of the cases. Years divisible with 100 aren't leap-years but years divisible with 400 are leap-years. In this program years are divided into two registers, year (low 2 digits) and century (high 2 digits). This makes leap-year calculation easy. The procedure for examing leap-years is as follows:
1. new year is not leap-year
2. if two last significant bits of year are zero then the year is leap-year
3. new century is not leap-year
4. if two last significant bits of century are zero then the year is leap year
This calculation is done at the time of updating year register. The information is stored into the leap-year flag. This is the reason why bit 7 of timef register is now read only. If a process want to clear all
time flags is should use command:
this will clear all other flags but keep leap-year flag untouched.
movlw 0x80
addwf timef
4.3 Initializations
The initialization routine is the same with 24h clock, except there is some
more registers to initialize. Day and month registers gets initial value of 1 and century = 20 and year = 0. the initial date is 1st January 2000.
4.4 Program flow

5. Source code
DISCLAIMER:
5.1 Clock
;;-------------------------------------------------
;; Interrupt driven real time clock for PIC16F84
;; Clock crystal 2.4576 MHz
;;
;; Author: Jaakko Ala-Paavola, 7 Jan. 2000
;; http://www.iki.fi/jap jap@iki.fi
;; ------------------------------------------------
include "p16c84.inc"
;; registers
msec equ 0x0c ; tens of milliseconds
sec equ 0x0d ; seconds
min equ 0x0e ; minutes
hour equ 0x0f ; hours
timef equ 0x10 ; register for time flags
save equ 0x11 ; save for ACCU
;; constants
msf equ 0x00 ; millisecond flag
sf equ 0x01 ; second flag
mf equ 0x02 ; minute flag
hf equ 0x03 ; hour flag
df equ 0x04 ; day flag
MSD equ 0x4b ; crystal divider (75)
PSD equ 0x05 ; millisecond divider
org 0
goto _main
org 0x04 ; void interrupt(void)
_interrupt ; {
movwf save ; save(ACCU);
bcf INTCON,T0IF ; INTCON,T0IF = 0;
incf msec,F ; msec++;
bsf timef,msf ; msf = 1;
movf msec,W ; ACCU = msec;
sublw XD ; if ((ACCU-XD) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf msec ; msec = 0;
bsf timef,sf ; msf = 1;
incf sec,F ; sec++;
movf sec,W ; ACCU = sec;
sublw 0x3c ; if ((ACCU-60) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf sec ; sec = 0;
bsf timef,minf ; sf = 1;
incf min,F ; min++;
movf min,W ; ACCU = min;
sublw 0x3c ; if ((ACCU-60) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf min ; min = 0;
bsf timef,hf ; hf = 1;
incf hour,F ; hour++;
movf hour,W ; ACCU = hour;
sublw 0x18 ; if ((ACCU-24) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf hour ; hour = 0;
bsf timef,df ; df = 1;
movf save,W ; }}}} restore(ACCU);
retfie ; }
_initialize
bsf STATUS,RP0 ; bank 1
movlw 0x7d ; RBPU=off, INTEDG=off, T0CS=osc, PSA=TMR0
addlw PSD ; PSD = b'101' [64]
movwf OPTIO ;
bcf STATUS,RP0 ; bank 0
movlw 0xa0 ; enable TMR0 interrupt
movwf INTCON ;
clrf msec ; msec = 0;
clrf sec ; sec = 0;
clrf min ; min = 0;
clrf hour ; hour = 0;
clrf timef ; all flags off;
;; ADD YOUR OWN INITIALIZATIONS HERE!
return
_main
call _init ; initialize();
;; ADD YOUR OWN PROGRAM CODE HERE !
END
5.2 Calendar
;;-------------------------------------------------
;; Interrupt driven real time clock with calendar
;; for PIC16F84 and derivatives
;; Clock crystal 2.4576 MHz
;;
;; Author: Jaakko Ala-Paavola, 7 Jan. 2000
;; http://www.iki.fi/jap jap@iki.fi
;; ------------------------------------------------
include "p16c84.inc"
;; registers
msec equ 0x0c ; tens of milliseconds
sec equ 0x0d ; seconds
min equ 0x0e ; minutes
hour equ 0x0f ; hours
day equ 0x10 ; days
month equ 0x11 ; months
year equ 0x12 ; years, low 2 digits
century equ 0x13 ; years, high 2 digits
timef equ 0x14 ; time flag register
save equ 0x15 ; save for ACCU
;; constants
msecf equ 0x00 ; (1000/XD) milliseconds flag
secf equ 0x01 ; second flag
minf equ 0x02 ; minute flag
hourf equ 0x03 ; hour flag
dayf equ 0x04 ; day flag
monthf equ 0x05 ; month flag
yearf equ 0x06 ; year flag
lyf equ 0x07 ; leap-year flag
XD equ 0x4b ; crystal divider (75)
org 0
goto _main
org 0x04 ; void interrupt(void)
_interrupt ; {
movwf save ; save(ACCU);
; Clock
bcf INTCON,T0IF ; INTCON,T0IF = 0;
incf msec,F ; msec++;
bsf timef,msecf ; msecf = 1;
movf msec,W ; ACCU = msec;
sublw XD ; if ((ACCU-XD) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf msec ; msec = 0;
bsf timef,secf ; secf = 1;
incf sec,F ; sec++;
movf sec,W ; ACCU = sec;
sublw 0x3c ; if ((ACCU-60) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf sec ; sec = 0;
bsf timef,minf ; minf = 1;
incf min,F ; min++;
movf min,W ; ACCU = min;
sublw 0x3c ; if ((ACCU-60) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf min ; min = 0;
bsf timef,hourf ; hourf = 1;
incf hour,F ; hour++;
movf hour,W ; ACCU = hour;
sublw 0x18 ; if ((ACCU-24) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf hour ; hour = 0;
; Calendar
bsf timef,dayf ; dayf = 1;
incf day ; day++;
movf month,W ; ACCU = month;
sublw 0x02 ;
btfss STATUS,Z ; if ((ACCU - 2) == 0)
goto _noleap ; {
btfsc timef,lyf ; if (leap_year)
andlw 0x00 ; ACCU = 0;
goto _leap ; }else
_noleap movf month,W ; ACCU = month;
_leap call _days ; ACCU = days[ACCU];
subwf day,W ; if ((day-ACCU) != 0)
btfss STATUS,Z ; return;
retfie ; else {
movlw 0x01 ; ACCU = 1;
clrf day ; day = ACCU;
bsf timef,monthf ; monthf = 1;
incf month ; month++;
movf month,W ; ACCU = month;
sublw 0x0d ; if ((ACCU-13) != 0)
btfss STATUS,Z ; return;
retfie ; else {
movlw 0x01 ; ACCU = 1;
movwf month ; month = ACCU;
bsf timef,yearf ; yearf = 1;
incf year ; year++;
bcf timef,lyf ; lyf = 0;
movf year,W ; ACCU = year;
andlw 0x03 ; if ((ACCU & 00000011) == 0)
btfsc STATUS,Z ; {
bsf timef,lyf ; lyf = 1;}
movf year,W ; ACCU = year;
sublw 0x64 ; if ((ACCU-100) != 0)
btfss STATUS,Z ; return;
retfie ; else {
clrf year ; year = 0;
incf century ; century++;
bcf timef,lyf ; lyf = 0;
movf century,W ; ACCU = century;
andlw 0x03 ; if ((ACCU & 00000011) == 0)
btfsc STATUS,Z ; {
bsf timef,lyf ; lyf = 1;
movf save,0 ; }}}}}}}}} restore(ACCU);
retfie ; }
_days addwf PCL ; Number of days per month
retlw 0x00 ; Leap-day 29
retlw 0x1f ; January 31
retlw 0x1c ; February 28
retlw 0x1f ; Mars 31
retlw 0x1e ; April 30
retlw 0x1f ; May 31
retlw 0x1e ; June 30
retlw 0x1f ; July 31
retlw 0x1f ; August 31
retlw 0x1e ; September 30
retlw 0x1f ; October 31
retlw 0x1e ; November 30
retlw 0x1f ; December 31
_initialize
bsf STATUS,RP0 ; bank 1
movlw 0x82 ; RBPU=off, INTEDG=off, T0CS=osc, PSA=TMR0
movwf OPTIO ; divider = 64 b'101'
bcf STATUS,RP0 ; bank 0
movlw 0xa0 ; enable TMR0 interrupt
movwf INTCON ;
clrf msec ; msec = 0;
clrf sec ; sec = 0;
clrf min ; min = 0;
clrf hour ; hour = 0;
movlw 0x01 ;
movwf day ; day = 1;
movwf month ; month = 0;
clrf year ;
movlw 0x14 ;
movwf century ; year = 2000
movlw 0x80 ;
movwf timef ; leap-year flag = 1; all others = 0
;; ADD YOUR OWN INITIALIZATIONS HERE!
return
_main
call _initialize ; initialize();
;; ADD YOUR OWN PROGRAM CODE HERE !
END
UP to parent directory