STM32 HAL库RTC复位丢失年月日的解决办法

news2025/6/17 9:31:42

STM32 HAL库RTC复位丢失年月日的解决办法

  • 0.前言
  • 一、实现方式
    • 1.CubeMX配置:
    • 2.MX_RTC_Init()函数修改
    • 2.编写手动解析函数
  • 二、总结


参考文章:stm32f1 cubeMX RTC 掉电后日期丢失的问题

0.前言

  最近在使用STM32F103做RTC实验时,发现RTC复位后时间正常,但是日期丢失的问题。这个问题在之前使用的标准库中没有遇到,说明是HAL的bug,经过对HAL_RTC_SetDate()和HAL_RTC_GetDate()的解析之后,发现在上电初始化时,HAL库直接简单粗暴的对日期时间戳进行了重置,导致无法读取。
  经过多方查找,目前使用较多且较简单的方法是:直接将日期数据写入备份寄存器中,上电时重新进行获取。但是这种方法存在一个问题,假如掉电后经历了日期跨越,那么日期数据就不再准确。所以这里参考标准库的实现方式,直接将HAL库中的初始化过程注释掉,直接从RTC的时间戳寄存器中获取数据,然后手动进行解析。

一、实现方式

1.CubeMX配置:

在这里插入图片描述
直接使能RTC功能即可,日期可以不进行设置,后续手动进行设置。

2.MX_RTC_Init()函数修改

为了尽量保持CubeMX的生成格式,防止后续重新生成时自己的代码被覆盖,这里直接在MX_RTC_Init()函数中,使用宏定义注释掉HAL的日期初始化流程:
在这里插入图片描述
将添加的宏定义放在 USER CODE BEGIN 和 USER CODE END之间,即可保证重新生成时不被刷新。

2.编写手动解析函数

rtc.h:
在头文件中添加以下代码:
在这里插入图片描述
其中包括日历相关的结构体定义,以及日历的全局变量。手动初始化、设置日期、获取日期的相关函数。

rtc.c
在rtc的相关的功能代码中,添加以下代码段:

/* USER CODE BEGIN 0 */
_calendar_obj calendar; // 时钟结构体
// 月份数据表
const uint8_t table_week[12] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; // 月修正数据表
// 平年的月份日期表
const uint8_t mon_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// 判断是否是闰年函数
// 月份   1  2  3  4  5  6  7  8  9  10 11 12
// 闰年   31 29 31 30 31 30 31 31 30 31 30 31
// 非闰年 31 28 31 30 31 30 31 31 30 31 30 31
// year:年份
// 返回值:该年份是不是闰年.1,是.0,不是
static uint8_t Is_Leap_Year(uint16_t year)
{
    if (year % 4 == 0) // 必须能被4整除
    {
        if (year % 100 == 0)
        {
            if (year % 400 == 0)
                return 1; // 如果以00结尾,还要能被400整除
            else
                return 0;
        }
        else
            return 1;
    }
    else
        return 0;
}

// 获得现在是星期几
// 功能描述:输入公历日期得到星期(只允许1901-2099年)
// year,month,day:公历年月日
// 返回值:星期号
static uint8_t RTC_Get_Week(uint16_t year, uint8_t month, uint8_t day)
{
    uint16_t temp2;
    uint8_t yearH, yearL;

    yearH = year / 100;
    yearL = year % 100;
    // 如果为21世纪,年份数加100
    if (yearH > 19)
        yearL += 100;
    // 所过闰年数只算1900年之后的
    temp2 = yearL + yearL / 4;
    temp2 = temp2 % 7;
    temp2 = temp2 + day + table_week[month - 1];
    if (yearL % 4 == 0 && month < 3)
        temp2--;
    return (temp2 % 7);
}

void RTC_Set(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint16_t t;
    uint32_t seccount = 0;

    if (syear < 1970 || syear > 2099)
        return;
    for (t = 1970; t < syear; t++) // 把所有年份的秒钟相加
    {
        if (Is_Leap_Year(t))
            seccount += 31622400; // 闰年的秒钟数
        else
            seccount += 31536000; // 平年的秒钟数
    }
    smon -= 1;
    for (t = 0; t < smon; t++) // 把前面月份的秒钟数相加
    {
        seccount += (uint32_t)mon_table[t] * 86400; // 月份秒钟数相加
        if (Is_Leap_Year(syear) && t == 1)
            seccount += 86400; // 闰年2月份增加一天的秒钟数
    }
    seccount += (uint32_t)(sday - 1) * 86400; // 把前面日期的秒钟数相加
    seccount += (uint32_t)hour * 3600;        // 小时秒钟数
    seccount += (uint32_t)min * 60;           // 分钟秒钟数
    seccount += sec;                          // 最后的秒钟加上去

    // 设置时钟
    RCC->APB1ENR |= 1 << 28; // 使能电源时钟
    RCC->APB1ENR |= 1 << 27; // 使能备份时钟
    PWR->CR |= 1 << 8;       // 取消备份区写保护
    // 上面三步是必须的!
    RTC->CRL |= 1 << 4; // 允许配置
    RTC->CNTL = seccount & 0xffff;
    RTC->CNTH = seccount >> 16;
    RTC->CRL &= ~(1 << 4); // 配置更新
    while (!(RTC->CRL & (1 << 5))); // 等待RTC寄存器操作完成

    RTC_Get(); // 设置完之后更新一下数据
}

void RTC_Get(void)
{
    static uint16_t daycnt = 0;
    uint32_t timecount = 0;
    uint32_t temp = 0;
    uint16_t temp1 = 0;
    timecount = RTC->CNTH; // 得到计数器中的值(秒钟数)
    timecount <<= 16;
    timecount += RTC->CNTL;

    temp = timecount / 86400; // 得到天数(秒钟数对应的)
    if (daycnt != temp)       // 超过一天了
    {
        daycnt = temp;
        temp1 = 1970; // 从1970年开始
        while (temp >= 365)
        {
            if (Is_Leap_Year(temp1)) // 是闰年
            {
                if (temp >= 366)
                    temp -= 366; // 闰年的秒钟数
                else
                    break;
            }
            else
                temp -= 365; // 平年
            temp1++;
        }
        calendar.w_year = temp1; // 得到年份
        temp1 = 0;
        while (temp >= 28) // 超过了一个月
        {
            if (Is_Leap_Year(calendar.w_year) && temp1 == 1) // 当年是不是闰年/2月份
            {
                if (temp >= 29)
                    temp -= 29; // 闰年的秒钟数
                else
                    break;
            }
            else
            {
                if (temp >= mon_table[temp1])
                    temp -= mon_table[temp1]; // 平年
                else
                    break;
            }
            temp1++;
        }
        calendar.w_month = temp1 + 1; // 得到月份
        calendar.w_date = temp + 1;   // 得到日期
    }
    temp = timecount % 86400;                                                         // 得到秒钟数
    calendar.hour = temp / 3600;                                                      // 小时
    calendar.min = (temp % 3600) / 60;                                                // 分钟
    calendar.sec = (temp % 3600) % 60;                                                // 秒钟
    calendar.week = RTC_Get_Week(calendar.w_year, calendar.w_month, calendar.w_date); // 获取星期
}

void rtc_init_user(void)
{
    //HAL_RTCEx_SetSecond_IT(&hrtc);                        // 秒中断使能,没有配置这个中断可以不加
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x5050) // 是否第一次配置
    {
        RTC_Set(2022, 3, 9, 20, 58, 0);                  // 设置日期和时间
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5050); // 标记已经初始化过了
    }
    RTC_Get(); // 更新时间
}
/* USER CODE END 0 */

然后在主程序中,即可设置和获取时间信息,并且掉电后不会丢失。
在这里插入图片描述
在这里插入图片描述

二、总结

  HAL库RTC问题,主要就是在HAL_RTC_SetDate()和HAL_RTC_GetDate()这两个函数中,对日期相关的数据处理不当,粗暴的将日期的进位舍去了。修改的原理也很简单,获取到真实时间戳后手动解析即可,在笔者 的实现方式中,则主要对应RTC_Get()和RTC_Set()函数,这里笔者使用的函数还存在限制,对星期几的解析只能计算到2099年,其实这里还有更简单的方法,直接使用 time.h 中提供的时间戳处理方法,修改这两个函数进行日期的计算和解析,有兴趣的读者可以自行尝试(需要在Keil中使能MicroLib库)。不过肯定比通过日期备份的方式更加合理可靠。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1503825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web渗透测试流程

什么是渗透测试 渗透测试 (penetration test),是通过模拟恶意黑客的攻击方法&#xff0c;来评估计算机网络系统安全的一种评估方法。这个过程包括对系统的任何弱点、技术缺陷或漏洞的主动分析&#xff0c;这个分析是从一个攻击者可能存在的位置来进行的&#xff0c;并且从这个…

Visual Basic6.0零基础教学(1)—vb的介绍和布局及其小案例

Visual Basic6.0零基础教学(1) 文章目录 Visual Basic6.0零基础教学(1)前言一、vb6.0介绍二、vb的起源一、起源&#xff1a;Basic二、版本三、 Visual Basic6.0 三种版本&#xff1a;四、vb的特点 1.vb的布局介绍创建应用程序的步骤总结 前言 大家好,从今天开始我也会开始更新…

【数据结构六】图文结合详解二叉树(五千字)

二叉树 树是一种非线性的数据结构&#xff0c;它是由n个结点组成的具有层次关系的集合&#xff0c;把他叫做树是因为它的根朝上&#xff0c;叶子朝下&#xff0c;看起来像一颗倒挂的树。二叉树是一种最多只有两个节点的树型结构。这篇文章会用Java代码手撕二叉树的实现&#xf…

水果小程序有哪些功能 怎么制作

​水果店的小程序&#xff0c;通常都非常受欢迎&#xff0c;而且下单率非常不错。它可以帮助水果商家在线销售水果并提供更好的购物体验。在这篇文章中&#xff0c;我们将介绍水果小程序常见的功能以及制作方法。 1. **商品展示与购买**&#xff1a;水果小程序可以展示各种水…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的体育赛事目标检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;开发和研究体育赛事目标检测系统对于增强体育分析和观赏体验至关重要。本篇博客详细讲述了如何运用深度学习技术构建一个体育赛事目标检测系统&#xff0c;并提供了完整的实现代码。系统基于先进的YOLOv8算法&#xff0c;对比了YOLOv7、YOLOv6、YOLOv5的性能&a…

linux网络通信(TCP)

TCP通信 1.socket----->第一个socket 失败-1&#xff0c;错误码 参数类型很多&#xff0c;man查看 2.connect 由于s_addr需要一个32位的数&#xff0c;使用下面函数将点分十进制字符串ip地址以网络字节序转换成32字节数值 同理端口号也有一个转换函数 我们的端口号位两个字…

脚踩顺序表

目录 引言 一&#xff1a;顺序表的结构定义 二&#xff1a;顺序表的操作 1.顺序表的初始化 2.顺序表的销毁 3.顺序表数据的打印 4.顺序表的尾插 5.顺序表的头插 6.顺序表的尾删 7.顺序表的头删 8.顺序表的查找 9.顺序表的删除pos位置的值 10.顺序表的在…

微调模型——续(Machine Learning 研习之十三)

集成方法 微调系统的另一种方法是尝试组合性能最佳的模型。 群体&#xff08;或“整体”&#xff09;通常会比最好的单个模型表现得更好&#xff0c;就像随机森林比它们所依赖的单个决策树表现更好一样&#xff0c;特别是当各个模型犯下不同类型的错误时。 例如&#xff0c;您…

瑞_JVM虚拟机_类的生命周期

文章目录 1 JVM虚拟机概述2 类的生命周期2.1 加载阶段2.1.1 加载过程2.1.2 查看内存中的对象&#xff08;hsdb工具&#xff09; 2.2 连接阶段2.2.1 验证2.2.2 准备&#xff08;final特殊&#xff09;2.2.3 解析 2.3 初始化阶段\<client> ★★★2.4 使用阶段2.5 卸载阶段 …

深入理解神经网络

图片怎么被识别的过程 (每层神经网络是数组,会对进来的数据进行加权求和[(weight*数据 然后累加) bias])(激活函数是为了训练weight和bias偏移值,在每个神经网络)(分类器会统计概率分类) 2. 引用链接 https://mp.weixin.qq.com/s?__bizMzIyNjMxOTY0NA&mid2247500124&…

蓝桥集训之鱼塘钓鱼

蓝桥集训之鱼塘钓鱼 核心思想&#xff1a;多路归并 人不会在鱼塘间往返浪费时间将每个鱼塘的取值列出 想要最多鱼 就是每次在最顶上取最大注意&#xff1a;找最大的顺序和实际钓鱼的顺序不同 先在一个坑钓完再去另一个 #include <iostream>#include <cstring>#…

猫头虎分享已解决Bug || 系统监控故障:MonitoringServiceDown, MetricsCollectionError

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

把握机遇:2024年游戏行业春招提前批全攻略

当前&#xff0c;国内游戏行业正处于高速发展期&#xff0c;各大游戏公司对应届毕业生的人才需求十分旺盛。这一趋势不仅为即将步入职场的学生们提供了广阔的就业前景&#xff0c;也为游戏产业的创新和多元化发展注入了新鲜血液。 在这样的大环境下&#xff0c;2024年春季提前批…

2024038期传足14场胜负前瞻

2024038期售止时间为3月10日&#xff08;周日&#xff09;20点30分&#xff0c;敬请留意&#xff1a; 本期深盘多&#xff0c;1.5以下赔率3场&#xff0c;1.5-2.0赔率2场&#xff0c;其他场次是平半盘、平盘。本期14场整体难度中等偏上。以下为基础盘前瞻&#xff0c;大家可根据…

数字化转型导师坚鹏:大模型的应用实践(金融)

大模型的应用实践 ——开启人类AI新纪元 打造数字化转型新利器 课程背景&#xff1a; 很多企业和员工存在以下问题&#xff1a; 不清楚大模型对我们有什么影响&#xff1f; 不知道大模型的发展现状及作用&#xff1f; 不知道大模型的针对性应用案例&#xff1f; 课程…

GPT-4-turbo还是大家心中第一!Claude 3竞技场人类投票成绩出炉:仅居第三

Claude 3的竞技场排名终于揭晓了&#xff1a; 在仅仅3天的时间里&#xff0c;20000张投票使得排名的流量达到了前所未有的高度。 最后&#xff0c;Claude 3的"大杯"模型Opus以1233的分数赢得了胜利&#xff0c;成为了第一个能和GPT-4-Turbo匹敌的选手。 "中杯…

VUE Element例子学习

参考:【前端】VueElement UI案例&#xff1a;通用后台管理系统-项目总结_vue elementui 管理系统-CSDN博客 之前参考的el-admin-web太复杂了&#xff0c;不是纯净的demo. 所以找了一圈资料&#xff0c;找到了这个博客&#xff0c;很合适&#xff0c;有例子的代码&#xff0c;…

安全先行,合规的内外网文件摆渡要重点关注什么?

内外网隔离在政府、军工部门、科研单位等已成为很常见的网络安全建设措施&#xff0c;内外网隔离是一种网络安全措施&#xff0c;用于保护内部网络免受外部网络的攻击和威胁。 内外网隔离的目的在于限制内外网之间的通信和数据交换&#xff0c;但网络隔离后&#xff0c;仍有数据…

深入解析汽车MCU的软件架构

一、背景知识 电动汽车&#xff08;EV&#xff09;正在成为首选的交通方式&#xff0c;为传统内燃机汽车提供了一种可持续发展的环保型替代方案。在电动汽车复杂的生态系统中&#xff0c;众多电子控制单元&#xff08;ECU&#xff09;在确保其高效运行方面发挥着至关重要的作用…

ChatGPT 串接到 Discord - 团队协作好助理

ChatGPT 串接到 Discord - 团队协作好助理 ChatGPT 是由 OpenAI 开发的一个强大的语言模型&#xff0c;本篇文章教你如何串接 Discord Bot &#xff0c;协助团队在工作上更加高效并促进沟通与协作。使 ChatGPT 发挥出最大的功效&#xff0c;进一步提升工作效率和团队协作能力。…