【问题】STM32F103+STM32CubeMX RTC时钟掉电不更新日期

本文探讨了STM32F103 RTC 在日期保存和时间戳转换上的设计缺陷,并提供了将日期存储备份和时间戳计数器结合的方法来解决断电后计时问题。通过详细解释相关函数和操作,帮助开发者改进RTC时钟功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

问题描述

RTC函数设计缺陷

1、日期不保存

2、时间戳转换

解决方法

1.将日期参数保存到RTC后备区域存储器

2.将日期和时间换算为时间戳保存在计数器中


问题描述

使用STM32CubeMX生成STM32F103 RTC时钟例程项目中,虽然有外部电池供电,使得系统断电后依然能够计时,但在实际使用中,系统掉电后,日期参数会重置,只有时间参数正常运行。这是因为STM32F103系列的RTC只是一个简单的计数器,同时因为STM32CubeMX生成的HAL库中RTC函数的设计缺陷,并没有办法实现万年历功能。


RTC函数设计缺陷

1、日期不保存

STM32CubeMX生成的HAL库中RTC函数中,HAL_RTC_SetDate 日期设置函数只是将日期保存至hrtc结构体变量中,HAL_RTC_GetDate 日期获取函数也是直接从hrt结构体变量中获取日期数据。这种情况下,系统重启后数据丢失,日期会被重置。

2、时间戳转换

STM32CubeMX生成的HAL库中RTC函数中,HAL_RTC_SetTime 时间设置函数是将时间转换为时间戳保存到计数器中,HAL_RTC_GetTime 时间获取函数则是将计数器的数值转换为时间。计数器在断电后能够依靠外部电池供电继续计时,所以掉电后,时间依然能够正常走时。但是,在时间戳与时间的换算处理中,会自动将时间戳转换为24H内的时间,所以并不能记录所走时长的天数。


解决方法

1.将日期参数保存到RTC后备区域存储器

这种方法比较简单,在更新时间时,将日期参数保存至存储器中,数据掉电不丢失。但是没办法根治问题,断电时,一旦时间到达24:00,时间参数会重置从00:00重新开始计时,但是日期并不会更新。

2.将日期和时间换算为时间戳保存在计数器中

STM32F103的RTC本质上是一个32位的计数器,在断电后,由电池供电还能保持计数。所以可以将日期和时间换算为时间戳保存到计数器中,当需要读取时间时,从计数器中读取时间戳,重新换算成日期和时间即可。

相关接口函数:

// 设置时间戳计数的基准日期
RTC_DateTypeDef DateBase = {
    .Year = 22,
    .Month = 01,
    .Date = 01,
    .WeekDay = RTC_WEEKDAY_SATURDAY,
};

//===========================================================================
//函数名: GetDiffDate(uint32_t StartDate, uint32_t EndDate)
//功能  : 计算两个日期之间的天数并返回
//参数  : RTC_DateTypeDef StartDate, RTC_DateTypeDef EndDate
//返回值: uint32_t
//===========================================================================
uint32_t GetDiffDate(RTC_DateTypeDef StartDate, RTC_DateTypeDef EndDate)
{
    uint32_t DayDiff = 0; // 两个日期的天数差

    while (1)
    {
        if ((StartDate.Year == EndDate.Year) && (StartDate.Month == EndDate.Month))
        {
            DayDiff += EndDate.Date - StartDate.Date;
            break;
        }
        else
        {
            DayDiff += EndDate.Date;
            EndDate.Month--;
            if (EndDate.Month == 0)
            {
                EndDate.Month = 12;
                EndDate.Year--;
            }
            EndDate.Date = GetDayOfMonth(EndDate.Year, EndDate.Month);
        }
    }

    return DayDiff;
}

//===========================================================================
//函数名: CalculateDate
//功能  : 根据起始日期和天数,计算之后的日期并返回
//参数  : RTC_DateTypeDef FromDate, uint32_t DayDiff
//返回值: RTC_DateTypeDef
//===========================================================================
RTC_DateTypeDef CalculateDate(RTC_DateTypeDef FromDate, uint32_t DayDiff)
{
    uint16_t day = 0;

    FromDate.WeekDay = DateBase.WeekDay + (DayDiff % 7);
    if (FromDate.WeekDay >= 7)
        FromDate.WeekDay -= 7;

    while (1)
    {
        day = GetDayOfMonth(FromDate.Year, FromDate.Month);
        if (FromDate.Date + DayDiff > day)
        {
            DayDiff -= day - FromDate.Date;
            FromDate.Month++;
            FromDate.Date = 0;

            if (FromDate.Month == 13)
            {
                FromDate.Month = 1;
                FromDate.Year++;
            }
        }
        else
        {
            FromDate.Date += DayDiff;
            break;
        }
    }

    return FromDate;
}

//===========================================================================
//函数名: GetDayOfMonth
//功能  : 根据年月,获取当月的天数并返回
//参数  : uint8_t year, uint8_t month
//返回值: uint8_t
//===========================================================================
uint8_t GetDayOfMonth(uint8_t year, uint8_t month)
{
    uint8_t DayOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    if (month == 2)
    {
        if ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)))
            return 29;
        else
            return 28;
    }
    else
        return DayOfMonth[month - 1];
}


//===========================================================================
//函数名: RTC_Set_DateTimeCounter
//功能  : 设置RTC时间(将时间转化为时间戳保存到RTC中)
//参数  :
//返回值:
//===========================================================================
void RTC_Set_DateTimeCounter(RTC_DateTypeDef *sDate, RTC_TimeTypeDef *sTime)
{
    uint32_t timecounter = (uint32_t)sTime->Hours * 3600 + (uint32_t)sTime->Minutes * 60 + sTime->Seconds + GetDiffDate(DateBase, *sDate) * 3600 * 24;
    RTC_WriteTimeCounter(&hrtc, timecounter);
}

//===========================================================================
//函数名: RTC_Get_DateTimeCounter
//功能  : 获取RTC时间(获取存储在计数器中的时间戳,再将其转化为时间)
//参数  :
//返回值:
//===========================================================================
void RTC_Get_DateTimeCounter(RTC_DateTypeDef *sDate, RTC_TimeTypeDef *sTime)
{
    uint32_t timecounter = RTC_ReadTimeCounter(&hrtc);
    uint32_t SecOfToday = timecounter % (3600 * 24);
    uint32_t DiffDay = timecounter / (3600 * 24);

    sTime->Hours = SecOfToday / 3600;
    sTime->Minutes = SecOfToday % 3600 / 60;
    sTime->Seconds = SecOfToday % 60;

    *sDate = CalculateDate(DateBase, DiffDay);
}

改写RTC时钟初始化函数 void MX_RTC_Init(void)

void MX_RTC_Init(void)
{

    /* USER CODE BEGIN RTC_Init 0 */

    /* USER CODE END RTC_Init 0 */

    RTC_TimeTypeDef sTime = {0};
    RTC_DateTypeDef DateToUpdate = {0};

    /* USER CODE BEGIN RTC_Init 1 */
    __HAL_RCC_BKP_CLK_ENABLE();
    __HAL_RCC_PWR_CLK_ENABLE();
    /* USER CODE END RTC_Init 1 */
    /** 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 */
    // 判断RTC是否进行过初始化设置
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x0102)
    {
        /* USER CODE END Check_RTC_BKUP */

        /** Initialize RTC and set the Time and Date
         */
        sTime.Hours = 23;
        sTime.Minutes = 59;
        sTime.Seconds = 50;

        DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
        DateToUpdate.Month = 01;
        DateToUpdate.Date = 01;
        DateToUpdate.Year = 22;

        // 将日期和时间换算为时间戳保存到计数器中
        RTC_Set_DateTimeCounter(&DateToUpdate, &sTime);

        // 设置RTC初始化设置完成标志
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x0102);
        /* USER CODE BEGIN RTC_Init 2 */
    }
    /* USER CODE END RTC_Init 2 */
}

当需要更新时间时,只需要通过接口  RTC_Set_DateTimeCounter  设置时间 和 RTC_Get_DateTimeCounter 获取时间  即可

### STM32 CubeMX RTC 掉电保持计时配置 #### 配置RTC模块 在STM32CubeMX开发环境中,为了使RTC能够在掉电情况下继续工作并保存当前的时间和日期数据,需要进行如下设置: - **启动内部低速振荡器 (LSI)** 或者 **连接外部晶体 (LSE)** 来作为RTC的工作时钟源。通常推荐使用更稳定的LSE来提高精度[^1]。 ```c RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // 初始化 LSE 振荡器 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.LSEState = RCC_LSE_ON; // 开启 LSE if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){ Error_Handler(); } ``` - **开启备份区域访问权限**:因为RTC寄存器位于备份域内,在初始化之前必须允许对该区域的操作。 ```c __HAL_RCC_PWR_CLK_ENABLE(); // 启用 PWR 时钟 HAL_PWR_EnableBkUpAccess(); // 允许访问备份区 ``` - **激活RTC功能** 通过调用`HAL_RTC_Init()`函数完成RTC硬件资源的分配以及参数设定,确保传入正确的RTC初始化结构体指针。 ```c RTC_HandleTypeDef hrtc; void MX_RTC_Init(void) { __HAL_RCC_RTC_ENABLE(); hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; hrtc.Init.SynchPrediv = 255; if (HAL_RTC_Init(&hrtc) != HAL_OK){ Error_Handler(); } } ``` #### 设置备用电源模式下的持续供电 为了让RTC能在系统断电期间依然正常运作,还需要确保Vbat引脚接有独立电池或其他形式的能量供应装置给RTC电路提供必要的电力支持。这一步骤并非由软件定义而是依赖于实际硬件设计阶段的选择与安排。 #### 时间片轮询机制实现 对于希望利用时间片轮询方式更新显示或触发某些事件的情况,则可以在主循环里加入对RTC读取操作,并配合延时函数形成周期性的查询过程[^3]。 ```c while(1){ /* 获取当前时间 */ HAL_RTC_GetTime(&hrtc, &sTime, FORMAT_BIN); /* 显示时间到LCD屏或者其他接口 */ Display_Time(sTime.Hours,sTime.Minutes,sTime.Seconds); /* 延迟一段时间再重复上述动作 */ HAL_Delay(1000); } ``` 以上即是在STM32中借助CubeMX工具配置RTC以达成掉电保护效果的方法概览及其简单应用实例。
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值