就绪屏障与配置引擎重构,新增串口助手、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
+3 -1
View File
@@ -8,6 +8,7 @@
* 创建日期:2026-09-14
* 修改记录:
* v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成)
* v1.1 2026-09-15 王建锋 属主任务由 netTask 改为 defaultTask(配置引擎单一属主,P1-3)
*/
#ifndef CFG_FILE_H
@@ -103,7 +104,8 @@ int cfg_file_save(void);
* 函数功能:配置文件变更轮询(沉降去抖 + 重载 + 分段变更回调)
* 入口参数:无
* 返回值:无
* 限定条件:由单一任务周期调用(建议 2s,收敛于 netTask)
* 限定条件:由配置引擎属主任务周期调用(建议 2s;MCU = defaultTask,
* 模拟器 = 主循环);引擎静态状态无跨任务保护,属主唯一即无竞态
* 函数说明:(size, mtime 变更令牌) 相对基线变化且连续 CFG_FILE_SETTLE_CNT
* 次轮询稳定 → 重载全部段(各段复位工厂默认后解析);挂回调段
* 有差异 → 调用其回调(如触发网络重建);重载后刷新基线
+1
View File
@@ -28,6 +28,7 @@ extern osEventFlagsId_t sys_evtHandle;
#define SYS_EVT_FTP_READY (1u << 2) /* ftpTask:lftpd_start 完成 */
#define SYS_EVT_TIMESYNC_READY (1u << 3) /* timeSyncTask:首次时间同步完成 */
#define SYS_EVT_NET_RECONFIG (1u << 4) /* UI:请求立即应用新网口配置(netTask 消费后重建网络) */
#define SYS_EVT_CFG_SAVE (1u << 5) /* UI:网口配置保存请求(defaultTask = 配置引擎属主,消费并写盘) */
#ifdef __cplusplus
}
+30 -9
View File
@@ -11,6 +11,11 @@
* v1.2 2026-07-21 王建锋 网络初始化移入本任务(net_init + TCP 监听)
* v1.3 2026-07-22 王建锋 单连接模式重构,增加大文件/边界/KeepAlive 测试
* v1.4 2026-09-14 王建锋 新增自检埋点:PHY 链路 3s 未建立 → sys_log "通讯异常"
* v1.5 2026-09-15 王建锋 配置加载移交 defaultTask(配置引擎属主,P1-3);
* 本任务只读 RAM 副本
* v1.6 2026-09-15 王建锋 P1-6:入口加 CH395F 硬件层测试模式自挂起守卫
* (基础线程无条件创建,该模式下不运行);
* freertos.c USER CODE 区重复创建已删除
*/
#include <string.h>
@@ -22,6 +27,7 @@
#include "ch395f.h"
#include "sys_events.h"
#include "sys_log.h"
#include "test_config.h" /* 测试模式宏(ENABLE_HW_LAYER_TESTS);产品构建全未定义,无作用 */
#define DBG_TAG "[NET_TASK]"
#include "dbg_log.h"
@@ -30,7 +36,8 @@
* 函数功能:"立即应用网口配置"平台回调(注册给 net_cfg)
* 入口参数:无
* 返回值:无
* 限定条件:UI 保存成功后经 net_cfg_request_apply() 调用(任意任务上下文)
* 限定条件:保存写盘成功后经 net_cfg_request_apply() 调用(MCU 上由
* defaultTask 上下文调用;任意任务上下文均安全)
* 函数说明:只置事件位;真正的重建在主循环内完成(CH395F SPI 归 netTask 独占,
* UI 不得直接操作)
*/
@@ -44,7 +51,9 @@ static void net_reconfig_request_cb(void) {
* 函数功能:CH395F 协议栈一次性初始化(两种构建模式共用)
* 入口参数:无
* 返 回 值:0 - 成功,-1 - 失败
* 函数说明:net_cfg 加载(首启/无文件 = 工厂默认)→ net_init → MAC 回写
* 函数说明:net_cfg 已由 defaultTask 在就绪屏障前加载进 RAM 副本(配置引擎
* 属主 = defaultTask,本任务不触碰配置文件引擎)→ 只读副本组网 →
* net_init → MAC 回写
* 配置模块 → MAC 打印 → PHY 强制 100M 全双工 → 广播网络就绪事件。
* NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)netTask 不创建,
* 由 testTask 调用后独占 CH395F。
@@ -57,9 +66,9 @@ int net_stack_bring_up(void) {
DBG_INFO("=== Network Init ===");
/* 加载网络配置(存储后端:NAND 上 FatFS 文件 /cfg/netcfg.bin;
* 首启/存储未就绪 = 工厂默认,与既有硬编码组网值一致) */
(void)net_cfg_load();
/* 网络配置:defaultTask 已在 SYS_EVT_STORAGE_READY 屏障前置入 RAM 副本
* (首启/文件缺失/损坏 = 工厂默认,与既有硬编码组网值一致);
* 此处只读副本,不触碰配置文件引擎 */
cfg = net_cfg_get();
net_cfg_fmt_ipv4(cfg->ip, ip, sizeof(ip));
@@ -140,15 +149,27 @@ int net_task_init(void) {
* 函数功能:网络任务主函数
* 入口参数:arg - FreeRTOS 传入参数(未使用)
* 返 回 值:无
* 函数说明:等待存储就绪(就绪屏障,10s 超时回退工厂默认,存储故障
* 不阻塞组网)→ net_stack_bring_up() 初始化 CH395F 协议栈,
* 函数说明:等待存储就绪(就绪屏障 = 卷挂载 + 网络配置加载完成,10s 超时
* 回退工厂默认,存储故障不阻塞组网)→ net_stack_bring_up() 初始化
* CH395F 协议栈,
* 再进入轮询循环处理网络事件。
* 仅 NET 层测试模式(阶段 6~10)会创建本任务;
* 硬件层模式下 netTask 不创建,CH395F 由 testTask 独占。
* 本任务 = 基础线程:CubeMX 生成区无条件创建(全构建唯一实例);
* CH395F 硬件层测试模式(阶段 1~5)下入口自挂起(不 bring_up、
* 不轮询、不广播 NET_READY),由 testTask 独占 CH395F。
*/
void net_task_func(void const *arg) {
(void)arg;
/* 硬件层测试模式:testTask 独占 CH395F,本任务自挂起(基础线程恒创建,
* 该模式下"不运行"):不 bring_up、不 net_poll、不广播 NET_READY;
* ftpTask/timeSyncTask 将永久阻塞于屏障 = 预期行为(见《项目启动顺序管理》) */
#if defined(ENABLE_HW_LAYER_TESTS)
DBG_INFO("netTask parked: HW-layer test mode, CH395F owned by testTask");
for (;;) {
osDelay(1000);
}
#endif
/* 配置在 NAND 的 FatFS 卷上:等存储就绪再读(取代原 2s 魔法延时);
* 超时(存储异常)继续以工厂默认组网,不卡网络启动 */
if (sys_evtHandle != NULL) {
+5 -3
View File
@@ -7,6 +7,8 @@
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-08-24 王建锋 提取 net_stack_bring_up() 供硬件层/NET 层两种测试模式共用
* v1.2 2026-09-15 王建锋 硬件层模式描述修正:netTask 恒创建(基础线程),
* 该模式下自挂起不运行(P1-6 守卫)
*/
#ifndef NET_TASK_H
@@ -16,9 +18,9 @@
extern "C" {
#endif
/* CH395F 协议栈一次性初始化(net_init + MAC 打印 + PHY 配置 + 广播网络就绪事件)。
* 返回 0 成功 / -1 失败。NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)
* netTask 不创建,由 testTask 调用后独占 CH395F。 */
/* CH395F 协议栈一次性初始化(net_init + MAC 打印 + PHY 配置 + 广播网络就绪事件)。
* 返回 0 成功 / -1 失败。产品 / NET 层模式由 netTask 调用;硬件层模式(阶段 1~5)
* netTask 自挂起(创建但不运行),由 testTask 调用后独占 CH395F。 */
int net_stack_bring_up(void);
int net_task_init(void);
+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;