RTC: Real Time Clock

The real time clock uses the slow clock which can be set to the internal RC Oscillator or an external crystal. In this example we will use an external crystal, so we need to change from the default setting of using the RC Osc and change the slow clock to the external crystal.

Setup

We can accept most of the default settings for the RTC. We will change the slow clock to use the external crystal with one command which also disables the PIO controller on the XTAL pins. This command needs to be OR'd with a password to be able to change it. After selecting the external crystal, wait for it to be enabled by polling the SUPC status register's OSCSEL bit. The crystal that we are using has a frequency tolerance of +-20ppm. We will setup the drift correction for this as well. If the ppm is above 30 then we use the formula:

correction = (3906/20*ppm) - 1
If the crystal's ppm is 0 - 30 ppm then we use the following formula:
correction = (3906/ppm)-1
The frequency tolerance of our crystal is 20, so we will use the second formula. (3906/20*20)-1 = 8.765 ~ 9
void slowClock_init(){	
    //slow clock setup
    //switch from RC osc to external crystal, disable RC osc, disable PIO on xtal pins
    REG_SUPC_CR |= SUPC_CR_XTALSEL | SUPC_CR_KEY_PASSWD;
    //read status register.oscsel bit and wait for slow clock to be selected
    while (!(REG_SUPC_SR & SUPC_SR_OSCSEL));
    //set crystal correction
    REG_RTC_MR &= ~RTC_MR_HIGHPPM;
    //correction = (3906/20*ppm)-1 = 8.765
    REG_RTC_MR |= RTC_MR_CORRECTION(9);
}

Setting Time

To set the time, we set the bit to disable the rtc for date or time in the REG_RTC_CR register. Then we poll to make sure the clock has stopped and clear the ACKCLR bit in the SCCR register, update the time or date and then restart the rtc clock by clearing the CR_UPDTIM bit and setting the TIMCLR or CALCLR bit in the SCCR register.

Reading Time

To read the time, we just read the time and calendar registers return the 32bit variable and then we can decode the BCD values and display however we want.

uint32_t rtcGetTime (){
    uint32_t time1=1;
    uint32_t time2=2;
    /*read reg_rtc_timr twice and compare
    if it's the same then the data is good
    otherwise read again twice until it matches
    */
    while (!(time1==time2)){
        time1 = REG_RTC_TIMR;
        time2 = REG_RTC_TIMR;
    }
    return time2;
}

uint32_t rtcGetDate (){
    uint32_t date1=1;
    uint32_t date2=2;
    /*read reg_rtc_calr twice and compare
    if it's the same then the data is good
    otherwise read again twice until it matches
    */
    while (!(date1==date2)){
        date1 = REG_RTC_CALR;
        date2 = REG_RTC_CALR;
    }
    return date2;	
}

Example


#include "sam.h"
#include "basic_uart.h"

void clock_init(){
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCXTEN;
    while (!(REG_PMC_SR & PMC_SR_MOSCXTS));
    REG_CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;
    REG_PMC_MCKR |= MC_MCKR_CSS_MAIN_CLK;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
    REG_PMC_MCKR |= PMC_MCKR_PRES_CLK_1;
    while (!(REG_PMC_SR & PMC_SR_MCKRDY));
}

uint8_t bcdToDecimal(uint8_t bcdByte){	
    return ((((bcdByte & 0xF0) >> 4) * 10) + (bcdByte & 0x0F));	
}

uint8_t decimalToBCD(uint8_t decByte){	
    return (((decByte/10) <<4) | (decByte%10));
}

//cent		19-20(gregorian) / 13-14 (persian)
//year		00-99
//month		01-12
//day		1-7
//date		01-31

void rtcSetDate(uint8_t date, uint8_t day, uint8_t month, uint8_t year, uint8_t cent){	
    uint32_t newDate = 0;
	
    //convert input into BCD and pass values to register
    newDate |= RTC_CALR_DATE(decimalToBCD(date));
    newDate |= RTC_CALR_DAY(decimalToBCD(day));
    newDate |= RTC_CALR_MONTH(decimalToBCD(month));
    newDate |= RTC_CALR_YEAR(decimalToBCD(year));
    newDate |= RTC_CALR_CENT(decimalToBCD(cent));
	
    //stop rtc calendar counting
    REG_RTC_CR |= RTC_CR_UPDCAL;
    //check that the calendar is stopped
    while(!(REG_RTC_SR & RTC_SR_ACKUPD));
    //clear ackupd
    REG_RTC_SCCR |= RTC_SCCR_ACKCLR;		
    REG_RTC_CALR = newDate;
    //clear updcal in control register
    REG_RTC_CR &= ~RTC_CR_UPDCAL;
    //start clock
    REG_RTC_SCCR |= RTC_SCCR_CALCLR;	
}

//hour		1-12 in 12 hour mode / 1-23 in 24 hour mode
//minute	0-59
//second	0-59

void rtcSetTime(uint8_t hour, uint8_t minute, uint8_t second){
    uint32_t newTime = 0;
	
    //convert input into BCD and pass values to register
    newTime |= RTC_TIMR_SEC(decimalToBCD(second));
    newTime |= RTC_TIMR_HOUR(decimalToBCD(hour));
    newTime |= RTC_TIMR_MIN(decimalToBCD(minute));
    printWord (newTime>>16);
    printWord (newTime);
	
    //stop rtc time counting
    REG_RTC_CR |= RTC_CR_UPDTIM;
    //check that the time is stopped
    while(!(REG_RTC_SR & RTC_SR_ACKUPD));
    //clear ackupd
    REG_RTC_SCCR |= RTC_SCCR_ACKCLR;
	
    REG_RTC_TIMR = newTime;
    //clear updtim in rtc_cr
    REG_RTC_CR &= ~RTC_CR_UPDTIM;
    //start clock
    REG_RTC_SCCR |= RTC_SCCR_TIMCLR;
}

void slowClock_init(){	
    //slow clock setup
    //switch from RC osc to external crystal, disable RC osc, disable PIO on xtal pins
    REG_SUPC_CR |= SUPC_CR_XTALSEL | SUPC_CR_KEY_PASSWD;
    //read status register.oscsel bit and wait for slow clock to be selected
    while (!(REG_SUPC_SR & SUPC_SR_OSCSEL));
	
    //set crystal correction
    REG_RTC_MR &= ~RTC_MR_HIGHPPM;
    //correction = (3906/20*ppm)-1 = 8.765
    REG_RTC_MR |= RTC_MR_CORRECTION(9);
}

uint32_t rtcGetTime (){	
    uint32_t time1=1;
    uint32_t time2=2;
    /*read reg_rtc_timr twice and compare
    if it's the same then the data is good
    otherwise read again twice until it matches
    */
    while (!(time1==time2)){	
        time1 = REG_RTC_TIMR;
        time2 = REG_RTC_TIMR;	
    }
    return time2;	
}

uint32_t rtcGetDate (){
    uint32_t date1=1;
    uint32_t date2=2;
    /*read reg_rtc_calr twice and compare
    if it's the same then the data is good
    otherwise read again twice until it matches
    */
    while (!(date1==date2)){	
        date1 = REG_RTC_CALR;
        date2 = REG_RTC_CALR;
    }
    return date2;	
}

void printDate(){
    //get calendar register data
    uint32_t dateData = rtcGetDate();
    //mask the bits and shift to the first byte
    uint8_t month = (dateData & RTC_CALR_MONTH_Msk) >> RTC_CALR_MONTH_Pos;
    uint8_t date = (dateData & RTC_CALR_DATE_Msk) >> RTC_CALR_DATE_Pos;
    uint8_t day = (dateData & RTC_CALR_DAY_Msk) >> RTC_CALR_DAY_Pos;
    uint8_t year = (dateData & RTC_CALR_YEAR_Msk) >> RTC_CALR_YEAR_Pos;
    uint8_t cent = (dateData & RTC_CALR_CENT_Msk) >> RTC_CALR_CENT_Pos;
    //convert from bcd to decimal and format output
    printByte (bcdToDecimal(month));
    printString (" ");
    printByte (bcdToDecimal(date));
    printString(", ");
    printByte (bcdToDecimal(cent));
    printByte (bcdToDecimal(year));
    printString("\r\n");	
}

void printTime(){	
    uint32_t timeData = rtcGetTime();
	
    uint8_t hour = (timeData & RTC_TIMR_HOUR_Msk) >> RTC_TIMR_HOUR_Pos;
    uint8_t minute = (timeData & RTC_TIMR_MIN_Msk) >> RTC_TIMR_MIN_Pos;
    uint8_t second = (timeData & RTC_TIMR_SEC_Msk) >> RTC_TIMR_SEC_Pos;
	
    printByte (bcdToDecimal(hour));
    printString(":");
    printByte (bcdToDecimal(minute));
    printString(":");
    printByte (bcdToDecimal(second));
    printString ("\r\n");	
}

int main(void)
{    
    SystemInit();
    clock_init();
    UART_Init();
    slowClock_init();
	
    rtcSetDate(13, 1,12,15,20);
    rtcSetTime(10,14,1);
	    
    while (1) 
    {
        printDate();
        printTime();	
    }	
}