基于STM32的F1的RTC实时时钟+CUBEMX实时获取时间戳实现断电跨天同步时间戳
目录
问题描述
今需要把STM32所采集的数据带日期的形式发送到后端服务器上进行处理,由于STM32F103本身是自带有实时时钟的。该RTC(实时时钟)是一个不断递增的计数器,断电后由纽扣电池继续供电计数。奈何它没有万年历的功能,再加上用CUBEMX生成的hal库有一些相同的问题。这些问题总结起来就是:CUBEMX为STM32F103生成的hal库在获取日期和时间时会把时间戳(也就是计数器里的计数值单位为秒)转换成24h内的时间戳,(CUBEMX为F1生成的hal库就只是把实时时间戳为一天的时间戳)最大也就是24*3600s。我感觉可能是hal库并未完善这点。别的型号的芯片没有测试过。本文只针对于STM32F1论述。
看看CUBEMX配置后生成的hal库函数
在设置时分秒的库函数中
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
它只把时分秒转成了时间戳(24小时内)
在获取时分秒的库函数中
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
这里更加明显,但时间戳大于24h的时间戳则又转换成24内的时间戳。。
诸如此类的代码在 中都有体现。
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
另外在设置日期的库函数 HAL_RTC_SetDate()中并未把年月日转换成时间戳,这就导致尽管我们设置了年月日,尽管我们在后备寄存器写入了年月日,但跨天依然不会更新,读到的后备寄存器也只是原来的日期,因为RTC仅仅只是一个计数器,只是通过秒中断函数不断递增然后把递增的数据写进寄存器。
那么怎么解决这个日期的问题,实现跨日期也能达到日期同步,由于我本篇是记录的实时更新时间戳,
提供思路:
- 首先是能实现实时获取时间戳达到断电跨日期时间戳同步,这也就是我本篇文章的目的。
- 达到这个之后剩下的也就是自己写算法把时间戳转换成年月日 时分秒,然后自己手写获取日期,获取时分秒的函数。为什么要自己手写?因为hal库上面提供的接口都会改变我们现有的时间戳,只有时间戳才是产生日期的根本,这点是不能改变的。当然如果也想用原来hal库的代码那就只能改hal库的,这不推荐。遵循只增加代码不修改源代码的开闭原则。另外cubemx每次刷新都会覆盖你再hal库文件中修改的代码。。。
实时获取时间戳断电跨日期时间戳同步
CUBEMX配置
使用LSE外部时钟更加准确。日期格式使用BCD码 。后面配置的日期后面都会用自己的代码覆盖。
进入void MX_RTC_Init(void) ,覆盖原代码如下
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
__HAL_RCC_BKP_CLK_ENABLE(); //开启后备区域时钟
__HAL_RCC_PWR_CLK_ENABLE(); //开启电源时钟
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!= 0x5011)
{
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5011);//向指定的后备区域寄存器写入数据
uint32_t time= GetCurrentTimeStamp(); //获取设置时间的时间戳
printf("time:%d\r\n",time);
MY_RTC_WriteTimeCounter(&hrtc, time);
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
}
else
{
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
}
return ;
步骤
- 初始化RTC,开启对应时钟(电源时钟和后备区时钟)
- 通过向寄存器任意写一个值判断是否是第一次初始化(也就是我设置时间的初始化)。第二次复位重启后因为读出后备寄存器已经写入该值故不需要再次初始化。最后再开启秒中断,使得每次RTC计数器递增都能写进寄存器。
- 获取我们所设置的时间并把它转换成时间戳。
- 把我们转换后的时间戳写入寄存器。
中间调用两个自己的函数
GetCurrentTimeStamp(); //获取设置时间的时间戳
这函数在第一次初始化的时候算出所设置时间的时间戳。
uint32_t GetCurrentTimeStamp(void) {
//基准时间 2022年 1月 1日 0 时 -分 0秒 以下省略 时分秒
uint16_t base_year = 2022;
uint8_t base_month = 1;
uint8_t base_day = 1;
//设置时间 2022年 10月8日 时间戳=基准时间戳+相差时间戳
uint16_t set_year = 2022;
uint8_t set_month = 10;
uint8_t set_day = 8;
uint8_t set_h = 22;//设置时
uint8_t set_m = 02; //设置分
uint32_t _time = 1640966400;//2022年1月1日与1970 年1月1日相差时间戳
//对齐年
for (uint16_t i = base_year; i < set_year; i++) {
if ((0 == i % 4 && i % 100 != 0 )|| (0 == i % 400)) {
_time += 31536000;//平年365天时间戳
_time += (24 * 3600);//多一天
}
else
_time += 31536000;
}
//对齐月
uint8_t montharr[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//以平年算月份
for (int i = 1; i < set_month; i++) {
if (i == 2 && ((0 == set_year % 4 && set_year % 100 != 0) || (0 == set_year % 400)))//设置年是闰年
_time += 29 * 24 * 3600;
else
_time += montharr[i - 1] * 24 * 3600;
}
//对齐日和分
_time += (set_day - 1) * 24 * 3600;
_time += set_h * 3600;
_time += set_m * 60;
return _time;
}
写入时间戳
MY_RTC_WriteTimeCounter(&hrtc, time);
实际上在stm32f1xx_hal_rtc.c中有一个已经实现了的函数,奈何hal库把它定义成static,只在当前文件有效,且该函数中又调用其他的函数(且也被static修饰在本文件有效)。所以。在stm32f1xx_hal_rtc.c中写一个自己的函数调用原库里的这个函数。
原hal库里的这个写入寄存器的函数。
自己加的一个函数(写入时间戳)
void MY_RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter){
RTC_WriteTimeCounter(hrtc, TimeCounter);
}
在rtc.c中进行声明就能正常调用。
测试
结束,断电重启依然保留且持续更新。
附上设置时间戳并校验的一个工具
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?