RT-Thread Nano版本使用RTC,同时解决复位日期丢失,时间并正确问题

文章使用RT-Thread Nano,版本为3.1.5 。使用野火霸道主板,主控为STM32F103ZET6。

由于RT中使用的HAL库函数,而F1系列芯片RTC中并没有DR(存储日期)和TR(存储时间)寄存器,只有CNT寄存器,因此在使用硬件RTC时,不能在使用下面四个函数对RTC操作。F4并不影响F4硬件RTC(可直接复制)

HAL_RTC_SetTime(),HAL_RTC_GetTime(),

HAL_RTC_SetDate(),HAL_RTC_GetDate();

如果使用会在重启(断电或者不断电)复位时,导致日期丢失,时间并不丢失。原因如下:

在 HAL 库中,RTC_HandleTypeDef 结构体是全局变量,存储在 RAM 中。其中用于记录日期的成员(如 DateToUpdate在系统复位后会丢失,因此无法在重启后保持日期信息。

另一方面,HAL_RTC_SetTime() 写入到硬件寄存器 RTC_CNT 的值,并不是从某个固定纪元(如 2000-01-01)开始的总秒数,而是从当天 00:00:00 到当前设置时间所经过的秒数

也就是说,RTC_CNT 只保存"当天内的秒数",取值范围为 0 到 86399。

举例来说:

  • 如果你设置的时间是 2000-01-02 00:00:01

  • 实际写入 RTC_CNT 的值是 1 秒(只表示当天已过 1 秒)

  • 不会写入从 2000-01-01 00:00:00 到该时刻的总秒数(即 86401 秒)

这种设计导致:

  • 时间信息(秒数)保存在硬件中,由 VBAT 供电保持;

  • 日期信息仅保存在 RAM 的结构体中,重启即丢失;

  • 每次重启后,HAL 库读取 RTC_CNT 获取当前时间,但日期只能从默认值(2000-01-01)开始计算,因此出现"时间正确、日期重置"的现象。

用户程序:

以下给定直接操作CNT寄存器程序,需要用到hal库中两个函数。删除库函数中的static ,如以下:

以下给定用户程序,可复制粘贴。

cpp 复制代码
#include <rtthread.h>
#include <easyflash.h>
#include "stm32f1xx_hal.h"
#include "stm32f1xx.h"
#include <board.h>

#define DBG_TAG "rtc"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>






// 备份寄存器标志值
#define RTC_BKP_DATA    0x5AA7
#define RTC_BKP_DRX     RTC_BKP_DR1

#define RTC_BASE_YEAR       2000   //基础年
#define SECONDS_PER_DAY     86400UL  //每天的秒数
#define SECONDS_PER_HOUR    3600UL   //每小时秒数
#define SECONDS_PER_MINUTE  60UL     //每小时分钟数

// 平年/闰年秒数
#define SECONDS_PER_NORM_YEAR   (365UL * SECONDS_PER_DAY)  // 31536000
#define SECONDS_PER_LEAP_YEAR   (366UL * SECONDS_PER_DAY)  // 31622400

// 从公元1年到2000-01-01的总天数
#define DAYS_FROM_1_TO_2000     730484


//可放.h中
typedef struct
{
    int Year;//+2000 年
    uint8_t Month;
    uint8_t Date;
    uint8_t WeekDay;
    uint8_t Hours;
    uint8_t Minutes;
    uint8_t Seconds;
} Date_TimeTypeDef;










/* ---------------------------------------[    函数声明      相  关]--------------------------------------
 * ######################################################################################################
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以下----------|||||||||||||||||||||||||||||||||||||||
 */
extern uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef *hrtc);
extern HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter);
/* ---------------------------------------[    函数声明      相  关]--------------------------------------
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以上----------|||||||||||||||||||||||||||||||||||||||
 */






/* ---------------------------------------[        全局变量  相  关]--------------------------------------
 * ######################################################################################################
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以下----------|||||||||||||||||||||||||||||||||||||||
 */
/*刷新时钟数据*/
Date_TimeTypeDef Date_Time;  //系统主时钟

RTC_HandleTypeDef hrtc;


// 平年每月累积天数表 (索引0保留,month_days[1]=前1个月总天数)
static const uint16_t s_month_days[] = {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};
/* ---------------------------------------[        全局变量  相  关]--------------------------------------
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以上----------|||||||||||||||||||||||||||||||||||||||
 */














/* ---------------------------------------[     主要存储逻辑 相  关]--------------------------------------
 * ######################################################################################################
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以下----------|||||||||||||||||||||||||||||||||||||||
 */
/*****************************************************************************
 * RTC 自定义驱动 - 基于计数器存储绝对时间戳方案
 * 基准时间:2000-01-01 00:00:00
 ****************************************************************************/
/**
 * @brief 闰年判断
 * @param year 公历年(4位数)
 * @return 1:闰年, 0:平年
 */
static uint8_t IsLeapYear(int year)
{
    if (year % 4 != 0) return 0;
    if (year % 100 != 0) return 1;
    if (year % 400 != 0) return 0;
    return 1;
}

/**
 * @brief 计算从基准时间(2000-01-01 00:00)到指定日期0点的秒数
 * @param year  公历年(4位数)
 * @param month 月份 1-12
 * @param day   日期 1-31
 * @return 秒数
 */
static uint32_t GetSecondsSinceBase(int year, int month, int day)
{
    uint32_t total_seconds = 0;
    int days_in_year = 0;

    /* 参数合法性检查 */
    if (year < RTC_BASE_YEAR || month < 1 || month > 12 || day < 1 || day > 31) {
        return 0;  // 非法参数返回0
    }

    /* 1. 计算年内天数 (从1月1日到目标日期经过的天数) */
    days_in_year = s_month_days[month - 1];          // 前month-1个月的总天数
    days_in_year += (day - 1);                        // 加上当月天数(减1因为1号是第0天)

    /* 闰年且月份>2月时加1天 */
    if (IsLeapYear(year) && month > 2) {
        days_in_year++;
    }

    total_seconds = (uint32_t)days_in_year * SECONDS_PER_DAY;

    /* 2. 加上从基准年到目标年前一年的整年秒数 */
    for (int y = year - 1; y >= RTC_BASE_YEAR; y--) {
        total_seconds += IsLeapYear(y) ? SECONDS_PER_LEAP_YEAR : SECONDS_PER_NORM_YEAR;
    }

    return total_seconds;
}

/**
 * @brief 设置时间(保持日期不变)
 * @param time 时间结构体指针
 */
void RTC_SetTime(const RTC_TimeTypeDef *time)
{
    uint32_t counter;
    uint32_t day_seconds;

    if (!time) return;

    counter = RTC_ReadTimeCounter(&hrtc);

    /* 1. 提取当前日期对应的秒数(整天的倍数) */
    day_seconds = counter - (counter % SECONDS_PER_DAY);

    /* 2. 计算新的时间秒数 */
    uint32_t new_time_seconds = time->Hours * SECONDS_PER_HOUR +
                                time->Minutes * SECONDS_PER_MINUTE +
                                time->Seconds;

    /* 3. 合成新计数器值 */
    counter = day_seconds + new_time_seconds;

    RTC_WriteTimeCounter(&hrtc, counter);
}

/**
 * @brief 设置日期(保持时间不变)
 * @param date 日期结构体指针
 */
void RTC_SetDate(const RTC_DateTypeDef *date)
{
    uint32_t counter;
    uint32_t time_seconds;
    uint32_t date_seconds;

    if (!date) return;

    counter = RTC_ReadTimeCounter(&hrtc);

    /* 1. 提取当前时间秒数(一天内的秒数) */
    time_seconds = counter % SECONDS_PER_DAY;

    /* 2. 计算新日期对应的秒数(从基准开始) */
    date_seconds = GetSecondsSinceBase(
        date->Year + RTC_BASE_YEAR,
        date->Month,
        date->Date
    );

    /* 3. 合成新计数器值 */
    counter = date_seconds + time_seconds;

    RTC_WriteTimeCounter(&hrtc, counter);
}

/**
 * @brief 获取当前时间
 * @param time 输出时间结构体
 */
void RTC_GetTime(RTC_TimeTypeDef *time)
{
    uint32_t counter;
    uint32_t day_seconds;

    if (!time) return;

    counter = RTC_ReadTimeCounter(&hrtc);
    day_seconds = counter % SECONDS_PER_DAY;

    time->Hours   = (uint8_t)(day_seconds / SECONDS_PER_HOUR);
    day_seconds  %= SECONDS_PER_HOUR;
    time->Minutes = (uint8_t)(day_seconds / SECONDS_PER_MINUTE);
    day_seconds  %= SECONDS_PER_MINUTE;
    time->Seconds = (uint8_t)day_seconds;
}

/**
 * @brief 获取当前日期
 * @param date 输出日期结构体
 * @note 算法参考:从总秒数反推年月日
 */
void RTC_GetDate(RTC_DateTypeDef *date)
{
    uint32_t counter;
    int total_days;
    int year, yday, month, day, wday;

    if (!date) return;

    counter = RTC_ReadTimeCounter(&hrtc);
    total_days = (int)(counter / SECONDS_PER_DAY);

    /* 转换为从公元1年1月1日开始的天数(算法需要) */
    total_days = total_days - (31 + 28) + DAYS_FROM_1_TO_2000;

    /* 估算年份(400年周期公式)*/
    year = (total_days + 2) * 400 / (365 * 400 + 100 - 4 + 1);

    /* 计算年内天数 */
    yday = total_days - (365 * year + year / 4 - year / 100 + year / 400);

    /* 修正年份(如果yday为负)*/
    if (yday < 0) {
        if (IsLeapYear(year)) {
            yday += 366;
        } else {
            yday += 365;
        }
        year--;
    }

    /* 计算月份(算法以3月为起点)*/
    month = (yday + 31) * 10 / 306;
    day = yday - (367 * month / 12 - 30) + 1;

    /* 修正月份到正常范围 */
    if (yday >= 306) {
        year++;
        month -= 10;
    } else {
        month += 2;
    }

    /* 计算星期几(2000-01-01是星期六)*/
    wday = (6 + total_days) % 7;

    /* 填充输出结构 */
    date->Date    = (uint8_t)day;
    date->Month   = (uint8_t)month;
    date->Year    = (uint8_t)(year - RTC_BASE_YEAR);
    date->WeekDay = (uint8_t)wday;
}
/* ---------------------------------------[     主要存储逻辑 相  关]--------------------------------------
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以上----------|||||||||||||||||||||||||||||||||||||||
 */





















/* ---------------------------------------[RTC init         相  关]--------------------------------------
 * ######################################################################################################
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以下----------|||||||||||||||||||||||||||||||||||||||
 */
/**
  * @brief  RTC MSP 初始化
  * @param  rtcHandle RTC句柄
  */
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
    if(rtcHandle->Instance == RTC)
    {
        RCC_OscInitTypeDef RCC_OscInitStruct = {0};
        RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

        /* 使能PWR时钟 */
        __HAL_RCC_PWR_CLK_ENABLE();
        /* 使能BKP时钟(STM32F1需要)*/
        __HAL_RCC_BKP_CLK_ENABLE();
        rt_thread_mdelay(5);  // 等待PWR模块稳定
        /* 使能备份域访问 */
        HAL_PWR_EnableBkUpAccess();



        /* 配置LSE作为RTC时钟源 */
        RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
        RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
        RCC_OscInitStruct.LSEState = RCC_LSE_ON;  // LSE打开
        if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
        {
            LOG_E("LSE Config Failed!");
        }

        /* 选择LSE作为RTC时钟源 */
        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
        PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
        {
            LOG_E("RTC Clock Config Failed!");
        }

        /* 使能RTC时钟 */
        __HAL_RCC_RTC_ENABLE();
    }
}

/**
  * @brief  RTC MSP 反初始化
  * @param  rtcHandle RTC句柄
  */
void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{
    if(rtcHandle->Instance == RTC)
    {
        __HAL_RCC_RTC_DISABLE();
        __HAL_RCC_BKP_CLK_DISABLE();
    }
}


/**
  * @brief  RTC 初始化
  */
void MX_RTC_Init(void)
{
    RTC_TimeTypeDef time = {12, 30, 0};  // 12:30:00
    RTC_DateTypeDef date = {4, 3, 20, 26}; // 2026-03-20 周五
    /* 清除复位标志 */
    __HAL_RCC_CLEAR_RESET_FLAGS();

    /* 配置RTC句柄 */
    hrtc.Instance = RTC;
    hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;  // 使用127+255分频得到1Hz
    hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;

    /* 初始化RTC */
    if (HAL_RTC_Init(&hrtc) != HAL_OK)
    {
        LOG_E("RTC Init Failed!");
    }

    /* 等待RTC APB寄存器同步 */
    HAL_RTC_WaitForSynchro(&hrtc);


    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DRX) != RTC_BKP_DATA)
    {
        LOG_I("RTC First Init...");
        // 设置完整的日期时间
        RTC_SetDate(&date);   // 先设置日期
        RTC_SetTime(&time);   // 再设置时间
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DRX, RTC_BKP_DATA);
        LOG_I("RTC initialized with default time: 20%02d-%02d-%02d %02d:%02d:%02d",
                date.Year,date.Month,date.Date,time.Hours,time.Minutes,time.Seconds);
    }
    else {
        LOG_I("RTC Already Initialized.");
        /* 确保备份域访问使能 */
        HAL_PWR_EnableBkUpAccess();
    }

    // 读取
    RTC_GetDate(&date);
    RTC_GetTime(&time);
    LOG_I("get RTC time: 20[%02d]-%02d-%02d %02d:%02d:%02d",
            date.Year,date.Month,date.Date,
            time.Hours,time.Minutes,time.Seconds);
    Date_Time.Year    = date.Year + 2000;
    Date_Time.Month   = date.Month;
    Date_Time.Date    = date.Date;
    Date_Time.Hours   = time.Hours;
    Date_Time.Minutes = time.Minutes;
    Date_Time.Seconds = time.Seconds;
    LOG_I("system main time: [%04d]-%02d-%02d %02d:%02d:%02d",
            Date_Time.Year,Date_Time.Month,Date_Time.Date,
            Date_Time.Hours,Date_Time.Minutes,Date_Time.Seconds);
}
/* ---------------------------------------[RTC init          相  关]--------------------------------------
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以上----------|||||||||||||||||||||||||||||||||||||||
 */














/* ---------------------------------------[   主用户函数     相  关]--------------------------------------
 * ######################################################################################################
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以下----------|||||||||||||||||||||||||||||||||||||||
 */
/**
  * @brief  刷新RTC时间到Date_Time结构体
  * @return 成功读取的项数(2表示全部成功)
  */
uint8_t ReFlash_RTC(void)
{
    RTC_TimeTypeDef GetTime_cache = {0};
    RTC_DateTypeDef GetDate_cache = {0};

    /* 读取时间 */
    RTC_GetTime(&GetTime_cache);
    RTC_GetDate(&GetDate_cache);
    Date_Time.Year    = GetDate_cache.Year + 2000;
    Date_Time.Month   = GetDate_cache.Month;
    Date_Time.Date    = GetDate_cache.Date;
    Date_Time.Hours   = GetTime_cache.Hours;
    Date_Time.Minutes = GetTime_cache.Minutes;
    Date_Time.Seconds = GetTime_cache.Seconds;

    LOG_I("Current RTC: [%04d]-%02d-%02d %02d:%02d:%02d",
            Date_Time.Year, Date_Time.Month, Date_Time.Date,
            Date_Time.Hours, Date_Time.Minutes, Date_Time.Seconds);
    return 2;
}


/**
  * @brief  设置RTC时间
  * @param  new_time 要设置的时间
  */
void set_rtc_user(Date_TimeTypeDef new_time)
{
    char RTC_flag[6] = {0};
    RTC_TimeTypeDef SetTime_cache = {0};
    RTC_DateTypeDef SetDate_cache = {0};

    const char weekdays[6] = {'H','M','S','D','M','Y'};  // 时、分、秒、日、月、年

    LOG_I("Set RTC target: %04d-%02d-%02d %02d:%02d:%02d",
          new_time.Year, new_time.Month, new_time.Date,
          new_time.Hours, new_time.Minutes, new_time.Seconds);

    /* 刷新当前时间 */
    ReFlash_RTC();

    /* 比较时间是否相同 */
    if(Date_Time.Hours != new_time.Hours)     RTC_flag[0] = 1;
    if(Date_Time.Minutes != new_time.Minutes) RTC_flag[1] = 1;
    if(Date_Time.Seconds != new_time.Seconds) RTC_flag[2] = 1;
    if(Date_Time.Date != new_time.Date)       RTC_flag[3] = 1;
    if(Date_Time.Month != new_time.Month)     RTC_flag[4] = 1;
    if(Date_Time.Year != new_time.Year)       RTC_flag[5] = 1;

    /* 检查时间是否合法 */
    if(new_time.Hours < 0 || new_time.Hours >= 24)     RTC_flag[0] = 0;
    if(new_time.Minutes < 0 || new_time.Minutes >= 60) RTC_flag[1] = 0;
    if(new_time.Seconds < 0 || new_time.Seconds >= 60) RTC_flag[2] = 0;
    if(new_time.Date < 1 || new_time.Date > 31)        RTC_flag[3] = 0;
    if(new_time.Month < 1 || new_time.Month > 12)      RTC_flag[4] = 0;
    if(new_time.Year < 2000 || new_time.Year > 2099)   RTC_flag[5] = 0;

    /* 打印比较结果 */
    rt_kprintf("Need update: \n");
    rt_kprintf("legal  ");for(int i=0;i<6;i++){rt_kprintf("%d.%d[%c]  ",i,RTC_flag[i],weekdays[i]);}rt_kprintf("\n");
    rt_kprintf("\n");

    /* 更新日期(如果需要)*/
    if(RTC_flag[3] || RTC_flag[4] || RTC_flag[5])
    {
        SetDate_cache.Year = new_time.Year - 2000;
        SetDate_cache.Month = new_time.Month;
        SetDate_cache.Date = new_time.Date;
        RTC_SetDate(&SetDate_cache);
        LOG_I("Date updated to: %04d-%02d-%02d",new_time.Year, new_time.Month, new_time.Date);
        LOG_I("             to: %04d-%02d-%02d",SetDate_cache.Year, SetDate_cache.Month, SetDate_cache.Date);
    }

    /* 更新时间(如果需要)*/
    if(RTC_flag[0] || RTC_flag[1] || RTC_flag[2])
    {
        SetTime_cache.Hours = new_time.Hours;
        SetTime_cache.Minutes = new_time.Minutes;
        SetTime_cache.Seconds = new_time.Seconds;
        RTC_SetTime(&SetTime_cache);
        LOG_I("Time updated to: %02d:%02d:%02d",new_time.Hours, new_time.Minutes, new_time.Seconds);
        LOG_I("             to: %02d:%02d:%02d",SetTime_cache.Hours, SetTime_cache.Minutes, SetTime_cache.Seconds);
    }

    /* 刷新显示 */
    ReFlash_RTC();
}
/* ---------------------------------------[   主用户函数     相  关]--------------------------------------
 * ******************************************************************************************************
 * ||||||||||||||||||||||||||||||||||||||||----------以上----------|||||||||||||||||||||||||||||||||||||||
 */



//测试,只提供个简单的用法
void set_rtc_user_1(void)
{
    Date_TimeTypeDef new_time;
    new_time.Year = Date_Time.Year + 1;
    new_time.Date = Date_Time.Date ;
    new_time.Hours = Date_Time.Hours ;
    new_time.Minutes = Date_Time.Minutes ;
    new_time.Seconds = Date_Time.Seconds ;
    new_time.Month = Date_Time.Month ;

    set_rtc_user(new_time);
}





/**
  * @brief  RTC初始化导出函数(RT-Thread自动初始化)
  */
int RTC_Word(void)
{
    MX_RTC_Init();
    return 0;
}

INIT_ENV_EXPORT(RTC_Word);





/*

//设置时间   测试程序,用户在在使用时,请使用  set_rtc_user()  设置时间
set_rtc_user_1()


//更新显示
ReFlash_RTC();
 */
相关推荐
LCG元2 小时前
STM32实战:基于STM32CubeMX的串口通信与DMA传输优化
stm32·单片机·嵌入式硬件
heanyu2 小时前
STM32学习 1 ----串口通讯--阻塞式收发+支持printf
stm32·嵌入式硬件·学习
逐步前行13 小时前
STM32_TIM_寄存器操作
stm32·单片机·嵌入式硬件
0南城逆流014 小时前
【STM32】知识点介绍七:PWM功能
stm32·单片机·嵌入式硬件
dashizhi201514 小时前
服务器共享禁止保存到本地磁盘、共享文件禁止另存为本地磁盘、移动硬盘等
运维·网络·stm32·安全·电脑
三佛科技-1873661339718 小时前
FT32F030F6AP7高性能32位RISC内核MCU解析(兼容STM32F030K6TP7)
stm32·单片机·嵌入式硬件
LCMICRO-1331084774619 小时前
长芯微LDC90810完全P2P替代ADC128D818,是一款八通道系统监控器,专为监控复杂系统状态而设计。
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换芯片adc
csaaa20051 天前
STM32F103 开发USB设备端点超过ENDP4以上时崩溃问题的解决
stm32·单片机·嵌入式硬件
LCG元1 天前
故障预测与健康管理:STM32G4监控自身参数,早期预警
stm32·单片机·嵌入式硬件