时间同步已经完成
This commit is contained in:
@@ -44,7 +44,16 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX;
|
||||
* 内部辅助函数: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)
|
||||
{
|
||||
uint32_t wy = y;
|
||||
@@ -54,10 +63,22 @@ static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
|
||||
wy--;
|
||||
wm += 12u;
|
||||
}
|
||||
/* Zeller 公式:世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
|
||||
return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
|
||||
(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)
|
||||
{
|
||||
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--;
|
||||
wm += 12uL;
|
||||
}
|
||||
/* Zeller 公式同 rtc_calc_week():世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
|
||||
uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
|
||||
(uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
|
||||
(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)
|
||||
{
|
||||
sd2506_time_t sd_time;
|
||||
@@ -216,6 +249,15 @@ int sys_clock_init(void)
|
||||
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 now_tick = HAL_GetTick();
|
||||
@@ -228,6 +270,17 @@ uint32_t sys_clock_get(void)
|
||||
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)
|
||||
{
|
||||
uint32_t unix_secs = sys_clock_get();
|
||||
@@ -245,6 +298,16 @@ char *sys_clock_get_str(char *buf, size_t buf_size)
|
||||
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)
|
||||
{
|
||||
/* Step 1: 更新墙钟基准 */
|
||||
@@ -261,3 +324,14 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* 函数功能:以 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
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -109,6 +109,6 @@ void net_task_func(void const *arg)
|
||||
for (;;) {
|
||||
net_poll();
|
||||
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 */
|
||||
Reference in New Issue
Block a user