STM32 RTC 驱动代码(解决了使用HAL库函数导致的复位或者掉电后导致RTC年月日日期清零的问题)

问题背景:在RTC中断里面使用HAL库HAL_RTC_GetDate()和HAL_RTC_GetTime()来获取RTC时间日期。

源码如下图:

问题描述:单片机断电或者复位后的时分秒的时间可以接上,但年月日的日期就会被清零。如图:

导致问题的根本原因:

        通过查询 stm32f1xx_hal_rtc.c 里面的 HAL_RTC_GetDate()和HAL_RTC_GetTime()2个HAL库函数的源码

        在 HAL_RTC_GetDate()源码中,可以清楚看到日期是由DateToUpdate结构体获得的,然而通过逐步查询 DateToUpdate结构体来源发现DateToUpdate结构体和RTC计数器的值是没有关联的,所以单片机掉电或者复位后日期(年月日)就会清0 。

        在 HAL_RTC_GetTime()源码中,可以清楚看到时间是由RTC_ReadTimeCounter()获得的,也就是说时间是和RTC计数器的值关联的,所以单片机掉电或者复位后时间(时分秒)不会清0 ,依然可以正常走时。

解决办法:不用HAL库!!! 自己直接编写RTC相关的函数接口。

        定义日期时间结构体:

/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    uint32_t unix_time; /* 时间戳 */
} RTC_DateTimeTypeDef;

1.获取日期和时间(年月日 时分秒)接口函数 :

         RTC_GetTime(RTC_DateTimeTypeDef *calendar)

/**
功能:得到当前的时间(年月日 时分秒)。该函数不直接返回时间, 时间数据保存在calendar结构体里面
 */
void RTC_GetTime(RTC_DateTimeTypeDef *calendar)
{
    static uint16_t daycnt = 0;
    uint32_t seccount = 0;
    uint32_t temp = 0;
    uint16_t temp1 = 0;
    const uint8_t month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /* 平年的月份日期表 */
    seccount = RTC_Get_Sec();
    calendar->unix_time = seccount;
    temp = seccount / 86400; /* 得到天数(秒钟数对应的) */
    if (daycnt != temp) /* 超过一天了 */
    {
        daycnt = temp;
        temp1 = 1970;   /* 从1970年开始 */
        while (temp >= 365)
        {
            if (RTC_Is_Leap_Year(temp1)) /* 是闰年 */
            {
                if (temp >= 366)
                {
                    temp -= 366;    /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                temp -= 365;        /* 平年 */
            }

            temp1++;
        }
        calendar->year = temp1;      /* 得到年份 */
        temp1 = 0;
        while (temp >= 28)      /* 超过了一个月 */
        {
            if (RTC_Is_Leap_Year(calendar->year) && temp1 == 1) /* 当年是不是闰年/2月份 */
            {
                if (temp >= 29)
                {
                    temp -= 29; /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                if (temp >= month_table[temp1])
                {
                    temp -= month_table[temp1]; /* 平年 */
                }
                else
                {
                    break;
                }
            }
            temp1++;
        }
        calendar->month = temp1 + 1; /* 得到月份 */
        calendar->date = temp + 1;   /* 得到日期 */
    }
    temp = seccount % 86400;                                                    /* 得到秒钟数 */
    calendar->hour = temp / 3600;                                                /* 小时 */
    calendar->min = (temp % 3600) / 60;                                          /* 分钟 */
    calendar->sec = (temp % 3600) % 60;                                          /* 秒钟 */
    calendar->week = RTC_GetWeek(calendar->year, calendar->month, calendar->date); /* 获取星期 */
    
}

/*
获取RTC计数器CNT里面的值(既时间戳)
*/
uint32_t RTC_Get_Sec(void)
{
    uint16_t seccount_high1 = 0;
    uint16_t seccount_high2 = 0;
    uint16_t seccount_low = 0;
    uint32_t seccount = 0;
     /*摘抄于STM32 rtc库,优化可能在读CNT的时候出现了溢出*/
    seccount_high1 = RTC->CNTH; /* 得到计数器中的值高16位(秒钟数) */
    seccount_low = RTC->CNTL;   /* 得到计数器中的值低16位(秒钟数) */
    seccount_high2 = RTC->CNTH; /* 得到计数器中的值高16位(秒钟数) */
    if(seccount_high1 != seccount_high2) /*防止在获取秒钟低16位时,计数器刚好溢出导致获取的秒钟高16位就是错误的*/
    {
        seccount = (seccount_high2 << 16) | (RTC->CNTL & 0xFFFF );
    }
    else
    {
        seccount = (seccount_high1 << 16) | seccount_low;
    }
    return seccount;
}


/**
 * @brief       判断年份是否是闰年
 *   @note      月份天数表:
 *              月份   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
 * @param       year : 年份
 * @retval      0, 非闰年; 1, 是闰年;
 */
static uint8_t RTC_Is_Leap_Year(uint16_t year)
{
    /* 闰年规则: 四年闰百年不闰,四百年又闰 */
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
 * @brief       输入公历日期得到星期
 *   @note      起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @retval      0, 星期天; 1 ~ 6: 星期一 ~ 星期六
 */
uint8_t RTC_GetWeek(uint16_t syear, uint8_t smonth, uint8_t sday)
{
    uint8_t week = 0;

    if (smonth < 3)
    {
        smonth += 12;
        --syear;
    }
    week = (sday + 1 + 2 * smonth + 3 * (smonth + 1) / 5 + syear + (syear >> 2) - syear / 100 + syear / 400) % 7;
    return week;
}

2.设置日期和时间(年月日 时分秒)接口函数:

        RTC_SetTime(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec) 

/**
 * @brief       设置时间, 包括年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
                HAL默认为年份起点为2000年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
void RTC_SetTime(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)   /* 设置时间 */
{
    uint32_t seccount = 0;
    seccount = RTC_Data2Sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 上面三步是必须的! */
    SET_BIT(RTC->CRL,RTC_CRL_CNF); /* 进入配置模式 */
//    RTC->CRL |= 1 << 4;         /* 进入配置模式 */ 
    RTC->CNTL = seccount & 0xFFFF;
    RTC->CNTH = seccount >> 16;
    CLEAR_BIT(RTC->CRL,RTC_CRL_CNF); /* 退出配置模式 */
//    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */
    while (!__HAL_RTC_ALARM_GET_FLAG(&gRTC_Handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      以1970年1月1日为基准, 1970年1月1日, 0时0分0秒, 表示第0秒钟
 *              最大表示到2105年, 因为uint32_t最大表示136年的秒钟数(不包括闰年)!
 *              本代码参考只linux mktime函数, 原理说明见此贴:
 *              http://www.openedv.com/thread-63389-1-1.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      转换后的秒钟数
 */
static long RTC_Data2Sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t Y, M, D, X, T;
    signed char monx = smon;    /* 将月份转换成带符号的值, 方便后面运算 */

    if (0 >= (monx -= 2))       /* 1..12 -> 11,12,1..10 */
    {
        monx += 12; /* Puts Feb last since it has leap day */
        syear -= 1;
    }
    Y = (syear - 1) * 365 + syear / 4 - syear / 100 + syear / 400; /* 公元元年1到现在的闰年数 */
    M = 367 * monx / 12 - 30 + 59;
    D = sday - 1;
    X = Y + M + D - 719162;                      /* 减去公元元年到1970年的天数 */
    T = ((X * 24 + hour) * 60 + min) * 60 + sec; /* 总秒钟数 */
    return T;
}

3.设置RTC闹钟接口函数:

RTC_SetAlarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)


void RTC_SetAlarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;
    seccount = RTC_Data2Sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */    
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 设置闹钟 */
    SET_BIT(RTC->CRL,RTC_CRL_CNF); /* 进入配置模式 */
//    RTC->CRL |= 1 << 4;         /* 进入配置模式 */
    RTC->ALRL = seccount & 0xffff;
    RTC->ALRH = seccount >> 16;
    CLEAR_BIT(RTC->CRL,RTC_CRL_CNF); /* 退出配置模式 */
//    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */
    while (!__HAL_RTC_ALARM_GET_FLAG(&gRTC_Handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
    /* RTC 闹钟中断配置 */
    /* EXTI 配置 */
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 3, 3);
    /* 使能RTC闹钟中断 */
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

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

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

相关文章

SpringBoot3+SpringSecurity6基于若依系统整合自定义登录流程

SpringBoot3SpringSecurity6基于若依系统整合自定义登录流程 问题背景 在做项目时遇到了要对接统一认证的需求&#xff0c;但是由于框架的不兼容性&#xff08;我们项目是springboot3&#xff0c;jdk17&#xff0c;springsecurity6.1.5&#xff09;等因素&#xff0c;不得不使…

Mount Image Pro,在取证安全的环境中挂载和访问镜像文件内容

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据备份解决方案与服务&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 天津鸿萌科贸发展有限公司是 GetData 公司数据恢复与取证工…

PHP合成图片,生成海报图,poster-editor使用说明

之前写过一篇使用Grafika插件生成海报图的文章&#xff0c;但是当我再次使用时&#xff0c;却发生了错误&#xff0c;回看Grafika文档&#xff0c;发现很久没更新了&#xff0c;不兼容新版的GD&#xff0c;所以改用了intervention/image插件来生成海报图。 但是后来需要对海报…

React 前端框架全面教程:从入门到进阶

React 前端框架全面教程&#xff1a;从入门到进阶 引言 在现代前端开发中&#xff0c;React 作为一款流行的 JavaScript 库&#xff0c;以其组件化、声明式的特性和强大的生态系统&#xff0c;成为了开发者的首选。无论是构建单页应用&#xff08;SPA&#xff09;还是复杂的用…

基于Python的自然语言处理系列(42):Token Classification(标注分类)

在本篇文章中&#xff0c;我们将探讨如何进行 Token Classification&#xff08;标注分类&#xff09;&#xff0c;这是一类为句子中的每个 token&#xff08;词或子词&#xff09;分配标签的任务。该任务可以解决很多问题&#xff0c;例如命名实体识别&#xff08;NER&#xf…

用Pyhon写一款简单的益智类小游戏——2048

文字版——代码及讲解 代码—— import random# 初始化游戏棋盘 def init_board():return [[0] * 4 for _ in range(4)]# 在棋盘上随机生成一个2或4 def add_new_tile(board):empty_cells [(i, j) for i in range(4) for j in range(4) if board[i][j] 0]if empty_cells:i,…

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC

『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 文章目录 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC1. 介绍 二. 参考文献 一. 『Linux学习笔记』如何在 Ubuntu 22.04 上安装和配置 VNC 如何在 Ubuntu 22.04 上安装和配置 VNC 1. 介绍 虚拟网络计算…

【Java】方法的使用 —— 语法要求、方法的重载和签名、方法递归

目录 1. 方法基础知识 1.1 方法的概念 1.2 语法格式 * 注意事项【与C不同】 1.3 return —— 返回值的严格检查【比C语言严格】 2. 形参与实参的关系 3. 方法重载 3.1 什么是方法重载&#xff1f;为什么要方法重载&#xff1f; 3.2 方法重载的规则 4. 方法签名 5. 递…

HT7178 带输出关断的20V,14A全集成同步升压转换器

1、特点 输入电压范围VpIN:2.7V-20V 输出电压范围VouT:4.5V-20V 可编程峰值电流:14A 高转换效率: 95%(VPIN7.2V, VoUT 16V, IouT3A) 94%(VPIN12V,VoUT18V,IoUT4A) 90%(VPIN3.3, VoUT-9V,IOUT3A) 轻载条件下两种调制方式:脉频调制(PFM)和 强制脉宽调试(PWM) 集成输出关断的栅极…

【史上最全SD教程】Stable Diffusion系统教学!Ai绘画零基础入门到精通商业实战 人工智能绘图画图商业变现

一、为什么要学Stable Diffusion&#xff0c;它究竟有多强大&#xff1f; 1.Stable Diffusion能干嘛 Stable Diffusion&#xff08;SD&#xff09;作为一种先进的AI图像生成技术&#xff0c;其功能和应用场景非常广泛。以下是SD的一些主要功能和应用领域&#xff1a; \1. 图…

《链表篇》---两数相加(中等)

题目传送门 方法一&#xff1a;迭代 文字描述看代母注释 class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//定义头结点和当前节点ListNode head null,cur null;//carry记录进位情况。int carry 0; while(l1 ! null || l2 ! null){//判断节点是…

QT找不到ffmpeg链接库解决方法

error: undefined reference to avformat_network_init() 一个神奇的报错&#xff0c;查了很久&#xff0c;检查步骤&#xff1a; 1、检查了 pro工程文件 2、链接库的真实性和正确性 在main.cpp中调用没有报错&#xff0c;在其它cpp文件中调用就报错。 破案了&#xff0c;…

详细了解C++11(1)

大家好呀&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流哦 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#xff0c;欢迎各…

04.DDD与CQRS

学习视频来源&#xff1a;DDD独家秘籍视频合集 https://space.bilibili.com/24690212/channel/collectiondetail?sid1940048&ctype0 文章目录 定义职责分离DDD与CQRS的关系领域模型和查询模型特点命令场景的领域模型查询场景的查询模型 架构方案领域事件方案1&#xff1a…

【运动的&高尔夫球】高尔夫球检测系统源码&数据集全套:改进yolo11-CA-HSFPN

改进yolo11-HWD等200全套创新点大全&#xff1a;高尔夫球检测系统源码&#xff06;数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.10.30 注意&#xff1a;由于项目一直在更新迭代&#xff0c;上面“1.图片效果展示”和“2.视频效果展示”展示的系统图片或者视频可…

【python】flash-attn安装

这个命令&#xff1a; 确保使用正确的 CUDA 12.6 工具链 设置必要的 CUDA 环境变量 包含了常见的 GPU 架构支持 利用你的128核心进行并行编译 # 清理之前的安装 proxychains4 pip uninstall -y flash-attn# 获取 CUDA 路径 CUDA_PATH$(dirname $(dirname $(which nvcc)))# 使用…

得计算题者得天下!软考系统集成计算题详解!

软考中级系统集成项目管理工程师考试一共有《综合知识》和《案例分析》两门科目&#xff0c;而在这两科中都会涉及到计算题&#xff0c;特别是案例分析中&#xff0c;计算题每次考试都会占到一道大题&#xff0c;共25分&#xff0c;占到了科目总分的1/4&#xff0c;所以对于系统…

第2章 Android App开发基础

第 2 章 Android App开发基础 bilibili学习地址 github代码地址 本章介绍基于Android系统的App开发常识&#xff0c;包括以下几个方面&#xff1a;App开发与其他软件开发有什么不一 样&#xff0c;App工程是怎样的组织结构又是怎样配置的&#xff0c;App开发的前后端分离设计…

腾讯云视频文件上传云存储时自动将mp4格式转码成m3u8

针对问题&#xff1a; 弱网环境下或手机网络播放mp4格式视频卡顿。 存储环境&#xff1a;腾讯云对象存储。 处理流程&#xff1a; 1&#xff1a;登录腾讯云控制台&#xff0c;进入对象存储服务&#xff0c;找到对应的存储桶&#xff0c;点击进入。 在任务与工作流选项卡中找…

如何下载安装TestLink?

一、下载TestLink、XAMPP TestLink 下载 |SourceForge.net 备用&#xff1a;GitHub - TestLinkOpenSourceTRMS/testlink-code&#xff1a; TestLink开源测试和需求管理系统 下载XAMPP&#xff1a; Download XAMPP 注意&#xff1a;TestLink与PHP版本有关系&#xff0c;所以XA…