学习记录-解决STM32HAL库RTC掉电丢失日期数据
声明:以下仅为本人的理解,不一定完全正确(勿喷),有不对的请指正,可以共同交流学习进步
众所周知,STM32HAL库的RTC有这个断电重新上电后时间正常但是丢失日期的bug, 这主要是因为HAL库处理时间是最终还是将时间转换为秒然后处理,而日期的处理主要是通过RTC_DateTypeDef结构体DateToUpdate变量进行处理。下面我们来分析下HAL_RTC_Init初始化函数

可以看到在RTC初始化函数中,简单粗暴的将这个日期数据处理的结构体复位了,而没有去读取时间戳将其转换成日期。
接下来分析HAL_RTC_SetDate日期设置和HAL_RTC_GetDate日期获取函数


可以看到无论是HAL库的设置日期和获取日期函数都是经过这个DateToUpdate变量的处理,那么一旦我们断电重新上电,那么HAL_RTC_Init函数获就将DateToUpdate变量复位,所以我们调用HAL_RTC_GetDate获取到的就不是你所设置的日期了。
所以我们可以将年月日时分秒格式数据转换成时间戳直接写入RTC_CNT寄存器,然后读取时也是直接读取寄存器值,再转换为年月日时分秒格式。这里可以使用<time.h>中的库函数解决, 以下是时间设置函数以及时间获取函数
void myRTC_setTime(uint16_t* time_data)
{
uint32_t set_tim_stamp;
struct tm set_tim_data;
set_tim_data.tm_year = time_data[0] - 1900;
set_tim_data.tm_mon = time_data[1] - 1;
set_tim_data.tm_mday = time_data[2];
set_tim_data.tm_hour = time_data[3];
set_tim_data.tm_min = time_data[4];
set_tim_data.tm_sec = time_data[5];
set_tim_stamp = mktime(&set_tim_data);
// 等待上一次写操作完成
while(!(hrtc.Instance->CRL & RTC_CRL_RTOFF));
// 失能写保护
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
// 将时间戳写入RTC_CNT寄存器
WRITE_REG(hrtc.Instance->CNTH, (set_tim_stamp >> 16U));
WRITE_REG(hrtc.Instance->CNTL, (set_tim_stamp & RTC_CNTL_RTC_CNT));
// 写入完毕开启写保护
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
// 等待写操作完成
while(!(hrtc.Instance->CRL & RTC_CRL_RTOFF));
}
void myRTC_getTime(uint16_t* get_time_datas)
{
uint32_t get_tim_stamp;
struct tm get_tim_data = {0};
// 从RTC_CNT寄存器获取RTC时间戳
get_tim_stamp = RTC->CNTH; // 获取高16位
get_tim_stamp <<= 16U;
get_tim_stamp |= RTC->CNTL; // 获取低16位
get_tim_data = *localtime(&get_tim_stamp);
get_time_datas[0] = get_tim_data.tm_year + 1900;
get_time_datas[1] = get_tim_data.tm_mon + 1;
get_time_datas[2] = get_tim_data.tm_mday;
get_time_datas[3] = get_tim_data.tm_hour;
get_time_datas[4] = get_tim_data.tm_min;
get_time_datas[5] = get_tim_data.tm_sec;
}
这里函数传进来的参数是存放日期时间的数组,如下分别表示年月日时分秒
uint16_t time_data[6] = {2024, 12, 18, 10, 30, 0};
这里使用了时间戳转换的知识,大家可以自行搜索,这里不过多赘述。
一般情况下,我们在每一次RTC初始化后再设置日期时间,但这样每次上电后都会是原先所设置的日期时间,我们需要的是让时间沿着上次断电时的时间继续往下走,所以我们可以利用备份寄存器第一次设置时间时,写入一个特定值存到备份寄存器,后续每次上电都读取备份寄存器,如果读取的和这个值相等那么就可以不在设置时间,以下是代码
if(myRTC_read_BKP(RTC_BKP_DR1) != 0x66) // 当备份寄存器的值为0x66时 时间不复位
{
myRTC_setTime(time_data);
myRTC_write_BKP(RTC_BKP_DR1, 0x66);
}
注意:如果你有设置闹钟,那么闹钟也需要使用这种方式写入和读取,否则调用库函数日期时间会不正确,这里我估计是HAL_RTC_SetAlarm和HAL_RTC_GetAlarm这两个库函数中的处理对日期有影响,但是没看出为啥,反正用这两个函数就乱了。下面给出闹钟设置和读取的代码:
void myRTC_set_Alarm(uint8_t* alarm_time)
{
// RTC_AlarmTypeDef sAlarm;
// //sAlarm.Alarm = RTC_ALARM_A; // 指定alarm ID
// // 指定告警具体时间
// sAlarm.AlarmTime.Hours = alarm_time[0];
// sAlarm.AlarmTime.Minutes = alarm_time[1];
// sAlarm.AlarmTime.Seconds = alarm_time[2];
// HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
uint32_t alarm_counter = 0;
uint32_t hour_s = 0, minute_s = 0, sec = 0;
// 将闹钟时间转换为秒数
hour_s = alarm_time[0] * 3600U; // 每小时3600秒
minute_s = alarm_time[1] * 60U;
sec = alarm_time[2];
alarm_counter = hour_s + minute_s + sec; // 总秒数
// 等待上次写操作结束
while ((hrtc.Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);
// 失能写保护
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
// 将闹钟数据写入RTC闹钟寄存器
WRITE_REG(hrtc.Instance->ALRH, (alarm_counter >> 16U));
WRITE_REG(hrtc.Instance->ALRL, (alarm_counter & RTC_ALRL_RTC_ALR));
// 写入完毕,打开写保护
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
// 等待本次写操作结束
while ((hrtc.Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);
/* 清闹钟中断标志位 */
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
// 开启闹钟中断
__HAL_RTC_ALARM_ENABLE_IT(&hrtc, RTC_IT_ALRA);
}
void myRTC_get_Alarm(uint8_t* alarm_time)
{
// RTC_AlarmTypeDef sAlarm;
// HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
// alarm_time[0] = sAlarm.AlarmTime.Hours;
// alarm_time[1] = sAlarm.AlarmTime.Minutes;
// alarm_time[2] = sAlarm.AlarmTime.Seconds;
uint32_t read_alarm_counter = 0U;
// 读出闹钟数据
read_alarm_counter = RTC->ALRH; // 获取高16位
read_alarm_counter <<= 16U;
read_alarm_counter |= RTC->ALRL; // 获取低16位
// 转换为标准格式(时分秒)
alarm_time[0] = (uint8_t)((read_alarm_counter / 3600U) % 24U);
if(read_alarm_counter % 3600U == 0)
{
alarm_time[1] = 0;
alarm_time[2] = 0;
}
else
{
alarm_time[1] = (uint8_t)((read_alarm_counter % 3600U) / 60U);
alarm_time[2] = (uint8_t)((read_alarm_counter % 3600U) % 60U);
}
}
这里我将库函数部分注释掉了,因为一直在调试。这里我也开启闹钟中断了。
还有一个问题用这种方法之后,闹钟中断莫名奇妙的无法触发,但是时间日期,闹钟时间均正常。而我使用注释掉的库函数配置就能触发中断,但是日期却乱了。。。。
这里因为我的板子LSE不起振,我用的是LSI时钟,所以断电RTC不会继续走时,但是会保留断电前的时间。以下是全部代码
main.c
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); // 延时初始化 依据时钟频率为72mhz
key_init();
OLED_Init();
serial_Init();
led_init();
myRTC_Init();
uint16_t time_data[] = {2024, 12, 18, 10, 30, 0};
uint8_t alarm_time[3] = {10, 30, 10};
if(myRTC_read_BKP(RTC_BKP_DR1) != 0x66) // 当备份寄存器的值为0x66时 时间不复位
{
myRTC_setTime(time_data);
myRTC_write_BKP(RTC_BKP_DR1, 0x66);
}
myRTC_set_Alarm(alarm_time);
while(1)
{
// 获取时间和闹钟
myRTC_getTime(time_data);
myRTC_get_Alarm(alarm_time);
OLED_Show_RTC_Time_Alarm(time_data, alarm_time); // 显示时间和闹钟时间
}
}
myRTC.c
RTC_HandleTypeDef hrtc;
void myRTC_Init(void)
{
// 使能电源时钟 BKP时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess(); // 使能访问bkp和rtc寄存器
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = 40000 - 1; // 分频
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE; // 忽略RTC入侵功能
HAL_RTC_Init(&hrtc);
}
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
__HAL_RCC_RTC_ENABLE();
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI; // 振荡器选择LSI
RCC_OscInitStruct.LSIState = RCC_LSI_ON; // LSI状态开启
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; // 不使用PLL
HAL_RCC_OscConfig(&RCC_OscInitStruct); // 初始化振荡器
RCC_PeriphCLKInitTypeDef PeriphClkInit;
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC; // 外设时钟配置为RTC
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; // 选择RTC时钟源为LSI
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
// 为闹钟设置中断
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
// RTC闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{
if (__HAL_RTC_ALARM_GET_IT_SOURCE(&hrtc, RTC_IT_ALRA))
{
if(__HAL_RTC_ALARM_GET_FLAG(&hrtc, RTC_CRL_ALRF) != (uint32_t)RESET)
{
led_on(LED1);
/* 清闹钟溢出中断标志位 */
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_CRL_ALRF);
}
}
/* 清闹钟外部触发中断标志位 */
__HAL_RTC_ALARM_EXTI_CLEAR_FLAG();
}
uint16_t myRTC_read_BKP(uint32_t BackupRegister)
{
return HAL_RTCEx_BKUPRead(&hrtc, BackupRegister);
}
void myRTC_write_BKP(uint32_t BackupRegister, uint16_t data)
{
HAL_RTCEx_BKUPWrite(&hrtc, BackupRegister, data);
}
void myRTC_setTime(uint16_t* time_data)
{
uint32_t set_tim_stamp;
struct tm set_tim_data;
set_tim_data.tm_year = time_data[0] - 1900;
set_tim_data.tm_mon = time_data[1] - 1;
set_tim_data.tm_mday = time_data[2];
set_tim_data.tm_hour = time_data[3];
set_tim_data.tm_min = time_data[4];
set_tim_data.tm_sec = time_data[5];
set_tim_stamp = mktime(&set_tim_data);
// 等待上一次写操作完成
while(!(hrtc.Instance->CRL & RTC_CRL_RTOFF));
// 失能写保护
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
// 将时间戳写入RTC_CNT寄存器
WRITE_REG(hrtc.Instance->CNTH, (set_tim_stamp >> 16U));
WRITE_REG(hrtc.Instance->CNTL, (set_tim_stamp & RTC_CNTL_RTC_CNT));
// 写入完毕开启写保护
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
// 等待写操作完成
while(!(hrtc.Instance->CRL & RTC_CRL_RTOFF));
}
void myRTC_getTime(uint16_t* get_time_datas)
{
uint32_t get_tim_stamp;
struct tm get_tim_data = {0};
// 从RTC_CNT寄存器获取RTC时间戳
get_tim_stamp = RTC->CNTH; // 获取高16位
get_tim_stamp <<= 16U;
get_tim_stamp |= RTC->CNTL; // 获取低16位
get_tim_data = *localtime(&get_tim_stamp);
get_time_datas[0] = get_tim_data.tm_year + 1900;
get_time_datas[1] = get_tim_data.tm_mon + 1;
get_time_datas[2] = get_tim_data.tm_mday;
get_time_datas[3] = get_tim_data.tm_hour;
get_time_datas[4] = get_tim_data.tm_min;
get_time_datas[5] = get_tim_data.tm_sec;
}
void myRTC_set_Alarm(uint8_t* alarm_time)
{
// RTC_AlarmTypeDef sAlarm;
// //sAlarm.Alarm = RTC_ALARM_A; // 指定alarm ID
// // 指定告警具体时间
// sAlarm.AlarmTime.Hours = alarm_time[0];
// sAlarm.AlarmTime.Minutes = alarm_time[1];
// sAlarm.AlarmTime.Seconds = alarm_time[2];
// HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
uint32_t alarm_counter = 0;
uint32_t hour_s = 0, minute_s = 0, sec = 0;
// 将闹钟时间转换为秒数
hour_s = alarm_time[0] * 3600U; // 每小时3600秒
minute_s = alarm_time[1] * 60U;
sec = alarm_time[2];
alarm_counter = hour_s + minute_s + sec; // 总秒数
// 等待上次写操作结束
while ((hrtc.Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);
// 失能写保护
__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
// 将闹钟数据写入RTC闹钟寄存器
WRITE_REG(hrtc.Instance->ALRH, (alarm_counter >> 16U));
WRITE_REG(hrtc.Instance->ALRL, (alarm_counter & RTC_ALRL_RTC_ALR));
// 写入完毕,打开写保护
__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
// 等待本次写操作结束
while ((hrtc.Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);
/* 清闹钟中断标志位 */
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
// 开启闹钟中断
__HAL_RTC_ALARM_ENABLE_IT(&hrtc, RTC_IT_ALRA);
}
void myRTC_get_Alarm(uint8_t* alarm_time)
{
// RTC_AlarmTypeDef sAlarm;
// HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
// alarm_time[0] = sAlarm.AlarmTime.Hours;
// alarm_time[1] = sAlarm.AlarmTime.Minutes;
// alarm_time[2] = sAlarm.AlarmTime.Seconds;
uint32_t read_alarm_counter = 0U;
// 读出闹钟数据
read_alarm_counter = RTC->ALRH; // 获取高16位
read_alarm_counter <<= 16U;
read_alarm_counter |= RTC->ALRL; // 获取低16位
// 转换为标准格式(时分秒)
alarm_time[0] = (uint8_t)((read_alarm_counter / 3600U) % 24U);
if(read_alarm_counter % 3600U == 0)
{
alarm_time[1] = 0;
alarm_time[2] = 0;
}
else
{
alarm_time[1] = (uint8_t)((read_alarm_counter % 3600U) / 60U);
alarm_time[2] = (uint8_t)((read_alarm_counter % 3600U) % 60U);
}
}
最后看看实际效果

时间日期都是正常走,就是到了设置的闹钟时间就触发不了中断,分析了HAL_RTC_SetAlarm_IT这个函数的中断配置部分,照着它的流程配置还是没用。。
有大佬会可以给分析下,感谢。。。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)