This post share codes which I have written to implement a software RTC for the Gregorian calendar.
Although the dsPIC33EP512MC502
does not have an integrated Real Time Clock (RTC) for time keeping, this can easily be overcome by configuring timer 1 to run from external 32768Hz crystal and implementing the clock routines in software instead. This post will share the codes which I have written to implement a software RTC for the Gregorian calendar.
First, we need to configure timer 1 to run from an external 32768 Hz crystal connected to T1CK pin and interrupt every second:
T1CONbits.TON = 0; T1CONbits.TSIDL = 0; T1CONbits.TCKPS = 0b00; T1CONbits.TSYNC = 1;
T1CONbits.TCS = 1;
IPC0bits.T1IP = 0x06; IFS0bits.T1IF = 0; IEC0bits.T1IE = 1;
PR1 = 32768; T1CONbits.TON = 1;
In the above code, TSYNC
is set to 1
to disable timer 1
asynchronous mode. According to the datasheet, when the External Clock mode is selected as the Timer1
source, it can run asynchronously to the system clock. If TSYNC
is 0
, it will make the external clock un-synchronized. The timer will then increment asynchronously to the internal phase clocks. Running asynchronously allows the external clock source to continue incrementing the timer during sleep and can generate an interrupt on overflow. The interrupt will wake up the processor so an internal time-based application can be updated. However, special precautions in software are needed to read/write the timer in this mode. For a start, we set TSYNC
to 1
so that the external clock input is always synchronized.
Remember to purchase the crystal from reliable vendors such as Mouser or Digikey. Otherwise, if you are unlucky enough, your crystal (like mine) will be widely inaccurate and your clock will be slow by as much as 10 minutes after just a couple of days! While I loved eBay and AliExpress and have purchased many things from them without issues, the accuracy of 32.768 kHz crystals
purchased from many sellers on these sites is questionable.
Next, we declare our global variables for time keeping:
unsigned char curSecond, curMinute, curHour, curDay, curMonth, curYear2k;
When the PIC
starts, or when the user sets the clock, the code will need to set above time keeping variables to the current date and time. After that, we need to implement timer 1 interrupt routine, which is called every second:
void __attribute__((interrupt, no_auto_psv)) _T1Interrupt (void)
{
IFS0bits.T1IF = 0;
rtcAddSec();
}
Function rtcAddSec()
is written below, which updates the clock given that there are 60 seconds in a minutes, 60 minutes in an hour, 24 hours in a day and anything from 28 to 31 days in a month:
void rtcAddSec()
{
curSecond++;
if (curSecond >= 60)
{
curSecond = 0;
curMinute++;
}
if (curMinute >= 60)
{
curMinute = 0;
curHour++;
}
if (curHour >= 24)
{
curHour = 0;
curDay++;
}
unsigned char maxDays = getNumOfDaysInMonths(curMonth, curYear2k);
if (curDay > maxDays)
{
curDay = 1;
curMonth++;
}
if (curMonth > 12)
{
curMonth = 1;
curYear2k++;
}
}
The number of days in a month will then be calculated by getNumOfDaysInMonths()
:
unsigned char getNumOfDaysInMonths(unsigned char month, unsigned char year2k)
{
if (month == 1 || month == 3 || month == 5 || month == 7 ||
month == 8 || month == 10 || month == 12)
return 31;
if (month == 4 || month == 6 || month == 9 || month == 11)
return 30;
if (month == 2)
{
if (isLeapYear(year2k))
return 29;
return 28;
}
return 0;
}
Function isLeapYear()
helps us know whether a year is a leap year or not:
BOOL isLeapYear(unsigned char year2k)
{
unsigned int year = 2000 + year2k;
if (year % 400 == 0)
return TRUE;
else if (year % 100 == 0)
return FALSE;
else if (year % 4 == 0)
return TRUE;
else
return FALSE;
}
With the above code, our basic RTC
is done and the current time can be retrieved from the global variables. To retrieve the day of week, use the following function, which will return 0 for Sunday, 1 for Monday, 2 for Tuesdays, etc:
unsigned char getDayOfWeek(unsigned int year4d, unsigned int month, unsigned int day)
{
unsigned char t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
unsigned int y = year4d - (month < 3); return (y + y/4 - y/100 + y/400 + t[month-1] + day) % 7;
}
To test the RTC
implementation, pick two different dates and use something like this to retrieve the time difference in seconds between the two dates:
After that, without enabling timer 1 interrupt, begin with the first date and call rtcAddSec()
in a for
loop until you reach the end date. If the implementation is correct, the number of calls to rtcAddSec()
should be equal to the time difference in seconds. With a large time difference, this may take a long time on the PIC
so you might want to test the codes using some C compiler on your desktop PC instead.
The above code, despite being written for the dsPIC
, can be adapted to work on any micro-controller that can generate an interrupt every second using an external 32.768kHz crystal. Feel free to use it in your projects and let me know if you have any comments.