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

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库)。不过肯定比通过日期备份的方式更加合理可靠。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode-Hot100

哈希 1.两数之和&#xff1a; 给定一个整数数组nums和一个整数目标值target&#xff0c;请你再该数组中找出和为目标值target的那两个整数&#xff0c;并返回它们的数组下标。 思路&#xff1a;暴力解法是使用两层循环来遍历每一个数&#xff0c;然后找出两数之和等于target的…

2024/3/9d打卡整数划分---背包动态规划方式,计数类动态规划

目录 题目 DP分析 第一种方法&#xff0c;背包DP 代码 第二种方法&#xff08;有点难想到&#xff09; 代码 题目 一个正整数 n 可以表示成若干个正整数之和&#xff0c;形如&#xff1a;nn1n2…nk&#xff0c;其中 n1≥n2≥…≥nk,k≥1。 我们将这样的一种表示称为正整数 …

maven项目引入私有jar,并打包到java.jar中

私有jar存放位置 maven依赖 <dependency><groupId>com.hikvision.ga</groupId><artifactId>artemis-http-client</artifactId><version>1.1.10</version><scope>system</scope><systemPath>${project.basedir}/s…

FPGA高端项目:FPGA基于GS2971的SDI视频接收+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收转HDMI输出应用本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收OSD多路视频融合叠加应用本方案的SDI接收HLS多路视频融合叠加应用本方案…

基于YOLOv8深度学习的葡萄病害智能诊断与防治系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:LoadingProgress)

用于显示加载动效的组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 LoadingProgress() 创建加载进展组件。 从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使…

Angular基础---HelloWorld---Day2

文章目录 1.循环语句&#xff1a; *ngfor2.循环语句&#xff1a;ngSwitch4.事件的绑定:click5.事件的绑定:input6.模版引用变量7.数据双向绑定ngModel8.动态表单控件9.动态表单空间组 文末附有代码仓库地址&#xff01;&#xff01;&#xff01; 1.循环语句&#xff1a; *ngfor…

大语言模型在科技研发与创新中的角色在快速变化

在技术研发与创新中&#xff0c;比如在软件开发、编程工具、科技论文撰写等方面&#xff0c;大语言模型可以辅助工程师和技术专家进行快速的知识检索、代码生成、技术文档编写等工作。在当今的软件工程和研发领域&#xff0c;尤其是随着大语言模型技术的快速发展&#xff0c;它…

保姆级讲解字符串函数(上篇)

目录 字符分类函数 导图 函数介绍 1.getchar 2. isupper 和 islower 字符转换函数&#xff1a;&#xff08;toupper , tolower&#xff09; 与 putchar 字符串函数 导图 string函数的使用和模拟实现 string的使用 求字符串长度 字符串的比较 string函数的模拟实现…

300分钟吃透分布式缓存-23讲:Redis是如何淘汰key的?

淘汰原理 首先我们来学习 Redis 的淘汰原理。 系统线上运行中&#xff0c;内存总是昂贵且有限的&#xff0c;在数据总量远大于 Redis 可用的内存总量时&#xff0c;为了最大限度的提升访问性能&#xff0c;Redis 中只能存放最新最热的有效数据。 当 key 过期后&#xff0c;或…

一个足球粉丝该怎么建个个人博客?

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

oracle 获取两个时间相差天数,以及指定一个日期相差天数后的日期

1、获取两个时间相差天数 -- 两个日期相差天数 select (trunc(TO_DATE( 2024-02-28, YYYY-MM-DD ) -TO_DATE( 2024-02-25, YYYY-MM-DD ) )1) from dual2、获取日期减去指定天数后的时间 -- 两个日期相差天数的日期 select (TRUNC(TO_DATE( 2024-02-25, YYYY-MM-DD )- (trunc…

java-ssm-jsp-基于ssm的宠物领养系统的设计与实现

java-ssm-jsp-基于ssm的宠物领养系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

jupyter notebook 调整深色背景与单元格宽度与自动换行

# 安装jupyter主题 pip install jupyterthemes # 列举主题 jt -l # 设置主题 jt -t chesterish设置宽度 打开users 当前用户目录下的custom.css文件 写入.container { width:80% !important; } 即可 设置自动换行 查找创建这个目录以及文件notebook.json 写入配置 “li…

PHAMB: 病毒数据分箱

Genome binning of viral entities from bulk metagenomics data | Nature Communications 安装 ### New dependencies *Recommended* conda install -c conda-forge mamba mamba create -n phamb python3.9 conda activate phamb mamba install -c conda-forge -c biocond…

Python基础!入门必备知识及基本语句,初学者必过的一道门槛!

Python入门必备知识 1 标识符 标识符是编程时使用的名字&#xff0c;用于给变量、函数、语句块等命名&#xff0c;Python中标识符由字母、数字、下划线组成&#xff0c;不能以数字开头&#xff0c;区分大小写。 ①以下划线开头的标识符有特殊含义&#xff0c;单下划线开头的…

【vue.js】文档解读【day 4】 | 事件处理

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 文章目录 事件处理前言监听事件内联事件处理器方法事件处理器方法与内联事件判断在内联处理器中调用方法在内联事件处理器中访问事件参数修饰符事件修饰符按键修饰符常规按键别名系统按键别名组合按键ex…

java-ssm-jsp-基于ssm的宝文理学生社团管理系统

java-ssm-jsp-基于ssm的宝文理学生社团管理系统 获取源码——》公主号&#xff1a;计算机专业毕设大全

【数据分享】2013-2022年全国范围逐月CO栅格数据(免费获取)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年全国范围逐月的PM2.5栅格数据和2013-2022年全国范围逐月SO2栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;。 本次我们给大家带来的是2013-2022年全国范围的逐月的CO栅格…

STL容器之哈希的补充——其他哈希问题

1.其他哈希问题 ​ 减少了空间的消耗&#xff1b; 1.1位图 ​ 位图判断在不在的时间复杂度是O(1)&#xff0c;速度特别快; ​ 使用哈希函数直接定址法&#xff0c;1对1映射&#xff1b; ​ 对于海量的数据判断在不在的问题&#xff0c;使用之前的一些结构已经无法满足&…