文章使用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();
*/