196 lines
8.0 KiB
C
196 lines
8.0 KiB
C
/*
|
||
* 模块名称:Time Sync — PC 本地时间推送接收(TCP)
|
||
* 模块功能:在独立任务 timeSyncTask 中以 TCP Server 方式监听端口,接收 PC 推送的本地时间包,
|
||
* 调用 sys_clock_set_unix() 更新系统墙钟并写回 SD2506 RTC
|
||
* 适用平台: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,多任务并发安全
|
||
*/
|
||
|
||
#include <string.h>
|
||
#include "time_sync.h"
|
||
#include "net_socket.h"
|
||
#include "sys_clock.h"
|
||
#include "sys_events.h"
|
||
|
||
#define DBG_TAG "[TIME_SYNC]"
|
||
#include "dbg_log.h"
|
||
|
||
/* 监听端口:PC 推送端(TCP 客户端)需连到本端口 */
|
||
#define TIME_SYNC_PORT 8888
|
||
|
||
/*
|
||
* E-02 头阻塞修复(timeSync 侧;lftpd 侧见 lftpd_inet.c):
|
||
* 旧实现用单次 30s 阻塞 net_recv(默认 NET_RECV_TIMEOUT_MS)等时间包——等待期间
|
||
* netTask(全部 socket 操作的唯一执行者)被独占,其余任务的网络操作(远端显示 /
|
||
* FTP)整体停摆(docs/问题记录/错误码传播审计.md E-02)。
|
||
* 修法:等待切成 ≤TIME_SYNC_NET_SLICE_MS 的片(与 lftpd 同片长 = 其他任务最坏
|
||
* 等待,见 docs/测试说明/链路延迟测试说明.md §8.3),调用方累计总预算
|
||
* TIME_SYNC_TOTAL_BUDGET_MS——总超时语义与旧 30s 一致,任何时刻 netTask 占用
|
||
* 有界;片间 vTaskDelay(1) 让出 CPU,防空闲忙循环饿死低优先级任务。
|
||
*/
|
||
#define TIME_SYNC_NET_SLICE_MS 5U
|
||
#define TIME_SYNC_TOTAL_BUDGET_MS 30000U
|
||
|
||
/*
|
||
* 函数功能:创建并监听 TCP 时间同步服务端口
|
||
* 入口参数:无
|
||
* 返回值:监听 Socket 描述符(>=0),失败返回 -1
|
||
* 限定条件:net_init() 已成功;可在非 netTask 上下文调用
|
||
* 函数说明:net_socket/net_bind/net_listen 均为线程安全 API,内部经消息队列由 netTask 串行处理。
|
||
* 单连接模式下监听 Socket 在收到连接后会转为数据通道,连接关闭后即被释放,
|
||
* 因此每次调用都会新建监听 Socket(与 lftpd 同模式),失败自动重试。
|
||
*/
|
||
static int time_sync_open_listener(void) {
|
||
int retry;
|
||
|
||
for (retry = 0; retry < 5; retry++) {
|
||
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
if (s < 0) {
|
||
DBG_ERROR("time_sync: net_socket FAIL");
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
continue;
|
||
}
|
||
|
||
struct net_sockaddr_in addr;
|
||
memset(&addr, 0, sizeof(addr));
|
||
addr.sin_family = NET_AF_INET;
|
||
addr.sin_port = net_htons(TIME_SYNC_PORT);
|
||
addr.sin_addr.s_addr = NET_INADDR_ANY;
|
||
|
||
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
|
||
DBG_ERROR("time_sync: bind port %d FAIL", TIME_SYNC_PORT);
|
||
net_close(s);
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
continue;
|
||
}
|
||
|
||
if (net_listen(s) != 0) {
|
||
DBG_ERROR("time_sync: listen FAIL");
|
||
net_close(s);
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
continue;
|
||
}
|
||
|
||
DBG_INFO("time_sync: TCP listen on :%d (sock=%d)", TIME_SYNC_PORT, s);
|
||
return s;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:处理一个已建立的时间同步连接
|
||
* 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身)
|
||
* 返回值:无
|
||
* 函数说明:分片有界读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后
|
||
* 写回 RTC,然后关闭连接。读取用 net_recv_to 5ms 分片(E-02):单片不
|
||
* 长期占用 netTask,总预算 30s 与旧实现语义一致;跨片累积,容忍时间包
|
||
* 被 TCP 分段送达
|
||
*/
|
||
static void time_sync_handle_conn(int conn_sock) {
|
||
uint8_t buf[16];
|
||
int got = 0;
|
||
TickType_t t0 = xTaskGetTickCount();
|
||
|
||
while (got < (int)sizeof(buf)) {
|
||
/* 分片有界读(E-02):单片 netTask 占用 ≤ TIME_SYNC_NET_SLICE_MS */
|
||
int n = net_recv_to(conn_sock, buf + got, (int)(sizeof(buf) - got),
|
||
TIME_SYNC_NET_SLICE_MS);
|
||
if (n > 0) {
|
||
got += n;
|
||
if (got >= 8) {
|
||
break; /* 时间包收齐,解析 */
|
||
}
|
||
continue; /* 包未收齐(分段送达),继续等余下字节 */
|
||
}
|
||
if (n == 0) {
|
||
break; /* 对端关闭(EOF) */
|
||
}
|
||
if (n != -2) {
|
||
break; /* 真错误 */
|
||
}
|
||
/* 本片超时无数据:总预算未耗尽则继续等。
|
||
* **必须显式让出 CPU**:若某片因 net_poll 自身耗时在首轮就返回 -2,
|
||
* 本任务(Normal)立即重排队会形成忙循环 → 饿死 BelowNormal 的 uiTask
|
||
* (lftpd 侧同因,见链路延迟测试说明.md §8.3 第 3 条) */
|
||
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(TIME_SYNC_TOTAL_BUDGET_MS)) {
|
||
DBG_INFO("time_sync: no TIME packet in 30s, drop conn");
|
||
break; /* 总预算耗尽(语义同旧 30s 超时) */
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(1));
|
||
}
|
||
|
||
if (got >= 8) {
|
||
if (buf[0] == 'T' && buf[1] == 'I' && buf[2] == 'M' && buf[3] == 'E') {
|
||
uint32_t epoch = ((uint32_t)buf[4] << 24) |
|
||
((uint32_t)buf[5] << 16) |
|
||
((uint32_t)buf[6] << 8) |
|
||
((uint32_t)buf[7]);
|
||
sys_clock_set_unix(epoch);
|
||
DBG_INFO("time_sync: wall clock updated (epoch=%lu)", (unsigned long)epoch);
|
||
}
|
||
}
|
||
|
||
net_close(conn_sock);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:时间同步任务主体(独立 FreeRTOS 任务)
|
||
* 入口参数:argument - 未使用
|
||
* 返回值:无
|
||
* 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列
|
||
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> 关闭 -> 重建监听。
|
||
* 全部 socket 操作经消息队列由 netTask 串行执行,与本任务/其它网络任务并发安全。
|
||
*/
|
||
void time_sync_task(void *argument) {
|
||
(void)argument;
|
||
|
||
/* 等网络栈真正就绪(取代拍脑袋的 8s 魔法延时),确保 netTask 已开始处理消息;
|
||
* osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒) */
|
||
if (sys_evtHandle != NULL) {
|
||
(void)osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
|
||
osFlagsWaitAny | osFlagsNoClear, osWaitForever);
|
||
}
|
||
|
||
for (;;) {
|
||
/* 记录本次监听对应的网络代次;代次变化(网络重建)则放弃旧监听重建 */
|
||
uint32_t gen = net_get_generation();
|
||
|
||
int ls = time_sync_open_listener();
|
||
if (ls < 0) {
|
||
DBG_ERROR("time_sync: open listener FAIL, retry later");
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
continue;
|
||
}
|
||
|
||
/* 等待 PC 连接:net_accept 在无连接时立即返回 -1,循环重试同一监听 Socket */
|
||
int conn = -1;
|
||
do {
|
||
conn = net_accept(ls, NULL, NULL);
|
||
if (conn < 0) {
|
||
if (net_get_generation() != gen) {
|
||
net_close(ls); /* 网络重建:旧监听失效,回外层重建 */
|
||
break;
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
}
|
||
} while (conn < 0);
|
||
|
||
if (conn < 0) {
|
||
DBG_INFO("time_sync: network re-init, relisten");
|
||
continue;
|
||
}
|
||
|
||
time_sync_handle_conn(conn);
|
||
/* conn(单连接模式下即 ls)已被 net_close 释放,下次循环重建监听 */
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(50));
|
||
}
|
||
}
|