/****************************************************************************** * 模块名称:CH395F 链路延迟测试 * 模块功能:链路分段时延测量的微秒时基(DWT CYCCNT)、采样统计 * (min/med/p95/max + 尖峰计数)、机器可读 [LAT] 报告输出与判定。 * Phase 12(硬件层,M1):SPI 字节命令 / DMA 时延基线(SPI_CMD/SPI_DMA,无需 PC); * Phase 13/14(NET 层,M2/M3 规划):芯片发送握手 / 队列往返 / * 生产路径时延。分段定义、判定基线与执行流程见 * docs/测试说明/专项验证/链路延迟测试说明.md。 * 适用平台:STM32F4 系列(CH395F) * 作者:王建锋 * 创建日期:2026-09-22 * 修改记录: * 2026-09-22 王建锋 初版:时基 + 统计/报告 + Phase 12(SPI_CMD/SPI_DMA SPI 时延基线) ******************************************************************************/ #ifndef NET_LATENCY_TEST_H #define NET_LATENCY_TEST_H #ifdef __cplusplus extern "C" { #endif #include /* * === 链路时延测试阶段一览(见 链路延迟测试说明.md §4.2) === * Phase 12 [硬件层] SPI 字节命令 / DMA 时延基线(SPI_CMD/SPI_DMA,无需 PC) —— M1(已完成) * Phase 13 [NET 层] 芯片 + 队列时延(CHUNK_WAIT~FRAME_RT/LOGTAX,需 PC peer) —— M2(已实现) * Phase 14 [NET 层] 生产路径时延(PROD_CYCLE~UI_LOOP + 模拟器 A/B,需 PC peer)—— M3(规划) * * 时基说明:DWT CYCCNT 为 32 位,168MHz 下回绕周期 25.17ms。 * M1 各段(<5ms)与 M2 调用方侧测量(单次 <25ms)用单次读数差即可; * 跨 25ms 的段(TO_PC/PROD_CYCLE 整帧与重传尖峰)由 PC 端 peer 计时。 * * Phase 13 协议(服务器驱动:MCU 发操作字节,peer 执行;详见说明文档 §4.5): * HELLO = 5A A5 58 01(peer → MCU,4 字节) * 操作字节(MCU → peer):0x01=QUEUE_RT recv flood / 0x02=QUEUE_RT send flood / * 0x03=ECHO_RT 单请求 / 0x04=FRAME_RT LCDMEM 全帧 / 0x05=CHUNK_WAIT send flood / 0xFF=完成 * Phase 13 需**独立运行**(同 Phase 4 约定):与其他 Phase 共用 8080 端口 * 的 socket,混跑会污染帧测量。 */ /* verdict 三态(lat_stat_report 输出,调用方据此汇总) */ #define LAT_VERDICT_OK 0 #define LAT_VERDICT_WARN 1 #define LAT_VERDICT_FAIL 2 /* * 采样保存上限:静态数组容量(自研代码禁用 C 库堆,见 AGENTS.md)。 * n 可超过该值(min/max/spikes 仍对全量累计;med/p95 按已保存样本计算)。 */ #define LAT_MAX_SAMPLES 128 /* * 采样统计容器。 * n - 总样本数(可 > LAT_MAX_SAMPLES) * mn/mx - 全量 min/max(us) * med - 中位数(us,lat_stat_finish 后有效) * p95 - 95 分位(us,lat_stat_finish 后有效) * spikes - 达到尖峰阈值的样本数(spike_th_us=0 表示本段不统计) */ typedef struct { uint16_t n; uint32_t mn; uint32_t med; uint32_t p95; uint32_t mx; uint16_t spikes; } lat_stats_t; /* * 函数功能:使能 DWT CYCCNT 周期计数器时基(幂等) * 入口参数:无 * 返回值:无 * 限定条件:任何测量前调用一次即可;仅测试构建路径会执行(产品构建不调用) * 函数说明:CoreDebug->DEMCR(TRCENA) + DWT->CYCCNT 清零 + CYCCNTENA 置位 */ void lat_time_init(void); /* * 函数功能:重置统计容器(mn 置 0xFFFFFFFF,空集可辨识) * 入口参数:p - 统计容器 */ void lat_stat_reset(lat_stats_t *p); /* * 函数功能:追加一个时延样本(微秒) * 入口参数:p - 统计容器 * us - 本样本测量时长(微秒) * spike_th_us - 尖峰阈值(us);0 表示本段不统计尖峰 */ void lat_stat_add(lat_stats_t *p, uint32_t us, uint32_t spike_th_us); /* * 函数功能:采样结束后排序计算 med/p95(内部静态工作区,零堆) * 入口参数:p - 统计容器(lat_stat_add 全部追加完成后调用) */ void lat_stat_finish(lat_stats_t *p); /* * 函数功能:输出 [LAT] 报告行并给出本段 verdict * 入口参数:p_seg - 段名(纯 ASCII 标识,如 "spi_dma_1024") * p - 统计容器(须先 lat_stat_finish) * warn_med_us - WARN 阈值:med 超过 → WARN(重锁阈值提示,计 PASS) * fail_med_us - FAIL 阈值:med 超过 → FAIL * max_spikes - 尖峰数上限:spikes 超过 → FAIL * p_verdict - 输出:本段 verdict(LAT_VERDICT_*) * 返回值:无 * 函数说明:报告行格式见 链路延迟测试说明.md §4.4;字符串字面量纯 ASCII */ void lat_stat_report(const char *p_seg, const lat_stats_t *p, uint32_t warn_med_us, uint32_t fail_med_us, uint16_t max_spikes, int *p_verdict); /* * 函数功能:Phase 12 入口(硬件层:SPI 字节命令 / DMA 时延基线,SPI_CMD/SPI_DMA,无需 PC) * 入口参数:无 * 返回值:无 * 限定条件:由 ch395f_test_run() 硬件层分支调用(net_stack_bring_up 完成后 * testTask 独占 CH395F,无 SPI 并发);仅台架执行 * 函数说明:内部先做 check_exist 存活自检,失败即判 FAIL 并跳过; * 套件未选中本 Phase 时函数体为空(产品构建可编译、不执行) */ void net_latency_phase12_run(void); /* * 函数功能:Phase 13 入口(NET 层:芯片 + 队列时延 CHUNK_WAIT~FRAME_RT/LOGTAX,需 PC peer) * 入口参数:sockfd - 测试监听 socket(连接后的数据通道 fd,8080 端口) * 返回值:无 * 限定条件:由 net_layer_test_main() 监听循环每 ~10ms 调用一次(事件驱动 * 状态机);需 test/host/latency_peer.py --mode net 配合;须独立运行 * 函数说明:服务器驱动协议(见文件头协议注释);会话完成后 s_done 置位, * 后续调用为空操作;套件未选中本 Phase 时函数体为空 */ void net_latency_phase13_run(int32_t sockfd); /* * 函数功能:Phase 13 会话是否活动(HELLO 等待/进行中/abort 重连等待) * 返回值:1 - 活动(监听循环应跳过 net_accept,由本模块轮询接管) * 0 - 非活动(正常完成/终态,恢复常规 accept 等待) * 限定条件:Phase 13 未启用时恒返回 0 */ int net_latency_phase13_busy(void); /* * 函数功能:Phase 14 入口(NET 层:生产路径时延 PROD_CYCLE~UI_LOOP,真实 uiTask + :7003) * 入口参数:无(本 Phase 不占用测试 socket,测量生产 socket 的服务端行为) * 返回值:无 * 限定条件:由 net_layer_test_main() 循环每 ~10ms 调用一次;需 * test/host/latency_peer.py --mode remo --port 7003 配合;须单独使能 * (与 Phase 13 同启时两者都会跑,M3 台架建议只开 Phase 14) * 函数说明:状态机顺序 = ui_loop(本地节拍窗口)→ prod_cycle_mcu(等 peer * 发满 N 帧 LCDMEM)→ relisten(等 peer 断开重连)→ DONE; * 各窗口通过 TEST_LATENCY_PROBE 采集(uiTask/netTask 侧打点); * 未定义 TEST_LATENCY_PROBE 时只打印跳过说明;套件未选中时函数体为空 */ void net_latency_phase14_run(void); /* * 函数功能:Phase 14 是否活动(活动期监听循环应让位,保证 10ms 轮询节拍) * 返回值:1 - 活动;0 - 非活动/未启用 */ int net_latency_phase14_busy(void); /* * === TEST_LATENCY_PROBE 探针接口(M2/M3)=== * * 用途:netTask 上下文(net_socket.c 发送分块循环)向 testTask 报告的 * 段内打点。单写者(netTask)/单读者(testTask),32 位变量原子 * 读写,无锁。仅在 TEST_LATENCY_PROBE 定义(测试构建)时编译。 */ /* 当前是否处于采样窗口(netTask 侧高频查询,须极廉价) */ uint8_t lat_probe_armed(void); /* testTask 侧开一个采样窗口:seg 段名、max_n 样本上限、spike_th_us 尖峰阈值 */ void lat_probe_arm(const char *seg, uint32_t max_n, uint32_t spike_th_us); /* netTask 侧追加一个样本(us);未武装或已满 → 无操作 */ void lat_probe_add(uint32_t us); /* * 函数功能:**按段名**追加样本(多打点站点共存时唯一安全的写法) * 入口参数:seg - 本打点站点所属段名(须与 lat_probe_arm 的 seg 完全一致) * us - 样本值(us) * 返回值:无 * 函数说明:探针是**全局单窗口**:窗口武装成 A 段时,B 段站点的 lat_probe_add * 会把 B 的样本灌进 A 的窗口(2026-09-22 run17 实证:Phase 14 三段 * 全被 uiTask 循环样本填满,prod/relisten 都读出 ~11ms 的 ui_loop 值)。 * 只有段名匹配才落样本 → 各站点互不污染。多站点构建**一律**用本接口。 */ void lat_probe_add_seg(const char *seg, uint32_t us); /* 已采集样本数(32 位原子读) */ uint32_t lat_probe_count(void); /* 关闭采样窗口(此后的 lat_probe_add 无操作;在飞样本最多超 1 个) */ void lat_probe_disarm(void); /* testTask 侧取窗口统计(disarm 后调用;内部 lat_stat_finish 已含 med/p95) */ const lat_stats_t *lat_probe_stats(void); /* * 函数功能:DWT CYCCNT 读数 / 差值换算(供 net_socket.c 探针使用) * 入口参数:c_start - 起始读数(lat_cycles_now 返回值) * 返回值:当前读数(us 计数域)/ 自 c_start 起的微秒数(<25.17ms 有效) */ uint32_t lat_cycles_now(void); uint32_t lat_cycles_diff_us(uint32_t c_start); /* ---- M3 探针计时标记(可跨 >25ms 区间的安全计时) ---- * DWT CYCCNT 为 32 位 @168MHz → 单次差值超过 **25.17ms 即回绕**,会把 30ms 读成 * 4.8ms 这类"静默错误小值"(M1 各段均 <25ms 时无此问题,M3 的压力档/重监听会超)。 * lat_mark/lat_mark_us 同时记 DWT 与 HAL tick,跨度 ≥20ms 时自动改用 tick 兜底 * (分辨率降为 1ms,量级正确);<20ms 用 DWT(亚微秒精度)。 */ typedef struct { uint32_t cyc; /* DWT->CYCCNT 起始值 */ uint32_t tick; /* HAL_GetTick() 起始值 */ } lat_tmark_t; void lat_mark(lat_tmark_t *p); uint32_t lat_mark_us(const lat_tmark_t *p); #ifdef __cplusplus } #endif #endif /* NET_LATENCY_TEST_H */