时间同步已经完成
This commit is contained in:
@@ -44,7 +44,16 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX;
|
|||||||
* 内部辅助函数:Unix time ↔ sd2506_time_t 转换
|
* 内部辅助函数:Unix time ↔ sd2506_time_t 转换
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
/* 计算星期(Zeller 公式,0=Sunday),与下方 unix_to_sd2506 中算法一致 */
|
/*
|
||||||
|
* 函数功能:根据公历日期计算星期几
|
||||||
|
* 入口参数:y - 年份 uint16_t 1970~2099
|
||||||
|
* m - 月份 uint8_t 1~12
|
||||||
|
* d - 日 uint8_t 1~31
|
||||||
|
* 返回值:星期编号 uint8_t 0=Sunday, 1=Monday, ... 6=Saturday
|
||||||
|
* 限定条件:输入为公历合法日期
|
||||||
|
* 函数说明:1. 采用 Zeller 公式(变种),结果 0 表示周日
|
||||||
|
* 2. 与 unix_to_sd2506() 内星期算法保持一致
|
||||||
|
*/
|
||||||
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
|
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
|
||||||
{
|
{
|
||||||
uint32_t wy = y;
|
uint32_t wy = y;
|
||||||
@@ -54,10 +63,22 @@ static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
|
|||||||
wy--;
|
wy--;
|
||||||
wm += 12u;
|
wm += 12u;
|
||||||
}
|
}
|
||||||
|
/* Zeller 公式:世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
|
||||||
return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
|
return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
|
||||||
(uint32_t)((26u * (wm + 1u)) / 10u) + (uint32_t)(d % 100u)) % 7u);
|
(uint32_t)((26u * (wm + 1u)) / 10u) + (uint32_t)(d % 100u)) % 7u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:将 Unix 时间戳(自 1970-01-01 00:00:00 起的秒数)转换为 SD2506 RTC 时间结构
|
||||||
|
* 入口参数:unix_secs - Unix 时间戳 uint32_t 0~0x7FFFFFFF(~2038)
|
||||||
|
* p_sd_time - 输出时间结构指针 sd2506_time_t* 不得为 NULL
|
||||||
|
* 出口参数:p_sd_time - 填充年/月/日/时/分/秒/星期
|
||||||
|
* 返回值:无
|
||||||
|
* 限定条件:p_sd_time 必须指向有效缓冲区
|
||||||
|
* 函数说明:1. 先取模拆分时分秒,再按累计天数推算年月日
|
||||||
|
* 2. 星期由 Zeller 公式(与 rtc_calc_week 同算法)得出
|
||||||
|
* 3. SD2506 年份字段为相对值(0~99,+2000),故回写时减 2000
|
||||||
|
*/
|
||||||
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
||||||
{
|
{
|
||||||
uint32_t y, m, d;
|
uint32_t y, m, d;
|
||||||
@@ -108,6 +129,7 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
|||||||
wy--;
|
wy--;
|
||||||
wm += 12uL;
|
wm += 12uL;
|
||||||
}
|
}
|
||||||
|
/* Zeller 公式同 rtc_calc_week():世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
|
||||||
uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
|
uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
|
||||||
(uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
|
(uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
|
||||||
(uint32_t)(d % 100uLL)) % 7uLL;
|
(uint32_t)(d % 100uLL)) % 7uLL;
|
||||||
@@ -129,6 +151,17 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
|
|||||||
* 系统墙钟时间维护
|
* 系统墙钟时间维护
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:初始化系统墙钟时间基准
|
||||||
|
* 入口参数:无
|
||||||
|
* 返回值:0 - 成功(始终返回 0)
|
||||||
|
* 限定条件:CubeMX 外设初始化完成、SD2506 RTC 驱动已初始化
|
||||||
|
* 函数说明:1. 从 SD2506 读取初始时间,必要时按编译期默认宏校时(RTC_DEFAULT_INIT_*)
|
||||||
|
* 2. 将 RTC 时间换算为 Unix epoch 秒数
|
||||||
|
* 3. 记录当前 HAL Tick 与 Unix 秒数为基准,供 sys_clock_get() 后续推算
|
||||||
|
* 4. 注意:sd2506_get_time() 返回的 year 已是完整年份(驱动内 +2000),
|
||||||
|
* 换算时不得再加 2000,否则年份虚增导致 Unix 秒数超 uint32 回绕
|
||||||
|
*/
|
||||||
int sys_clock_init(void)
|
int sys_clock_init(void)
|
||||||
{
|
{
|
||||||
sd2506_time_t sd_time;
|
sd2506_time_t sd_time;
|
||||||
@@ -216,6 +249,15 @@ int sys_clock_init(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:获取当前系统墙钟时间(Unix 时间戳)
|
||||||
|
* 入口参数:无
|
||||||
|
* 返回值:当前 Unix 秒数 uint32_t 未初始化时返回 UINT32_MAX
|
||||||
|
* 限定条件:sys_clock_init() 已成功调用
|
||||||
|
* 函数说明:1. 以基准 Tick 与基准 Unix 秒数为起点,按当前 HAL Tick 与 1000ms
|
||||||
|
* 的整除差推算流逝秒数(避免每毫秒累加,减少漂移与溢出风险)
|
||||||
|
* 2. 若推算结果为负(基准未建立),返回 UINT32_MAX 表示无效
|
||||||
|
*/
|
||||||
uint32_t sys_clock_get(void)
|
uint32_t sys_clock_get(void)
|
||||||
{
|
{
|
||||||
uint32_t now_tick = HAL_GetTick();
|
uint32_t now_tick = HAL_GetTick();
|
||||||
@@ -228,6 +270,17 @@ uint32_t sys_clock_get(void)
|
|||||||
return (uint32_t)unix_secs;
|
return (uint32_t)unix_secs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:获取当前墙钟时间的可读字符串(YYYY-MM-DD HH:MM:SS)
|
||||||
|
* 入口参数:buf - 输出字符串缓冲区 char* 不得为 NULL
|
||||||
|
* buf_size - 缓冲区大小 size_t 须 ≥ 20(含结尾 '\0')
|
||||||
|
* 出口参数:buf - 填充格式化时间字符串
|
||||||
|
* 返回值:buf 指针(便于链式调用)
|
||||||
|
* 限定条件:buf 指向至少 buf_size 字节的有效缓冲区;sys_clock_init() 已调用
|
||||||
|
* 函数说明:1. 内部先取 Unix 秒数,未初始化时输出占位串 "----/--/-- --:--:--"
|
||||||
|
* 2. 通过 unix_to_sd2506() 转换为 SD2506 结构后格式化
|
||||||
|
* 3. 使用 snprintf 防止缓冲区溢出
|
||||||
|
*/
|
||||||
char *sys_clock_get_str(char *buf, size_t buf_size)
|
char *sys_clock_get_str(char *buf, size_t buf_size)
|
||||||
{
|
{
|
||||||
uint32_t unix_secs = sys_clock_get();
|
uint32_t unix_secs = sys_clock_get();
|
||||||
@@ -245,6 +298,16 @@ char *sys_clock_get_str(char *buf, size_t buf_size)
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:设置(更新)系统墙钟时间基准,并可写回 SD2506 RTC
|
||||||
|
* 入口参数:unix_secs - 新的 Unix 时间戳 uint32_t 0~0x7FFFFFFF
|
||||||
|
* sd_time - 对应的 RTC 时间结构指针 sd2506_time_t* 可为 NULL(仅更新基准)
|
||||||
|
* 出口参数:无
|
||||||
|
* 返回值:0 - 成功(始终返回 0)
|
||||||
|
* 限定条件:unix_secs 为合法 Unix 时间戳
|
||||||
|
* 函数说明:1. 重置基准 Tick 与基准 Unix 秒数,使后续 sys_clock_get() 以此为准
|
||||||
|
* 2. 若 sd_time 非 NULL,将其写回 SD2506 RTC,实现断电保持
|
||||||
|
*/
|
||||||
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
|
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
|
||||||
{
|
{
|
||||||
/* Step 1: 更新墙钟基准 */
|
/* Step 1: 更新墙钟基准 */
|
||||||
@@ -261,3 +324,14 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sys_clock_set_unix(uint32_t unix_secs)
|
||||||
|
{
|
||||||
|
sd2506_time_t sd;
|
||||||
|
|
||||||
|
/* 内部静态函数:将 Unix 秒数拆分为 SD2506 时间结构 */
|
||||||
|
unix_to_sd2506(unix_secs, &sd);
|
||||||
|
|
||||||
|
/* 更新运行期基准并写回 RTC,实现掉电保持 */
|
||||||
|
return sys_clock_set(unix_secs, &sd);
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,16 @@ char *sys_clock_get_str(char *buf, size_t buf_size);
|
|||||||
*/
|
*/
|
||||||
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time);
|
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:以 Unix 时间戳设置系统墙钟并写回 SD2506 RTC
|
||||||
|
* 入口参数:unix_secs - Unix epoch time (秒,本地时间) uint32_t 0~0x7FFFFFFF
|
||||||
|
* 返回值:0 - 成功
|
||||||
|
* 限定条件:sys_clock_init() 已调用
|
||||||
|
* 函数说明:内部将 Unix 秒数转换为 sd2506_time_t 后调用 sys_clock_set(),
|
||||||
|
* 便于时间同步模块(如 PC 推送)直接以时间戳校时
|
||||||
|
*/
|
||||||
|
int sys_clock_set_unix(uint32_t unix_secs);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -109,6 +109,6 @@ void net_task_func(void const *arg)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
net_poll();
|
net_poll();
|
||||||
net_process_messages();
|
net_process_messages();
|
||||||
osDelay(10);
|
osDelay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
138
App/time_sync.c
Normal file
138
App/time_sync.c
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 模块名称: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"
|
||||||
|
|
||||||
|
#define DBG_TAG "[TIME_SYNC]"
|
||||||
|
#include "dbg_log.h"
|
||||||
|
|
||||||
|
/* 监听端口:PC 推送端(TCP 客户端)需连到本端口 */
|
||||||
|
#define TIME_SYNC_PORT 8888
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:创建并监听 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;
|
||||||
|
int s = -1;
|
||||||
|
|
||||||
|
for (retry = 0; retry < 5; retry++) {
|
||||||
|
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,然后关闭连接
|
||||||
|
*/
|
||||||
|
static void time_sync_handle_conn(int conn_sock)
|
||||||
|
{
|
||||||
|
uint8_t buf[16];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = net_recv(conn_sock, buf, sizeof(buf), 0);
|
||||||
|
if (len >= 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 timeSyncTask(void *argument)
|
||||||
|
{
|
||||||
|
(void)argument;
|
||||||
|
|
||||||
|
/* 等网络栈起来(与 StartFtpTask 的 7s 延迟同风格,确保 netTask 已开始处理消息) */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(8000));
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
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) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
}
|
||||||
|
} while (conn < 0);
|
||||||
|
|
||||||
|
time_sync_handle_conn(conn);
|
||||||
|
/* conn(单连接模式下即 ls)已被 net_close 释放,下次循环重建监听 */
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
}
|
||||||
40
App/time_sync.h
Normal file
40
App/time_sync.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 模块名称:Time Sync — PC 本地时间推送接收(TCP)
|
||||||
|
* 模块功能:以 TCP Server 方式监听 TIME_SYNC_PORT,接收 PC 推送的本地时间包,
|
||||||
|
* 更新系统墙钟并写回 SD2506 RTC
|
||||||
|
* 适用平台:STM32F407ZGTx + CH395F
|
||||||
|
* 作者:王建锋
|
||||||
|
* 创建日期:2026-08-27
|
||||||
|
* 修改记录:
|
||||||
|
* v1.0 2026-08-27 王建锋 创建初始版本(UDP 推送)
|
||||||
|
* v1.1 2026-08-28 王建锋 改为 TCP Server,迁入独立任务 timeSyncTask
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __TIME_SYNC_H
|
||||||
|
#define __TIME_SYNC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 监听端口:PC 推送端(TCP 客户端)需连到本端口 */
|
||||||
|
#define TIME_SYNC_PORT 8888
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:时间同步任务主体(独立 FreeRTOS 任务)
|
||||||
|
* 入口参数:argument - FreeRTOS 传入参数(未使用)
|
||||||
|
* 返回值:无
|
||||||
|
* 限定条件:net_init() 已由 netTask 完成;网络消息队列由 netTask 处理
|
||||||
|
* 函数说明:以 TCP Server 方式监听 TIME_SYNC_PORT,接收 PC 推送的本地时间包,
|
||||||
|
* 经消息队列(由 netTask 串行执行)完成 socket 操作,更新系统墙钟。
|
||||||
|
* 该任务与 FTP 等其它网络任务并发安全。
|
||||||
|
*/
|
||||||
|
void timeSyncTask(void *argument);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __TIME_SYNC_H */
|
||||||
@@ -11,7 +11,7 @@ int lftpd_inet_listen(int port)
|
|||||||
int s;
|
int s;
|
||||||
|
|
||||||
for (retry = 0; retry < 5; retry++) {
|
for (retry = 0; retry < 5; retry++) {
|
||||||
s = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||||
if (s < 0) {
|
if (s < 0) {
|
||||||
if (retry == 0) DBG_ERROR("create socket");
|
if (retry == 0) DBG_ERROR("create socket");
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
|
|||||||
ch395f_set_retrans_count(8); /* 重传 8 次*/
|
ch395f_set_retrans_count(8); /* 重传 8 次*/
|
||||||
ch395f_set_retrans_period(500); /* 每次间隔 500ms,总计 4s 重传窗口 */
|
ch395f_set_retrans_period(500); /* 每次间隔 500ms,总计 4s 重传窗口 */
|
||||||
|
|
||||||
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,IDLE > INTVL<EFBFBD>?*/
|
/* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,且 IDLE > INTVL)*/
|
||||||
ch395f_set_keepalive_idle(60000); /* 空闲 60 秒后开始探测*/
|
ch395f_set_keepalive_idle(60000); /* 空闲 60 秒后开始探测*/
|
||||||
ch395f_set_keepalive_intvl(5000); /* 5 秒探测一次*/
|
ch395f_set_keepalive_intvl(5000); /* 5 秒探测一次*/
|
||||||
ch395f_set_keepalive_cnt(3); /* 最多探 3 次*/
|
ch395f_set_keepalive_cnt(3); /* 最多探 3 次*/
|
||||||
@@ -362,12 +362,11 @@ void net_process_messages(void) {
|
|||||||
* 函数功能:创建 Socket,分配空闲 Socket 控制块并初始化状态
|
* 函数功能:创建 Socket,分配空闲 Socket 控制块并初始化状态
|
||||||
* 入口参数:domain - 地址族,仅支持 NET_AF_INET int
|
* 入口参数:domain - 地址族,仅支持 NET_AF_INET int
|
||||||
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int
|
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int
|
||||||
* protocol - 通常为 0 int
|
|
||||||
* 返回值:Socket 描述符 (0~7),失败返回 -1 int
|
* 返回值:Socket 描述符 (0~7),失败返回 -1 int
|
||||||
* 限定条件:net_init() 已调用成功;s_net_initialized == 1
|
* 限定条件:net_init() 已调用成功;s_net_initialized == 1
|
||||||
* 函数说明:自动分配一个空闲的 Socket 控制块(in_use=0),初始化为 CREATED 状态
|
* 函数说明:自动分配一个空闲的 Socket 控制块(in_use=0),初始化为 CREATED 状态
|
||||||
*/
|
*/
|
||||||
int net_socket(int domain, int type, int protocol) {
|
int net_socket(int domain, int type) {
|
||||||
int sockfd;
|
int sockfd;
|
||||||
net_sock_t *p_sock;
|
net_sock_t *p_sock;
|
||||||
|
|
||||||
@@ -411,7 +410,7 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
|||||||
net_sock_t *p_sock;
|
net_sock_t *p_sock;
|
||||||
const struct net_sockaddr_in *p_addr_in;
|
const struct net_sockaddr_in *p_addr_in;
|
||||||
|
|
||||||
/* 参数检<EFBFBD>?*/
|
/* 参数检查 */
|
||||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||||
s_net_errno = NET_ERR_BADF;
|
s_net_errno = NET_ERR_BADF;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1257,7 +1256,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
|||||||
int sent = 0;
|
int sent = 0;
|
||||||
uint32_t tick_start;
|
uint32_t tick_start;
|
||||||
|
|
||||||
/* 参数检<EFBFBD>?*/
|
/* 参数检查 */
|
||||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||||
s_net_errno = NET_ERR_BADF;
|
s_net_errno = NET_ERR_BADF;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1292,7 +1291,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
|||||||
|
|
||||||
tick_start = HAL_GetTick();
|
tick_start = HAL_GetTick();
|
||||||
|
|
||||||
/* 发送数<EFBFBD>?*/
|
/* 发送数据 */
|
||||||
while (sent < len) {
|
while (sent < len) {
|
||||||
int chunk = len - sent;
|
int chunk = len - sent;
|
||||||
if (chunk > NET_SEND_CHUNK_MAX) {
|
if (chunk > NET_SEND_CHUNK_MAX) {
|
||||||
@@ -1315,7 +1314,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
|
|||||||
sent += chunk;
|
sent += chunk;
|
||||||
p_sock->send_bytes += (uint32_t)chunk;
|
p_sock->send_bytes += (uint32_t)chunk;
|
||||||
|
|
||||||
/* 等待发送完<EFBFBD>?*/
|
/* 等待发送完成 */
|
||||||
tick_start = HAL_GetTick();
|
tick_start = HAL_GetTick();
|
||||||
while (!p_sock->send_ready) {
|
while (!p_sock->send_ready) {
|
||||||
net_poll();
|
net_poll();
|
||||||
@@ -1346,7 +1345,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
|||||||
int recv_len;
|
int recv_len;
|
||||||
uint32_t tick_start;
|
uint32_t tick_start;
|
||||||
|
|
||||||
/* 参数检<EFBFBD>?*/
|
/* 参数检查 */
|
||||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||||
s_net_errno = NET_ERR_BADF;
|
s_net_errno = NET_ERR_BADF;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1370,7 +1369,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
|||||||
|
|
||||||
recv_len = ch395f_get_recv_len(sockfd);
|
recv_len = ch395f_get_recv_len(sockfd);
|
||||||
if (recv_len > 0) {
|
if (recv_len > 0) {
|
||||||
/* UDP Server 模式:前 8 字节为固定格式(IP + 端口 + 长度<EFBFBD>?*/
|
/* UDP Server 模式:前 8 字节为固定格式(IP + 端口 + 长度)*/
|
||||||
if (p_sock->type == NET_SOCK_DGRAM && recv_len >= 8) {
|
if (p_sock->type == NET_SOCK_DGRAM && recv_len >= 8) {
|
||||||
uint8_t header[8];
|
uint8_t header[8];
|
||||||
ch395f_read_recv_buf(sockfd, header, 8);
|
ch395f_read_recv_buf(sockfd, header, 8);
|
||||||
@@ -1381,7 +1380,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
|||||||
*addrlen >= (int)sizeof(struct net_sockaddr_in)) {
|
*addrlen >= (int)sizeof(struct net_sockaddr_in)) {
|
||||||
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
|
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
|
||||||
p_addr_in->sin_family = NET_AF_INET;
|
p_addr_in->sin_family = NET_AF_INET;
|
||||||
/* CH395F UDP <EFBFBD>? [0-1] reserved [2-3] src_port(LE) [4-7] src_ip */
|
/* CH395F UDP 头 [0-1] reserved [2-3] src_port(LE) [4-7] src_ip */
|
||||||
p_addr_in->sin_port = net_htons((uint16_t)(header[2] | (header[3] << 8)));
|
p_addr_in->sin_port = net_htons((uint16_t)(header[2] | (header[3] << 8)));
|
||||||
p_addr_in->sin_addr.s_addr = ((uint32_t)header[4]) |
|
p_addr_in->sin_addr.s_addr = ((uint32_t)header[4]) |
|
||||||
((uint32_t)header[5] << 8) |
|
((uint32_t)header[5] << 8) |
|
||||||
@@ -1400,13 +1399,13 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 非阻塞模式检<EFBFBD>?*/
|
/* 非阻塞模式检查 */
|
||||||
if (flags & NET_MSG_DONTWAIT) {
|
if (flags & NET_MSG_DONTWAIT) {
|
||||||
s_net_errno = NET_ERR_WOULDBLOCK;
|
s_net_errno = NET_ERR_WOULDBLOCK;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 超时检<EFBFBD>?*/
|
/* 超时检查 */
|
||||||
if (NET_RECV_TIMEOUT_MS > 0 &&
|
if (NET_RECV_TIMEOUT_MS > 0 &&
|
||||||
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
|
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
|
||||||
s_net_errno = NET_ERR_TIMEDOUT;
|
s_net_errno = NET_ERR_TIMEDOUT;
|
||||||
@@ -1482,7 +1481,7 @@ int net_close_sock(net_sock_t *p_sock) {
|
|||||||
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
|
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
|
||||||
net_sock_t *p_sock;
|
net_sock_t *p_sock;
|
||||||
|
|
||||||
/* 参数检<EFBFBD>?*/
|
/* 参数检查 */
|
||||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||||
s_net_errno = NET_ERR_BADF;
|
s_net_errno = NET_ERR_BADF;
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -76,12 +76,11 @@ int net_poll(void);
|
|||||||
* 函数功能:创建 socket
|
* 函数功能:创建 socket
|
||||||
* 入口参数:domain - 地址族,仅支持 AF_INET
|
* 入口参数:domain - 地址族,仅支持 AF_INET
|
||||||
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP)
|
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP)
|
||||||
* protocol - 通常为 0
|
|
||||||
* 返回值:socket 描述符 (0~7),失败返回 -1
|
* 返回值:socket 描述符 (0~7),失败返回 -1
|
||||||
* 限定条件:net_init() 已调用
|
* 限定条件:net_init() 已调用
|
||||||
* 函数说明:自动分配空闲 Socket 控制块
|
* 函数说明:自动分配空闲 Socket 控制块
|
||||||
*/
|
*/
|
||||||
int net_socket(int domain, int type, int protocol);
|
int net_socket(int domain, int type);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 函数功能:绑定本地地址和端口
|
* 函数功能:绑定本地地址和端口
|
||||||
|
|||||||
@@ -765,6 +765,11 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>..\App\sys_clock.c</FilePath>
|
<FilePath>..\App\sys_clock.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>time_sync.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>..\App\time_sync.c</FilePath>
|
||||||
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<FileName>net_task.c</FileName>
|
<FileName>net_task.c</FileName>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
|
|||||||
@@ -4,71 +4,72 @@ assembling startup_stm32f407xx.s...
|
|||||||
compiling ringbuf.c...
|
compiling ringbuf.c...
|
||||||
compiling crc.c...
|
compiling crc.c...
|
||||||
compiling stm32f4xx_hal_flash_ramfunc.c...
|
compiling stm32f4xx_hal_flash_ramfunc.c...
|
||||||
compiling stm32f4xx_hal_gpio.c...
|
|
||||||
compiling i2c.c...
|
|
||||||
compiling stm32f4xx_hal_msp.c...
|
|
||||||
compiling stm32f4xx_hal_timebase_tim.c...
|
|
||||||
compiling dma.c...
|
|
||||||
compiling stm32f4xx_hal_dma_ex.c...
|
|
||||||
compiling spi.c...
|
|
||||||
compiling stm32f4xx_hal_flash_ex.c...
|
compiling stm32f4xx_hal_flash_ex.c...
|
||||||
compiling stm32f4xx_hal_dma.c...
|
compiling stm32f4xx_hal_timebase_tim.c...
|
||||||
compiling stm32f4xx_it.c...
|
compiling spi.c...
|
||||||
|
compiling gpio.c...
|
||||||
|
compiling dma.c...
|
||||||
|
compiling i2c.c...
|
||||||
|
compiling stm32f4xx_hal_rcc.c...
|
||||||
compiling stm32f4xx_hal_flash.c...
|
compiling stm32f4xx_hal_flash.c...
|
||||||
|
compiling adc_task.c...
|
||||||
|
compiling stm32f4xx_hal_msp.c...
|
||||||
|
compiling stm32f4xx_it.c...
|
||||||
|
compiling usart.c...
|
||||||
|
compiling app_main.c...
|
||||||
|
compiling net_task.c...
|
||||||
compiling sys_clock.c...
|
compiling sys_clock.c...
|
||||||
|
compiling rs485_task.c...
|
||||||
compiling stm32f4xx_hal_rcc_ex.c...
|
compiling stm32f4xx_hal_rcc_ex.c...
|
||||||
compiling stm32f4xx_hal_tim_ex.c...
|
compiling stm32f4xx_hal_tim_ex.c...
|
||||||
compiling app_main.c...
|
compiling time_sync.c...
|
||||||
compiling gpio.c...
|
|
||||||
compiling usart.c...
|
|
||||||
compiling stm32f4xx_hal_rcc.c...
|
|
||||||
compiling rs485_task.c...
|
|
||||||
compiling adc_task.c...
|
|
||||||
compiling freertos.c...
|
|
||||||
compiling main.c...
|
compiling main.c...
|
||||||
compiling net_task.c...
|
compiling freertos.c...
|
||||||
compiling lftpd_string.c...
|
compiling stm32f4xx_hal_gpio.c...
|
||||||
compiling stm32f4xx_hal_tim.c...
|
compiling stm32f4xx_hal_tim.c...
|
||||||
compiling croutine.c...
|
compiling stm32f4xx_hal_dma_ex.c...
|
||||||
compiling event_groups.c...
|
compiling lftpd_string.c...
|
||||||
compiling list.c...
|
|
||||||
compiling queue.c...
|
|
||||||
compiling stream_buffer.c...
|
|
||||||
compiling stm32f4xx_hal_pwr.c...
|
compiling stm32f4xx_hal_pwr.c...
|
||||||
compiling timers.c...
|
compiling stm32f4xx_hal_dma.c...
|
||||||
|
compiling croutine.c...
|
||||||
|
compiling list.c...
|
||||||
|
compiling event_groups.c...
|
||||||
|
compiling stream_buffer.c...
|
||||||
|
compiling queue.c...
|
||||||
compiling tasks.c...
|
compiling tasks.c...
|
||||||
|
compiling timers.c...
|
||||||
|
compiling stm32f4xx_hal_cortex.c...
|
||||||
|
compiling stm32f4xx_hal_pwr_ex.c...
|
||||||
compiling heap_4.c...
|
compiling heap_4.c...
|
||||||
|
compiling system_stm32f4xx.c...
|
||||||
compiling port.c...
|
compiling port.c...
|
||||||
compiling journal.c...
|
compiling journal.c...
|
||||||
compiling map.c...
|
compiling map.c...
|
||||||
compiling ff.c...
|
|
||||||
compiling stm32f4xx_hal_pwr_ex.c...
|
|
||||||
compiling stm32f4xx_hal_exti.c...
|
|
||||||
compiling stm32f4xx_hal_i2c_ex.c...
|
|
||||||
compiling stm32f4xx_hal_cortex.c...
|
|
||||||
compiling stm32f4xx_hal.c...
|
|
||||||
compiling system_stm32f4xx.c...
|
|
||||||
compiling sd2506.c...
|
|
||||||
compiling rs485.c...
|
|
||||||
compiling stm32f4xx_hal_spi.c...
|
|
||||||
compiling ch395f.c...
|
|
||||||
compiling cmsis_os2.c...
|
|
||||||
compiling lftpd_io.c...
|
compiling lftpd_io.c...
|
||||||
compiling tpafe5160.c...
|
compiling stm32f4xx_hal.c...
|
||||||
compiling nand_ftl.c...
|
compiling stm32f4xx_hal_i2c_ex.c...
|
||||||
compiling gd5f2gq5ue.c...
|
compiling stm32f4xx_hal_exti.c...
|
||||||
compiling net_select.c...
|
compiling ch395f.c...
|
||||||
compiling stm32f4xx_hal_uart.c...
|
compiling rs485.c...
|
||||||
compiling lftpd_inet.c...
|
compiling lftpd_inet.c...
|
||||||
|
compiling ff.c...
|
||||||
|
compiling tpafe5160.c...
|
||||||
|
compiling sd2506.c...
|
||||||
|
compiling gd5f2gq5ue.c...
|
||||||
|
compiling nand_ftl.c...
|
||||||
|
compiling stm32f4xx_hal_uart.c...
|
||||||
|
compiling stm32f4xx_hal_spi.c...
|
||||||
|
compiling net_select.c...
|
||||||
compiling lftpd.c...
|
compiling lftpd.c...
|
||||||
compiling net_socket.c...
|
compiling net_socket.c...
|
||||||
compiling stm32f4xx_hal_i2c.c...
|
compiling stm32f4xx_hal_i2c.c...
|
||||||
compiling ch395f_test_task.c...
|
compiling cmsis_os2.c...
|
||||||
compiling net_test_task.c...
|
|
||||||
compiling gd5f_test_task.c...
|
compiling gd5f_test_task.c...
|
||||||
|
compiling net_test_task.c...
|
||||||
|
compiling ch395f_test_task.c...
|
||||||
compiling storage_test_task.c...
|
compiling storage_test_task.c...
|
||||||
linking...
|
linking...
|
||||||
Program Size: Code=58880 RO-data=2436 RW-data=408 ZI-data=51872
|
Program Size: Code=59744 RO-data=2556 RW-data=412 ZI-data=51868
|
||||||
FromELF: creating hex file...
|
FromELF: creating hex file...
|
||||||
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
|
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
|
||||||
Build Time Elapsed: 00:00:17
|
Build Time Elapsed: 00:00:15
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "lftpd.h"
|
#include "lftpd.h"
|
||||||
#include "../test/ch395f_test_task.h"
|
#include "../test/ch395f_test_task.h"
|
||||||
#include "../test/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */
|
#include "../test/ch395f_test.h" /* ENABLE_NET_LAYER_TESTS:netTask 条件创建 */
|
||||||
|
#include "time_sync.h"
|
||||||
/* USER CODE END Includes */
|
/* USER CODE END Includes */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
@@ -55,6 +56,12 @@
|
|||||||
/* Private variables ---------------------------------------------------------*/
|
/* Private variables ---------------------------------------------------------*/
|
||||||
/* USER CODE BEGIN Variables */
|
/* USER CODE BEGIN Variables */
|
||||||
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/storage_test_task.c */
|
/* 存储测试变量(g_storage_stats / s_perf_buf)已迁至 test/storage_test_task.c */
|
||||||
|
osThreadId_t timeSyncTaskHandle;
|
||||||
|
const osThreadAttr_t timeSyncTask_attributes = {
|
||||||
|
.name = "timeSyncTask",
|
||||||
|
.stack_size = 512 * 4,
|
||||||
|
.priority = (osPriority_t) osPriorityNormal,
|
||||||
|
};
|
||||||
/* USER CODE END Variables */
|
/* USER CODE END Variables */
|
||||||
/* Definitions for defaultTask */
|
/* Definitions for defaultTask */
|
||||||
osThreadId_t defaultTaskHandle;
|
osThreadId_t defaultTaskHandle;
|
||||||
@@ -141,6 +148,8 @@ void MX_FREERTOS_Init(void) {
|
|||||||
|
|
||||||
/* creation of ftpTask */
|
/* creation of ftpTask */
|
||||||
ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes);
|
ftpTaskHandle = osThreadNew(StartFtpTask, NULL, &ftpTask_attributes);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* creation of ch395fTestTask */
|
/* creation of ch395fTestTask */
|
||||||
ch395fTestTaskHandle = osThreadNew(StartCh395fTestTask, NULL, &ch395fTestTask_attributes);
|
ch395fTestTaskHandle = osThreadNew(StartCh395fTestTask, NULL, &ch395fTestTask_attributes);
|
||||||
@@ -150,6 +159,9 @@ void MX_FREERTOS_Init(void) {
|
|||||||
/* NET 层测试模式(阶段 6~10):完整网络栈,netTask 负责轮询与消息处理 */
|
/* NET 层测试模式(阶段 6~10):完整网络栈,netTask 负责轮询与消息处理 */
|
||||||
netTaskHandle = osThreadNew(StartNetTask, NULL, &netTask_attributes);
|
netTaskHandle = osThreadNew(StartNetTask, NULL, &netTask_attributes);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */
|
||||||
|
timeSyncTaskHandle = osThreadNew(timeSyncTask, NULL, &timeSyncTask_attributes);
|
||||||
/* USER CODE END RTOS_THREADS */
|
/* USER CODE END RTOS_THREADS */
|
||||||
|
|
||||||
/* USER CODE BEGIN RTOS_EVENTS */
|
/* USER CODE BEGIN RTOS_EVENTS */
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -719,7 +719,7 @@ MCU (TCP Client, ch395f_* 裸命令) PC (tcp_bulk --port 8081)
|
|||||||
### 8.1 架构与会话结构
|
### 8.1 架构与会话结构
|
||||||
|
|
||||||
```
|
```
|
||||||
net_socket(NET_AF_INET, NET_SOCK_STREAM, 0) // 取空闲 Socket(非监听 Socket 0)
|
net_socket(NET_AF_INET, NET_SOCK_STREAM) // 取空闲 Socket(非监听 Socket 0)
|
||||||
→ net_connect(PC:8081) // 异步下发,net_poll 推进状态机
|
→ net_connect(PC:8081) // 异步下发,net_poll 推进状态机
|
||||||
→ net_select(sockfd+1, NULL, &wfds, NULL, &tv) // 等可写 = ESTABLISHED
|
→ net_select(sockfd+1, NULL, &wfds, NULL, &tv) // 等可写 = ESTABLISHED
|
||||||
→ net_send(64B) → net_recv(64B) 校验 // 主路径(701)
|
→ net_send(64B) → net_recv(64B) 校验 // 主路径(701)
|
||||||
@@ -782,7 +782,7 @@ net_socket(NET_AF_INET, NET_SOCK_STREAM, 0) // 取空闲 Socket(非监听
|
|||||||
|
|
||||||
| 步 | 操作 | 成功标准 | 失败标准 |
|
| 步 | 操作 | 成功标准 | 失败标准 |
|
||||||
|----|------|----------|----------|
|
|----|------|----------|----------|
|
||||||
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM, 0)` 创建 TCP socket | 返回 sockfd(`0~7`),非 `-1` | 返回 `-1`(无空闲 Socket 或 net 层未初始化) |
|
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM)` 创建 TCP socket | 返回 sockfd(`0~7`),非 `-1` | 返回 `-1`(无空闲 Socket 或 net 层未初始化) |
|
||||||
| 2 | 构造 `struct net_sockaddr_in`(sin_family=`NET_AF_INET`,sin_port=`net_htons(8081)`,sin_addr=`net_inet_addr("192.168.1.2")`)后 `net_connect(sockfd, &addr, len)` | 返回 `0`(连接命令已异步下发) | 返回 `-1`(地址非法或协议栈异常) |
|
| 2 | 构造 `struct net_sockaddr_in`(sin_family=`NET_AF_INET`,sin_port=`net_htons(8081)`,sin_addr=`net_inet_addr("192.168.1.2")`)后 `net_connect(sockfd, &addr, len)` | 返回 `0`(连接命令已异步下发) | 返回 `-1`(地址非法或协议栈异常) |
|
||||||
| 3 | 在 netTask 周期 `net_poll()` 推进下,调用 `net_select(sockfd+1, NULL, &wfds, NULL, &tv)` 等待可写事件,超时 10s | 返回正数且 `NET_FD_ISSET(sockfd)` 指示连接建立(ESTABLISHED) | 超时未连接,`net_errno == NET_ERR_WOULDBLOCK` 持续 |
|
| 3 | 在 netTask 周期 `net_poll()` 推进下,调用 `net_select(sockfd+1, NULL, &wfds, NULL, &tv)` 等待可写事件,超时 10s | 返回正数且 `NET_FD_ISSET(sockfd)` 指示连接建立(ESTABLISHED) | 超时未连接,`net_errno == NET_ERR_WOULDBLOCK` 持续 |
|
||||||
| 4 | `net_send(sockfd, buf, 64, 0)` 发送 64 字节递增模式数据(`0x00..0x3F`) | 返回值 == `64`(全部发出) | 返回值 `< 0`(发送失败)或 `≠ 64`(未完全发出) |
|
| 4 | `net_send(sockfd, buf, 64, 0)` 发送 64 字节递增模式数据(`0x00..0x3F`) | 返回值 == `64`(全部发出) | 返回值 `< 0`(发送失败)或 `≠ 64`(未完全发出) |
|
||||||
@@ -807,7 +807,7 @@ net_socket(NET_AF_INET, NET_SOCK_STREAM, 0) // 取空闲 Socket(非监听
|
|||||||
|
|
||||||
| 步 | 操作 | 成功标准 | 失败标准 |
|
| 步 | 操作 | 成功标准 | 失败标准 |
|
||||||
|----|------|----------|----------|
|
|----|------|----------|----------|
|
||||||
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM, 0)` 创建 socket | 返回 sockfd 非 `-1` | 返回 `-1`(无空闲 Socket) |
|
| 1 | `net_socket(NET_AF_INET, NET_SOCK_STREAM)` 创建 socket | 返回 sockfd 非 `-1` | 返回 `-1`(无空闲 Socket) |
|
||||||
| 2 | 构造地址 + `net_connect(sockfd, &addr, len)`,再 `net_select` 等待可写确认 ESTABLISHED | 连接建立成功 | 连接失败或超时未建立 |
|
| 2 | 构造地址 + `net_connect(sockfd, &addr, len)`,再 `net_select` 等待可写确认 ESTABLISHED | 连接建立成功 | 连接失败或超时未建立 |
|
||||||
| 3 | `net_send(sockfd, buf, 64, 0)` → `net_recv(sockfd, buf, 64, 0)` | 收发各 `64` 字节、固件字节级比对 + 累加和 `TEST_CHECK` 通过 | 收发失败 / 数据不符 / 固件 `TEST_CHECK` 失败 |
|
| 3 | `net_send(sockfd, buf, 64, 0)` → `net_recv(sockfd, buf, 64, 0)` | 收发各 `64` 字节、固件字节级比对 + 累加和 `TEST_CHECK` 通过 | 收发失败 / 数据不符 / 固件 `TEST_CHECK` 失败 |
|
||||||
| 4 | `net_close(sockfd)` 关闭并释放控制块 | 返回 `0` | 返回 `-1` |
|
| 4 | `net_close(sockfd)` 关闭并释放控制块 | 返回 `0` | 返回 `-1` |
|
||||||
|
|||||||
348
docs/系统墙钟时间维护说明.md
Normal file
348
docs/系统墙钟时间维护说明.md
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
# 系统墙钟时间维护 设计说明
|
||||||
|
|
||||||
|
> 模块:`App/sys_clock.c` / `App/sys_clock.h`
|
||||||
|
> 依赖:SD2506API-G RTC 驱动(`Drivers/BSP/SD2506/`)、HAL(`HAL_GetTick()`)
|
||||||
|
> 适用平台:STM32F407ZGTx(Cortex-M4,168MHz),I2C1 接 SD2506
|
||||||
|
> 作者:王建锋 创建日期:2026-07-19
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
系统需要一个"墙钟时间"(wall clock,即真实日历时间 `YYYY-MM-DD HH:MM:SS`),用于:
|
||||||
|
|
||||||
|
- 日志/调试信息打时间戳;
|
||||||
|
- 文件系统的文件创建/修改时间(FatFS 需要);
|
||||||
|
- 业务逻辑中基于真实时间的判定(定时、超时、调度等);
|
||||||
|
- 对外接口(如 FTP `LIST` 返回的文件时间、SNTP/NTP 校时等)。
|
||||||
|
|
||||||
|
本模块在 **SD2506 硬件 RTC(掉电保持)** 与 **HAL 系统节拍(运行期计时)** 之间做桥接:
|
||||||
|
|
||||||
|
- 启动时从 SD2506 读取一次真实时间,换算为 Unix 时间戳,建立"墙钟基准";
|
||||||
|
- 运行期间不再频繁访问 I2C RTC,而是以 **HAL Tick 流逝量** 推算当前时间,避免每次取时都走 I2C 总线;
|
||||||
|
- 仅在 `sys_clock_set()` 被调用(如用户校时、SNTP 同步)时,才把时间写回 SD2506 实现掉电保持。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 角色与依赖
|
||||||
|
|
||||||
|
| 角色 | 提供方 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 硬件实时钟(RTC) | SD2506API-G | 温补晶振,带备份电池,掉电后继续走时;I2C1(PB6-SCL/PB7-SDA) |
|
||||||
|
| 系统节拍 | `HAL_GetTick()` | 毫秒级计数,HAL 时基由 TIM7 提供(168MHz 主频派生,精度远高于走时需求) |
|
||||||
|
| Unix 时间戳 | 本模块计算 | 自 1970-01-01 00:00:00 UTC 起的秒数(`uint32_t`) |
|
||||||
|
| 时间结构 | `sd2506_time_t` | SD2506 驱动定义,`year` 为**完整年份(2000~2099)**,含 `week` |
|
||||||
|
|
||||||
|
**调用顺序(见 `Src/main.c`):**
|
||||||
|
|
||||||
|
```
|
||||||
|
app_main_init(); /* 外设/日志等前置初始化 */
|
||||||
|
sd2506_init(); /* 初始化 RTC(I2C 通信、24h 制、充电配置) */
|
||||||
|
sys_clock_init(); /* 读取 RTC → 建立墙钟基准 */
|
||||||
|
```
|
||||||
|
|
||||||
|
> 必须在 `sd2506_init()` 之后调用 `sys_clock_init()`;其余 `sys_clock_*` 接口必须在 `sys_clock_init()` 之后使用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 核心思想:基准法(drift-free 维持)
|
||||||
|
|
||||||
|
为什么不每次都读 RTC?
|
||||||
|
|
||||||
|
- SD2506 走 I2C,单次读取有总线开销与阻塞延迟;
|
||||||
|
- RTC 自身已足够精确,无需运行时反复校准;
|
||||||
|
- 运行期真正变化的只是"过了多少时间",由 HAL Tick 提供。
|
||||||
|
|
||||||
|
因此模块只在初始化(`sys_clock_init`)与显式设置(`sys_clock_set`)时接触 RTC,其余时刻用以下公式维持时间:
|
||||||
|
|
||||||
|
```
|
||||||
|
当前 Unix 秒数 = s_base_unix_secs + (HAL_GetTick() - s_base_tick) / 1000
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `(now_tick - s_base_tick)` 为 **unsigned 减法**,在 Tick 每约 49 天回绕一次时依然正确(只要两次采样间隔 < 2³² ms ≈ 49 天,单次运行必然满足),所以连续上电不超过 49 天的场景无回绕问题。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 内部数据结构
|
||||||
|
|
||||||
|
`App/sys_clock.c` 中定义两个静态变量作为墙钟基准:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准(建立基准时的 Tick 值) */
|
||||||
|
static volatile uint32_t s_base_unix_secs = UINT32_MAX; /* 基准对应的 Unix 秒数;初始为 UINT32_MAX 表示"未建立" */
|
||||||
|
```
|
||||||
|
|
||||||
|
- `s_base_tick`:`sys_clock_init()` / `sys_clock_set()` 时采样一次。
|
||||||
|
- `s_base_unix_secs`:与 `s_base_tick` 同一时刻的 Unix 秒数;`sys_clock_get()` 以它为锚推算当前值。
|
||||||
|
- 二者均带 `volatile`,因为时间是跨任务共享的状态(虽更新在 nets/默认任务中,但读取可能发生在任意任务上下文)。
|
||||||
|
|
||||||
|
> 注:当前工程仅 `main.c` 调用了 `sys_clock_init()`;`sys_clock_get()` / `sys_clock_get_str()` / `sys_clock_set()` 是供应用层(日志、文件时间、校时协议等)调用的 API,尚未被其他模块引用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 对外 API
|
||||||
|
|
||||||
|
| 函数 | 功能 | 返回值 | 限定条件 |
|
||||||
|
|------|------|--------|----------|
|
||||||
|
| `int sys_clock_init(void)` | 从 SD2506 读取初始时间,建立墙钟基准(含可选校时) | `0`(始终成功) | `sd2506_init()` 已调用 |
|
||||||
|
| `uint32_t sys_clock_get(void)` | 获取当前 Unix 时间戳(秒) | 当前秒数;未初始化返回 `UINT32_MAX` | `sys_clock_init()` 已调用 |
|
||||||
|
| `char *sys_clock_get_str(char *buf, size_t buf_size)` | 获取格式化字符串 `YYYY-MM-DD HH:MM:SS` | `buf` 指针;未初始化输出 `----/--/-- --:--:--` | `buf` 有效且 `buf_size ≥ 20` |
|
||||||
|
| `int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)` | 更新基准,可写回 SD2506 | `0`(始终成功) | `unix_secs` 为合法时间戳 |
|
||||||
|
|
||||||
|
> 头文件 `sys_clock.h` 中注释的返回值含 `-1` 失败码,但实际实现恒返回 `0`,以"成功"语义对外;调用方按 `0` 成功处理即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 初始化流程(`sys_clock_init`)
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1 从 SD2506 读取初始时间 sd2506_get_time(&sd_time)
|
||||||
|
├─ 注意:sd_time.year 已是完整年份(驱动内已 +2000),
|
||||||
|
│ 后续换算【不得】再加 2000,否则年份虚增 → Unix 秒数超 uint32 回绕
|
||||||
|
└─ 可选校时(见第 9 节 RTC_DEFAULT_INIT_* 宏)
|
||||||
|
|
||||||
|
Step 2 将 RTC 时间换算为 Unix epoch 秒数
|
||||||
|
├─ 累计 1970 至当前年份的整年天数(含闰年 366 天判定)
|
||||||
|
├─ 加上当年 1 月至当前月的累计天数
|
||||||
|
├─ 加当月已过天数、时/分/秒
|
||||||
|
└─ 用 int64_t 累加,避免中间溢出;结果截断存入 uint32_t(受 2038 限制,见第 12 节)
|
||||||
|
|
||||||
|
Step 3 建立基准
|
||||||
|
s_base_tick = HAL_GetTick();
|
||||||
|
s_base_unix_secs = (uint32_t)unix_secs;
|
||||||
|
```
|
||||||
|
|
||||||
|
初始化后,所有后续取时都基于 Step 3 的基准推算,不再访问 RTC。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 时间获取与漂移维持(`sys_clock_get`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
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);
|
||||||
|
if (unix_secs < 0) return UINT32_MAX; /* 基准未建立(理论上不会发生) */
|
||||||
|
return (uint32_t)unix_secs;
|
||||||
|
```
|
||||||
|
|
||||||
|
要点:
|
||||||
|
|
||||||
|
- 用 **整除 1000** 把毫秒 Tick 折算为秒,**不**做每毫秒累加,避免累加误差累积与频繁除法;
|
||||||
|
- `now_tick - s_base_tick` 为无符号减法,自动正确处理 Tick 回绕;
|
||||||
|
- 若基准未建立(`s_base_unix_secs` 仍为 `UINT32_MAX` 且未初始化),返回 `UINT32_MAX` 作为"无效"哨兵。
|
||||||
|
|
||||||
|
### 7.1 字符串格式化(`sys_clock_get_str`)
|
||||||
|
|
||||||
|
内部先 `sys_clock_get()`,若无效则输出占位串 `----/--/-- --:--:--`;否则用 `unix_to_sd2506()` 转回 `sd2506_time_t` 后,以 `snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d", ...)` 格式化(年份显示时 `+2000` 还原为完整年份)。使用 `snprintf` 防止缓冲区溢出,调用方需保证 `buf_size ≥ 20`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 时间设置与回写(`sys_clock_set`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
s_base_tick = HAL_GetTick();
|
||||||
|
s_base_unix_secs = unix_secs;
|
||||||
|
if (sd_time != NULL) {
|
||||||
|
sd2506_set_time(sd_time); /* 写回 RTC,实现掉电保持 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 仅更新"基准",之后 `sys_clock_get()` 即以此为准;
|
||||||
|
- 若传入 `sd_time != NULL`,同步写回 SD2506,使断电后时间不丢失;
|
||||||
|
- `sd_time` 为 `NULL` 时只改运行期基准、不动 RTC(适用于临时/网络校时尚未落盘的场景)。
|
||||||
|
|
||||||
|
典型应用:SNTP/NTP 同步得到 Unix 秒数 → 转成 `sd2506_time_t` → `sys_clock_set(secs, &sd_time)` 一次性更新运行基准与掉电保持。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 开发期校时宏(`sys_clock.c` 顶部)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define RTC_DEFAULT_INIT_ENABLE 1
|
||||||
|
#define RTC_DEFAULT_INIT_FORCE 1
|
||||||
|
#define RTC_DEFAULT_YEAR 2026
|
||||||
|
#define RTC_DEFAULT_MONTH 8
|
||||||
|
#define RTC_DEFAULT_DAY 24
|
||||||
|
#define RTC_DEFAULT_HOUR 0
|
||||||
|
#define RTC_DEFAULT_MINUTE 0
|
||||||
|
#define RTC_DEFAULT_SECOND 0
|
||||||
|
```
|
||||||
|
|
||||||
|
行为(`sys_clock_init` 内,`#if defined(RTC_DEFAULT_INIT_ENABLE)` 包裹):
|
||||||
|
|
||||||
|
- 判定 RTC 时间是否"无效":`year` 越界(<2000 或 >2099)、`month` 越界、`day` 越界;
|
||||||
|
- `RTC_DEFAULT_INIT_FORCE == 1`:强制每次启动都用上述宏写入默认时间;
|
||||||
|
- 否则仅在 RTC 时间无效时写入;
|
||||||
|
- 写入后重新 `sd2506_get_time()` 读取校准后的值,再进入 Step 2 换算。
|
||||||
|
|
||||||
|
> ⚠️ **使用注意**:`FORCE=1` 会**每次开机覆盖用户/网络已设的时间**,仅用于首次烧录或硬件时钟丢失时的开发期校时。校时完成后应将 `RTC_DEFAULT_INIT_FORCE` 置 `0`(或注释 `ENABLE`),否则会抹掉正常运行中通过 SNTP 等写入的正确时间。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 时间换算算法
|
||||||
|
|
||||||
|
### 10.1 Unix 秒 ↔ SD2506(`unix_to_sd2506`)
|
||||||
|
|
||||||
|
- 时分秒:对 `unix_secs` 连续 `%60 /60 /24` 拆分;
|
||||||
|
- 年月日:先算总天数 `unix_secs / 86400`,再从 1970 年起逐年减去平/闰年天数,定位到具体年;再按月累减定位月、日;
|
||||||
|
- 星期:由 Zeller 公式(见 10.2)计算,结果 `0=Sunday`;
|
||||||
|
- SD2506 年份字段为 0~99(+2000),故回写时 `year - 2000`。
|
||||||
|
|
||||||
|
### 10.2 星期计算(Zeller 公式)
|
||||||
|
|
||||||
|
`rtc_calc_week()` 与 `unix_to_sd2506()` 内使用同一套算法:
|
||||||
|
|
||||||
|
```
|
||||||
|
week = (century_term + month_term + day_term) % 7 /* 0=Sunday */
|
||||||
|
```
|
||||||
|
|
||||||
|
- `1、2 月视作上一年的 13、14 月`(公式要求),对应代码里 `wm<=2` 时 `wy--, wm+=12`;
|
||||||
|
- 计算与闰年无关,纯日历代数,结果稳定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 集成与调用示例
|
||||||
|
|
||||||
|
`Src/main.c` 中的初始化顺序(节选):
|
||||||
|
|
||||||
|
```c
|
||||||
|
HAL_Delay(100);
|
||||||
|
app_main_init();
|
||||||
|
sd2506_init(); /* 先初始化 RTC */
|
||||||
|
sys_clock_init(); /* 再建立墙钟基准:所有后续日志即带正确时间戳 */
|
||||||
|
```
|
||||||
|
|
||||||
|
应用层取时间示例:
|
||||||
|
|
||||||
|
```c
|
||||||
|
char ts[24];
|
||||||
|
DBG_INFO("now: %s", sys_clock_get_str(ts, sizeof(ts)));
|
||||||
|
|
||||||
|
uint32_t now = sys_clock_get(); /* Unix 秒,可用于 FatFS 文件时间、超时判定等 */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 关键设计要点与陷阱
|
||||||
|
|
||||||
|
1. **`year` 已经 `+2000`,禁止二次加 2000**
|
||||||
|
`sd2506_get_time()` 返回的 `year` 是完整年份(如 2026)。`sys_clock_init` 的 Step 2 直接把它当完整年份参与 Unix 换算;若再 `+2000` 会得到 4026 年,换算出的 Unix 秒数远超 `uint32_t` 上限,发生截断/回绕,时间彻底错乱。代码中相关注释已明确标注。
|
||||||
|
|
||||||
|
2. **`uint32_t` Unix 时间戳的 2038 上限**
|
||||||
|
基准与返回值均为 `uint32_t`,时间范围约为 `1970-01-01 ~ 2038-01-19`。超过 2038 年换算会回绕。当前设备生命周期内可接受;若需长期运行,需改用 `uint64_t` 或 `time_t`。
|
||||||
|
|
||||||
|
3. **Tick 回绕安全**
|
||||||
|
`(now_tick - s_base_tick)` 为无符号减法,Tick 每 ~49 天回绕一次时计算依然正确,无需特殊处理。
|
||||||
|
|
||||||
|
4. **运行期不访问 RTC**
|
||||||
|
`sys_clock_get()` 无任何 I2C 操作,零阻塞、可随时在任意任务/中断上下文安全调用(仅读 `volatile` 基准)。
|
||||||
|
|
||||||
|
5. **校时宏的副作用**
|
||||||
|
见第 9 节 `FORCE=1` 会覆盖既有正确时间,仅限开发期使用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 已知限制与可扩展方向
|
||||||
|
|
||||||
|
| 项目 | 现状 | 可扩展方向 |
|
||||||
|
|------|------|------------|
|
||||||
|
| 时间范围 | 受 `uint32_t` 限制至 2038 | 改用 `uint64_t`/`time_t` 扩展 |
|
||||||
|
| 运行期校准 | 仅初始化时读 RTC | 可定时(如每天)重新 `sd2506_get_time()` 校准,消除 RTC 与 Tick 的微小累积偏差 |
|
||||||
|
| 网络校时 | 已实现(PC 推送 / TCP,见第 15 节) | SNTP/NTP 公网校时仍可作为后续扩展 |
|
||||||
|
| 时区 | 按 RTC 本地时间处理 | 如需 UTC/时区,可在 API 层增加偏移参数 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. 相关文件
|
||||||
|
|
||||||
|
| 路径 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `App/sys_clock.c` | 墙钟维护实现(基准法、换算、校时宏) |
|
||||||
|
| `App/sys_clock.h` | 对外 API 声明 |
|
||||||
|
| `Drivers/BSP/SD2506/sd2506.c` / `.h` | SD2506 RTC I2C 驱动(`sd2506_get_time` / `sd2506_set_time`) |
|
||||||
|
| `Src/main.c` | 初始化调用顺序:`sd2506_init()` → `sys_clock_init()` |
|
||||||
|
| `App/time_sync.c` / `time_sync.h` | 时间同步实现(TCP Server,`timeSyncTask`) |
|
||||||
|
| `test/time_sync_server.py` | PC 端时间推送客户端(TCP) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 时间同步(PC 推送 / TCP)
|
||||||
|
|
||||||
|
除 RTC 自身走时与开发期校时宏外,系统支持由 **PC 通过 TCP 主动推送本地墙钟** 来同步板子时间,使板子时间与开发电脑一致(无需公网 NTP)。
|
||||||
|
|
||||||
|
### 15.1 模块与角色
|
||||||
|
|
||||||
|
| 角色 | 提供方 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 时间同步服务端(板子) | `App/time_sync.c` / `time_sync.h` | TCP Server,监听 `TIME_SYNC_PORT`(默认 8888) |
|
||||||
|
| 同步任务 | `timeSyncTask`(FreeRTOS 任务) | 独立任务,由 `Src/freertos.c` 创建(优先级 `osPriorityNormal`,栈 2KB) |
|
||||||
|
| 时间推送端(PC) | `test/time_sync_server.py` | Python TCP 客户端,周期推送本机本地时间 |
|
||||||
|
| 落盘接口 | `sys_clock_set_unix()` | 收到时间包后调用,内部转 `sd2506_time_t` 并写回 SD2506 |
|
||||||
|
|
||||||
|
### 15.2 协议
|
||||||
|
|
||||||
|
PC 每轮发送固定 8 字节帧:
|
||||||
|
|
||||||
|
```
|
||||||
|
字节 0..3 : 魔术字 "TIME"(0x54 0x49 0x4D 0x45)
|
||||||
|
字节 4..7 : 本地 Unix 时间戳,uint32,大端(PC 本地时间,含时区,与电脑显示一致)
|
||||||
|
```
|
||||||
|
|
||||||
|
板子 `time_sync_handle_conn()` 读取后校验魔术字,取大端 uint32 调用 `sys_clock_set_unix(epoch)` 更新运行基准并写回 RTC。
|
||||||
|
|
||||||
|
### 15.3 任务流程
|
||||||
|
|
||||||
|
`timeSyncTask` 启动后延迟 8s(等 netTask 完成 `net_init` 与 PHY 协商),进入循环:
|
||||||
|
|
||||||
|
```c
|
||||||
|
for (;;) {
|
||||||
|
ls = time_sync_open_listener(); /* net_socket + net_bind + net_listen,失败重试 */
|
||||||
|
conn = net_accept(ls, NULL, NULL); /* 阻塞等待 PC 连接(无连接立即返回 -1,循环重试) */
|
||||||
|
time_sync_handle_conn(conn); /* net_recv 读到 8 字节 → sys_clock_set_unix → net_close */
|
||||||
|
/* conn(单连接模式下即 ls)被 net_close 释放,下次循环重建监听 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 单连接模式下监听 Socket 收到连接后即转为数据通道,连接关闭后被释放;因此每轮循环都**重新创建监听 Socket**(与 `lftpd` 同模式),与 FTP 不冲突。
|
||||||
|
|
||||||
|
### 15.4 为什么用 TCP 而非 UDP
|
||||||
|
|
||||||
|
- 所有 socket 操作(`net_socket` / `net_bind` / `net_listen` / `net_accept` / `net_recv` / `net_close`)均走 `net_socket` 的**线程安全 API**,内部经消息队列由 `netTask` 串行执行;
|
||||||
|
- netTask 因此只需做 `net_poll()` + `net_process_messages()`,不掺杂任何应用层网络逻辑;
|
||||||
|
- **TCP 的全部 API 已经是基于消息队列的**(已在 `net_socket.c` 实现),而 UDP 的 `net_bind` / `net_sendto` / `net_recvfrom` 绕过队列。选用 TCP 即可让时间同步作为独立任务运行,且**无需改动 `net_socket.c` 的 UDP 路径**。
|
||||||
|
|
||||||
|
### 15.5 Socket 分配与冲突
|
||||||
|
|
||||||
|
- `net_socket()` 从 0 开始分配第一个空闲 Socket 控制块(`alloc_socket()`),并把硬件 Socket 号设为该下标;调用时的第三个参数(0)仅为协议占位,不影响分配。
|
||||||
|
- 当前启动顺序:FTP 任务(延迟 7s)先监听 → 占用 **socket 0**(控制)+ **socket 1**(数据);时间同步任务(延迟 8s)后监听 → 占用 **socket 1**(或下一个空闲)。二者不冲突。
|
||||||
|
- 单连接模式允许**任意** Socket 作为"监听 + 数据复用"通道,且总共 8 个 Socket,FTP 用 2 个、时间同步用 1 个,余量充足。
|
||||||
|
|
||||||
|
### 15.6 客户端实现要点(避坑)
|
||||||
|
|
||||||
|
PC 端 `test/time_sync_server.py` 发送 8 字节后**必须保持连接打开**,等待板子读取(或短延时)后再 `close()`:
|
||||||
|
|
||||||
|
- 若发送后立刻关闭,CH395F 在收到 FIN 时会**丢弃接收缓冲里尚未被板子读出的 8 字节**;
|
||||||
|
- 板子 `net_recv` 经消息队列异步执行(有 ~10ms+ 调度延迟),等它去读时连接已 DISCONNECT、缓冲已清空,`net_recv` 走 CLOSED 分支返回 0,时间包丢失、同步失败;
|
||||||
|
- 客户端改为 `sendall` 后 `recv` 直到板子主动关闭(EOF)或 2s 超时,即可保证板子先读到数据。
|
||||||
|
|
||||||
|
### 15.7 验证
|
||||||
|
|
||||||
|
板子日志应出现:
|
||||||
|
|
||||||
|
```
|
||||||
|
[TIME_SYNC] time_sync: TCP listen on :8888 (sock=1)
|
||||||
|
[NET] sock1 standalone accept
|
||||||
|
[NET] recv sock1 len=8
|
||||||
|
[CLK] sys_clock_set: wall clock updated to <epoch>
|
||||||
|
[CLK] sys_clock_set: SD2506 RTC written
|
||||||
|
[TIME_SYNC] time_sync: wall clock updated (epoch=<epoch>)
|
||||||
|
```
|
||||||
|
|
||||||
|
PC 端 `python test/time_sync_server.py` 每 `interval` 秒推送一次,epoch 随电脑本地时间递增即表示同步正常。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档依据 `App/sys_clock.c` / `App/time_sync.c` 实现与《嵌入式C语言代码规范(V1.0)》整理。*
|
||||||
@@ -121,7 +121,7 @@ static uint32_t phase7_cksum(const uint8_t *p, uint32_t n) {
|
|||||||
* 返回 1 表示全流程通过,0 表示中途失败(已记录 TEST_CHECK)。 */
|
* 返回 1 表示全流程通过,0 表示中途失败(已记录 TEST_CHECK)。 */
|
||||||
static int32_t phase7_session(uint16_t local_port)
|
static int32_t phase7_session(uint16_t local_port)
|
||||||
{
|
{
|
||||||
int32_t sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
int32_t sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||||
if (sockfd < 0) {
|
if (sockfd < 0) {
|
||||||
TEST_CHECK(0, "TC701 socket create");
|
TEST_CHECK(0, "TC701 socket create");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -220,7 +220,7 @@ static int32_t phase7_session(uint16_t local_port)
|
|||||||
* 701/702 要求服务端在线,703 要求服务端离线,故 703 应在独立的一轮中运行)。 */
|
* 701/702 要求服务端在线,703 要求服务端离线,故 703 应在独立的一轮中运行)。 */
|
||||||
static void phase7_test_timeout(void)
|
static void phase7_test_timeout(void)
|
||||||
{
|
{
|
||||||
int32_t sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
int32_t sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||||
if (sockfd < 0) { TEST_CHECK(0, "TC703 socket create"); return; }
|
if (sockfd < 0) { TEST_CHECK(0, "TC703 socket create"); return; }
|
||||||
|
|
||||||
struct net_sockaddr_in raddr;
|
struct net_sockaddr_in raddr;
|
||||||
@@ -271,7 +271,7 @@ static int32_t phase7_session_multi(void)
|
|||||||
|
|
||||||
/* 1) 打开并连接所有 Socket(各自绑定不同本地端口规避 TIME_WAIT) */
|
/* 1) 打开并连接所有 Socket(各自绑定不同本地端口规避 TIME_WAIT) */
|
||||||
for (int32_t i = 0; i < PHASE7_MULTI_COUNT; i++) {
|
for (int32_t i = 0; i < PHASE7_MULTI_COUNT; i++) {
|
||||||
sockfd[i] = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
sockfd[i] = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||||
if (sockfd[i] < 0) {
|
if (sockfd[i] < 0) {
|
||||||
TEST_CHECK(0, "TC704 socket create #%d", i);
|
TEST_CHECK(0, "TC704 socket create #%d", i);
|
||||||
for (int32_t j = 0; j < i; j++) net_close(sockfd[j]);
|
for (int32_t j = 0; j < i; j++) net_close(sockfd[j]);
|
||||||
@@ -822,7 +822,7 @@ void net_layer_test_main(void)
|
|||||||
struct net_sockaddr_in client_addr;
|
struct net_sockaddr_in client_addr;
|
||||||
int32_t addrlen = sizeof(client_addr);
|
int32_t addrlen = sizeof(client_addr);
|
||||||
|
|
||||||
sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
|
sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||||||
if (sockfd < 0) {
|
if (sockfd < 0) {
|
||||||
DBG_ERROR("Test task: net_socket failed");
|
DBG_ERROR("Test task: net_socket failed");
|
||||||
return;
|
return;
|
||||||
|
|||||||
75
test/time_sync_server.py
Normal file
75
test/time_sync_server.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
本地时间同步服务(PC -> MCU 板子)
|
||||||
|
|
||||||
|
功能:
|
||||||
|
周期性把本机(PC)的本地时间,通过 TCP 推送到 STM32 板子。
|
||||||
|
板子端在 timeSyncTask 中以 TCP Server 方式监听端口,收到后调用
|
||||||
|
sys_clock_set_unix() 更新系统墙钟并写回 SD2506 RTC,使板子时间 == 你电脑的时间。
|
||||||
|
|
||||||
|
协议(板子端实现见 App/time_sync.c 的 time_sync_handle_conn):
|
||||||
|
字节 0..3 : 魔术字 "TIME" (0x54 0x49 0x4D 0x45)
|
||||||
|
字节 4..7 : 本地 Unix 时间戳,uint32,大端(主机字节序转换后由板子分解)
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python time_sync_server.py # 默认推送到 192.168.1.100:8888
|
||||||
|
python time_sync_server.py 192.168.1.100 # 指定板子 IP
|
||||||
|
python time_sync_server.py 192.168.1.100 9000 3 # 指定端口与间隔(秒)
|
||||||
|
|
||||||
|
按 Ctrl+C 停止。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
DEFAULT_MCU_IP = "192.168.1.100"
|
||||||
|
DEFAULT_PORT = 8888
|
||||||
|
DEFAULT_INTERVAL = 5.0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
mcu_ip = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_MCU_IP
|
||||||
|
port = int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_PORT
|
||||||
|
interval = float(sys.argv[3]) if len(sys.argv) > 3 else DEFAULT_INTERVAL
|
||||||
|
|
||||||
|
print("[time_sync] 本地时间推送服务已启动(TCP 客户端)")
|
||||||
|
print(f" 目标板子 : {mcu_ip}:{port}")
|
||||||
|
print(f" 推送间隔 : {interval}s")
|
||||||
|
print(f" 协议帧 : 'TIME' + uint32(BE) 本地 Unix 时间戳")
|
||||||
|
print("[time_sync] 按 Ctrl+C 停止")
|
||||||
|
print("-" * 48)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 本地 wall-clock 的 epoch(与电脑显示一致;板子按本地时间分解)
|
||||||
|
local_epoch = int(time.mktime(time.localtime()))
|
||||||
|
pkt = b"TIME" + struct.pack(">I", local_epoch)
|
||||||
|
|
||||||
|
with socket.create_connection((mcu_ip, port), timeout=5) as sock:
|
||||||
|
sock.sendall(pkt)
|
||||||
|
# 关键:发送后保持连接打开,等板子读完再关。
|
||||||
|
# 若立即 close,CH395F 收到 FIN 会丢弃接收缓冲里尚未被板子读出的
|
||||||
|
# 8 字节时间包,导致板子 net_recv 看到 CLOSED 而无数据、时间同步失败。
|
||||||
|
# 这里 recv 直到板子主动关闭(EOF)或 2s 超时,确保板子先读到数据。
|
||||||
|
sock.settimeout(2.0)
|
||||||
|
try:
|
||||||
|
sock.recv(64)
|
||||||
|
except (socket.timeout, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
now_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
|
print(f"[{time.strftime('%H:%M:%S')}] -> {now_str} (epoch={local_epoch})")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[{time.strftime('%H:%M:%S')}] 连接/发送失败: {e}")
|
||||||
|
|
||||||
|
time.sleep(interval)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[time_sync] 已停止")
|
||||||
Reference in New Issue
Block a user