Software Real Time Clock (RTC) for the dsPIC33EP512MC502

4.00 avg. rating (83% score) - 1 vote

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;          // Disable Timer
T1CONbits.TSIDL = 0;        // Continues module operation in Idle mode
T1CONbits.TCKPS = 0b00;     // 1:1 prescaler
T1CONbits.TSYNC = 1;        // Disable asynchronous mode

T1CONbits.TCS = 1;          // External clock is from pin T1CK on rising edge, connected to 32768 Hz crystal oscillator circuit

IPC0bits.T1IP = 0x06;       // Interrupt priority level (highest lvl 6 for RTC). Do not use level 7, as __builtin_disi (need to write flash) will not work.
IFS0bits.T1IF = 0;          // Clear Timer Interrupt Flag
IEC0bits.T1IE = 1;          // Enable timer 1 interrupt

PR1 = 32768;                // interrupt every 1 second (32768 count)
T1CONbits.TON = 1;          // Enable timer

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:

// RTC variables
// second: 0 .. 59
// minute: 0 .. 59
// hour:   0 .. 23
// day:    1 .. 31
// month:  1 .. 12
// year:   0 .. 99 for 2000 .. 2099
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++;        

    // range: 0 .. 59 (inclusive)
    if (curSecond >= 60)
    {
        curSecond = 0;
        curMinute++;
    }    

    // range: 0 .. 59
    if (curMinute >= 60)
    {
        curMinute = 0;
        curHour++;
    }        

    // range: 0 .. 23
    if (curHour >= 24)
    {
        curHour = 0;
	curDay++;
    }    

    // range: 1 .. maxDays (inclusive)
    unsigned char maxDays = getNumOfDaysInMonths(curMonth, curYear2k);
    if (curDay > maxDays)
    {
	    curDay = 1;
	    curMonth++;
    }

    // range: 1 .. 12
    if (curMonth > 12)
    {
	    curMonth = 1;
	    curYear2k++;

	    // Not catering for year overflow
    }
}

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)          // Exactly divisible by 400 e.g. 1600, 2000
      return TRUE;
  else if (year % 100 == 0)     // Exactly divisible by 100 and not by 400 e.g. 1900, 2100
      return FALSE;
  else if (year % 4 == 0)       // Exactly divisible by 4 and neither by 100 nor 400 e.g. 2020
      return TRUE;
  else                          // Not divisible by 4 or 100 or 400 e.g. 2017, 2018, 2019
      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);        // subtract a day from the first two months
  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:

time difference

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.

4.00 avg. rating (83% score) - 1 vote
ToughDev

ToughDev

A tough developer who likes to work on just about anything, from software development to electronics, and share his knowledge with the rest of the world.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>