d64bbdc57f
数据面(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 条仍全过
678 lines
31 KiB
C
678 lines
31 KiB
C
/*
|
||
* 模块名称:录波触发与封存链路(触发到封段、ACK 解冻、全链路落盘字节)主机侧单元测试
|
||
* 被测对象:真实源文件(#include 原样纳入,测生产代码而非副本):
|
||
* App/algo/wave_ring.c + App/algo/wave_store.c
|
||
* 两份都是纯逻辑(无 HAL / CMSIS / RTOS / FatFs / 日志)→ **无需任何桩目录**。
|
||
*
|
||
* 与前两套单测的分工:
|
||
* test/host/wave_ring/ 管环本身(几何、读写窗口、点序、丢样本计数、状态机骨架)
|
||
* test/host/wave_store/ 管段文件字节(布局等式、CRC、编解码往返、拒绝清单、段名轮转)
|
||
* 本目录(wave_trig) 管**两者接起来之后的语义**:三条触发来路、ACK/超时/冷却这三条
|
||
* 并发边界(lead 裁定 2026-10-29 点名必须主机可测、不许留给台架)、
|
||
* 以及「adcTask 封存 → adcTask latch → defaultTask 落盘」这条
|
||
* 握手链产出的字节确实等于已封存区的内容(§5.1 所有权、
|
||
* §5.2 第一条陷阱的机器证明)
|
||
*
|
||
* 为什么要主机侧:并发边界的错法全都是**静默**的。冷却期当冻结窗用 → 落盘道还在读、
|
||
* adcTask 已经重新推进写指针 → 半新半旧的段落进文件,没有任何错误码;没 latch 就放过
|
||
* ACK → 轮转号白吃一格、串口 seg=NN 对不上账。这些在板子上要等下一次真故障才暴露,
|
||
* 而那正是「无波可录」的时刻。用 1 kHz 逐拍模拟在主机上跑满整段(360 拍一段)成本几乎为零。
|
||
*
|
||
* 桩的边界:本套不桩任何东西——wave_ring.c 与 wave_store.c 零平台依赖,
|
||
* 封存瞬间事实(adc_cal 的 avail / k_q16 / b_codes)由本测试按 §6.3 口径
|
||
* 手工填进 wave_seal_cal_t,模拟接线道那一拍该交棒的内容。
|
||
* 真 adc_cal、真 FatFs、真 FTP、注入对表属台架(docs/任务规划/故障录波.md 卡 1)。
|
||
*
|
||
* 用法:python test/host/wave_trig/run_unit_test.py
|
||
* 退出码:0 = 全部通过;1 = 有用例失败;2 = 环境错误(找不到编译器)
|
||
* 设计依据:docs/子系统设计/故障录波设计说明.md §4.2(段长与冻结)、§5.1(所有权)、
|
||
* §5.2(三条陷阱)、§6.2(字节布局)、§6.3(字段口径裁定)、§7(预算)
|
||
* 创建日期:2026-10-29
|
||
*/
|
||
|
||
/* 主机测试专用:互验产物要落一个临时文件(MSVC 对 fopen 报 C4996;测试代码不受
|
||
* 生产侧禁用清单约束,见 docs/代码规范/嵌入式C语言代码规范_V1.0.md 适用范围) */
|
||
#define _CRT_SECURE_NO_WARNINGS 1
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <math.h>
|
||
|
||
#include "wave_ring.h"
|
||
|
||
/* 纳入生产源:测的是要烧进板子的那份代码,不是副本 */
|
||
#include "wave_ring.c"
|
||
#include "wave_store.c"
|
||
|
||
static int s_total = 0;
|
||
static int s_passed = 0;
|
||
static int 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)
|
||
|
||
#define WAVE_PI (3.14159265358979)
|
||
#define DEG(a_d) (((double)(a_d) * WAVE_PI) / 180.0)
|
||
#define FS_HZ_TEST (1000u)
|
||
#define CYC_HZ_TEST (50u)
|
||
#define SEG_IMG_CAP (WAVE_ST_SEG_SZ_CUR + 256u)
|
||
|
||
/* ===== 每拍信号:TPAFE 三路 50 Hz 正弦 + 片内五路方波/斜坡 ===== */
|
||
/* 幅值与相位与 wave-pc 那份合成金标同口径(9261 / 4630 / 2000 码,+90 / +30 / -60 度),
|
||
* 好让两份实现产出的段在她那边量出同一个数:量不齐就是有一边错了 */
|
||
|
||
static uint32_t s_n; /* 当前节拍 = 毫秒 = 帧序号(1 kHz 下同值) */
|
||
static int16_t s_sig[WAVE_RING_CH]; /* 本拍 8 路原码 */
|
||
static uint8_t s_sig_on = 0u; /* 1 = 本拍有样本(模拟采样故障时不压入) */
|
||
static int32_t s_last_push;
|
||
static uint32_t s_trig_k; /* 最近一次触发那一拍的节拍号 */
|
||
|
||
static int16_t sig_round(double a_v) {
|
||
return (int16_t)((a_v >= 0.0) ? (a_v + 0.5) : (a_v - 0.5));
|
||
}
|
||
|
||
static int16_t sig_at(uint32_t a_k, uint8_t a_ch) {
|
||
double w = (2.0 * WAVE_PI * (double)CYC_HZ_TEST * (double)a_k) / (double)FS_HZ_TEST;
|
||
|
||
switch (a_ch) {
|
||
case 0u:
|
||
return sig_round(9261.0 * sin(w + DEG(90)));
|
||
case 3u:
|
||
return sig_round(4630.0 * sin(w + DEG(30)));
|
||
case 4u:
|
||
return sig_round(2000.0 * sin(w + DEG(-60)));
|
||
case 1u:
|
||
return (int16_t)(((a_k / 40u) % 2u) != 0u ? 2048 : 0);
|
||
case 2u:
|
||
return (int16_t)(((a_k / 40u) % 2u) != 0u ? 0 : 2048);
|
||
case 5u:
|
||
return (int16_t)((a_k * 13u) % 4096u);
|
||
case 6u:
|
||
return (int16_t)((a_k * 29u) % 4096u);
|
||
case 7u:
|
||
return (int16_t)(((a_k / 40u) % 2u) != 0u ? 2048 : 0);
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
static void sig_fill(void) {
|
||
uint8_t c;
|
||
|
||
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
|
||
s_sig[c] = sig_at(s_n, c);
|
||
}
|
||
}
|
||
|
||
/* 一拍:先压样再推进(接线道在 adcTask 里的真实顺序:adc_input_poll → push → tick) */
|
||
static int32_t tick_one(void) {
|
||
int32_t evt;
|
||
|
||
sig_fill();
|
||
if (s_sig_on != 0u) {
|
||
s_last_push = wave_ring_push(s_sig, (uint8_t)WAVE_RING_CH);
|
||
} else {
|
||
s_last_push = WAVE_ST_OK;
|
||
}
|
||
evt = wave_ring_tick(s_n, s_n);
|
||
s_n++;
|
||
return evt;
|
||
}
|
||
|
||
static void run_n(uint32_t a_cnt) {
|
||
uint32_t i;
|
||
|
||
for (i = 0u; i < a_cnt; i++) {
|
||
(void)tick_one();
|
||
}
|
||
}
|
||
|
||
/* 跑到封段,返回用掉的拍数(0 = 没封上段);封段那一拍的样本仍然入库 */
|
||
static uint32_t run_until_sealed(uint32_t a_max) {
|
||
uint32_t i;
|
||
|
||
for (i = 1u; i <= a_max; i++) {
|
||
if (tick_one() == (int32_t)WAVE_RING_EVT_SEALED) {
|
||
return i;
|
||
}
|
||
}
|
||
return 0u;
|
||
}
|
||
|
||
static void fresh(void) {
|
||
wave_ring_init();
|
||
s_n = 0u;
|
||
s_trig_k = 0u;
|
||
s_sig_on = 1u;
|
||
}
|
||
|
||
/* 触发 + 封段一条龙:预触发区先滚 a_pre 拍,返回 1 = 恰好第 160 拍封段 */
|
||
static int trigger_and_seal(uint8_t a_src, uint8_t a_elm, uint32_t a_pre) {
|
||
uint32_t used;
|
||
|
||
run_n(a_pre);
|
||
s_trig_k = s_n;
|
||
if (wave_ring_request(a_src, a_elm) != WAVE_ST_OK) {
|
||
return 0;
|
||
}
|
||
(void)tick_one(); /* 触发那一拍:样本进段内点 159(预触发窗最后一点) */
|
||
used = run_until_sealed(WAVE_RING_POST + 40u);
|
||
return (used == (uint32_t)WAVE_RING_POST) ? 1 : 0;
|
||
}
|
||
|
||
/* ===== 封存瞬间事实(模拟接线道在 EVT_SEALED 那一拍现取现填的内容) ===== */
|
||
|
||
static wave_seal_cal_t s_cal;
|
||
|
||
static void cal_init(void) {
|
||
uint8_t j;
|
||
|
||
memset(&s_cal, 0, sizeof(s_cal));
|
||
s_cal.eng = (uint8_t)WAVE_ENG_PIN;
|
||
/* flags_seq 的定义(设计说明 §6.2 偏移 23)= 逐路 VALID 压成的位图。产品构建下一路都没做
|
||
* 两点标定、ADC_CAL_F_VALID 未置位 → 这份合成本波的真值形态取全零。 */
|
||
s_cal.flags_seq = 0x00u;
|
||
s_cal.clip_mask = 0x00E6u; /* V0.1 恒 0xE6 = UB UC IB IC IN 五个语义位 */
|
||
s_cal.wall = 1764374400u;
|
||
for (j = 0u; j < (uint8_t)WAVE_ST_CH_NUM; j++) {
|
||
int tpa = ((j == 0u) || (j == 3u) || (j == 4u)) ? 1 : 0;
|
||
|
||
s_cal.ch[j].sem = j;
|
||
s_cal.ch[j].conv = (uint8_t)((tpa != 0) ? 0 : 1);
|
||
s_cal.ch[j].bits = (uint8_t)((tpa != 0) ? 16 : 12);
|
||
/* avail = adc_cal_flags() 低 8 位原值,如实透传不加工:V0.1 下三路 TPAFE = 0x00
|
||
* (未标定、也不在削顶表里)、五路片内 ADC = 0x02(VALID = 0 而 CLIPPED = 1)。
|
||
* VALID 与 CLIPPED 是两件独立事实,本层不许据此造「削顶必无效」这类不变式,
|
||
* 也不许为迁就任何一份解码脚本改数据(lead 裁定 2026-10-29)。同时 VALID + CLIPPED
|
||
* 的构造形态由 test/host/wave_store 那套与它的合成段覆盖。 */
|
||
s_cal.ch[j].avail = (uint8_t)((tpa != 0) ? 0x00 : 0x02);
|
||
s_cal.ch[j].k_q16 = (tpa != 0) ? 10000000 : 52800000;
|
||
s_cal.ch[j].b_codes = 0;
|
||
}
|
||
}
|
||
|
||
/* ===== 全链路搬运:封存区 → 打包游标 → 内存镜像 ===== */
|
||
|
||
static uint8_t s_buf[SEG_IMG_CAP];
|
||
static uint32_t s_len;
|
||
static wave_seg_meta_t s_m;
|
||
|
||
static int pack_seg(void) {
|
||
wave_pack_ctx_t ctx;
|
||
uint32_t off = 0u;
|
||
int rc;
|
||
|
||
s_len = 0u;
|
||
if (wave_ring_sealed_meta(&s_m) != WAVE_ST_OK) {
|
||
return -1;
|
||
}
|
||
rc = wave_store_pack_start(&ctx, &s_m, wave_ring_read_cb, NULL);
|
||
if (rc != WAVE_ST_OK) {
|
||
return -2;
|
||
}
|
||
for (;;) {
|
||
uint32_t n = 0u;
|
||
|
||
if (off >= (uint32_t)sizeof(s_buf)) {
|
||
return -3;
|
||
}
|
||
rc = wave_store_pack_next(&ctx, &s_buf[off],
|
||
(uint32_t)sizeof(s_buf) - off, &n);
|
||
if (rc < 0) {
|
||
return -4;
|
||
}
|
||
off += n;
|
||
if (rc == WAVE_ST_DONE) {
|
||
break;
|
||
}
|
||
}
|
||
if (wave_store_pack_total(&ctx) != off) {
|
||
return -5;
|
||
}
|
||
if (wave_store_pack_offset(&ctx) != off) {
|
||
return -6;
|
||
}
|
||
s_len = off;
|
||
return 0;
|
||
}
|
||
|
||
/* 把已封存区某一列按 §6.2 的小端形态排进 p_dst(本测试自己排一遍,不信生产者的排法) */
|
||
static int col_to_bytes(uint8_t a_ch, uint8_t *p_dst, uint32_t a_cap) {
|
||
int16_t pts[WAVE_RING_PTS];
|
||
uint32_t j;
|
||
|
||
if (a_cap < ((uint32_t)WAVE_RING_PTS * 2u)) {
|
||
return 0;
|
||
}
|
||
/* read_ch 的返回值是「实际读出的点数」(到段尾会短读),不是 WAVE_ST_OK */
|
||
if (wave_ring_read_ch(a_ch, 0u, (uint16_t)WAVE_RING_PTS, pts) != (int32_t)WAVE_RING_PTS) {
|
||
return 0;
|
||
}
|
||
for (j = 0u; j < (uint32_t)WAVE_RING_PTS; j++) {
|
||
uint16_t u = (uint16_t)pts[j];
|
||
|
||
p_dst[j * 2u] = (uint8_t)(u & 0xFFu);
|
||
p_dst[(j * 2u) + 1u] = (uint8_t)(u >> 8);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static int point_is(uint8_t a_ch, uint16_t a_pt, uint32_t a_k) {
|
||
int16_t v = 12345;
|
||
|
||
if (a_k > 0xFFFFFu) {
|
||
return 0; /* 负数节拍:这段不校验(冷启动前导点另有专门用例) */
|
||
}
|
||
if (wave_ring_read_ch(a_ch, a_pt, 1u, &v) != 1) {
|
||
return 0;
|
||
}
|
||
return (v == sig_at(a_k, a_ch)) ? 1 : 0;
|
||
}
|
||
|
||
static int count_head_zero(uint8_t a_ch, uint16_t a_cnt) {
|
||
int16_t pts[WAVE_RING_PTS];
|
||
uint16_t j;
|
||
uint16_t z = 0u;
|
||
|
||
if (wave_ring_read_ch(a_ch, 0u, a_cnt, pts) != (int32_t)a_cnt) {
|
||
return -1;
|
||
}
|
||
for (j = 0u; j < a_cnt; j++) {
|
||
if (pts[j] == 0) {
|
||
z++;
|
||
}
|
||
}
|
||
return (int)z;
|
||
}
|
||
|
||
int main(void) {
|
||
wave_ring_stat_t st;
|
||
uint8_t j;
|
||
|
||
printf("=== Wave Trig Tests:\n");
|
||
|
||
/* ---------- 用例:三条触发来路 ---------- */
|
||
printf("-- 用例:三条触发来路 --\n");
|
||
cal_init();
|
||
{
|
||
static const uint8_t s_src[3] = {
|
||
(uint8_t)WAVE_TRIG_MANUAL, (uint8_t)WAVE_TRIG_PROTECT, (uint8_t)WAVE_TRIG_BENCH
|
||
};
|
||
static const uint8_t s_elm[3] = { 0u, 5u, 2u };
|
||
int all_ok = 1;
|
||
int all_evt = 1;
|
||
int all_geo = 1;
|
||
uint8_t k;
|
||
|
||
for (k = 0u; k < 3u; k++) {
|
||
fresh();
|
||
if (trigger_and_seal(s_src[k], s_elm[k], 200u) == 0) {
|
||
all_evt = 0;
|
||
}
|
||
if (wave_ring_latch_meta(&s_cal) != WAVE_ST_OK) {
|
||
all_ok = 0;
|
||
}
|
||
(void)memset(&s_m, 0, sizeof(s_m));
|
||
if (wave_ring_sealed_meta(&s_m) != WAVE_ST_OK) {
|
||
all_ok = 0;
|
||
}
|
||
if ((s_m.trig_src != s_src[k]) || (s_m.trig_elm != s_elm[k])) {
|
||
all_geo = 0;
|
||
}
|
||
}
|
||
CHECK(all_evt != 0, "手动 / 保护 / bench 三条来路都在触发后第 160 拍封段");
|
||
CHECK(all_ok != 0, "三条来路都完成 latch 并取到段头快照");
|
||
CHECK(all_geo != 0, "trig_src 与 trig_elm 原样透传进段头(谁触的、哪个元件触的)");
|
||
}
|
||
{
|
||
int bad = 0;
|
||
|
||
fresh();
|
||
if (wave_ring_request(0u, 0u) != WAVE_ST_E_PARAM) {
|
||
bad++;
|
||
}
|
||
if (wave_ring_request((uint8_t)(WAVE_TRIG_BENCH + 1u), 0u) != WAVE_ST_E_PARAM) {
|
||
bad++;
|
||
}
|
||
if (wave_ring_request(200u, 0u) != WAVE_ST_E_PARAM) {
|
||
bad++;
|
||
}
|
||
CHECK(bad == 0, "触发源非法(0 / 4 / 200)一律拒,不挂号也不计数");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.ovf_cnt == 0u, "非法触发源不计 ovf(ovf 只统计被抑制的真请求)");
|
||
}
|
||
|
||
/* ---------- 用例:同一拍多次请求只录一段 ---------- */
|
||
printf("-- 用例:同一拍多次请求与 ovf 计数 --\n");
|
||
fresh();
|
||
run_n(50u);
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, 7u) == WAVE_ST_OK, "保护元件先挂号被受理");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u) == WAVE_ST_DONE, "同一拍第二个请求只算溢出(不排队)");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.ovf_cnt == 1u, "被抑制的请求进 ovf 计数(§9 ovf=)");
|
||
(void)tick_one();
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.state == (uint8_t)WAVE_RING_ST_FREEZE) && (st.trig_src == (uint8_t)WAVE_TRIG_PROTECT) &&
|
||
(st.trig_elm == 7u), "本段归属先挂号者(多个元件同时 picked 只录一段)");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_BENCH, 1u) == WAVE_ST_DONE, "FREEZE 期间新触发只加 ovf");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.ovf_cnt == 2u, "FREEZE 期间的请求也计 ovf");
|
||
CHECK(run_until_sealed(WAVE_RING_POST + 20u) == (uint32_t)WAVE_RING_POST, "仍然照原计划封段(第二路触发不改变段内容)");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u) == WAVE_ST_DONE, "SEALED 期间新触发只加 ovf");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.ovf_cnt == 3u) && (st.err_cnt == 0u) && (st.seg_total == 0u), "被抑制的触发既不算成功也不算失败");
|
||
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "抑制触发不改变状态(还等着 ACK)");
|
||
|
||
/* ---------- 用例:封存后区内容不可变(§5.1 唯一写者) ---------- */
|
||
printf("-- 用例:已封存区不可变与 drop 计数 --\n");
|
||
CHECK(wave_ring_sealed() == 0u, "刚封段还没 latch -> 仍不可读(防半份快照)");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "adcTask 在同一拍交棒封存瞬间事实");
|
||
CHECK(wave_ring_sealed() == 1u, "latch 之后才亮「已封存可读」");
|
||
{
|
||
static uint8_t s_snap0[WAVE_RING_PTS * 2u];
|
||
static uint8_t s_snap5[WAVE_RING_PTS * 2u];
|
||
static uint8_t s_again0[WAVE_RING_PTS * 2u];
|
||
static uint8_t s_again5[WAVE_RING_PTS * 2u];
|
||
int same = 0;
|
||
int drop_all = 0;
|
||
uint32_t k;
|
||
|
||
CHECK((col_to_bytes(0u, s_snap0, sizeof(s_snap0)) != 0) &&
|
||
(col_to_bytes(5u, s_snap5, sizeof(s_snap5)) != 0), "封存区两列读成功");
|
||
drop_all = 1;
|
||
for (k = 0u; k < 50u; k++) {
|
||
(void)tick_one();
|
||
if (s_last_push != WAVE_ST_DONE) {
|
||
drop_all = 0;
|
||
}
|
||
}
|
||
CHECK(drop_all != 0, "SEALED 期间每一拍压样都被丢(返回 DONE 而不是 OK)");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.drop_cnt == (50u * (uint32_t)WAVE_RING_CH), "drop 计数单位 = 点(一拍 8 点,50 拍 = 400)");
|
||
CHECK((col_to_bytes(0u, s_again0, sizeof(s_again0)) != 0) &&
|
||
(col_to_bytes(5u, s_again5, sizeof(s_again5)) != 0), "再读一次两列");
|
||
if ((memcmp(s_snap0, s_again0, sizeof(s_snap0)) == 0) &&
|
||
(memcmp(s_snap5, s_again5, sizeof(s_snap5)) == 0)) {
|
||
same = 1;
|
||
}
|
||
CHECK(same != 0, "50 拍压样之后已封存区逐字节不变(defaultTask 读期间不会读到半新半旧)");
|
||
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "仍在 SEALED,等 ACK");
|
||
}
|
||
|
||
/* ---------- 用例:ACK 前不重录 + 冷却只在 ACK 后计时 ---------- */
|
||
printf("-- 用例:ACK 前不重录 / 冷却只在 ACK 后 --\n");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u) == WAVE_ST_DONE, "ACK 前来新触发只加 ovf(lead 裁定第一条)");
|
||
CHECK(wave_ring_write_done(0) == WAVE_ST_OK, "defaultTask 落盘成功后明确 ACK");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.state == (uint8_t)WAVE_RING_ST_COOL, "ACK 之后才转冷却(冻结持续到 ACK,不是按时间解冻)");
|
||
CHECK(st.cool_left == (uint16_t)WAVE_RING_COOL_MS, "冷却窗从 ACK 那一拍起算 500 拍");
|
||
CHECK((st.seg_total == 1u) && (st.seg_idx == 1u), "成功 ACK 才推进段计数与轮转号");
|
||
CHECK(st.err_cnt == 0u, "这条链路上没有失败");
|
||
CHECK(wave_ring_sealed() == 0u, "ACK 之后「已封存可读」立即熄灭(旧段不再对外)");
|
||
{
|
||
int no_seal = 1;
|
||
uint32_t k;
|
||
|
||
for (k = 0u; k < 499u; k++) {
|
||
if (tick_one() == (int32_t)WAVE_RING_EVT_SEALED) {
|
||
no_seal = 0;
|
||
}
|
||
}
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((no_seal != 0) && (st.state == (uint8_t)WAVE_RING_ST_COOL) && (st.cool_left == 1u),
|
||
"冷却差 1 拍时仍在冷却、期间没有任何新段");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u) == WAVE_ST_DONE, "冷却期内新触发只加 ovf 不重录(lead 裁定第三条)");
|
||
(void)tick_one();
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.state == (uint8_t)WAVE_RING_ST_IDLE, "冷却走完自动回 IDLE");
|
||
CHECK(wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u) == WAVE_ST_OK, "回 IDLE 后新触发被受理(下一次故障有波可录)");
|
||
}
|
||
|
||
/* ---------- 用例:滞留超时自弃(绝不让环永久焊死) ---------- */
|
||
printf("-- 用例:SEALED 滞留超时自弃 --\n");
|
||
fresh();
|
||
cal_init();
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_PROTECT, 2u, 200u) != 0, "造一段(故意不 latch,模拟接线道没交棒)");
|
||
run_n(WAVE_SEAL_TIMEOUT_MS - 2u);
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.state == (uint8_t)WAVE_RING_ST_SEALED) && (st.seal_hold == (uint16_t)(WAVE_SEAL_TIMEOUT_MS - 2u)) &&
|
||
(st.err_cnt == 0u), "差 2 拍还没到超时线,段仍留着等 ACK");
|
||
(void)tick_one();
|
||
(void)tick_one();
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.err_cnt == 1u) && (st.seg_total == 0u), "滞留满 WAVE_SEAL_TIMEOUT_MS 自弃并计 err(lead 裁定第二条)");
|
||
CHECK((st.state == (uint8_t)WAVE_RING_ST_COOL) && (st.cool_left == (uint16_t)WAVE_RING_COOL_MS),
|
||
"自弃也照样起冷却解冻(不是停在 SEALED)");
|
||
CHECK(wave_ring_sealed() == 0u, "自弃后对外不可读(绝不给落盘道一份已作废的段)");
|
||
CHECK(wave_ring_sealed_meta(&s_m) == WAVE_ST_E_STATE, "自弃后取段头快照被拒");
|
||
CHECK(wave_ring_write_done(0) == WAVE_ST_E_STATE, "自弃后补 ACK 被拒(不吃轮转号、不加 seg_total)");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.seg_idx == 0u) && (st.seg_total == 0u), "自弃的段不占段号(下一次故障仍写最早那个号)");
|
||
run_n(WAVE_RING_COOL_MS);
|
||
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "自弃之后环照常被救回来(回 IDLE)");
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_MANUAL, 0u, 100u) != 0, "自弃后仍能录下一段(录波不会永久停走)");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "新段照样能交棒");
|
||
CHECK(wave_ring_sealed() == 1u, "新段就绪");
|
||
|
||
/* ---------- 用例:ACK 失败留同号重写 ---------- */
|
||
printf("-- 用例:落盘失败不推进轮转号 --\n");
|
||
(void)wave_ring_get_stat(&st);
|
||
j = st.seg_idx;
|
||
CHECK(wave_ring_write_done(-5) == WAVE_ST_OK, "失败也照样 ACK(短写 / 打开失败都要解冻)");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.err_cnt == 2u) && (st.seg_idx == j) && (st.seg_total == 0u),
|
||
"失败只计 err,段号与段计数都不动(不留半截文件骗过 PC 脚本)");
|
||
CHECK(wave_ring_write_done(0) == WAVE_ST_E_STATE, "重复 ACK 被拒(防把上一段的 ACK 打到新段)");
|
||
run_n(WAVE_RING_COOL_MS);
|
||
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_IDLE, "失败段冷却后回 IDLE");
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_BENCH, 3u, 60u) != 0, "再来一段(预触发区只滚了 60 拍)");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "新段交棒");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK(st.seg_idx == j, "新段沿用同一个段号");
|
||
{
|
||
char name[WAVE_ST_PATH_MAX];
|
||
|
||
CHECK(wave_store_seg_path(name, (uint32_t)sizeof(name), st.seg_idx) == 16, "按 ring 的段号取名成功");
|
||
CHECK(strcmp(name, "/wave/WAVE00.BIN") == 0, "同号 = 同一个文件名(重写覆盖,不吃掉一个号)");
|
||
}
|
||
CHECK(wave_ring_write_done(0) == WAVE_ST_OK, "这次写成功");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.seg_idx == 1u) && (st.seg_total == 1u) && (st.err_cnt == 2u), "成功才推进,err 保留历史");
|
||
|
||
/* ---------- 用例:全链路字节(adcTask 封存 -> latch -> 落盘道搬运) ---------- */
|
||
printf("-- 用例:全链路封段到字节 --\n");
|
||
fresh();
|
||
cal_init();
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_PROTECT, 12u, 200u) != 0, "全链路:先滚 200 拍再触发封段");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "全链路:交棒封存瞬间事实");
|
||
CHECK(pack_seg() == 0, "全链路:打包游标跑到 DONE(数据源就是 wave_ring 的读口)");
|
||
CHECK(s_len == (uint32_t)WAVE_ST_SEG_SZ_CUR, "整段字节数 = 5250(§6.3 等式)");
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_OK, "自产段过整段自检(两个 CRC 都对)");
|
||
CHECK(wave_store_seg_check(s_buf, s_len, &s_m) == WAVE_ST_OK, "自检并回填段头");
|
||
CHECK((s_buf[0] == (uint8_t)WAVE_ST_MAGIC0) && (s_buf[1] == (uint8_t)WAVE_ST_MAGIC1) &&
|
||
(s_buf[2] == (uint8_t)WAVE_ST_VER), "魔数第三字节就是版本号");
|
||
CHECK((s_buf[WAVE_ST_OFF_FS_HZ] == (uint8_t)(FS_HZ_TEST & 0xFFu)) &&
|
||
(s_buf[WAVE_ST_OFF_FS_HZ + 1u] == (uint8_t)(FS_HZ_TEST >> 8)), "采样率 1000 Hz 写进头里(是数据不是编译期假设)");
|
||
CHECK(s_buf[WAVE_ST_OFF_PTS_CYC] == 20u, "每周波 20 点写进头里");
|
||
CHECK(s_buf[WAVE_ST_OFF_CH_CNT] == (uint8_t)WAVE_ST_CH_NUM, "通道数 8");
|
||
CHECK(s_buf[WAVE_ST_OFF_PRE_PTS] == (uint8_t)(WAVE_RING_PRE & 0xFFu), "预触发 160 点");
|
||
CHECK(s_buf[WAVE_ST_OFF_TRIG_SRC] == (uint8_t)WAVE_TRIG_PROTECT, "触发源 = 保护");
|
||
CHECK(s_buf[WAVE_ST_OFF_TRIG_ELM] == 12u, "元件序号 = 12");
|
||
CHECK(s_buf[WAVE_ST_OFF_ENG] == (uint8_t)WAVE_ENG_PIN, "折算档位 = 引脚域");
|
||
CHECK(s_buf[WAVE_ST_OFF_FLAGS] == 0x00u, "flags_seq = 未标定态的逐路有效位图原值(全零)");
|
||
CHECK((s_buf[WAVE_ST_OFF_CLIP] == (uint8_t)(0xE6u & 0xFFu)) &&
|
||
(s_buf[WAVE_ST_OFF_CLIP + 1u] == (uint8_t)(0xE6u >> 8)), "clip_mask = 0xE6(V0.1 恒值)");
|
||
{
|
||
wave_seg_meta_t back;
|
||
|
||
if (wave_store_head_parse(s_buf, (uint32_t)sizeof(s_buf), &back) == WAVE_ST_OK) {
|
||
CHECK(back.t0_ms == s_trig_k, "段首相对毫秒 = 触发那一拍");
|
||
CHECK(back.frame_seq == s_trig_k, "段首帧序号 = 触发那一拍(PC 拿它对 frames)");
|
||
CHECK(back.wall == s_cal.wall, "wall 取封存瞬间的对时秒(未对时应为 0)");
|
||
CHECK(back.pts_per_ch == (uint16_t)WAVE_RING_PTS, "每路点数 320");
|
||
} else {
|
||
CHECK(0, "段头解析成功(回填比对的前提)");
|
||
CHECK(0, "段首相对毫秒 = 触发那一拍");
|
||
CHECK(0, "段首帧序号 = 触发那一拍(PC 拿它对 frames)");
|
||
CHECK(0, "wall 取封存瞬间的对时秒(未对时应为 0)");
|
||
CHECK(0, "每路点数 320");
|
||
}
|
||
}
|
||
{
|
||
int ok_tbl = 1;
|
||
uint32_t pos;
|
||
|
||
for (j = 0u; j < (uint8_t)WAVE_ST_CH_NUM; j++) {
|
||
pos = WAVE_ST_OFF_TBL + ((uint32_t)WAVE_ST_CH_BYTES * j);
|
||
if ((s_buf[pos + WAVE_ST_CH_OFF_SEM] != j) ||
|
||
(s_buf[pos + WAVE_ST_CH_OFF_CONV] != s_cal.ch[j].conv) ||
|
||
(s_buf[pos + WAVE_ST_CH_OFF_BITS] != s_cal.ch[j].bits) ||
|
||
(s_buf[pos + WAVE_ST_CH_OFF_AVAIL] != s_cal.ch[j].avail)) {
|
||
ok_tbl = 0;
|
||
}
|
||
if (wave_st_get32(&s_buf[pos + WAVE_ST_CH_OFF_KQ16]) != (uint32_t)s_cal.ch[j].k_q16) {
|
||
ok_tbl = 0;
|
||
}
|
||
if (wave_st_get32(&s_buf[pos + WAVE_ST_CH_OFF_BCODE]) != (uint32_t)s_cal.ch[j].b_codes) {
|
||
ok_tbl = 0;
|
||
}
|
||
}
|
||
CHECK(ok_tbl != 0, "通道表 8 项:sem = 下标 + conv/bits/avail/k_q16/b_codes 全部落进字节");
|
||
}
|
||
{
|
||
uint16_t hcrc = wave_store_crc(s_buf, WAVE_ST_HEAD_CRC_POS((uint8_t)WAVE_ST_CH_NUM));
|
||
|
||
CHECK((s_buf[WAVE_ST_HEAD_CRC_POS((uint8_t)WAVE_ST_CH_NUM)] == (uint8_t)(hcrc & 0xFFu)) &&
|
||
(s_buf[WAVE_ST_HEAD_CRC_POS((uint8_t)WAVE_ST_CH_NUM) + 1u] == (uint8_t)(hcrc >> 8)),
|
||
"头 CRC 覆盖 [0, 30+12*ch) 且低字节在前");
|
||
}
|
||
{
|
||
uint16_t dcrc = wave_store_crc(&s_buf[WAVE_ST_HEAD_SZ_CUR],
|
||
WAVE_ST_DATA_BYTES((uint8_t)WAVE_ST_CH_NUM, (uint16_t)WAVE_ST_PTS_DEF));
|
||
|
||
CHECK((s_buf[s_len - 2u] == (uint8_t)(dcrc & 0xFFu)) && (s_buf[s_len - 1u] == (uint8_t)(dcrc >> 8)),
|
||
"数据 CRC 只覆盖数据区、贴在文件尾(头坏了不会被数据 CRC 掩盖)");
|
||
}
|
||
{
|
||
static uint8_t s_col[WAVE_RING_PTS * 2u];
|
||
int all_col = 1;
|
||
|
||
for (j = 0u; j < (uint8_t)WAVE_ST_CH_NUM; j++) {
|
||
if (col_to_bytes(j, s_col, sizeof(s_col)) == 0) {
|
||
all_col = 0;
|
||
} else if (memcmp(&s_buf[WAVE_ST_HEAD_SZ_CUR + ((uint32_t)WAVE_RING_PTS * 2u * j)],
|
||
s_col, (size_t)((uint32_t)WAVE_RING_PTS * 2u)) != 0) {
|
||
all_col = 0;
|
||
}
|
||
}
|
||
CHECK(all_col != 0,
|
||
"数据区每一列逐字节 == wave_ring_read_ch() 的已封存区内容(§5.1 只读已封存区的机器证明)");
|
||
}
|
||
{
|
||
int ok_trig = 1;
|
||
int ok_next = 1;
|
||
int ok_old = 1;
|
||
|
||
for (j = 0u; j < (uint8_t)WAVE_ST_CH_NUM; j++) {
|
||
if (point_is(j, (uint16_t)(WAVE_RING_PRE - 1u), s_trig_k) == 0) {
|
||
ok_trig = 0;
|
||
}
|
||
if (point_is(j, (uint16_t)WAVE_RING_PRE, s_trig_k + 1u) == 0) {
|
||
ok_next = 0;
|
||
}
|
||
if (point_is(j, 0u, s_trig_k - (WAVE_RING_PRE - 1u)) == 0) {
|
||
ok_old = 0;
|
||
}
|
||
}
|
||
CHECK(ok_trig != 0, "点序(lead 裁定 a):触发那一拍的样本落在段内点 159");
|
||
CHECK(ok_next != 0, "点序:点 160 = 触发后第一拍(两半各 160 点不重叠)");
|
||
CHECK(ok_old != 0, "点序:点 0 = 触发前第 159 拍(预触发窗最老一点)");
|
||
}
|
||
{
|
||
uint8_t save = s_buf[WAVE_ST_HEAD_SZ_CUR + 100u];
|
||
|
||
s_buf[WAVE_ST_HEAD_SZ_CUR + 100u] = (uint8_t)(save ^ 0x5Au);
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_E_CRC, "数据区改一字节 -> 整段作废(PC 侧必须拒绘)");
|
||
s_buf[WAVE_ST_HEAD_SZ_CUR + 100u] = save;
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_OK, "改回来又能过(判废只认 CRC,不认运气)");
|
||
}
|
||
|
||
/* ---------- 用例:未 latch 时落盘道必须当不可用 ---------- */
|
||
printf("-- 用例:未 latch 不许落盘 --\n");
|
||
fresh();
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_MANUAL, 0u, 200u) != 0, "封段(不 latch)");
|
||
CHECK(wave_ring_sealed() == 0u, "sealed() = SEALED 且已 latch 的合并判据:未交棒就是不可用");
|
||
CHECK(wave_ring_sealed_meta(&s_m) == WAVE_ST_E_STATE, "取不到段头快照 -> 落盘道无从开文件");
|
||
CHECK(wave_ring_write_done(0) == WAVE_ST_E_STATE, "没交棒就 ACK 被拒(不吃轮转号、不假加 seg_total)");
|
||
(void)wave_ring_get_stat(&st);
|
||
CHECK((st.seg_idx == 0u) && (st.seg_total == 0u) && (st.err_cnt == 0u), "被拒的 ACK 一个计数都不动");
|
||
CHECK(wave_ring_state() == (uint8_t)WAVE_RING_ST_SEALED, "段仍留在 SEALED 等交棒或超时自弃");
|
||
CHECK(wave_ring_latch_meta(NULL) == WAVE_ST_E_PARAM, "latch 空指针拒");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "补交棒成功");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_E_STATE, "重复交棒被拒(封存瞬间事实不许被后一拍覆盖)");
|
||
CHECK(pack_seg() == 0, "交棒之后全链路打包成功");
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_OK, "交棒后的段过自检");
|
||
|
||
/* ---------- 用例:冷启动第一段(环未填满) ---------- */
|
||
printf("-- 用例:环未填满就触发 --\n");
|
||
fresh();
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_PROTECT, 1u, 20u) != 0, "上电只滚 20 拍就触发(预触发区大部分是空的)");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "交棒");
|
||
CHECK(count_head_zero(0u, 139u) == 139, "点 0 到 138 全为 0(没采过的点不填假数据)");
|
||
CHECK(point_is(0u, 139u, 0u) != 0, "点 139 = 上电第一拍(第一个真样本)");
|
||
CHECK(point_is(7u, 159u, s_trig_k) != 0, "点 159 仍是触发那一拍(未填满时段序口径不变)");
|
||
CHECK(point_is(0u, 319u, s_trig_k + (uint32_t)WAVE_RING_POST) != 0, "点 319 = 段尾(最后一条后触发样本)");
|
||
CHECK(pack_seg() == 0, "冷启动段也能打包");
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_OK, "冷启动段过自检(回放页画出来的是前导平线加真波,不是伪证据)");
|
||
|
||
/* ---------- 用例:互验产物(不参与判定) ---------- */
|
||
printf("-- 用例:互验产物 --\n");
|
||
fresh();
|
||
cal_init();
|
||
CHECK(trigger_and_seal((uint8_t)WAVE_TRIG_MANUAL, 0u, 200u) != 0, "再造一段连续的 50 Hz 正弦段");
|
||
CHECK(wave_ring_latch_meta(&s_cal) == WAVE_ST_OK, "交棒");
|
||
if (pack_seg() == 0) {
|
||
const char *tmp = getenv("TEMP");
|
||
|
||
CHECK(wave_store_seg_check(s_buf, s_len, NULL) == WAVE_ST_OK, "互验段过自检");
|
||
if (tmp != NULL) {
|
||
char path[512];
|
||
FILE *f;
|
||
|
||
strcpy(path, tmp);
|
||
strcat(path, "\\wave_ring_synth8.bin");
|
||
f = fopen(path, "wb");
|
||
if (f != NULL) {
|
||
size_t w = fwrite(s_buf, 1u, s_len, f);
|
||
|
||
(void)fclose(f);
|
||
CHECK(w == (size_t)s_len, "互验产物写入字节数 == 段长");
|
||
/* 形态在日志里说清楚:本套给的是本波(V0.1 未标定)真值形态 */
|
||
printf("[INFO] 互验产物(本波真值形态:avail 0x00 / 0x02,逐路有效位图全零;"
|
||
"信号形态 = 三路 50 Hz 正弦(UA / UN / IA)+ 三路方波(UB / UC / IN)+ 两路满幅斜坡(IB / IC),"
|
||
"供 test/host/wave_read.py dump/shape/fft 只读校验):%s\n",
|
||
path);
|
||
} else {
|
||
printf("[INFO] 打不开临时目录文件,跳过互验产物\n");
|
||
}
|
||
} else {
|
||
printf("[INFO] 环境无 TEMP,跳过互验产物\n");
|
||
}
|
||
} else {
|
||
printf("[INFO] 互验段打包失败,跳过产物\n");
|
||
}
|
||
|
||
printf("\n=== Wave Trig Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|