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