就绪屏障与配置引擎重构,新增串口助手、V0.0网表分析报告及芯片手册,同步更新文档

This commit is contained in:
2026-09-15 21:13:20 +08:00
parent 4b5ce72e11
commit 1817fe792f
605 changed files with 54786 additions and 322 deletions
+97 -14
View File
@@ -8,12 +8,18 @@
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-09-13 王建锋 新增 sys_clock_get_time()(日历时间获取器,供 FatFs 时间钩子等使用)
* v1.2 2026-09-13 王建锋 文件移入 App/util/(改归 ④中间件层:通用平台时间服务,多服务共用)
* v1.3 2026-09-15 王建锋 修复 49.7 天墙钟回绕:sys_clock_get 每 30 天惰性再基准
* (自我参照、无损、零 I2C);修正"未初始化"哨兵判断;
* sys_clock_init 检查 RTC 读返回值/写回复验,失败时降级为
* 编译期默认时间基准并返回 -1(激活 app_main 时钟芯片自检)
*/
#include "sys_clock.h"
#include "main.h"
#include <string.h>
#include <stdio.h>
#include "FreeRTOS.h" /* taskENTER_CRITICAL:惰性再基准化的并发保护 */
#include "task.h"
#define DBG_TAG "[CLK]"
#include "dbg_log.h"
@@ -42,6 +48,13 @@
static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准 */
static volatile uint32_t s_base_unix_secs = UINT32_MAX;
/*
* 再基准化周期(惰性再基准化,见 sys_clock_get 说明):
* 32 位 tick 差值窗口必须恒 < 一整圈(2^32 ms ≈ 49.7 天),否则差值混叠、
* 墙钟"倒退"一圈。30 天 = 2,592,000,000 ms < 2^32,为惰性触发延迟留 19.7 天余量。
*/
#define SYS_CLOCK_REBASE_MS (30u * 24u * 3600u * 1000u)
/* ============================================================
* 内部辅助函数:Unix time ↔ sd2506_time_t 转换
* ============================================================ */
@@ -155,12 +168,17 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) {
/*
* 函数功能:初始化系统墙钟时间基准
* 入口参数:无
* 返回值:0 - 成功(始终返回 0)
* 返回值:0 - 成功;-1 - RTC 不可用(启用编译期默认值时降级为编译期默认时间
* 基准:墙钟仍可用,返回值报告芯片故障;否则不建基准,墙钟保持"未初始化")
* 限定条件:CubeMX 外设初始化完成、SD2506 RTC 驱动已初始化
* 函数说明:1. 从 SD2506 读取初始时间,必要时按编译期默认宏校时(RTC_DEFAULT_INIT_*)
* 2. 将 RTC 时间换算为 Unix epoch 秒数
* 函数说明:1. 从 SD2506 读取初始时间(检查返回值:I2C 读失败 → 判 RTC 异常),
* 必要时按编译期默认宏校时(RTC_DEFAULT_INIT_*),写回后复验范围
* 2. 将 RTC 时间换算为 Unix epoch 秒数(仅合法值进入换算,防垃圾月份在
* i < month - 1 处下溢空转约 40 亿次)
* 3. 记录当前 HAL Tick 与 Unix 秒数为基准,供 sys_clock_get() 后续推算
* 4. 注意:sd2506_get_time() 返回的 year 已是完整年份(驱动内 +2000),
* 4. RTC 不可用时:启用编译期默认值则以默认时间建基准(仅 RAM、不依赖芯片)
* 并返回 -1;否则不建基准,返回 -1
* 5. 注意:sd2506_get_time() 返回的 year 已是完整年份(驱动内 +2000),
* 换算时不得再加 2000,否则年份虚增导致 Unix 秒数超 uint32 回绕
*/
int sys_clock_init(void) {
@@ -168,14 +186,17 @@ int sys_clock_init(void) {
uint32_t year, month, day, hour, minute, second;
int64_t unix_secs = 0;
int i;
int rtc_ok; /* RTC 读取可用(1)/不可用(0) */
int degraded = 0; /* 1 = 以编译期默认时间建基准(RTC 故障,返回值报 -1) */
/* ============================================================
* Step 1:从 SD2506 硬件 RTC 读取初始时间
* ============================================================
* 注意:sd2506_get_time() 返回的 year 已是完整年份(如 2026,
* 驱动内部已 +2000)。此处【禁止】再 +2000,否则年份虚增到 4026,
* 换算出的 Unix 秒数远超 uint32_t 上限被截断回绕,时间彻底错乱。 */
sd2506_get_time(&sd_time);
* 换算出的 Unix 秒数远超 uint32_t 上限被截断回绕,时间彻底错乱。
* 返回值必须检查:I2C 失败时字段为垃圾,不得用于建立基准。 */
rtc_ok = (sd2506_get_time(&sd_time) == SD2506_OK) ? 1 : 0;
/* ============================================================
* Step 1.1:开发期校时(可选,由 RTC_DEFAULT_INIT_* 宏控制)
@@ -185,7 +206,8 @@ int sys_clock_init(void) {
* 判定"时间无效":年份越界、月/日越界(仅粗判,不细校 2 月 29 日等)。 */
#if defined(RTC_DEFAULT_INIT_ENABLE)
{
int rtc_invalid = (sd_time.year < 2000 || sd_time.year > 2099 ||
int rtc_invalid = (rtc_ok == 0) ||
(sd_time.year < 2000 || sd_time.year > 2099 ||
sd_time.month < 1 || sd_time.month > 12 ||
sd_time.day < 1 || sd_time.day > 31);
#if (RTC_DEFAULT_INIT_FORCE == 1)
@@ -207,7 +229,7 @@ int sys_clock_init(void) {
DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d",
init_t.year, init_t.month, init_t.day,
init_t.hour, init_t.minute, init_t.second);
sd2506_get_time(&sd_time); /* 重新读取校准后的值,确保后续换算用真实 RTC 数据 */
rtc_ok = (sd2506_get_time(&sd_time) == SD2506_OK) ? 1 : 0; /* 重新读取校准后的值(同样检查返回值) */
} else {
DBG_ERROR("sys_clock_init: RTC default write FAIL");
}
@@ -215,6 +237,39 @@ int sys_clock_init(void) {
}
#endif
/*
* 写回后复验范围:能写入但读回仍越界的芯片同样判故障;
* 同时保证进入 Step 2 换算循环的必为合法值(见函数说明第 2 条)。
*/
if (rtc_ok != 0 &&
(sd_time.year < 2000 || sd_time.year > 2099 ||
sd_time.month < 1 || sd_time.month > 12 ||
sd_time.day < 1 || sd_time.day > 31)) {
rtc_ok = 0;
}
if (rtc_ok == 0) {
#if (RTC_DEFAULT_INIT_ENABLE == 1)
/*
* 降级基准:以编译期默认时间建立基准(仅 RAM、不依赖芯片)。
* 墙钟保持可用(日志时间戳为默认时间);返回值报告芯片故障,
* 触发 app_main 的"时钟芯片异常"自检。
*/
DBG_ERROR("sys_clock_init: RTC abnormal, fallback to default time");
sd_time.year = RTC_DEFAULT_YEAR;
sd_time.month = RTC_DEFAULT_MONTH;
sd_time.day = RTC_DEFAULT_DAY;
sd_time.hour = RTC_DEFAULT_HOUR;
sd_time.minute = RTC_DEFAULT_MINUTE;
sd_time.second = RTC_DEFAULT_SECOND;
sd_time.week = rtc_calc_week(sd_time.year, sd_time.month, sd_time.day);
degraded = 1;
#else
DBG_ERROR("sys_clock_init: RTC read FAIL, wall clock uninitialized");
return -1;
#endif
}
/* 取出各时间字段(year 已是完整年份,直接用于下文 Unix 换算) */
year = (uint32_t)sd_time.year;
month = (uint32_t)sd_time.month;
@@ -275,24 +330,52 @@ int sys_clock_init(void) {
DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu",
(unsigned long)s_base_unix_secs, (unsigned long)s_base_tick);
return 0;
return (degraded != 0) ? -1 : 0;
}
/*
* 函数功能:获取当前系统墙钟时间(Unix 时间戳)
* 入口参数:无
* 返回值:当前 Unix 秒数 uint32_t 未初始化时返回 UINT32_MAX
* 限定条件:sys_clock_init() 已成功调用
* 限定条件:sys_clock_init() 已调用
* 函数说明:1. 以基准 Tick 与基准 Unix 秒数为起点,按当前 HAL Tick 与 1000ms
* 的整除差推算流逝秒数(避免每毫秒累加,减少漂移与溢出风险)
* 2. 若推算结果为负(基准未建立),返回 UINT32_MAX 表示无效
* 的整除差推算流逝秒数(避免每毫秒累加,减少漂移与溢出风险);
* 32 位无符号模减对 Tick 回绕天然无缝
* 2. 差值窗口达到 SYS_CLOCK_REBASE_MS(30 天)时触发"惰性再基准化":
* 基准前移流逝秒数(墙钟值不变)、差值窗口清零,亚秒余数结转入
* 新窗(base_tick = now − 余数)故无损,保证差值永不因一整圈
* 2^32 ms(≈49.7 天)而混叠
* 3. 再基准化为自我参照:纯内存操作、零 I2C、非阻塞——不重读 RTC
* 重锚(RTC 精度远逊 CPU Tick,重锚反而吸收其漂移);临界区 +
* 区内复核防多任务并发重复累加
* 4. 若基准未建立(init 失败),返回 UINT32_MAX 表示无效
*/
uint32_t sys_clock_get(void) {
uint32_t now_tick = HAL_GetTick();
int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL);
uint32_t diff = now_tick - s_base_tick; /* 32 位模减,跨 Tick 回绕无缝 */
int64_t unix_secs;
if (s_base_unix_secs != UINT32_MAX && diff > SYS_CLOCK_REBASE_MS) {
uint32_t secs = diff / 1000u;
uint32_t rem = diff - secs * 1000u; /* 亚秒余数,结转入新窗 → 无损 */
taskENTER_CRITICAL();
diff = now_tick - s_base_tick; /* 临界区内复核:另一任务可能已先再基准化 */
if (diff > SYS_CLOCK_REBASE_MS) {
s_base_unix_secs += secs; /* 基准前移:墙钟值本身不变 */
s_base_tick = now_tick - rem; /* 新窗自 (now − 余数) 起算,余数续计 */
diff = rem;
}
taskEXIT_CRITICAL();
}
if (s_base_unix_secs == UINT32_MAX) {
return UINT32_MAX; /* 未初始化(显式哨兵判断) */
}
unix_secs = (int64_t)s_base_unix_secs + (int64_t)(diff / 1000u);
if (unix_secs < 0) {
return UINT32_MAX; /* 未初始化 */
return UINT32_MAX;
}
return (uint32_t)unix_secs;