MCU型号:
STM32L071KBU
SDK:HAL库
工具:CubeMX + MDK
1 RTC硬件框架
1.1 时钟源
- LSI 37 kHz
- LSE 32.768 kHz
- HSE预分频后的时钟
f_ck_apre
时钟用于对二进制RTC_SSR
亚秒下行计数器进行计时。当其值为0时,RTC_SSR
重新加载PREDIV_S
的内容,f_ck_apre
时钟计算:
f_ck_spre
时钟既可以用于更新日历,也可以作为16位唤醒自动重新加载计时器的时间基础。为了获得较短的超时时间,16位唤醒自动重新加载定时器也可以使用RTCCLK
除以可编程的4位异步预分频器运行。f_ck_spre
时钟计算:
RTC时钟显示秒更新周期即有f_ck_spre
决定(重点):
- 当选择
LSE
(32.768 kHz)作为时钟源时,设置RTC_PRER
寄存器异步分频因子PREDIV_A[6:0]
为128 - 1
,同步分频因子PREDIV_S[14:0]
为256 - 1
时,f_ck_spre = 32768/128/256 = 1 HZ。 - 当选择**
LSI
**(37kHz)作为时钟源时,异步分频因子PREDIV_A[6:0]
设为124
,同步分频因子PREDIV_S[14:0]
设为295
,时,f_ck_spre = 37000/(124+1)/(295+1)= 1 HZ。
LORA物联网开发板没有HSE和LSE,因此只能选择LS1!
1.2 RTC框图
可屏蔽 interrupts/events:
- Alarm A 闹钟A
- Alarm B 闹钟B
- Wakeup interrupt 唤醒中断
- Time-stamp 时间戳
- Tamper detection 入侵检测
灰框部分为RTC核心寄存器:
RTC_SSR
for the subsecondsRTC_TR
for the timeRTC_DR
for the date
每两个RTCCLK周期,当前的日历值被复制到影子寄存器shadow register
,并且RTC_ISR
寄存器的RSF
位被设置。
注意:复制不会在“停止”和“待机”模式下进行。当退出这些模式时,影子寄存器最多在2个rtclk周期后更新。
当读取日历寄存器时,它将访问影子寄存器的内容。可以通过在RTC_CR
寄存器中设置BYPSHAD
位直接访问日历寄存器(默认除能)。
注意:在影子模式下(BYPSHAD
位为0)读取RTC_SSR、RTC_TR或RTC_DR寄存器时,APB时钟(f_APB)的频率必须至少是RTC时钟(f_RTCCLK)频率的7倍。
日历格式:
- time format:12 or 24 hours,通过
RTC_CR
寄存器的FMT
位设置。 - Data Format:
- Binary data format 二进制格式
- BCD data format BCD码格式
1.3 RTC初始化和配置流程
1.3.1 RTC 寄存器访问
系统复位后,RTC寄存器通过清除PWR_CR
寄存器中的DBP
位(参考电源控制部分)来保护RTC寄存器免受寄生写访问。为了启用RTC寄存器的写访问,必须设置DBP位:
1.3.2 RTC 寄存器写保护
RTC域复位后,所有的RTC寄存器都是写保护的。写入RTC寄存器是通过将一个密钥写入写入保护寄存器RTC_WPR来 实现的。通过以下方法解除RTC寄存器的写保护(除了RTC_TAMPCR、RTC_BKPxR、RTC_OR和RTC_ISR[13:8]):
-
Write ‘0xCA’ into the RTC_WPR register.
-
Write ‘0x53’ into the RTC_WPR register.
写错键将重新激活写保护。
1.3.3 日历初始化和配置
要编程初始时间和日期日历值,包括时间格式和预分频器配置,需要以下顺序:
- 将
RTC_ISR
寄存器的INIT
位设置为1,进入初始化模式。在这种模式下,日历计数器将停止,并且可以更新它的值。 - 轮询RTC_ISR寄存器中的INITF位。当“INITF”设置为1时,进入初始化阶段模式。它大约需要2个rtclk时钟周期(由于时钟同步)。
- 选择时钟源,设置
RTC_PRER
寄存器异步分频因子PREDIV_A[6:0]
和同步分频因子PREDIV_S[14:0]
,将f_ck_spre
频率配置为1HZ,为日历计数器生成1 Hz的时钟 - 在影子寄存器(
RTC_TR
和RTC_DR
)中加载初始时间和日期值,并通过RTC_CR寄存器中的FMT位配置时间格式(12或24小时)。 - 通过清除INIT位退出初始化模式。然后,将自动加载实际的日历计数器值,并在4个rtclk时钟周期后重新开始计数。当初始化序列完成时,日历开始计数。
Note:系统重置后,应用程序可以读取
RTC_ISR
寄存器中的INITS标志,以检查日历是否已初始化。如果这个标志等于0,日历还没有被初始化,因为year字段被设置在RTC域重置默认值(0x00)。为了在初始化后读取日历,软件必须首先检查RSF标志是否在
RTC_ISR
寄存器中设置。
1.3.4 夏令时
夏令时管理通过RTC_CR
寄存器的SUB1H、ADD1H和BKP位进行。使用SUB1H或ADD1H,软件可以一次操作在日历上减去或增加1小时,而不需要经过初始化过程。此外,软件还可以使用BKP
位来存储此操作(掉电保存)。
1.3.5 闹钟编程
以闹钟A为例:
- 清除RTC_CR中的ALRAE,使闹钟A失效。
- 编程闹钟A寄存器(RTC_ALRMASSR/RTC_ALRMAR)。
- 在RTC_CR寄存器中设置ALRAE使能闹钟A
Note:由于时钟同步,在大约2个RTCCLK时钟周期后,RTC_CR寄存器的每次更改都会被更新。
1.3.6 唤醒定时器编程
配置或更改唤醒计时器自动重新加载值(RTC_WUTR
中的WUT[15:0])需要如下序列:
- 清除
RTC_CR
中的WUTE
,关闭唤醒定时器。 - 轮询
WUTWF
位直到它在RTC_ISR
中被设置,以确保允许访问唤醒自动重载计数器和WUCKSEL[2:0]
位。它大约需要2个rtclk时钟周期(由于时钟同步)。 - 编程唤醒自动重载值
WUT[15:0]
,以及唤醒时钟选择(RTC_CR
中的WUCKSEL[2:0]
位)。在RTC_CR
中再次设置WUTE
使能定时器。唤醒定时器重新开始倒数计时。清除WUTE
后,由于时钟同步,WUTWF
位最多会被清除2个rtclk时钟周期。
夏令时、闹钟中断、唤醒定时器中断可选。
1.4 读取日历时间
1.4.1 RTC_CR
的BYPSHAD
位被清除时(使用影子寄存器)
为了正确读取RTC日历寄存器(RTC_SSR
、RTC_TR
和RTC_DR
), APB1时钟频率(fpcLk)必须等于或大于RTC时钟频率(fRTccLK)的7倍。这确保了同步机制的安全行为。
如果APB1时钟频率小于RTC时钟频率的7倍,软件必须读取两次日历时间和日期寄存器。如果RTC_TR的第二次读取与第一次读取的结果相同,这确保了数据是正确的。否则必须进行第三次读访问。在任何情况下,APB1时钟频率都不能低于RTC时钟频率。
每次将日历寄存器复制到RTC_SSR、RTC_TR和RTC_DR影子寄存器时,RSF位都会在RTC_ISR寄存器中设置。每两个rtclk周期执行一次复制。为了确保这3个值之间的一致性,读取RTC_SSR或RTC_TR将锁定高阶日历阴影寄存器中的值,直到读取RTC_DR。如果软件在小于2个RTCCLK周期的时间间隔内读取日历:第一次读取日历后,软件必须清除RSF,然后软件必须等待RSF被设置,才能再次读取RTC_SSR、RTC_TR和RTC_DR寄存器。
从低功耗模式(Stop或Standby)唤醒后,必须通过软件清除RSF。在读取RTC_SSR、RTC_TR和RTC_DR寄存器之前,软件必须等待它再次被设置。
RSF位必须在唤醒后清除,而不能在进入低功耗模式前清除。
系统复位后,软件必须等待RSF设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。实际上,系统重置会将影子寄存器重置为它们的默认值。
在初始化之后,软件必须等待RSF被设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。
同步后,软件必须等待RSF设置后才能读取RTC_SSR、RTC_TR和RTC_DR寄存器。
示例代码:
if((RTC->ISR & RTC_ISR_RSF) == RTC_ISR_RSF)
{
TimeToCompute = RTC->TR; /* get time */
DateToCompute = RTC->DR; /* need to read date also */
}
1.4.2 RTC_CR
的BYPSHAD
位被置位时(不使用影子寄存器)
读取日历寄存器可以直接从日历计数器中获得值,从而不需要等待RSF位被设置。这在退出低功耗模式(停止或待机)后特别有用,因为在这些模式期间影子寄存器不会更新。
当BYPSHAD
位设置为1时,如果在两次读取寄存器之间出现RTCCLK边沿,则不同寄存器的结果可能不一致。此外,如果在读取操作期间出现RTCCLK边沿,其中一个寄存器的值可能不正确。软件必须读取所有寄存器两次,然后比较结果,以确认数据是连贯和正确的。或者,该软件可以只比较最不重要的日历寄存器的两个结果。
Note:当BYPSHAD=1时,读取日历寄存器的指令需要一个额外的APB周期才能完成。
2 HAL库编程
2.1 CubeMX配置
- 24h时间制
- 选择了LSI(37K)时钟,配置异步和同步分频因子,将
f_ck_spre
配置为1HZ - 日期格式选择BCD码
- 年、月、日、星期、时分秒均为当前时间(自定义,年范围为0~99,基准为2000)
- 不使用夏令时校准
- 存储复位(RTC_TimeTypeDef中StoreOperation成员已弃用,改用HAL_RTC_DST_xxx函数设置夏令时)
2.2 RTC_HAL函数
2.2.1 读取和设置系统时间
- 读取系统时间
/**
* @brief Get RTC current time.
* @param hrtc RTC handle
* @param sTime 指向时间结构的指针,带有小时、分钟和秒字段,并以输入格式(BIN或BCD)返回
* @param Format 获取时间的格式
* @arg RTC_FORMAT_BIN: 使用16进制
* @arg RTC_FORMAT_BCD: BCD码格式
* @retval HAL status
*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
时间结构体RTC_TimeTypeDef
:
typedef struct
{
uint8_t Hours; /*!< Specifies the RTC Time Hour.
This parameter must be a number between Min_Data = 0 and Max_Data = 12 if the RTC_HourFormat_12 is selected.
This parameter must be a number between Min_Data = 0 and Max_Data = 23 if the RTC_HourFormat_24 is selected */
uint8_t Minutes; /*!< Specifies the RTC Time Minutes.
This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
uint8_t Seconds; /*!< Specifies the RTC Time Seconds.
This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
uint8_t TimeFormat; /*!< Specifies the RTC AM/PM Time.
This parameter can be a value of @ref RTC_AM_PM_Definitions */
uint32_t SubSeconds; /*!< Specifies the RTC_SSR RTC Sub Second register content.
This parameter corresponds to a time unit range between [0-1] Second
with [1 Sec / SecondFraction +1] granularity */
uint32_t SecondFraction; /*!< Specifies the range or granularity of Sub Second register content
corresponding to Synchronous pre-scaler factor value (PREDIV_S)
This parameter corresponds to a time unit range between [0-1] Second
with [1 Sec / SecondFraction +1] granularity.
This field will be used only by HAL_RTC_GetTime function */
uint32_t DayLightSaving; /*!< This interface is deprecated. To manage Daylight
Saving Time, please use HAL_RTC_DST_xxx functions */
uint32_t StoreOperation; /*!< This interface is deprecated. To manage Daylight
Saving Time, please use HAL_RTC_DST_xxx functions */
} RTC_TimeTypeDef;
注意:必须在HAL_RTC_GetTime()
之后调用HAL_RTC_GetDate()
来解锁这些值在日历影子寄存器中,以确保时间和日期值之间的一致性。读取RTC当前时间会锁定日历影子寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。
- 设置系统时间
/**
* @brief Set RTC current time.
* @param hrtc RTC handle
* @param sTime 指向时间结构的指针
* @param Format 获取时间的格式
* @arg RTC_FORMAT_BIN: 使用16进制
* @arg RTC_FORMAT_BCD: BCD码格式
* @retval HAL status
*/
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
- 读取系统日期
/**
* @brief Get RTC current date.
* @param hrtc RTC handle
* @param sDate 指向日期结构的指针
* @param Format 获取日期的格式
* @arg RTC_FORMAT_BIN: 使用16进制
* @arg RTC_FORMAT_BCD: BCD码格式
* @retval HAL status
*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
日期结构体RTC_TimeTypeDef
:
typedef struct
{
uint8_t WeekDay; /*!< Specifies the RTC Date WeekDay.
This parameter can be a value of @ref RTC_WeekDay_Definitions */
uint8_t Month; /*!< Specifies the RTC Date Month (in BCD format).
This parameter can be a value of @ref RTC_Month_Date_Definitions */
uint8_t Date; /*!< Specifies the RTC Date.
This parameter must be a number between Min_Data = 1 and Max_Data = 31 */
uint8_t Year; /*!< Specifies the RTC Date Year.
This parameter must be a number between Min_Data = 0 and Max_Data = 99 */
} RTC_DateTypeDef;
- 设置系统日期
/**
* @brief Set RTC current date.
* @param hrtc RTC handle
* @param sDate 指向日期结构的指针
* @param Format 获取日期的格式
* @arg RTC_FORMAT_BIN: 使用16进制
* @arg RTC_FORMAT_BCD: BCD码格式
* @retval HAL status
*/
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
夏令时、闹钟相关函数暂时不用。
2.3 时间读取测试
RTC_TimeTypeDef rtc_time; // 获取时间结构体
RTC_DateTypeDef rtc_data; // 获取日期结构体
void get_rtc_time(void)
{
// 获取当前时间
HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BIN);
// 获取当前日期
HAL_RTC_GetDate(&hrtc, &rtc_data, RTC_FORMAT_BIN);
}
void main_thread(void)
{
static uint32_t sec_temp = 0xff;
get_rtc_time();
if (sec_temp != rtc_time.Seconds)
{
sec_temp = rtc_time.Seconds;
printf("%02d/%02d/%02d\r\n",2000 + rtc_data.Year, rtc_data.Month, rtc_data.Date);
printf("%02d:%02d:%02d\r\n",rtc_time.Hours, rtc_time.Minutes, rtc_time.Seconds);
printf("\r\n");
}
}
RTC初始为BCD码格式,如果用BIN格式读取的话,结果是十进制:
如果改成BCD码格式读取RTC_FORMAT_BCD
,结果则是十六进制:
分析源码:
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
uint32_t tmpreg;
/* Get subseconds structure field from the corresponding register*/
sTime->SubSeconds = (uint32_t)(hrtc->Instance->SSR);
/* Get SecondFraction structure field from the corresponding register field*/
sTime->SecondFraction = (uint32_t)(hrtc->Instance->PRER & RTC_PRER_PREDIV_S);
/* Get the TR register */
tmpreg = (uint32_t)(hrtc->Instance->TR & RTC_TR_RESERVED_MASK);
/* Fill the structure fields with the read parameters */
sTime->Hours = (uint8_t)((tmpreg & (RTC_TR_HT | RTC_TR_HU)) >> 16U);
sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >> 8U);
sTime->Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16U);
/* Check the input parameters format */
if (Format == RTC_FORMAT_BIN)
{
/* Convert the time structure parameters to Binary format */
sTime->Hours = (uint8_t)RTC_Bcd2ToByte(sTime->Hours);
sTime->Minutes = (uint8_t)RTC_Bcd2ToByte(sTime->Minutes);
sTime->Seconds = (uint8_t)RTC_Bcd2ToByte(sTime->Seconds);
}
return HAL_OK;
}
/**
* @brief Convert from 2 digit BCD to Binary.
* @param Value BCD value to be converted
* @retval Converted word
*/
uint8_t RTC_Bcd2ToByte(uint8_t Value)
{
uint32_t tmp;
tmp = (((uint32_t)Value & 0xF0U) >> 4U) * 10U;
return (uint8_t)(tmp + ((uint32_t)Value & 0x0FU));
}
Hours/Minutes/Seconds从寄存器读取的数值为16进制(如RTC_TR_HT
为小时十位,RTC_TR_HU
为小时个位,均为16进制)
如果格式为RTC_FORMAT_BIN
会调用RTC_Bcd2ToByte()
函数,将16进制转换为10进制。
注意:这里是十进制不是数值上的转换,而是格式上的转换,如22点,BCD码值是0x22,转换后的BIN码值为22
网上有人说
RTC_FORMAT_BIN
格式初始化有问题,但测试后并没有问题。
END