Thirteen. RTC Clock Usage

Posted by poseidix on Fri, 21 Jan 2022 16:34:02 +0100

Today let's look at the RTC for I.MX6UL, which is really a SRTC. It is important to note that 6U and 6ULL RTCs are in Chapter 48 SNVS, but the registers of RTC-related functions are not clearly explained in 6ULL, but they are in the 6U manual. The main reason is that some content of SNVS is related to encryption. The specific content of SNVS should be signed with Enzhip to get the specific content. So there are differences between the contents of the SNVS in the 6U and 6ULL manuals, so the contents of this chapter are basically summarized by referencing the reference manual of IMX6UL.

SNVS

SNVS(Secure Non-Volatile Storage) is literally a secure non-volatile storage. SNVS of I.MX6U is divided into SNVS_HP and SNVS_LP two components, RTC we use belongs to SNVS_LP, a low-power module powered by backup batteries, includes a secure real-time counter and a universal counter, which is powered by batteries when the entire Soc is powered off, SNVS_LP registers continue to maintain count function. The overall architecture diagram is as follows

 

 

From the figure above, I don't know why to make a set of high-power SNVS, data can't be saved after the system is powered off, and what kind of data is not easy to see? The usage has not yet been clarified.

RTC usage

RTC usually uses 32.768 MHz crystal oscillation. After 2^15=32767, the corresponding clock source with 32788 crossover can get 1 Hz. I.MX's RTC is very simple to use. Turn on RTC and RTC will start working. We just need to read the value of the RTC counter to convert time. The counter's value is stored in two registers. In fact, I.MX also provides RTC with interrupt function, but we only use it to do the simplest RTC timing, just like the battery on the PC motherboard BIOS recording time bottom effect.

NXP's RTC is a bit weird. It's not called RTC, it's called HP and LP, LP corresponds to SRTC, HP corresponds to RTC, but if we're using HP(RTC) after the system is powered down, it's still the initial time (timestamp, 10:0 minutes and 0 seconds from January 1, 1970), even if the button battery is installed on the board. And RTC on Soc is generally less precise, so we can get a general idea of the functionality. The whole RTC is operated with only a few registers, and the clock tree in the CCM is not involved with him (I estimate that the RTC is a clock supplied by another crystal oscillator, and that clock source at 24MHz is fine).

Start RTC

SNVS_ The LPCR register bit[0] is set to 1.

Getting and setting timestamps

There are some problems with how to get the time stamp according to the 6U reference manual: according to the manual, LPSRTCMR is the high 15 bits of SRTC, LPSRTCLR is the low 32 bits of SRTC (IMX6UL manual 46.7.22, 46.7.23), corresponding RTC counter is 15+32=47 bits

But taking time out of this way of thinking is confusing. The big man gives the solution directly: the bit[31:15] of the LPSRTCLR register is the lower 17 bits of the SRTC, and the LPSRTCMR is the higher 15 bits of the counter. That is, the SRTC counter is 32-bit. This method refers to the SNVS_provided in the SDK given by NXP Driven by HP.

 

 

This is also true in the Linux kernel (rtc in the kernel is 47 bits, but 15 bits are moved to the right and only 32 bits are reserved).

Common Registers

There are only three RTC-related registers

SNVS_HPCOMR

The register structure is as follows

 

 

The main purpose is to use bit[31]

If non-privileged software wants to read the value of an SNVS register, set this bit to 1, and bit[8] is also privilege-related, the tutorial recommends setting 1.

There are also two registers that hold counter values, LPSRTCCLR and LPRTCMR. Notice that:

  • LPSRTCMR[14:0] is the 15-bit high of the SRTC counter
  • LPSRTCLR[31:15] is the lower 17 bits of the SRTC counter

Is it very winding, this must be clearly remembered!

RTC usage

There's really nothing to say about this use. There are three registers altogether. But there are some algorithms: converting time stamps to dates (also taking into account leap years), and converting formatted dates to time stamps (that's when you really miss Pyhton!). Put down the whole code:

/**
 * @file bsp_rtc.c
 * @author your name (you@domain.com)
 * @brief RTC drive
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "bsp_rtc.h"
/**
 * @brief   RTC Initialization
 * 
 */
void rtc_init(void)
{
    
    SNVS->HPCOMR |= (1<<31) | (1<<8);

/*The following section assigns an initial value to the RTC, and the actual process is in a button-triggered interrupt*/
#if 0
    struct rtc_datetime Date_init;

    Date_init.year = 2022;
    Date_init.month = 1;
    Date_init.day = 19;
    Date_init.hour = 2;
    Date_init.minute = 13;
    Date_init.minute = 12;

    rtc_setvalue(&Date_init);
#endif
    rtc_enable();
}

/**
 * @brief             rtc Enabling
 * 
 */
void rtc_enable(void)
{
    SNVS->LPCR |= (1<<0);
    while((SNVS->LPCR & 0x01) == 0);//Wait for success
}

/**
 * @brief             rtc prohibit
 * 
 */
void rtc_disable(void)
{
    SNVS->LPCR &= ~(1<<0);
    while(((SNVS->LPCR) & 0x01) == 1);//Wait for Disabling to Succeed
}


/**
 * @brief                 Get Timestamp
 * 
 * @return uint64_t     Timestamp - int64
 */
uint64_t rtc_getseconds(void)
{    
    static uint64_t seconds = 0;
    seconds = (SNVS->LPSRTCMR << 17) | (SNVS->LPSRTCLR >> 15);
    return seconds;
}


/**
 * @brief                 Set timestamp to RTC register
 * 
 * @param datetime         Date Time Structure
 */
void rtc_setvalue(struct rtc_datetime *datetime)
{
    unsigned int temp = SNVS->LPCR;         //current RTC running state

    uint64_t seconds = 0;

    seconds =  rtc_coverdate_to_seconds(datetime);

    rtc_disable();

    SNVS->LPSRTCMR = (unsigned int)(seconds >> 17);  //High 17 bits
    SNVS->LPSRTCLR = (unsigned int)(seconds << 15);  //Low 15 bits
    
    if(temp &0x01)          //If before setting RTC To run, restart RTC
        {rtc_enable();}
}

/**
 * @brief                 Get Date - Time
 * 
 * @param datetime         Return value by pointer
 */
void rtc_getdatetime(struct rtc_datetime *datetime)
{
    uint64_t seconds = 0;
    seconds = rtc_getseconds();
    
    rtc_convertseconds_to_datetime(seconds,datetime);
}


/**
 * @brief                     Leap year determination
 * 
 * @param year                 year
 * @return unsigned char    1 When leap year 
 */
unsigned char rtc_isleapyear(unsigned short year)
{    
    unsigned char value=0;
    
    if(year % 400 == 0)
        value = 1;
    else 
    {
        if((year % 4 == 0) && (year % 100 != 0))
            value = 1;
        else 
            value = 0;
    }
    return value;
}


/**
 * @brief                 Time Stamp Conversion to Time Structure
 * 
 * @param seconds         time stamp
 * @param datetime 
 */
void rtc_convertseconds_to_datetime(u64 seconds, struct rtc_datetime *datetime)
{
    u64 x;
    u64  secondsRemaining, days;
    unsigned short daysInYear;

    /* Days per month       */
    unsigned char daysPerMonth[] = {0U, 31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};

    secondsRemaining = seconds; /* Remaining seconds initialization */
    days = secondsRemaining / SECONDS_IN_A_DAY + 1;         /* Calculate days based on seconds, plus 1 is the current number of days */
    secondsRemaining = secondsRemaining % SECONDS_IN_A_DAY; /*Number of seconds remaining after counting days */

    /* Calculating hours, minutes, seconds */
    datetime->hour = secondsRemaining / SECONDS_IN_A_HOUR;
    secondsRemaining = secondsRemaining % SECONDS_IN_A_HOUR;
    datetime->minute = secondsRemaining / 60;
    datetime->second = secondsRemaining % SECONDS_IN_A_MINUTE;

    /* Calculated year */
    daysInYear = DAYS_IN_A_YEAR;
    datetime->year = YEAR_RANGE_START;
    while(days > daysInYear)
    {
        /* Calculate years based on days */
        days -= daysInYear;
        datetime->year++;

        /* Handling leap years */
        if (!rtc_isleapyear(datetime->year))
            daysInYear = DAYS_IN_A_YEAR;
        else    /*Leap year, days plus one */
            daysInYear = DAYS_IN_A_YEAR + 1;
    }
    /*Calculate months based on remaining days */
    if(rtc_isleapyear(datetime->year)) /* In leap years, February plus one day */
        daysPerMonth[2] = 29;

    for(x = 1; x <= 12; x++)
    {
        if (days <= daysPerMonth[x])
        {
            datetime->month = x;
            break;
        }
        else
        {
            days -= daysPerMonth[x];
        }
    }

    datetime->day = days;

}



/**
 * @brief                 Convert Date-Time Structure to Timestamp
 * 
 * @param datetime 
 * @return unsigned int 
 */
unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime)
{    
    unsigned short i = 0;
    unsigned int seconds = 0;
    unsigned int days = 0;
    unsigned short monthdays[] = {0U, 0U, 31U, 59U, 90U, 120U, 151U, 181U, 212U, 243U, 273U, 304U, 334U};
    
    for(i = 1970; i < datetime->year; i++)
    {
        days += DAYS_IN_A_YEAR;         /* Ordinary year, 365 days a year */
        if(rtc_isleapyear(i)) days += 1;/* Leap year plus one day         */
    }

    days += monthdays[datetime->month];
    if(rtc_isleapyear(i) && (datetime->month >= 3)) days += 1;/* Leap year with current month greater than or equal to March plus one day */

    days += datetime->day - 1;

    seconds = days * SECONDS_IN_A_DAY + 
                datetime->hour * SECONDS_IN_A_HOUR +
                datetime->minute * SECONDS_IN_A_MINUTE +
                datetime->second;

    return seconds;    
}

A new structure is defined in the header file and some macros are used to convert seconds and formatting time directly

/**
 * @file bsp_rtc.h
 * @author your name (you@domain.com)
 * @brief RTC header file
 * @version 0.1
 * @date 2022-01-20
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#ifndef __BSP_RTC_H
#define __BSP_RTC_H

#include "imx6ul.h"

/*Time-related macro definitions*/
#define SECONDS_IN_A_DAY        (86400)
#define SECONDS_IN_A_HOUR       (3600)
#define SECONDS_IN_A_MINUTE     (60)
#define DAYS_IN_A_YEAR          (365)
#define YEAR_RANGE_START        (1970)
#define YEAR_RANGE_END          (2099)

/*Time-dependent structure*/
struct rtc_datetime
{
    unsigned short year;
    unsigned char  month;
    unsigned char  day;
    unsigned char  hour;
    unsigned char  minute;
    unsigned char  second;
};

void rtc_init(void);                                            
void rtc_enable(void);
void rtc_disable(void);

void rtc_getdatetime(struct rtc_datetime *datetime);

uint64_t rtc_getseconds(void);
void rtc_convertseconds_to_datetime(u64 seconds, struct rtc_datetime *datetime);
unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime);
unsigned char rtc_isleapyear(unsigned short year);


void rtc_setvalue(struct rtc_datetime *datatime);
void rtc_getdatetime(struct rtc_datetime *datetime);

#endif

Note that I masked out a section of code in the initialization, otherwise RTC will be initialized for every time the initializer is executed on power. I put this initialization time part in the interrupt triggered by the key( EPIT Key Dithering ), note the service function triggered by EPIT.

/**
 * @brief Initialization date
 * 
 * @param gicciar 
 * @param param 
 */
void filter_irqhandler(int gicciar,void *param)
{
    struct rtc_datetime rtc_init_date;
    rtc_init_date.year = 2022;
    rtc_init_date.month = 1;
    rtc_init_date.day = 19;
    rtc_init_date.hour = 18;
    rtc_init_date.minute = 36;
    rtc_init_date.second = 20;



    if(EPIT1->SR &= (1<<0)) //1 Time is up
    {   
        filtertimer_stop();
        if(gpio_pinread(GPIO1,18) == 0)
        {
            rtc_setvalue(&rtc_init_date);
        }
    }
    EPIT1->SR |= (1<<0);        //Eliminate EPIT1_SR Sign bits
}

Since there is no display, the final result is that I print it through the serial port, and I also print the timestamp by the way. Look at the main function

int main(void)
{   
    int_init();
    imx6u_clkinit();
    clk_enable();
    delay_init();
    uart_init();
    keyfilter_init();
    rtc_init();

    struct rtc_datetime datetime;
    
    uint64_t s=0;
    printf("RTC Test:\r\n");

    while(1)        
    {   
        rtc_getdatetime(&datetime);
        s = rtc_getseconds();
        printf("Current time:%d year%d month%d day%d time%d branch%d second",datetime.year,datetime.month,datetime.day, 
                                            datetime.hour,datetime.minute,datetime.second);
        
        printf("\r\n");
        printf("%lld",s);
        delay_ms(2000);
    }
    return 0;
}

Note the statement that prints the timestamp. The timestamp is 64-bit and the format is specified with%lld.

Final results

The program starts running from power up, and on line 6 I reset the time by pressing a key, and I can see that the time has changed.