WSN 时间同步 — FTSP¶
基于 ESP-NOW 的泛洪时间同步协议
FTSP 模块为无线传感器网络(WSN)提供微秒级精度的时间同步。一个节点作为网关(根/主节点),通过 NTP 获取墙钟时间并通过 ESP-NOW 广播。其余节点作为叶子节点(从节点),接收广播并通过线性回归校正本地时钟。
演示:多节点 FTSP 时间同步 —— 所有节点同步循环跑马灯颜色
架构¶
┌─────────────────────────────────────────────────────────────┐
│ 网关 (root) │
│ │
│ NTP (互联网) ──► tiny_ntp_record_sync() │
│ │ │
│ ▼ │
│ ftsp_gateway_task() ──► esp_now_send() 广播 │
│ │ 每 FTSP_BEACON_INTERVAL_MS │
│ │ (默认 1000 ms = 1 Hz) │
│ ▼ │
│ 信标包 (26 字节扩展格式): │
│ ref_time_us – 网关本地发送时间 (µs) │
│ unix_us – NTP 世界时间 @ 发送 (µs, Unix 纪元) │
│ seq – 滚动序列号 │
│ node_id – 网关标识符 │
│ flags – 世界时间有效、同步序列 │
│ ntp_quality – NTP 同步质量 (0-100%) │
└───────────────────────┬─────────────────────────────────────┘
│ ESP-NOW (广播)
┌─────────────┼─────────────┐
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ 叶子节点 (slave) │
│ │
│ node_espnow_recv_cb() ──► ftsp_sniffer_cb() │
│ │ (捕获 rx_us,抖动 <1 ms) │
│ ▼ │
│ 环形缓冲区 (FTSP_TABLE_SIZE 对): │
│ (local_us, ref_us) │
│ │ │
│ ▼ │
│ 最小二乘线性回归: │
│ slope = Σ(xi − x̄)(yi − ȳ) / Σ(xi − x̄)² │
│ offset = ȳ − slope · x̄ │
│ global_us = slope × local_us + offset │
│ │ │
│ ▼ │
│ 世界时间 (µs 精度): │
│ world_us = global_us + unix_offset_us │
│ unix_offset_us = beacon.unix_us - beacon.ref_time_us │
│ │
│ 直接偏移回退(漂移每信标重置一次): │
│ world_us = unix_us_last + (now - local_rx) │
└──────────────────────────────────────────────────────────────┘
核心概念¶
节点角色¶
节点角色在编译时通过 node_config.h 中的 NODE_ROLE 选择:
#define NODE_ROLE_GATEWAY 0
#define NODE_ROLE_LEAFNODE 1
#ifndef NODE_ROLE
#define NODE_ROLE NODE_ROLE_GATEWAY
#endif
- 网关 (NODEROLEGATEWAY):连接 Enterprise WiFi,通过 NTP 同步,广播 FTSP 信标
- 叶子节点 (NODEROLELEAFNODE):监听 FTSP 信标,计算时钟校正,提供世界时间
时间源¶
| 来源 | 宏 | 说明 |
|---|---|---|
| 开机时间 | FTSP_TIME_BOOT (0) | esp_timer_get_time() — µs 自 MCU 启动,始终可用 |
| 世界时间 | FTSP_TIME_WORLD (1) | Unix 纪元 µs,需网关先完成 NTP 同步,自动回退到开机时间 |
信标数据包格式¶
基础信标(16 字节):包含魔法字、序列号、网关参考时间。
扩展信标(26 字节):增加 NTP 世界时间(unix_us,完整 µs 精度)、标志位和 NTP 质量——在世界时间可用时使用。
网关在 esp_now_send() 之前立即捕获 ref_time_us,以最小化发送抖动(通常 < 100 µs)。
时钟漂移补偿¶
叶子节点维护 (localrx, reftime_us) 对的环形缓冲区。收集到 FTSP_MIN_PAIRS_FOR_SYNC(默认为 4)个有效对后,求解最小二乘线性回归:
斜率校正相对晶振频率误差(通常 < 40 ppm)。ftsp_precision 模块通过指数移动平均独立追踪漂移。
直接偏移模式(零回归)¶
ftsp_get_world_time_direct_us() 函数直接使用最近信标的 (rx_us, unix_us) 对:
这完全消除了漂移累积——误差在每个新信标到达时归零。信标间的最大误差:晶振漂移_ppm × 信标间隔 ≈ 40 µs(1 Hz 时)。
编译期配置¶
| 宏 | 默认值 | 说明 |
|---|---|---|
FTSP_BEACON_INTERVAL_MS | 1000 | 信标间隔 (ms)。越短→收敛越快,但 RF 负载越高 |
FTSP_TABLE_SIZE | 10 | 环形缓冲区中的 (本地, 参考) 时间戳对数 |
FTSP_MIN_PAIRS_FOR_SYNC | 4 | 声明"已同步"所需的最小对数 |
FTSP_USE_WORLD_TIME | 0 | 0 = 开机时间,1 = 世界时间(需 NTP) |
FTSP_FAKE_EPOCH_US | 1577836800000000 | NTP 不可用时的伪纪元(2020-01-01) |
FTSP_GATEWAY_TASK_STACK | 2048 | 网关任务栈大小(字节) |
FTSP_GATEWAY_TASK_PRIORITY | 6 | 网关任务优先级 |
API 参考¶
网关¶
esp_err_t ftsp_gateway_start(void); // 启动信标广播
esp_err_t ftsp_gateway_stop(void); // 停止信标广播
bool ftsp_gateway_is_running(void);
叶子节点¶
esp_err_t ftsp_leafnode_start(void); // 启动信标接收器
esp_err_t ftsp_leafnode_stop(void); // 停止信标接收器
bool ftsp_is_synced(void); // 检查 FTSP 是否已同步
esp_err_t ftsp_get_global_time(int64_t *global); // 获取 FTSP 校正后的全局时间
// 世界时间 API(需网关 NTP)
esp_err_t ftsp_get_world_time_us(uint64_t *world_us); // FTSP 回归
esp_err_t ftsp_get_world_time_direct_us(uint64_t *world_us); // 直接偏移(无漂移)
esp_err_t ftsp_get_world_time_sec(uint32_t *world_sec); // 自纪元起的秒数
esp_err_t ftsp_get_world_datetime_us(TinyDateTime_t *dt); // 日历时间 + µs
esp_err_t ftsp_get_gateway_ntp_info(uint64_t *unix_us, uint8_t *ntp_quality);
同步可视化¶
esp_err_t ftsp_sync_viz_init(void); // 启动可视化任务
ftsp_sync_viz_state_t ftsp_sync_viz_get_state(void); // 获取当前同步状态
void ftsp_sync_viz_get_marquee_color(uint64_t world_time_us,
uint8_t *r, uint8_t *g, uint8_t *b);
esp_err_t ftsp_sync_viz_gateway_start_sync(void); // 网关:启动同步序列
bool ftsp_sync_viz_gateway_is_syncing(void);
同步可视化状态¶
| 状态 | LED 颜色 | 说明 |
|---|---|---|
INIT | 关闭 | 模块已启动,尚未激活 |
WAITING | 白色 | 网关:等待 NTP;叶子:等待信标 |
SYNC_SEQ | — | 网关:广播 6 信标同步序列 |
COLLECTING | — | 叶子:收集同步信标 |
SYNCED | 绿色 | 时间同步成功 |
MARQUEE | 7 色循环 | 所有节点同时显示相同颜色 |
跑马灯颜色¶
颜色由 world_time_sec % 7 决定,确保所有节点同时显示相同颜色——这是同步的可视化证明。
同步序列协议¶
NTP 同步后,网关以精确时序广播 6 个特殊信标:
| 信标 | 时间 (s) | 用途 |
|---|---|---|
| 0 | t=0 | 参考 (unixus0, rxus0) |
| 1 | t=8 | 第一时序对 |
| 2 | t=9 | 第二时序对 |
| 3 | t=10 | 第三时序对 |
| 4 | t=11 | 第四时序对 |
| 5 | t=12 | 第五时序对 |
叶子收集全部 6 个,计算每对斜率并取平均,然后进入 SYNCED 状态。网关和叶子都针对相同的跑马灯启动时间(last_beacon_unix_sec + HOLD_SEC),确保所有节点上的 LED 颜色同步循环。
与 tiny_toolbox 的集成¶
FTSP 模块使用 tiny_time 的 NTP 同步钩子来接收 NTP 同步通知,而无需循环 CMake 依赖:
// 在 tiny_toolbox (tiny_time.c) 中:
// - SNTP 响应到达时立即触发 s_ntp_sync_hook((uint32_t)tv->tv_sec)
// 在 tiny_measurement (ftsp_gateway.c) 中:
// - 注册钩子:tiny_ntp_set_sync_hook(ftsp_ntp_record_sync);
这种设计保持了清晰的层级: - tiny_toolbox — 最底层,以函数指针形式提供 tiny_ntp_set_sync_hook() - tiny_measurement — 上层,实现 ftsp_ntp_record_sync() 并注册为钩子