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.