feat: 添加RTC管理模块并集成SNTP时间同步功能

实现RTC时间管理功能,支持通过SNTP自动同步网络时间并持久化存储
提供时间来源追踪和掉电保持功能,优化LCD显示的时间更新机制
This commit is contained in:
2026-02-24 01:06:41 +08:00
parent 43a5042c9e
commit addfb3faad
9 changed files with 798 additions and 50 deletions

View File

@@ -0,0 +1,146 @@
# RTC 管理模块使用说明
## 概述
本模块提供了系统RTC时间的统一管理功能支持通过SNTP网络时间同步自动更新RTC时间确保即使在网络未连接的情况下LCD也能正常显示当前时间。
## 功能特点
- **独立时间管理**: RTC时间独立于SNTP即使SNTP未同步RTC仍能保持运行
- **自动同步**: 获取到有效的SNTP时间后自动更新RTC
- **时间来源追踪**: 记录时间设置来源(未设置/编译时间/网络同步/用户设置)
- **掉电保持**: RTC时间在掉电后通过备份寄存器保持
## 文件说明
- `bsp_rtc.h`: RTC管理模块头文件
- `bsp_rtc.c`: RTC管理模块实现
## 使用方法
### 1. 初始化
`main.c` 中调用初始化函数:
```c
#include "bsp_rtc.h"
void main(void) {
// ... 其他初始化 ...
MX_RTC_Init(); // HAL库RTC初始化由CubeMX生成
BSP_RTC_Init(); // RTC管理模块初始化
// ... 其他初始化 ...
}
```
### 2. 在LCD显示任务中读取RTC时间
`freertos.c` 的LCD显示任务中
```c
#include "bsp_rtc.h"
void lcd_task(void *argument) {
RTC_Time_t rtc_time;
for (;;) {
if (BSP_RTC_GetTime(&rtc_time) == 0) {
// RTC时间读取成功显示到LCD
char time_str[32];
snprintf(time_str, sizeof(time_str),
"%04d-%02d-%02d %02d:%02d:%02d",
rtc_time.year, rtc_time.month, rtc_time.day,
rtc_time.hour, rtc_time.minute, rtc_time.second);
ST7735_WriteString(5, 5, time_str, &Font_11x18,
ST7735_WHITE, ST7735_BLACK);
}
osDelay(1000); // 每秒更新一次
}
}
```
### 3. SNTP自动同步
WiFi模块在获取到有效的SNTP时间后会自动调用 `BSP_RTC_UpdateFromSNTP()` 更新RTC
```c
// 在 dx_wf_24.c 的 WIFI_Get_SNTP_Time() 函数中
if (WIFI_Get_SNTP_Time()) {
// SNTP时间获取成功后自动更新RTC
BSP_RTC_UpdateFromSNTP(sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
}
```
### 4. 手动设置时间
如果需要手动设置时间:
```c
RTC_Time_t manual_time;
manual_time.year = 2026;
manual_time.month = 2;
manual_time.day = 23;
manual_time.hour = 15;
manual_time.minute = 30;
manual_time.second = 0;
manual_time.weekday = get_weekday(2026, 2, 23); // 自动计算星期
BSP_RTC_SetTime(&manual_time, RTC_SOURCE_USER_SET);
```
### 5. 检查时间是否已设置
```c
if (BSP_RTC_IsTimeSet()) {
// RTC时间已设置可以正常显示
} else {
// RTC时间为默认值等待时间同步
ST7735_WriteString(5, 5, "等待时间同步...", &Font_11x18,
ST7735_YELLOW, ST7735_BLACK);
}
```
## API参考
### `void BSP_RTC_Init(void)`
初始化RTC管理模块读取当前RTC时间并记录时间来源。
### `int8_t BSP_RTC_SetTime(const RTC_Time_t *time, RTC_Source_t source)`
设置RTC时间。
- **参数**: `time` - 时间结构体指针
- **参数**: `source` - 时间来源
- **返回**: 0成功-1失败
### `int8_t BSP_RTC_GetTime(RTC_Time_t *time)`
获取RTC当前时间。
- **参数**: `time` - 输出时间结构体指针
- **返回**: 0成功-1失败
### `int8_t BSP_RTC_UpdateFromSNTP(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)`
使用SNTP时间更新RTC内部自动计算星期
- **参数**: `year` - 年份
- **参数**: `month` - 月份
- **参数**: `day` - 日期
- **参数**: `hour` - 小时
- **参数**: `minute` - 分钟
- **参数**: `second` - 秒
- **返回**: 0成功-1失败
### `uint8_t BSP_RTC_IsTimeSet(void)`
检查RTC时间是否已设置非默认值2000-01-01 00:00:00
- **返回**: 1已设置0未设置
### `const char* BSP_RTC_GetSourceString(RTC_Source_t source)`
获取时间来源描述字符串。
- **参数**: `source` - 时间来源枚举
- **返回**: 描述字符串
## 注意事项
1. **初始时间**: 系统首次启动时RTC默认时间为 2000-01-01 00:00:00需要等待SNTP同步后才会显示正确时间
2. **掉电保持**: RTC时间通过备份寄存器保持掉电后时间不会丢失需要VBAT电池供电
3. **网络依赖**: SNTP时间同步需要网络连接但RTC时间独立运行不影响LCD显示
4. **首次同步**: 首次SNTP同步可能需要较长时间30秒到几分钟建议在LCD上显示"等待时间同步"

View File

@@ -0,0 +1,382 @@
/**
******************************************************************************
* @file bsp_rtc.c
* @brief RTC管理模块实现
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "bsp_rtc.h"
#include "rtc.h"
#include "elog.h"
#include <string.h>
/* Private defines ----------------------------------------------------------*/
#define TAG "BSP_RTC"
#define RTC_INIT_FLAG_BKP RTC_BKP_DR1 // 初始化标志 (0xA5A5)
#define RTC_SOURCE_BKP RTC_BKP_DR2 // 时间来源
#define RTC_YEAR_BKP RTC_BKP_DR3 // 年份 (高8位) + 月份 (低8位)
#define RTC_DAY_BKP RTC_BKP_DR4 // 日期 (高8位) + 星期 (低8位)
#define RTC_TIME_BKP RTC_BKP_DR5 // 时分秒 (BCD格式: HHMMSS)
/* Private variables ---------------------------------------------------------*/
static RTC_Source_t rtc_source = RTC_SOURCE_NONE;
static RTC_Update_Callback_t rtc_update_callback = NULL;
static uint8_t rtc_initialized = 0; // 标记RTC是否已初始化
/* Private function prototypes -----------------------------------------------*/
static uint8_t get_weekday(uint16_t year, uint8_t month, uint8_t day);
static void trigger_callback(void);
static void save_time_to_bkp(const RTC_Time_t *time, RTC_Source_t source);
static int8_t restore_time_from_bkp(void);
/* Exported functions --------------------------------------------------------*/
/**
* @brief 初始化RTC管理模块
* @note 使用HAL库访问备份寄存器检查RTC是否已初始化
* 注意MX_RTC_Init()会先设置默认时间,这里需要检测并恢复
*/
void BSP_RTC_Init(void) {
RTC_TimeTypeDef rtc_time;
RTC_DateTypeDef rtc_date;
// 检查备份寄存器中的初始化标志
uint32_t init_flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_INIT_FLAG_BKP);
uint32_t year_month_bkp = HAL_RTCEx_BKUPRead(&hrtc, RTC_YEAR_BKP);
uint32_t time_bkp = HAL_RTCEx_BKUPRead(&hrtc, RTC_TIME_BKP);
elog_i(TAG, ">>> BKP寄存器状态: FLAG=0x%04X, YEAR_MONTH=0x%04X, TIME=0x%04X",
(unsigned int)init_flag, (unsigned int)year_month_bkp, (unsigned int)time_bkp);
if (init_flag != 0xA5A5) {
// 首次初始化:备份寄存器中没有标志
// 此时MX_RTC_Init()已经设置了默认时间2000-01-01 00:00:00
// 我们将其作为首次启动的默认时间
rtc_source = RTC_SOURCE_NONE;
rtc_initialized = 1;
elog_i(TAG, "RTC首次初始化等待时间同步");
} else {
// 非首次初始化:备份寄存器已有标志
// 始终从备份寄存器恢复时间,确保数据一致性
rtc_initialized = 1;
// 先读取当前时间用于日志BCD格式
if (HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BCD) == HAL_OK &&
HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BCD) == HAL_OK) {
// BCD转二进制
uint8_t year = ((rtc_date.Year >> 4) * 10 + (rtc_date.Year & 0x0F));
uint8_t month = ((rtc_date.Month >> 4) * 10 + (rtc_date.Month & 0x0F));
uint8_t day = ((rtc_date.Date >> 4) * 10 + (rtc_date.Date & 0x0F));
uint8_t hour = ((rtc_time.Hours >> 4) * 10 + (rtc_time.Hours & 0x0F));
uint8_t minute = ((rtc_time.Minutes >> 4) * 10 + (rtc_time.Minutes & 0x0F));
uint8_t second = ((rtc_time.Seconds >> 4) * 10 + (rtc_time.Seconds & 0x0F));
elog_i(TAG, "当前RTC时间: %04d-%02d-%02d %02d:%02d:%02d",
2000 + year, month, day, hour, minute, second);
}
// 尝试从备份寄存器恢复时间
if (restore_time_from_bkp() == 0) {
// 再次读取恢复后的时间用于日志BCD格式
if (HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BCD) == HAL_OK &&
HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BCD) == HAL_OK) {
// BCD转二进制
uint8_t year = ((rtc_date.Year >> 4) * 10 + (rtc_date.Year & 0x0F));
uint8_t month = ((rtc_date.Month >> 4) * 10 + (rtc_date.Month & 0x0F));
uint8_t day = ((rtc_date.Date >> 4) * 10 + (rtc_date.Date & 0x0F));
uint8_t hour = ((rtc_time.Hours >> 4) * 10 + (rtc_time.Hours & 0x0F));
uint8_t minute = ((rtc_time.Minutes >> 4) * 10 + (rtc_time.Minutes & 0x0F));
uint8_t second = ((rtc_time.Seconds >> 4) * 10 + (rtc_time.Seconds & 0x0F));
elog_i(TAG, "RTC时间已从备份寄存器恢复: %04d-%02d-%02d %02d:%02d:%02d",
2000 + year, month, day, hour, minute, second);
}
} else {
rtc_source = RTC_SOURCE_NONE;
elog_w(TAG, "备份寄存器无有效时间数据");
}
}
}
/**
* @brief 从备份寄存器恢复时间
*/
static int8_t restore_time_from_bkp(void) {
// 读取保存的时间数据
uint32_t year_month = HAL_RTCEx_BKUPRead(&hrtc, RTC_YEAR_BKP);
uint32_t day_week = HAL_RTCEx_BKUPRead(&hrtc, RTC_DAY_BKP);
uint32_t time_data = HAL_RTCEx_BKUPRead(&hrtc, RTC_TIME_BKP);
// 检查数据是否有效 (年份范围2000-2099)
uint8_t year_high = (year_month >> 8) & 0xFF;
if (year_high > 99) {
return -1; // 无效数据
}
RTC_Time_t time;
time.year = 2000 + year_high;
time.month = year_month & 0xFF;
time.day = (day_week >> 8) & 0xFF;
time.weekday = day_week & 0xFF;
time.hour = (time_data >> 16) & 0xFF;
time.minute = (time_data >> 8) & 0xFF;
time.second = time_data & 0xFF;
// 参数检查
if (time.month < 1 || time.month > 12 ||
time.day < 1 || time.day > 31 ||
time.hour > 23 ||
time.minute > 59 ||
time.second > 59) {
return -1;
}
// 读取时间来源
uint32_t source_flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_SOURCE_BKP);
RTC_Source_t source = RTC_SOURCE_COMPILED;
if (source_flag == 1) {
source = RTC_SOURCE_SNTP;
} else if (source_flag == 2) {
source = RTC_SOURCE_USER_SET;
}
// 设置时间
return BSP_RTC_SetTime(&time, source);
}
/**
* @brief 保存时间到备份寄存器
*/
static void save_time_to_bkp(const RTC_Time_t *time, RTC_Source_t source) {
// 保存年份和月份: 高8位=年-2000, 低8位=月份
uint32_t year_month = ((time->year - 2000) << 8) | time->month;
HAL_RTCEx_BKUPWrite(&hrtc, RTC_YEAR_BKP, year_month);
// 保存日期和星期: 高8位=日期, 低8位=星期
uint32_t day_week = (time->day << 8) | time->weekday;
HAL_RTCEx_BKUPWrite(&hrtc, RTC_DAY_BKP, day_week);
// 保存时间: 高8位=时, 中8位=分, 低8位=秒
uint32_t time_data = (time->hour << 16) | (time->minute << 8) | time->second;
HAL_RTCEx_BKUPWrite(&hrtc, RTC_TIME_BKP, time_data);
// 保存时间来源
uint32_t source_flag = 0;
switch (source) {
case RTC_SOURCE_SNTP:
source_flag = 1;
break;
case RTC_SOURCE_USER_SET:
source_flag = 2;
break;
default:
source_flag = 0;
break;
}
HAL_RTCEx_BKUPWrite(&hrtc, RTC_SOURCE_BKP, source_flag);
}
/**
* @brief 设置RTC时间
*/
int8_t BSP_RTC_SetTime(const RTC_Time_t *time, RTC_Source_t source) {
if (!time) {
elog_e(TAG, "时间参数为空");
return -1;
}
// 参数检查
if (time->year < 2000 || time->year > 2099 ||
time->month < 1 || time->month > 12 ||
time->day < 1 || time->day > 31 ||
time->hour > 23 ||
time->minute > 59 ||
time->second > 59) {
elog_e(TAG, "时间参数无效");
return -1;
}
RTC_TimeTypeDef rtc_time = {0};
RTC_DateTypeDef rtc_date = {0};
// 转换为BCD格式与MX_RTC_Init一致
// 注意STM32F1系列的RTC_TimeTypeDef只有Hours/Minutes/Seconds三个字段
rtc_time.Hours = (time->hour / 10) << 4 | (time->hour % 10);
rtc_time.Minutes = (time->minute / 10) << 4 | (time->minute % 10);
rtc_time.Seconds = (time->second / 10) << 4 | (time->second % 10);
rtc_date.Year = ((time->year - 2000) / 10) << 4 | ((time->year - 2000) % 10);
rtc_date.Month = (time->month / 10) << 4 | (time->month % 10);
rtc_date.Date = (time->day / 10) << 4 | (time->day % 10);
rtc_date.WeekDay = time->weekday;
// 设置RTC时间使用BCD格式
if (HAL_RTC_SetTime(&hrtc, &rtc_time, RTC_FORMAT_BCD) != HAL_OK) {
elog_e(TAG, "RTC时间设置失败");
return -1;
}
if (HAL_RTC_SetDate(&hrtc, &rtc_date, RTC_FORMAT_BCD) != HAL_OK) {
elog_e(TAG, "RTC日期设置失败");
return -1;
}
rtc_source = source;
// 保存完整时间到备份寄存器(使用二进制格式保存,方便后续使用)
save_time_to_bkp(time, source);
// 确保初始化标志已设置
uint32_t init_flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_INIT_FLAG_BKP);
if (init_flag != 0xA5A5) {
HAL_RTCEx_BKUPWrite(&hrtc, RTC_INIT_FLAG_BKP, 0xA5A5);
}
rtc_initialized = 1;
elog_i(TAG, "RTC时间已更新: %04d-%02d-%02d %02d:%02d:%02d (来源: %s)",
time->year, time->month, time->day,
time->hour, time->minute, time->second,
BSP_RTC_GetSourceString(source));
// 触发时间更新回调通知LCD刷新
trigger_callback();
return 0;
}
/**
* @brief 获取RTC当前时间
*/
int8_t BSP_RTC_GetTime(RTC_Time_t *time) {
if (!time) {
return -1;
}
RTC_TimeTypeDef rtc_time;
RTC_DateTypeDef rtc_date;
// 使用BCD格式读取与MX_RTC_Init一致
if (HAL_RTC_GetTime(&hrtc, &rtc_time, RTC_FORMAT_BCD) != HAL_OK) {
return -1;
}
if (HAL_RTC_GetDate(&hrtc, &rtc_date, RTC_FORMAT_BCD) != HAL_OK) {
return -1;
}
// BCD转换为二进制
time->year = 2000 + ((rtc_date.Year >> 4) * 10 + (rtc_date.Year & 0x0F));
time->month = ((rtc_date.Month >> 4) * 10 + (rtc_date.Month & 0x0F));
time->day = ((rtc_date.Date >> 4) * 10 + (rtc_date.Date & 0x0F));
time->hour = ((rtc_time.Hours >> 4) * 10 + (rtc_time.Hours & 0x0F));
time->minute = ((rtc_time.Minutes >> 4) * 10 + (rtc_time.Minutes & 0x0F));
time->second = ((rtc_time.Seconds >> 4) * 10 + (rtc_time.Seconds & 0x0F));
time->weekday = rtc_date.WeekDay;
return 0;
}
/**
* @brief 使用SNTP时间更新RTC
*/
int8_t BSP_RTC_UpdateFromSNTP(uint16_t year, uint8_t month, uint8_t day,
uint8_t hour, uint8_t minute, uint8_t second) {
RTC_Time_t time;
time.year = year;
time.month = month;
time.day = day;
time.hour = hour;
time.minute = minute;
time.second = second;
time.weekday = get_weekday(year, month, day);
return BSP_RTC_SetTime(&time, RTC_SOURCE_SNTP);
}
/**
* @brief 检查RTC时间是否已设置
*/
uint8_t BSP_RTC_IsTimeSet(void) {
RTC_Time_t time;
if (BSP_RTC_GetTime(&time) != 0) {
return 0;
}
// 检查是否为默认时间
if (time.year == RTC_DEFAULT_YEAR &&
time.month == RTC_DEFAULT_MONTH &&
time.day == RTC_DEFAULT_DAY &&
time.hour == 0 &&
time.minute == 0 &&
time.second == 0) {
return 0;
}
return 1;
}
/**
* @brief 获取RTC时间来源描述
*/
const char* BSP_RTC_GetSourceString(RTC_Source_t source) {
switch (source) {
case RTC_SOURCE_NONE:
return "未设置";
case RTC_SOURCE_COMPILED:
return "编译时间";
case RTC_SOURCE_SNTP:
return "网络同步";
case RTC_SOURCE_USER_SET:
return "用户设置";
default:
return "未知";
}
}
/* Private functions ---------------------------------------------------------*/
// 预留函数,暂未使用
// static uint8_t is_leap_year(uint16_t year) {
// return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
// }
/**
* @brief 计算某年某月某日是星期几
* @return 1=周一, 7=周日
*/
static uint8_t get_weekday(uint16_t year, uint8_t month, uint8_t day) {
// Zeller公式计算星期几
if (month < 3) {
month += 12;
year--;
}
uint16_t c = year / 100;
uint16_t y = year % 100;
uint8_t w = (y + y/4 + c/4 - 2*c + 26*(month+1)/10 + day - 1) % 7;
// 转换为RTC格式 (1=周一, 7=周日)
if (w == 0) {
return 7; // 周日
} else {
return w;
}
}
/**
* @brief 注册RTC时间更新回调函数
*/
void BSP_RTC_RegisterCallback(RTC_Update_Callback_t callback) {
rtc_update_callback = callback;
}
/**
* @brief 触发时间更新回调
*/
static void trigger_callback(void) {
if (rtc_update_callback != NULL) {
rtc_update_callback();
}
}

View File

@@ -0,0 +1,118 @@
/**
******************************************************************************
* @file bsp_rtc.h
* @brief RTC管理模块头文件
* 用于管理系统RTC时间支持通过SNTP网络时间同步更新
******************************************************************************
*/
#ifndef __BSP_RTC_H__
#define __BSP_RTC_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include <stdint.h>
/* Exported types ------------------------------------------------------------*/
/**
* @brief RTC时间结构体
*/
typedef struct {
uint16_t year; ///< 年份 (2000-2099)
uint8_t month; ///< 月份 (1-12)
uint8_t day; ///< 日期 (1-31)
uint8_t hour; ///< 小时 (0-23)
uint8_t minute; ///< 分钟 (0-59)
uint8_t second; ///< 秒 (0-59)
uint8_t weekday; ///< 星期 (1=周一, 7=周日)
} RTC_Time_t;
/**
* @brief RTC时间来源
*/
typedef enum {
RTC_SOURCE_NONE = 0, ///< 未设置时间
RTC_SOURCE_COMPILED, ///< 编译时间
RTC_SOURCE_SNTP, ///< 网络时间同步
RTC_SOURCE_USER_SET ///< 用户手动设置
} RTC_Source_t;
/* Exported constants --------------------------------------------------------*/
#define RTC_DEFAULT_YEAR 2000
#define RTC_DEFAULT_MONTH 1
#define RTC_DEFAULT_DAY 1
/* Exported macro ------------------------------------------------------------*/
/* Exported functions prototypes ---------------------------------------------*/
/**
* @brief RTC时间更新回调函数类型
*/
typedef void (*RTC_Update_Callback_t)(void);
/**
* @brief 注册RTC时间更新回调函数
* @param callback 回调函数指针
* @note 当RTC时间通过SNTP更新时会调用此回调
*/
void BSP_RTC_RegisterCallback(RTC_Update_Callback_t callback);
/**
* @brief 初始化RTC管理模块
* @note 此函数会在main中调用RTC硬件已由CubeMX初始化
*/
void BSP_RTC_Init(void);
/**
* @brief 设置RTC时间
* @param time 要设置的时间
* @param source 时间来源
* @return 0成功, -1失败
*/
int8_t BSP_RTC_SetTime(const RTC_Time_t *time, RTC_Source_t source);
/**
* @brief 获取RTC当前时间
* @param[out] time 输出时间
* @return 0成功, -1失败
*/
int8_t BSP_RTC_GetTime(RTC_Time_t *time);
/**
* @brief 使用SNTP时间更新RTC
* @note 此函数应由WiFi模块在获取到有效的SNTP时间后调用
* @param year 年份
* @param month 月份
* @param day 日期
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @return 0成功, -1失败
*/
int8_t BSP_RTC_UpdateFromSNTP(uint16_t year, uint8_t month, uint8_t day,
uint8_t hour, uint8_t minute, uint8_t second);
/**
* @brief 检查RTC时间是否已设置
* @return 1已设置, 0未设置(为默认时间2000-01-01 00:00:00)
*/
uint8_t BSP_RTC_IsTimeSet(void);
/**
* @brief 获取RTC时间来源描述
* @param source 时间来源
* @return 描述字符串
*/
const char* BSP_RTC_GetSourceString(RTC_Source_t source);
#ifdef __cplusplus
}
#endif
#endif /* __BSP_RTC_H__ */