/* * 模块名称:Time Sync — timeSyncTask 载体(网络校时节拍宿主) * 模块功能:独立任务 timeSyncTask:等网络就绪后按固定节拍驱动 SNTP 校时客户端 * (App/sync/time_sntp.c),长等待一律分片并逐片喂任务心跳 * 适用平台:STM32F407ZGTx + CH395F * 作者:王建锋 * 创建日期:2026-08-27 * 修改记录: * v1.0 2026-08-27 王建锋 创建初始版本(UDP 推送,netTask 内轮询) * v1.1 2026-08-28 王建锋 改为 TCP Server,迁入独立任务 timeSyncTask, * 所有 socket 操作经 net_socket 线程安全 API 走消息队列, * netTask 仅负责 net_poll/net_process_messages,多任务并发安全 * v1.2 2026-10-02 王建锋 第 8b 轮消抖:等待片长 200ms→50ms,并把各处长 * 等待(500/2000/200ms)统一走 time_sync_delay_fed() * 按 ≤50ms 分片、每片喂心跳——消除"心跳滑出两个连续 * 100ms 抽样窗"的节拍抖动(第 7 轮台架 snap=0x17) * v1.3 2026-10-27 DTU 项目组 道G task-50:对时**成功应用**后投一条 quiet * 操作记录("clock set by 8888")——task-49 盘点盘出 * "104 对时留痕、本端口不留"的同类不对称,Lead 批准补齐 * v1.4 2026-11-04 DTU 项目组 SNTP 客户端接入(#57 调研落地):accept 轮询 * 片内驱动 time_sntp_run()(48B UDP 请求/应答状态机 * 在 App/sync/time_sntp.c,节奏/退避/步进策略见该文件) * v2.0 2026-11-04 DTU 项目组 私有 TCP :8888 对时退役(SNTP 上线后退出历史 * 舞台):监听重建、"TIME" 包重组、OPER 留痕、头阻塞 * 收口整条链随退——历史协议与退役理由见 * docs/子系统设计/系统墙钟时间维护说明.md §15 退役注记; * PC 推送工具 time_sync_server.py 与探针 e02_probe.py * 同日删除。本任务收缩为纯节拍宿主:NET_READY 屏障 → * 200ms 循环驱动 time_sntp_run()。心跳分片纪律 * (v1.2,第 8b 轮消抖)与屏障喂心跳(2026-10-02 * 台架修复)原样保留。 */ #include "time_sync.h" #include "time_sntp.h" #include "net_socket.h" #include "sys_events.h" #include "task_watchdog.h" /* 看门狗分片等待:单次延时的分片上限。须显著小于看门狗抽样窗 * (task_watchdog.c WD_POLL_PERIOD_MS=100ms)——片长 ≤50ms 时每片前喂的心跳 * 必然落在同一个 100ms 抽样窗内,消除"心跳恰好滑过两个连续窗口"的边界抖动 * (第 7 轮台架稳态 snap=0x17 即抽样窗 vs 心跳节拍的良性错位,第 8b 轮消抖)。 * 语义上仅把一次长等待拆成多次短等待,总时长不变。 */ #define TIME_SYNC_WAIT_SLICE_MS 50U /* 主循环节拍:每轮驱动一次 SNTP 状态机后的等待时长,与 :8888 时代的 * accept 轮询片同值(time_sntp_run 对单次驻留另有 ≤50ms 收片上限, * 与本延时片叠加不破 100ms 抽样窗,心跳纪律不变)。 */ #define TIME_SYNC_TICK_MS 200U /* * 函数功能:可喂心跳的延时(把长等待切成 ≤50ms 短片,每片喂一次心跳) * 入口参数:ms - 总延时(ms) uint32_t * 返回值:无 * 限定条件:任务上下文调用 * 函数说明:看门狗抽样窗为 100ms(task_watchdog.c WD_POLL_PERIOD_MS)。把任意长 * 等待切成 ≤TIME_SYNC_WAIT_SLICE_MS 的短片、每片前喂心跳,使心跳必然 * 落在单个抽样窗内——语义等价于一次长延时,仅把"心跳节奏"钉在窗内 * (第 8b 轮消抖;不改变总延时预算)。 */ static void time_sync_delay_fed(uint32_t ms) { while (ms > 0U) { uint32_t slice = (ms > TIME_SYNC_WAIT_SLICE_MS) ? TIME_SYNC_WAIT_SLICE_MS : ms; task_wd_beat(TASK_WD_TIMESYNC); vTaskDelay(pdMS_TO_TICKS(slice)); ms -= slice; } } /* * 函数功能:时间同步任务主体(独立 FreeRTOS 任务) * 入口参数:argument - 未使用 * 返回值:无 * 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列 * 函数说明:等待网络栈就绪后,按 TIME_SYNC_TICK_MS 节拍驱动 SNTP 校时客户端 * (time_sntp_run 内部:UDP socket 懒建/代际变化自弃旧归还, * 等待应答期单次驻留 ≤50ms)。心跳 = 每轮循环头一次 + 延时片每片 * 一次;本任务在 IWDG required_mask 内,节拍上没有任何超过抽样窗 * 的不喂窗口。全部 socket 操作经消息队列由 netTask 串行执行。 */ void time_sync_task(void *argument) { (void)argument; task_wd_bind(TASK_WD_TIMESYNC); /* FTL 心跳路由:登记本任务句柄(入口一次) */ /* 等网络栈真正就绪(取代拍脑袋的 8s 魔法延时),确保 netTask 已开始处理消息; * osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒)。 * 2026-10-02 台架修复:等待改 100ms 分片 + 每片喂心跳——IWDG 必需掩码含 * TS 位,原 osWaitForever 无心跳 → 启动初期(存储已就绪、网络未就绪) * 掩码 800ms 不可达 → 停喂 → 复位循环(2026-10-02 台架根因之一)。 * 100ms 分片 = 抽样窗,纯任务睡眠零队列开销。 */ if (sys_evtHandle != NULL) { for (;;) { uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, osFlagsWaitAny | osFlagsNoClear, 100u); if ((r & osFlagsError) == 0u) { break; /* NET_READY 已置位(状态位,未被消费) */ } task_wd_beat(TASK_WD_TIMESYNC); /* 看门狗:分片喂心跳(屏障等待态) */ } } for (;;) { /* 心跳:本任务在 required_mask 内。time_sntp_run 的收片驻留 ≤50ms * 落在两次心跳之间(循环头 + 下面延时片每片前置),抽样窗不破。 */ task_wd_beat(TASK_WD_TIMESYNC); /* 网络代次随参数下传:断网/重建由 time_sntp_run 内部自理(关闭旧 * socket 归还),代次变化后下一轮自然在新网上重新发起。 */ time_sntp_run(net_get_generation()); time_sync_delay_fed(TIME_SYNC_TICK_MS); } }