Owner: Jaakko Ala-Paavola Date:1.Jan.2000

Software interrupt based real time clock

source code project

for PIC microcontroller

Jaakko Ala-Paavola

http://www.iki.fi/jap jap@iki.fi


16 january 2000

Document version: 0.2

Document status: under testing


Contents:

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

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.

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.

3. Clock technical specification

4.1 Registers

For basic clock operations six registers are needed:
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

4.2 Crystal division

There are two constants in the code needed for exact time calculation
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
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
Examples:
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)
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
PSD = 4*PSD2 + 2*PSD1 + 1*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

Real time clock interrupt 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:

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
The unused bits of time flag register are used:

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:

movlw    0x80
addwf	 timef
this will clear all other flags but keep leap-year flag untouched.

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

Calendar function program flow

5. Source code

DISCLAIMER:

THE FOLLOWING SOURCE CODES ARE PROVIDED AS IS WITHOUT ANY WARANTY.

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