Files
STM32F4-Base/test/host/wave_ring/wave_ring_test.c
T
wandering1 d64bbdc57f feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译)
- wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask
  唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃
  兜底——下一次真故障无波可录比丢一段更贵
- wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带
  锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度
- 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式
- 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样
  纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉
- test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条
  自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合
- 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定)

接线(任务层)
- adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图),
  段头按最低有效位精确记账,不经浮点回算
- adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实;
  台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行
- defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到
  关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms
  补一条告警并记历史峰值(喂狗余量可核)
- Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留
  超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的,
  半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进
- Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到
  上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数;
  段文件字节布局零改动,三份合成段哈希逐字节不变
- protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体
  零改动

人机(160×160 单色屏)
- 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器
  得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30
- 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与
  垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、
  单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据
- ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、
  手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口
- 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有
  60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形
- 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变
- 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏
  22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、
  仪器用已在跑的单轮工作耗时探针,不新加埋点

台账与文档
- 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936
  字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名;
  散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建
  重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置)
- 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据);
  §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径
  风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」
- 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项
- 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划
  索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动)
- 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件
  形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记
  「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场
- 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行
  227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退

为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与
体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账
同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道

编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串
生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
2026-10-29 19:23:15 +08:00

539 lines
26 KiB
C
Raw 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.
/*
* 模块名称:录波环(滚动环 + 触发冻结状态机 + 已封存区读口)主机侧单元测试
* 被测对象:真实源文件(#include 原样纳入,测生产代码而非副本):
* App/algo/wave_store.c + App/algo/wave_ring.c
* 两源均为纯逻辑(无 HAL / CMSIS / RTOS / FatFs / 日志)→ **无需任何桩目录**。
*
* 为什么要主机侧(而不是只等台架):本层的判据几乎全是**结构与并发**类——
* 段首基址与点序、SEALED 期间写指针必须真冻结、ACK 前不许重录、滞留超时自弃、
* 冷却只在 ACK 后计时、读口在未就绪时绝不返回旧数据。这些在台架上要么不可观测
* (1 ms 一拍的窗口,人工读不到)、要么无法安全注入(真造一次「defaultTask 挂住
* 两秒」就是把看门狗窗口拿去赌)。主机侧可以拿影子样本序列把它逐点钉死。
* 需要板子的部分(注入 1 V/50 Hz 后 FTP 拉回对表、实机 FAT 落盘)不在本套件,
* 见 docs/任务规划/故障录波.md 的「录波数据面最小闭环」退出判据。
*
* 覆盖点(用例名写「它自己在验证什么」,不发编号;属主登记处 = 本目录):
* 用例:形态与预算钉死:int16×320×8 = 5120、两半各 160、
* sizeof(wave_seg_meta_t) = 136、sizeof(wave_ch_desc_t) = 12
* 用例:init 复位语义:IDLE / 不可读 / 计数与轮转号归零
* 用例:参数守卫:push / request / latch / read / get_stat 的非法入参
* 用例:触发到封段的时序:封段恰在触发后第 160 拍,事件一次性
* 用例:段内容与点序:8 列 × 320 点逐点等于影子样本,
* 触发那一拍的样本在点 159、点 160 是其后第一拍(列序 = 语义序)
* 用例:SEALED 丢样本:push 返 DONE、drop 计点、写指针真冻结、内容不变
* 用例:封存交棒握手:未 latch 一律不可用;latch 只准一次、不覆盖;
* latch 内容进段头(eng / flags_seq / clip_mask / wall + 8 路系数)
* 用例:读窗夹取与跨环回绕;read_cb 的 32 位形态
* 用例:列号映射:0..7 恒等、TEMP 与越界返 WAVE_ST_E_RANGE
* 用例:ACK 语义:成功推进 seg_idx 与 seg_total 并起冷却;重复 ACK 拒绝;
* 未就绪时读口返 E_STATE 而不是旧数据
* 用例:环未填满就触发:前导点必须是 0(不是上一段的残留),真实样本从
* 点 129 起(本次上电只有 31 个点可给)
*
* 通过:全部 CHECK 通过(main 返回 0)。
* 创建日期:2026-10-29(故障录波 task-1)
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
/* ===================== 断言 ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
/* ===================== 被测代码(生产源原样纳入) ===================== */
#include "wave_store.c"
#include "wave_ring.c"
/* ===================== 影子样本模型 ===================== */
#define HIST_N 2048u /* 影子样本环(比录波环大,够跑完整个场景) */
#define TRIG_K 400u /* 第一段:消费请求那一拍的样本号 */
static int16_t s_hist[HIST_N][WAVE_RING_CH];
static uint32_t s_k; /* 下一个要压的样本号 */
static uint32_t s_evt_sealed; /* 累计看到多少次 EVT_SEALED */
static uint32_t s_push_bad; /* push 返回了 OK/DONE 之外的值 */
static int32_t s_push_last; /* 最近一次 push 的返回值 */
/* 每路一条可辨认的斜坡:不同样本、不同列都不重复,负值也覆盖(TPAFE 是双极性补码) */
static int16_t wave_sig(uint32_t a_k, uint8_t a_col) {
return (int16_t)(((int32_t)a_k * 7) - 1200 + ((int32_t)a_col * 91));
}
/* 压 n 拍(push 之后 tick,与 adcTask 的接线顺序一致:先写样本再算点数) */
static void wave_step(uint32_t a_n) {
uint32_t i;
for (i = 0u; i < a_n; i++) {
int16_t codes[WAVE_RING_CH];
uint8_t c;
int32_t rc;
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
codes[c] = wave_sig(s_k, c);
s_hist[s_k % HIST_N][c] = codes[c];
}
rc = wave_ring_push(codes, (uint8_t)WAVE_RING_CH);
s_push_last = rc;
if ((rc != WAVE_ST_OK) && (rc != WAVE_ST_DONE)) {
s_push_bad++;
}
rc = wave_ring_tick(s_k, s_k);
if (rc == (int32_t)WAVE_RING_EVT_SEALED) {
s_evt_sealed++;
}
s_k++;
}
}
/* 影子样本 -> 段内点:点 i 对应样本 (a_trig - 159 + i),负数表示当时还没采到 */
static int32_t wave_pt_sample(uint32_t a_trig, uint32_t a_pt) {
return ((int32_t)a_trig - (int32_t)(WAVE_RING_PRE - 1u)) + (int32_t)a_pt;
}
/* 整段逐点比对(a_trig = 消费请求那一拍的样本号) */
static int wave_cmp_all(uint32_t a_trig) {
int16_t buf[WAVE_RING_PTS];
uint8_t c;
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
int32_t n;
uint32_t i;
n = wave_ring_read_ch(c, 0u, (uint16_t)WAVE_RING_PTS, buf);
if (n != (int32_t)WAVE_RING_PTS) {
return 0;
}
for (i = 0u; i < (uint32_t)WAVE_RING_PTS; i++) {
int32_t ks = wave_pt_sample(a_trig, i);
int16_t want = (ks < 0) ? (int16_t)0 : wave_sig((uint32_t)ks, c);
if (buf[i] != want) {
return 0;
}
}
}
return 1;
}
/* 封好段并交棒(返回消费请求那一拍的样本号) */
static uint32_t s_trig_first;
static void wave_seal_once(uint8_t a_src, uint8_t a_elm) {
wave_ring_request(a_src, a_elm);
wave_step(1u); /* 本拍样本 = 点 159,同拍 tick 消费请求钉基址 */
s_trig_first = s_k - 1u;
wave_step((uint32_t)WAVE_RING_POST); /* 后触发 160 点,最后一拍封段 */
}
/* 造一份封存瞬间事实 */
static void wave_fill_cal(wave_seal_cal_t *p_cal, uint8_t a_seq_tag) {
uint8_t i;
p_cal->eng = (uint8_t)WAVE_ENG_PIN;
p_cal->flags_seq = (uint8_t)(0x50u ^ a_seq_tag);
p_cal->clip_mask = (uint16_t)0x00E6u;
p_cal->wall = 1764374400u + (uint32_t)a_seq_tag;
for (i = 0u; i < (uint8_t)WAVE_ST_CH_MAX; i++) {
p_cal->ch[i].sem = i;
p_cal->ch[i].conv = (uint8_t)((i == 0u) || (i == 3u) || (i == 4u)) ? 0u : 1u;
p_cal->ch[i].bits = (uint8_t)(((i == 0u) || (i == 3u) || (i == 4u)) ? 16u : 12u);
p_cal->ch[i].avail = (uint8_t)(0x01u | (((i == 1u) || (i == 5u)) ? 0x02u : 0x00u));
p_cal->ch[i].k_q16 = (int32_t)(((i == 0u) || (i == 3u) || (i == 4u)) ? 10000000L : 52800000L);
p_cal->ch[i].b_codes = (int32_t)(a_seq_tag + i);
}
}
static wave_seal_cal_t s_cal;
/* ===================== 主测 ===================== */
int main(void) {
printf("=== Wave Ring Tests:\n");
/* ---------- 用例:形态与预算 ---------- */
printf("-- 用例:形态与预算 --\n");
CHECK(sizeof(int16_t) == 2u, "int16 是两字节(不许 float)");
CHECK(WAVE_RING_BYTES == 5120u, "环 = 5120 字节");
CHECK(((uint32_t)WAVE_RING_PTS * (uint32_t)WAVE_RING_CH * 2u) == WAVE_RING_BYTES, "环长等式");
CHECK(WAVE_RING_PTS == (WAVE_RING_PRE + WAVE_RING_POST), "段长 = 预触发 + 后触发");
CHECK(WAVE_RING_PRE == 160u, "预触发 160 点 = 8 周波 @20 点/周波");
CHECK(sizeof(wave_ch_desc_t) == (size_t)WAVE_ST_CH_BYTES, "通道表一项正好 12 字节");
CHECK(sizeof(wave_seg_meta_t) == 136u, "段头快照 136 字节(本卡静态增量台账用)");
/* ---------- 用例:init 复位 ---------- */
printf("-- 用例:init 复位语义 --\n");
wave_ring_init();
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "init 后 IDLE");
CHECK(wave_ring_sealed() == 0u, "init 后无可读段");
{
wave_ring_stat_t st;
int16_t buf[8];
CHECK(wave_ring_read_ch(0u, 0u, 8u, buf) == WAVE_ST_E_STATE, "init 后读已封存区被拒");
CHECK(wave_ring_get_stat(&st) == WAVE_ST_OK, "get_stat OK");
CHECK((st.seg_idx == 0u) && (st.seg_total == 0u) && (st.ovf_cnt == 0u) &&
(st.drop_cnt == 0u) && (st.err_cnt == 0u), "init 后计数与轮转号归零");
}
/* ---------- 用例:参数守卫 ---------- */
printf("-- 用例:参数守卫 --\n");
CHECK(wave_ring_push(NULL, (uint8_t)WAVE_RING_CH) == WAVE_ST_E_PARAM, "push 空指针");
{
int16_t codes[WAVE_RING_CH];
CHECK(wave_ring_push(codes, 7u) == WAVE_ST_E_PARAM, "push 长度不等于 8 拒绝");
CHECK(wave_ring_push(codes, 9u) == WAVE_ST_E_PARAM, "push 长度大于 8 拒绝");
}
CHECK(wave_ring_request(0u, 0u) == WAVE_ST_E_PARAM, "request 触发源 0 拒绝");
CHECK(wave_ring_request(4u, 0u) == WAVE_ST_E_PARAM, "request 触发源 4 拒绝");
CHECK(wave_ring_latch_meta(NULL) == WAVE_ST_E_PARAM, "latch 空指针");
CHECK(wave_ring_sealed_meta(NULL) == WAVE_ST_E_PARAM, "sealed_meta 空指针");
CHECK(wave_ring_get_stat(NULL) == WAVE_ST_E_PARAM, "get_stat 空指针");
CHECK(wave_ring_write_done(0) == WAVE_ST_E_STATE, "没段可 ACK 时拒绝");
/* ---------- 用例:触发到封段时序 ---------- */
printf("-- 用例:触发到封段时序 --\n");
wave_ring_init();
s_k = 0u;
s_evt_sealed = 0u;
s_push_bad = 0u;
wave_step(TRIG_K);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "400 拍后仍 IDLE(没请求就不该动)");
wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 7u);
wave_step(1u);
s_trig_first = s_k - 1u;
CHECK(s_trig_first == TRIG_K, "请求在下一拍被消费(本层不自己冻结,交给 adcTask 的 tick)");
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_FREEZE, "消费请求即 FREEZE");
CHECK(s_evt_sealed == 0u, "还没封段");
CHECK(wave_ring_sealed() == 0u, "FREEZE 期间不可读");
wave_step((uint32_t)WAVE_RING_POST - 1u);
CHECK((wave_ring_state() == (uint8_t)WAVE_RING_ST_FREEZE) && (s_evt_sealed == 0u),
"后触发 159 点时仍未封段");
wave_step(1u);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "后触发计满 160 点才封段");
CHECK(s_evt_sealed == 1u, "EVT_SEALED 恰好一次");
wave_step(1u);
CHECK(s_evt_sealed == 1u, "事件不重复报(只封段那一拍报)");
CHECK(s_push_bad == 0u, "全程 push 无非法返回");
/* ---------- 用例:a 未 latch 前一律不可用(必须先于内容比对) ---------- */
printf("-- 用例:封存交棒握手 --\n");
CHECK(wave_ring_sealed() == 0u, "刚封段还没交棒:不可用");
{
wave_ring_stat_t st;
wave_seg_meta_t meta;
int16_t buf[8];
(void)wave_ring_get_stat(&st);
CHECK(st.meta_ready == 0u, "meta_ready = 0");
CHECK(wave_ring_read_ch(0u, 0u, 8u, buf) == WAVE_ST_E_STATE, "未 latch 读被拒");
CHECK(wave_ring_sealed_meta(&meta) == WAVE_ST_E_STATE, "未 latch 段头被拒");
/* 此刻 latch 是合法时机 */
wave_fill_cal(&s_cal, 0u);
s_cal.eng = 9u;
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_E_PARAM, "折算档位非法拒绝");
s_cal.eng = (uint8_t)WAVE_ENG_PIN;
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "latch 成功");
CHECK(wave_ring_sealed() == 1u, "latch 后可用(两个标志并成一个)");
/* 第二次交棒:必须整份拒绝,锁存值一个字节都不许被换掉 */
{
uint32_t wall0 = s_cal.wall;
s_cal.wall = 123u;
s_cal.clip_mask = 0x0000u;
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_E_STATE, "重复 latch 拒绝");
CHECK(wave_ring_sealed_meta(&meta) == WAVE_ST_OK, "latch 后段头可读");
CHECK((meta.wall == wall0) && (meta.clip_mask == 0x00E6u),
"重复 latch 没有覆盖已锁存值(半路换系数会让头与数据不同源)");
s_cal.wall = wall0;
}
CHECK((meta.fs_hz == (uint16_t)WAVE_ST_FS_DEF) && (meta.pts_per_cycle == (uint8_t)WAVE_ST_PPC_DEF),
"段头采样率与每周波点数按 §6.2 落值");
CHECK((meta.ch_count == (uint8_t)WAVE_RING_CH) && (meta.pts_per_ch == (uint16_t)WAVE_RING_PTS) &&
(meta.pre_pts == (uint16_t)WAVE_RING_PRE), "段头几何三要素与环一致");
CHECK((meta.trig_src == (uint8_t)WAVE_TRIG_MANUAL) && (meta.trig_elm == 7u),
"触发源与元件序号进段头");
CHECK((meta.t0_ms == s_trig_first) && (meta.frame_seq == s_trig_first),
"段首毫秒与帧序号取触发那一拍(不是封段那一拍)");
CHECK((meta.flags_seq == s_cal.flags_seq) && (meta.clip_mask == 0x00E6u),
"有效性位图与削顶位图进段头");
{
uint8_t i;
int ok = 1;
for (i = 0u; i < (uint8_t)WAVE_RING_CH; i++) {
if ((meta.ch[i].sem != i) || (meta.ch[i].conv != s_cal.ch[i].conv) ||
(meta.ch[i].bits != s_cal.ch[i].bits) || (meta.ch[i].avail != s_cal.ch[i].avail) ||
(meta.ch[i].k_q16 != s_cal.ch[i].k_q16) || (meta.ch[i].b_codes != s_cal.ch[i].b_codes)) {
ok = 0;
}
}
CHECK(ok != 0, "通道表 8 项逐项进段头(sem = 下标)");
}
}
/* ---------- 用例:段内容与点序 ---------- */
printf("-- 用例:段内容与点序 --\n");
CHECK(wave_cmp_all(s_trig_first) != 0, "8 列 × 320 点逐点等于影子样本(列序 = 语义序)");
{
int16_t buf[WAVE_RING_PTS];
(void)wave_ring_read_ch(0u, 0u, (uint16_t)WAVE_RING_PTS, buf);
CHECK(buf[WAVE_RING_PRE - 1u] == wave_sig(s_trig_first, 0u),
"触发那一拍的样本落在点 159(预触发窗最后一点)");
CHECK(buf[WAVE_RING_PRE] == wave_sig(s_trig_first + 1u, 0u),
"点 160 是触发之后第一拍");
CHECK(buf[0] == wave_sig(s_trig_first - (WAVE_RING_PRE - 1u), 0u),
"点 0 是最老的预触发点(段首基址钉对了)");
CHECK(buf[1] > buf[0], "斜坡单调 -> 点序没有倒过来");
}
/* ---------- 用例:SEALED 丢样本 + 写指针真冻结 ---------- */
printf("-- 用例:SEALED 丢样本与写指针冻结 --\n");
{
wave_ring_stat_t st0;
wave_ring_stat_t st1;
(void)wave_ring_get_stat(&st0);
wave_step(10u);
(void)wave_ring_get_stat(&st1);
CHECK(s_push_last == WAVE_ST_DONE, "SEALED 期间 push 返 DONE(样本被丢)");
CHECK(st1.drop_cnt == (st0.drop_cnt + (10u * (uint32_t)WAVE_RING_CH)),
"drop 计数单位 = 点(一次丢一拍加 8)");
CHECK(st1.seal_hold == (uint16_t)(st0.seal_hold + 10u), "SEALED 滞留按拍累计");
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "滞留未超时仍是 SEALED");
CHECK(wave_cmp_all(s_trig_first) != 0, "已封存区一个点都没被动过(写指针真冻住)");
}
/* ---------- 用例:读窗夹取 / 跨回绕 / read_cb ---------- */
printf("-- 用例:读窗夹取与跨回绕 --\n");
{
int16_t buf[WAVE_RING_PTS];
int32_t n;
uint32_t i;
int ok;
n = wave_ring_read_ch(3u, 300u, 40u, buf);
CHECK(n == 20, "超出段尾自动夹到剩余点数");
ok = 1;
for (i = 0u; i < 20u; i++) {
int32_t ks = wave_pt_sample(s_trig_first, 300u + i);
if (buf[i] != wave_sig((uint32_t)ks, 3u)) {
ok = 0;
}
}
CHECK(ok != 0, "夹出来的 20 点仍与影子样本一致(回绕段尾正确)");
/* base = 241(400 拍场景)-> 段内跨环尾的点必须靠取模接上 */
n = wave_ring_read_ch(5u, 70u, 20u, buf);
ok = 1;
for (i = 0u; i < (uint32_t)n; i++) {
int32_t ks = wave_pt_sample(s_trig_first, 70u + i);
if (buf[i] != wave_sig((uint32_t)ks, 5u)) {
ok = 0;
}
}
CHECK((n == 20) && (ok != 0), "跨越环尾的窗取模展开正确(段首基址 241)");
n = wave_ring_read_cb(NULL, 2u, 0u, (uint32_t)WAVE_RING_PTS, buf);
CHECK(n == (int32_t)WAVE_RING_PTS, "read_cb 32 位形态整段可读");
CHECK(wave_ring_read_cb(NULL, 2u, 0u, 9999u, buf) == WAVE_ST_E_PARAM, "read_cb 点数超过环长拒绝");
CHECK(wave_ring_read_cb(NULL, 2u, 9999u, 1u, buf) == WAVE_ST_E_RANGE, "read_cb 起点越界拒绝");
CHECK(wave_ring_read_ch(8u, 0u, 1u, buf) == WAVE_ST_E_RANGE, "列号越界");
CHECK(wave_ring_read_ch(0u, (uint16_t)WAVE_RING_PTS, 1u, buf) == WAVE_ST_E_RANGE, "起点越界");
CHECK(wave_ring_read_ch(0u, 0u, 0u, buf) == WAVE_ST_E_PARAM, "点数为 0");
CHECK(wave_ring_read_ch(0u, 0u, 1u, NULL) == WAVE_ST_E_PARAM, "读口空指针");
}
/* ---------- 用例:列号映射 ---------- */
printf("-- 用例:列号映射 --\n");
{
int ok = 1;
uint8_t i;
for (i = 0u; i < (uint8_t)WAVE_RING_CH; i++) {
if (wave_ring_col(i) != (int32_t)i) {
ok = 0;
}
}
CHECK(ok != 0, "0..7 语义号 -> 同列号(本波恒等)");
CHECK(wave_ring_col(8u) == WAVE_ST_E_RANGE, "TEMP 不录 -> 越界");
CHECK(wave_ring_col(200u) == WAVE_ST_E_RANGE, "非法语义号 -> 越界");
}
/* ---------- 用例:ACK 成功 + 冷却 + 二次触发 ---------- */
printf("-- 用例:ACK 与冷却 --\n");
{
wave_ring_stat_t st;
int16_t buf[8];
CHECK(wave_ring_write_done(0) == WAVE_ST_OK, "ACK 成功");
(void)wave_ring_get_stat(&st);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_COOL, "ACK 后转冷却");
CHECK(st.cool_left == (uint16_t)WAVE_RING_COOL_MS, "冷却起表 500 拍");
CHECK((st.seg_total == 1u) && (st.seg_idx == 1u), "成功才推进轮转号与段计数");
CHECK(wave_ring_sealed() == 0u, "解冻后立刻不可读");
CHECK(wave_ring_read_ch(0u, 0u, 8u, buf) == WAVE_ST_E_STATE, "解冻后读被拒(不返旧数据)");
CHECK(wave_ring_write_done(0) == WAVE_ST_E_STATE, "重复 ACK 拒绝");
/* 冷却期内再触发:只加 ovf,绝不重录 */
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, 3u) == WAVE_ST_DONE, "冷却期内请求被忽略");
(void)wave_ring_get_stat(&st);
CHECK(st.ovf_cnt == 1u, "ovf 计一次");
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_COOL, "被忽略的请求没有改状态");
wave_step(100u);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_COOL, "冷却未走完仍在 COOL");
wave_step(400u);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "冷却走完后回 IDLE");
}
/* ---------- 用例:ACK 失败不推进轮转号 ---------- */
printf("-- 用例:ACK 失败不推进号 --\n");
wave_seal_once((uint8_t)WAVE_TRIG_PROTECT, 2u);
wave_fill_cal(&s_cal, 1u);
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "第二段交棒 OK");
CHECK(wave_cmp_all(s_trig_first) != 0, "第二段内容 = 第二次故障的影子样本");
{
wave_ring_stat_t st;
CHECK(wave_ring_write_done(-5) == WAVE_ST_OK, "失败 ACK 仍解冻(错误只计数)");
(void)wave_ring_get_stat(&st);
CHECK(st.err_cnt == 1u, "err 计一次");
CHECK((st.seg_idx == 1u) && (st.seg_total == 1u), "失败不推进轮转号与段计数(下段同号)");
}
/* ---------- 用例:滞留超时自弃 ---------- */
printf("-- 用例:SEALED 滞留超时自弃 --\n");
wave_step((uint32_t)WAVE_RING_COOL_MS);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "冷却走完回 IDLE");
wave_seal_once((uint8_t)WAVE_TRIG_BENCH, 0u);
wave_fill_cal(&s_cal, 2u);
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "第三段交棒 OK");
wave_step((uint32_t)WAVE_SEAL_TIMEOUT_MS - 1u);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "差一拍还没到超时:仍 SEALED");
{
wave_ring_stat_t st0;
wave_ring_stat_t st1;
(void)wave_ring_get_stat(&st0);
wave_step(1u);
(void)wave_ring_get_stat(&st1);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_COOL, "超时自弃并起冷却(绝不让环永久焊死)");
CHECK(st1.err_cnt == (st0.err_cnt + 1u), "自弃计 err");
CHECK(st1.seal_hold == 0u, "滞留计数清零");
CHECK((st1.seg_idx == st0.seg_idx) && (st1.seg_total == st0.seg_total),
"自弃不算成功:号与段计数都不动");
CHECK(wave_ring_sealed() == 0u, "自弃后不可读");
}
/* 自弃之后必须还能录下一段 */
wave_step((uint32_t)WAVE_RING_COOL_MS);
wave_seal_once((uint8_t)WAVE_TRIG_MANUAL, 0u);
wave_fill_cal(&s_cal, 3u);
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "自弃之后仍能录第四段");
CHECK(wave_cmp_all(s_trig_first) != 0, "第四段内容是新的故障(没被上一段污染)");
/* ---------- 用例:环未填满就触发 ---------- */
printf("-- 用例:环未填满就触发 --\n");
wave_ring_write_done(0);
wave_step((uint32_t)WAVE_RING_COOL_MS);
wave_ring_init();
s_k = 0u;
s_evt_sealed = 0u;
wave_step(30u);
wave_seal_once((uint8_t)WAVE_TRIG_MANUAL, 0u);
wave_fill_cal(&s_cal, 4u);
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "只采了 31 点也能封段交棒");
{
int16_t buf[WAVE_RING_PTS];
uint32_t i;
int zeros = 1;
int reals = 1;
(void)wave_ring_read_ch(0u, 0u, (uint16_t)WAVE_RING_PTS, buf);
for (i = 0u; i < 129u; i++) {
if (buf[i] != 0) {
zeros = 0;
}
}
for (i = 129u; i < (uint32_t)WAVE_RING_PTS; i++) {
if (buf[i] != wave_sig(i - 129u, 0u)) {
reals = 0;
}
}
CHECK(zeros != 0, "前导 129 点是 0(不是上一段残留,init 清零生效)");
CHECK(reals != 0, "真实样本从点 129 起接上(点 159 = 第 30 拍)");
}
printf("-- 用例:挂号载荷单字节往返(lead 评审 2026-10-29 交错缺陷) --\n");
{
static const uint8_t k_src[3] = { 1u, 2u, 3u };
static const uint8_t k_elm[3] = { 0u, 1u, 31u };
uint32_t a;
uint32_t b;
int accept_all = 1;
int second_rejected = 1;
int ovf_once = 1;
int decoded_ok = 1;
int head_identity = 1;
for (a = 0u; a < 3u; a++) {
for (b = 0u; b < 3u; b++) {
wave_ring_stat_t st0;
wave_ring_stat_t st1;
wave_seg_meta_t meta;
uint8_t src = k_src[a];
uint8_t elm = k_elm[b];
wave_ring_init();
s_k = 0u;
s_evt_sealed = 0u;
wave_step(20u);
if (wave_ring_request(src, elm) != WAVE_ST_OK) {
accept_all = 0;
}
/* 交错形状:adcTask 还没 tick(挂号未被消费)时又来了第二个请求。
* 旧实现在这里已经写完了挂号位、载荷还没写完,本条用例就是那个形状 */
if (wave_ring_request(k_src[(a + 1u) % 3u], 9u) != WAVE_ST_DONE) {
second_rejected = 0;
}
(void)wave_ring_get_stat(&st0);
if (st0.ovf_cnt != 1u) {
ovf_once = 0;
}
wave_step(1u);
(void)wave_ring_get_stat(&st1);
if ((st1.trig_src != src) || (st1.trig_elm != elm)) {
decoded_ok = 0;
}
wave_step((uint32_t)WAVE_RING_POST);
wave_fill_cal(&s_cal, 0u);
if ((wave_ring_latch_meta(&s_cal) != WAVE_ST_OK)
|| (wave_ring_sealed_meta(&meta) != WAVE_ST_OK)
|| (meta.trig_src != src) || (meta.trig_elm != elm)) {
head_identity = 0;
}
(void)wave_ring_write_done(0);
wave_step((uint32_t)WAVE_RING_COOL_MS);
}
}
CHECK(accept_all == 1, "九个触发源与元件号组合全部受理");
CHECK(second_rejected == 1, "挂号未被消费的窗口里第二个请求一律被拒(交错形状)");
CHECK(ovf_once == 1, "被拒那次只加一次 ovf,不重录不排队");
CHECK(decoded_ok == 1, "下一拍解码出的触发源与元件号就是本次投递值");
CHECK(head_identity == 1, "封段交棒后段头身份仍是本次投递值(文件格式没多一个字节)");
}
printf("-- 用例:元件号越界当场拒且不记 ovf 账 --\n");
{
wave_ring_stat_t st;
wave_ring_init();
s_k = 0u;
s_evt_sealed = 0u;
wave_step(10u);
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, 32u) == WAVE_ST_E_PARAM, "元件号 32 拒绝(载荷只有 5 位,越界不许静默截断)");
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, 255u) == WAVE_ST_E_PARAM, "元件号 255 拒绝");
CHECK(wave_ring_request(0u, 5u) == WAVE_ST_E_PARAM, "触发源 0 带合法元件号也拒");
CHECK(wave_ring_request(4u, 5u) == WAVE_ST_E_PARAM, "触发源 4 带合法元件号也拒");
(void)wave_ring_get_stat(&st);
CHECK(st.ovf_cnt == 0u, "四条参数错一条都没记进 ovf(参数错不是第二次触发)");
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "参数错没有留下任何挂号");
wave_step(1u);
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "空跑一拍仍是 IDLE(没有凭空开录)");
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, 31u) == WAVE_ST_OK, "越界值拒完之后 31 号元件照样能挂号");
}
printf("\n=== Wave Ring Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}