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__ */

View File

@@ -3,6 +3,7 @@
#include "cmsis_os2.h"
#include "elog.h"
#include "mp3_driver.h" // 添加MP3模块头文件
#include "bsp_rtc.h" // RTC管理模块
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -234,19 +235,8 @@ uint8_t WIFI_Connect_MQTT(const char *wifi_ssid, const char *wifi_pass,
elog_i(TAG, "开始MQTT连接流程");
// 只在第一次配置SNTP避免重复重启模块
if (!sntp_configured) {
elog_i(TAG, "首次配置SNTP服务器...");
if (!WIFI_Enable_SNTP()) {
elog_e(TAG, "SNTP设置失败");
return 0;
}
sntp_configured = 1;
// SNTP配置后会重启模块等待模块恢复
elog_i(TAG, "等待模块重启后恢复...");
osDelay(3000);
}
/* 注意SNTP配置需要WiFi连接后才能生效
因此在WIFI_Connect_WiFi成功后再配置 */
elog_i(TAG, "断开 MQTT 连接(如果已连接)");
@@ -284,6 +274,49 @@ uint8_t WIFI_Connect_MQTT(const char *wifi_ssid, const char *wifi_pass,
mqtt_state = MQTT_STATE_WIFI_CONNECTED;
elog_i(TAG, "WiFi连接完成状态: %d", mqtt_state);
/* WiFi连接成功后配置SNTP仅首次配置需要模块重启 */
if (!sntp_configured) {
elog_i(TAG, "WiFi已连接现在配置SNTP服务器...");
if (!WIFI_Enable_SNTP()) {
elog_e(TAG, "SNTP设置失败");
// SNTP失败不阻断MQTT连接继续执行
} else {
sntp_configured = 1;
/* SNTP配置后模块会重启等待模块恢复 */
elog_i(TAG, "等待模块重启...");
osDelay(15000);
/* 模块重启后需要重新检查响应并连接WiFi */
elog_i(TAG, "检查模块恢复状态...");
uint8_t module_ready = 0;
uint32_t wait_start = HAL_GetTick();
while (HAL_GetTick() - wait_start < 30000) {
if (WIFI_CheckAck("AT\r\n", "OK", 1000)) {
module_ready = 1;
break;
}
osDelay(1000);
}
if (!module_ready) {
elog_e(TAG, "模块重启后未恢复响应");
return 0;
}
/* 检查WiFi是否已连接避免重复连接和重复播报 */
if (!WIFI_CheckAck("AT+CWJAP?\r\n", "OK", 2000) ||
!strstr((char *)wifi.rx_buffer, "+CWJAP:")) {
/* WiFi未连接重新连接 */
if (!WIFI_Connect_WiFi(wifi_ssid, wifi_pass, 15000)) {
elog_e(TAG, "SNTP配置后WiFi重连失败");
return 0;
}
} else {
elog_i(TAG, "WiFi已连接跳过重新连接");
}
}
}
elog_i(TAG, "4. 设置MQTT客户端ID: %s", client_id);
snprintf(cmd, sizeof(cmd), "AT+MQTTLONGCLIENTID=%s\r\n", client_id);
if (!WIFI_CheckAck(cmd, "OK", 2000)) {
@@ -493,13 +526,27 @@ void wifi_task_mqtt(void *argument) {
elog_i(TAG, "发布设备上线状态");
WIFI_MQTT_Publish_Status("{\"status\":\"online\"}");
// 获取SNTP时间
// 获取SNTP时间需要等待模块同步NTP服务器
elog_i(TAG, "等待NTP服务器同步...");
osDelay(10000); // 等待10秒让模块同步NTP
elog_i(TAG, "获取SNTP时间...");
WIFI_Get_SNTP_Time();
WIFI_Get_Current_Time();
elog_i(TAG, "当前SNTP时间为: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
uint8_t sntp_ok = 0;
// 首次获取失败后等待3秒再重试最多重试5次
for (int retry = 0; retry < 5 && !sntp_ok; retry++) {
if (retry > 0) {
osDelay(3000); // 等待3秒再重试
}
sntp_ok = WIFI_Get_SNTP_Time();
}
if (sntp_time.valid) {
elog_i(TAG, "当前SNTP时间为: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
} else {
elog_w(TAG, "SNTP时间尚未同步将在后台继续尝试");
}
} else {
elog_w(TAG, "MQTT连接失败5秒后重试...");
osDelay(5000);
@@ -538,6 +585,30 @@ void wifi_task_mqtt(void *argument) {
}
}
// ========== 后台SNTP时间同步 ==========
static uint32_t sntp_sync_timer = 0;
static uint8_t sntp_reconfig_count = 0;
const uint32_t SNTP_SYNC_INTERVAL = 30000; // 30秒尝试同步一次
if (!sntp_time.valid && (osKernelGetTickCount() - sntp_sync_timer > SNTP_SYNC_INTERVAL)) {
sntp_sync_timer = osKernelGetTickCount();
// 如果连续10分钟20次都没同步成功尝试重新配置SNTP
if (++sntp_reconfig_count > 20) {
elog_w(TAG, "SNTP长时间未同步尝试重新配置...");
sntp_configured = 0; // 清除配置标志,下次重新配置
sntp_reconfig_count = 0;
}
// 尝试同步时间(静默尝试,不打印日志)
WIFI_Get_SNTP_Time();
if (sntp_time.valid) {
elog_i(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
}
}
// 检查连接状态:如果长时间没收到数据,认为可能断线
// 这里可以添加心跳检测机制
static uint32_t last_activity = 0;
@@ -625,17 +696,17 @@ uint8_t WIFI_MQTT_Publish_Status(const char *json) {
uint8_t WIFI_Enable_SNTP(void) {
elog_i(TAG, "使能SNTP服务器设置中国时区");
// 发送AT+CIPSNTPCFG=1,8,cn.ntp.org.cn命令
if (!WIFI_CheckAck("AT+CIPSNTPCFG=1,8,cn.ntp.org.cn\r\n", "OK", 3000)) {
// 发送AT+CIPSNTPCFG=1,8,ntp.aliyun.com命令阿里云NTP更稳定
if (!WIFI_CheckAck("AT+CIPSNTPCFG=1,8,ntp.aliyun.com\r\n", "OK", 3000)) {
elog_e(TAG, "SNTP配置失败");
return 0;
}
elog_i(TAG, "SNTP服务器配置成功模块将自动重启以应用设置");
osDelay(15000); // 等待模块重启并连接到SNTP服务器
/* 注意配置SNTP后模块会重启这里只等待基本重启时间
WiFi重连和模块恢复由调用者处理 */
return 1;
}
@@ -653,13 +724,11 @@ uint8_t WIFI_Get_SNTP_Time(void) {
// 发送AT+CIPSNTPTIME命令
if (WIFI_SEND_DMA("AT+CIPSNTPTIME\r\n") != HAL_OK) {
elog_w(TAG, "SNTP时间查询命令发送失败");
return 0;
}
// 等待响应
if (!WIFI_WaitEvent("+CIPSNTPTIME:", "ERROR", 3000)) {
// 超时不打印错误日志,避免刷屏
return 0;
}
@@ -686,6 +755,12 @@ uint8_t WIFI_Get_SNTP_Time(void) {
int year, month, day, hour, minute, second;
if (sscanf(time_buf, "%d-%d-%d %d:%d:%d",
&year, &month, &day, &hour, &minute, &second) == 6) {
// 检查是否为默认时间2021-01-01如果是则说明未同步成功
if (year == 2021 && month == 1 && day == 1) {
// 静默返回,不打印日志
return 0;
}
sntp_time.year = (uint16_t)year;
sntp_time.month = (uint8_t)month;
sntp_time.day = (uint8_t)day;
@@ -702,6 +777,11 @@ uint8_t WIFI_Get_SNTP_Time(void) {
sntp_time.hour, sntp_time.minute, sntp_time.second);
first_time_sync = 1;
}
// 自动更新RTC时间
BSP_RTC_UpdateFromSNTP(sntp_time.year, sntp_time.month, sntp_time.day,
sntp_time.hour, sntp_time.minute, sntp_time.second);
return 1;
} else {
return 0;
@@ -718,3 +798,7 @@ uint8_t WIFI_Is_Time_Valid(void) {
return sntp_time.valid;
}
// 检查MQTT是否已连接
uint8_t WIFI_Is_MQTT_Connected(void) {
return mqtt_connected;
}

View File

@@ -75,10 +75,12 @@ void wifi_task_mqtt(void *argument);
uint8_t WIFI_MQTT_Publish_Sensor(const char *json);
uint8_t WIFI_MQTT_Publish_Status(const char *json);
/* ================= SNTP函数声明 ================= */
uint8_t WIFI_Enable_SNTP(void);
uint8_t WIFI_Get_SNTP_Time(void);
SNTP_Time_t WIFI_Get_Current_Time(void);
uint8_t WIFI_Is_Time_Valid(void);
uint8_t WIFI_Is_MQTT_Connected(void);
#endif /* __DX_WF_24_H__ */

View File

@@ -34,6 +34,7 @@
#include "spi_st7735s.h"
#include "stdio.h"
#include "bsp_aht30.h"
#include "bsp_rtc.h"
#include "i2c.h"
/* USER CODE END Includes */
@@ -82,6 +83,7 @@ typedef struct {
// 全局变量
static LCD_Page_t current_page = LCD_PAGE_TIME; // 当前显示页面
static Sensor_Data_t sensor_data = {0}; // 传感器数据
static volatile uint8_t lcd_force_refresh = 0; // 强制刷新标志RTC更新时设置
/* USER CODE END Variables */
/* Definitions for defaultTask */
@@ -130,6 +132,7 @@ void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
uint8_t mode);
Sensor_Data_t *LCD_GetSensorData(void);
void user_button_init(void);
void RTC_TimeUpdateCallback(void); // RTC时间更新回调函数
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
@@ -208,6 +211,9 @@ void StartDefaultTask(void *argument)
// 2. 初始化日志和屏幕(在 RTOS 任务中)
easylogger_init();
// 3. 初始化RTC管理模块在日志初始化后
BSP_RTC_Init();
// 初始化MP3模块
HAL_StatusTypeDef ret = MP3_Init();
if (ret != HAL_OK) {
@@ -241,7 +247,7 @@ void LCD_Task(void *argument)
/* USER CODE BEGIN LCD_Task */
char display_str[32];
SNTP_Time_t now;
RTC_Time_t rtc_time;
uint16_t text_color = ST7735_WHITE;
uint16_t bg_color = ST7735_BLACK;
@@ -255,6 +261,9 @@ void LCD_Task(void *argument)
sensor_data.water_level = 0; // NC - 未检测到水
sensor_data.system_mode = 1; // 自动模式
// 注册RTC时间更新回调SNTP同步后立即刷新LCD
BSP_RTC_RegisterCallback(RTC_TimeUpdateCallback);
/* Infinite loop */
for (;;) {
// 根据当前页面显示不同内容
@@ -265,22 +274,16 @@ void LCD_Task(void *argument)
ST7735_FillScreen(bg_color);
ST7735_WriteString(2, 2, "Time", &Font_7x10, text_color, bg_color);
if (WIFI_Is_Time_Valid()) {
WIFI_Get_SNTP_Time();
now = WIFI_Get_Current_Time();
if (now.valid) {
// 显示时间(大字体,只显示时:分,不显示秒)
snprintf(display_str, sizeof(display_str), "%02d:%02d", now.hour, now.minute);
ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color);
// 使用RTC时间独立于SNTP即使网络断开也能显示
if (BSP_RTC_GetTime(&rtc_time) == 0) {
// 显示时间(大字体,只显示时:分,不显示秒)
snprintf(display_str, sizeof(display_str), "%02d:%02d", rtc_time.hour, rtc_time.minute);
ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color);
// 显示年月日(小字体,向下移动并居中)
snprintf(display_str, sizeof(display_str), "%04d-%02d-%02d", now.year,
now.month, now.day);
ST7735_WriteString(20, 60, display_str, &Font_7x10, ST7735_CYAN, bg_color);
} else {
ST7735_WriteString(25, 20, "--:--", &Font_16x26, text_color, bg_color);
ST7735_WriteString(20, 60, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color);
}
// 显示年月日(小字体,向下移动并居中)
snprintf(display_str, sizeof(display_str), "%04d-%02d-%02d",
rtc_time.year, rtc_time.month, rtc_time.day);
ST7735_WriteString(20, 60, display_str, &Font_7x10, ST7735_CYAN, bg_color);
} else {
ST7735_WriteString(25, 20, "--:--", &Font_16x26, text_color, bg_color);
ST7735_WriteString(20, 60, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color);
@@ -340,11 +343,11 @@ void LCD_Task(void *argument)
ST7735_FillScreen(bg_color);
ST7735_WriteString(2, 2, "System Status", &Font_7x10, text_color, bg_color);
// 显示WiFi状态
if (WIFI_Is_Time_Valid()) {
ST7735_WriteString(5, 20, "WiFi: OK", &Font_11x18, ST7735_GREEN, bg_color);
// 显示MQTT连接状态
if (WIFI_Is_MQTT_Connected()) {
ST7735_WriteString(5, 20, "MQTT: OK", &Font_11x18, ST7735_GREEN, bg_color);
} else {
ST7735_WriteString(5, 20, "WiFi: OFF", &Font_11x18, ST7735_RED, bg_color);
ST7735_WriteString(5, 20, "MQTT: OFF", &Font_11x18, ST7735_RED, bg_color);
}
// 显示系统模式
@@ -366,6 +369,11 @@ void LCD_Task(void *argument)
if (current_page != displayed_page) {
break; // 检测到页面切换,立即跳出
}
if (lcd_force_refresh) {
// RTC时间更新立即刷新LCD
lcd_force_refresh = 0;
break;
}
osDelay(100); // 每100ms检查一次总共30秒
}
} else {
@@ -601,6 +609,14 @@ void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water,
*/
Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; }
/**
* @brief RTC时间更新回调函数
* @note 当RTC通过SNTP更新时间时调用立即刷新LCD显示
*/
void RTC_TimeUpdateCallback(void) {
lcd_force_refresh = 1;
}
/* USER CODE END LCD_Page_Functions */
/* USER CODE END Application */

View File

@@ -74,7 +74,6 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* I2C1 clock enable */

View File

@@ -28,8 +28,7 @@
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_rtc.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/