Files
STM32F4-Base/test/board/net_latency_test.h
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

229 lines
10 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* 模块名称: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 <stdint.h>
/*
* === 链路时延测试阶段一览(见 链路延迟测试说明.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 */