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 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.
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.
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
Time flag register 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
MSD equ 0x4b ; millisecond divider
PSD equ 0x05 ; prescale divider
The produce 1 Hz clock frequency the crystal frequency
is divided as follows:
1 Hz = Fosc / (4 * PSD * 256 * MSD)
The PreScaler division factor is ruled with three most
low bits of 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
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
Real time clock interrupt program flow
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)
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 yearThis 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
Calendar function program flow
DISCLAIMER:
THE FOLLOWING SOURCE CODES ARE PROVIDED AS IS WITHOUT ANY WARANTY.
;;-------------------------------------------------
;; 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
;;-------------------------------------------------
;; 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