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 条仍全过
492 lines
31 KiB
C
492 lines
31 KiB
C
/*
|
||
* 模块名称:录波段格式层(故障录波 · 段文件字节布局编解码 + 打包游标 + 段名轮转)
|
||
* 模块功能:把「一段录波」在内存字节缓冲与结构化元数据之间逐字节翻译,并给落盘道
|
||
* 提供分次搬运的打包游标。口径唯一属主 = docs/子系统设计/故障录波设计说明.md §6.2
|
||
* (字节布局)与 §6.3(九问裁定:板端编码器与 PC 解码器 test/host/wave_read.py
|
||
* 两份独立实现的共同依据)。本文件是它的机器可执行形态,不在此重新定义任何口径;
|
||
* 两处冲突以设计文档为准并回报 lead,不在代码里单方面「修正」。
|
||
* 纯逻辑层纪律:不 include HAL / CMSIS / RTOS 头、不调 FatFs、不碰文件系统、
|
||
* 不建任务、不打日志(文件 I/O 与任务钩子全在接线层 App/task/ 里做),
|
||
* 因此可被主机侧单测 test/host/wave_store/ 原样纳入做逐字节断言。
|
||
* 职责边界(为什么这样切):
|
||
* 1) 固定头 + 通道表编解码、头 CRC 与数据区 CRC 自检 —— 格式知识只此一处;
|
||
* 2) 打包游标(分次搬运):文件系统唯一写者只要「每次给我不超过 256 字节的下一片」,
|
||
* 整段 5250 字节不驻留 RAM(设计说明 §7:搬运缓冲 256 B × 2,不做整段驻留);
|
||
* 3) 段文件名轮转与反解:只算名字,不打开文件(FatFs FF_USE_LFN=0 只能严格
|
||
* 8.3 短名,见 docs/架构设计/STM32F4-Base存储架构说明.md §10.1);
|
||
* 4) 数据区偏移换算 + 小端解码:回放页只画一路(设计说明 §8.1),按通道分列
|
||
* 让「只读那一路」成为一次连续读。
|
||
* 不链 App/algo/adc_cal(零依赖 = 主机免桩可测):通道表里的 k_q16 / b_codes /
|
||
* 可用性由调用方在封存瞬间取 int32 原值填进 meta 后交给本层编码。系数必须走
|
||
* adc_cal_k_q16()(接线道补的 int32 直读入口),不得经 adc_cal_k_uv() 的 float
|
||
* 通道回算 —— float 在 5.28e7 量级掉尾数(设计说明 §6.3「k / b 宽度与角色」行)。
|
||
* CRC 一张表的原始说法不成立,lead 已在 §6.3 记录实测依据:全仓无可复用的
|
||
* CRC-16 表(App/util/README.md 记 crc.c 与 crc.h 已于 2026-10-02 删除;屏板链路
|
||
* Drivers/BSP/UI/ui_net_sink.c 的 remo_crc() 是单字节异或、不是 CRC-16)。
|
||
* 故本层自带无表位运算 CRC-16/MODBUS,并用规范锚点自校验。
|
||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_store/
|
||
* 作者:DTU 项目组
|
||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||
* 修改记录:
|
||
* v1.0 2026-10-29 DTU 项目组 创建:段头/通道表编解码、CRC-16/MODBUS 无表实现、
|
||
* 打包游标、段名轮转与反解、数据区偏移换算与小端解码
|
||
*
|
||
* 二期不返工的结构约束(设计说明 §2.1,改本层前先读):
|
||
* 1) 采样率与每周波点数是写进文件头的数据,不是编译期假设 —— 回放端与 PC 脚本一律
|
||
* 从头里读,本层算长度也照 meta 里的值算,不在代码里写死 1000 / 20;
|
||
* 2) 版本号并进魔数第三字节,换格式即变;本层解到不认识的版本一律判废;
|
||
* 3) 「带 TEMP 就要连 flags_seq 与 clip_mask 一起升版本」同源(§6.2 那两行的括注),
|
||
* 所以本层的通道数上限留到 9,但本波实际只用 8。
|
||
*/
|
||
|
||
#ifndef WAVE_STORE_H
|
||
#define WAVE_STORE_H
|
||
|
||
/* 同 App/algo/wave_ring.h:本头的 NULL 只出现在注释里(字段与入参的约束说明),
|
||
* 代码只用 stdint 的类型,故不 include stddef.h;用 NULL 的 .c 自己带。 */
|
||
#include <stdint.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* ===== 一、段文件布局常量(逐字段照 docs/子系统设计/故障录波设计说明.md §6.2 与 §6.3) ===== */
|
||
|
||
/* 魔数前两字节固定 'W' 与 'R',第三字节 = 版本号(设计说明 §6.2 偏移 0 行) */
|
||
#define WAVE_ST_MAGIC0 (0x57u) /* 'W' */
|
||
#define WAVE_ST_MAGIC1 (0x52u) /* 'R' */
|
||
/* 版本:本波恒 1。ver 字段必须同时等于魔数第三字节与本宏,否则整段判废(§6.2 末行) */
|
||
#define WAVE_ST_VER (1u)
|
||
|
||
/* 固定头字段偏移(字节,小端) */
|
||
#define WAVE_ST_OFF_MAGIC (0u) /* 3 字节:'W' 'R' ver */
|
||
#define WAVE_ST_OFF_VER (3u) /* 1 字节 */
|
||
#define WAVE_ST_OFF_FS_HZ (4u) /* 2 字节 本波 1000 */
|
||
#define WAVE_ST_OFF_PTS_CYC (6u) /* 1 字节 本波 20 */
|
||
#define WAVE_ST_OFF_CH_CNT (7u) /* 1 字节 本波 8 */
|
||
#define WAVE_ST_OFF_PTS_CH (8u) /* 2 字节 本波 320 */
|
||
#define WAVE_ST_OFF_PRE_PTS (10u) /* 2 字节 本波 160 */
|
||
#define WAVE_ST_OFF_TRIG_SRC (12u) /* 1 字节 */
|
||
#define WAVE_ST_OFF_TRIG_ELM (13u) /* 1 字节 */
|
||
#define WAVE_ST_OFF_T0_MS (14u) /* 4 字节 u32 */
|
||
#define WAVE_ST_OFF_WALL (18u) /* 4 字节 u32 */
|
||
#define WAVE_ST_OFF_ENG (22u) /* 1 字节 */
|
||
#define WAVE_ST_OFF_FLAGS (23u) /* 1 字节 */
|
||
#define WAVE_ST_OFF_CLIP (24u) /* 2 字节 */
|
||
#define WAVE_ST_OFF_SEQ (26u) /* 4 字节 u32 */
|
||
#define WAVE_ST_OFF_TBL (30u) /* 通道表起点 */
|
||
|
||
/* 通道表一路 12 字节 = 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1 + k_q16 4 + b_codes 4
|
||
* 设计说明 §6.3「LSB 从哪来」:不设独立 LSB 字段,k_q16 就是 LSB(单位 µV/码 × 65536) */
|
||
#define WAVE_ST_CH_OFF_SEM (0u) /* 1 字节 */
|
||
#define WAVE_ST_CH_OFF_CONV (1u) /* 1 字节 */
|
||
#define WAVE_ST_CH_OFF_BITS (2u) /* 1 字节 */
|
||
#define WAVE_ST_CH_OFF_AVAIL (3u) /* 1 字节 = adc_cal_flags(sem) 低 8 位原值,不自造位义 */
|
||
#define WAVE_ST_CH_OFF_KQ16 (4u) /* 4 字节 int32 */
|
||
#define WAVE_ST_CH_OFF_BCODE (8u) /* 4 字节 int32 */
|
||
#define WAVE_ST_CH_BYTES (12u)
|
||
|
||
/* 本波通道数固定 8(TEMP 不录:理由见 §6.3 末段与 docs/任务规划/数据可信.md §7) */
|
||
#define WAVE_ST_CH_NUM (8u)
|
||
/* 通道数上限:给二期「带 TEMP 且同时升版本」留位;真正的口径是 meta 里的 ch_count 字段 */
|
||
#define WAVE_ST_CH_MAX (9u)
|
||
/* 每通道点数默认(= 预触发 + 后触发 = 160 + 160;长度是文件里的数据,宏只作默认值) */
|
||
#define WAVE_ST_PTS_DEF (320u)
|
||
#define WAVE_ST_PRE_DEF (160u)
|
||
#define WAVE_ST_FS_DEF (1000u)
|
||
#define WAVE_ST_PPC_DEF (20u)
|
||
|
||
/* 尺寸一律由字段算出,不写死字面量(改通道数/点数时自动跟着走):
|
||
* 头 CRC 覆盖区 = 30 + 12 × ch_count;头 CRC 自身紧随其后 2 字节;数据区自 32 + 12 × ch 起
|
||
* ch_count = 8 时:覆盖区 126、整头 128、数据区 5120、整段 5250(§6.3「CRC 覆盖范围」行) */
|
||
#define WAVE_ST_HEAD_CRC_POS(ch) ((uint32_t)(WAVE_ST_OFF_TBL) + \
|
||
((uint32_t)(ch) * WAVE_ST_CH_BYTES))
|
||
#define WAVE_ST_HEAD_BYTES(ch) ((uint32_t)(WAVE_ST_HEAD_CRC_POS(ch)) + 2u)
|
||
#define WAVE_ST_DATA_BYTES(ch, pts) ((uint32_t)(pts) * (uint32_t)(ch) * 2u)
|
||
#define WAVE_ST_SEG_TOTAL(ch, pts) (WAVE_ST_HEAD_BYTES(ch) + \
|
||
WAVE_ST_DATA_BYTES(ch, pts) + 2u)
|
||
/* 本波实际值(只给注释与单测引用,代码里一律用上面的参数化形态) */
|
||
#define WAVE_ST_HEAD_SZ_CUR (128u)
|
||
#define WAVE_ST_SEG_SZ_CUR (5250u)
|
||
|
||
/* 段目录与轮转(设计说明 §5.2 第三条:只能严格 8.3 短名;目录与 /log 平级、FTP 根下可见)
|
||
* 名字形态 = 目录 + 前缀 + 两位十进制 + 扩展名,号 0..99 共 100 个轮转覆盖 */
|
||
#define WAVE_ST_DIR "/wave"
|
||
#define WAVE_ST_PFX "WAVE"
|
||
#define WAVE_ST_EXT ".BIN"
|
||
#define WAVE_ST_SEG_MAX (100u)
|
||
#define WAVE_ST_PATH_MAX (17u) /* /wave/WAVE00.BIN = 16 字符 + 结束符 = 17 字节 */
|
||
|
||
/* CRC-16/MODBUS:多项式 0x8005 反射形态 = 0xA001、初值 0xFFFF、异或输出 0、低字节在前 */
|
||
#define WAVE_ST_CRC_POLY (0xA001u)
|
||
#define WAVE_ST_CRC_INIT (0xFFFFu)
|
||
#define WAVE_ST_CRC_XOROUT (0x0000u)
|
||
/* 自检锚点:串 123456789 的 CRC(§6.3 首行钉的常量) */
|
||
#define WAVE_ST_CRC_CHECK (0x4B37u)
|
||
|
||
/* ===== 二、返回码(int 通道;调用方必须接住或显式 (void),不得裸丢) ===== */
|
||
|
||
#define WAVE_ST_OK (0) /* 成功 */
|
||
#define WAVE_ST_DONE (1) /* 打包游标:整段已产完(不是错误) */
|
||
#define WAVE_ST_E_PARAM (-1) /* 空指针 / 容量不足 / 字段非法 */
|
||
#define WAVE_ST_E_CRC (-2) /* CRC 不符(头或数据区):整段判废 */
|
||
#define WAVE_ST_E_FORMAT (-3) /* 魔数不符 / 版本不认识 / 表内语义号不等于下标 */
|
||
#define WAVE_ST_E_SIZE (-4) /* 长度为 0 或长度与头部字段矛盾:整段判废 */
|
||
#define WAVE_ST_E_RANGE (-5) /* 列号 / 点下标 / 段号越界 */
|
||
#define WAVE_ST_E_STATE (-6) /* 状态错:未封存就读、未 start 就步进、缺数据源 */
|
||
#define WAVE_ST_E_NAME (-7) /* 文件名不是本层命名形态(扫目录重建段账时用) */
|
||
|
||
/* ===== 三、类型 ===== */
|
||
|
||
/* 触发源(§6.2 偏移 12 的取值;0 表示「无触发」,只出现在段外的状态查询里) */
|
||
typedef enum {
|
||
WAVE_TRIG_NONE = 0, /* 无 */
|
||
WAVE_TRIG_MANUAL = 1, /* 手动(UI「手动录波」确认,task-2 接线) */
|
||
WAVE_TRIG_PROTECT = 2, /* 保护启动(App/algo/prot_elm.c 的 picked 上升沿) */
|
||
WAVE_TRIG_BENCH = 3 /* bench 定周期自抓(只在 DTU_BENCH 构建里有这条来路) */
|
||
} wave_trig_src_t;
|
||
|
||
/* 折算档位(§6.2 偏移 22;单位一律以头里的 eng 为准,PC 不许按当前固件口径猜) */
|
||
typedef enum {
|
||
WAVE_ENG_PIN = 0, /* 引脚域(未标定;V0.1 产品构建停在这档) */
|
||
WAVE_ENG_SEC = 1, /* 二次值 */
|
||
WAVE_ENG_PRI = 2 /* 一次值 */
|
||
} wave_eng_t;
|
||
|
||
/* 通道表一路的内存形态(字段序 = §6.2 通道表字节序) */
|
||
typedef struct {
|
||
uint8_t sem; /* 语义号(adc_semantic_t 值;必须等于表内下标,§6.3) */
|
||
uint8_t conv; /* 来源转换器(adc_source_t 值:0 = TPAFE,1 = STM32) */
|
||
uint8_t bits; /* 转换器输出位宽(TPAFE 16 / STM32 12) */
|
||
uint8_t avail; /* 可用性 = adc_cal_flags(sem) 低 8 位原值(不自造位义) */
|
||
int32_t k_q16; /* 封存瞬间生效系数:µV/码 × 65536,就是 LSB */
|
||
int32_t b_codes; /* 封存瞬间生效零点偏移:码值 */
|
||
} wave_ch_desc_t;
|
||
|
||
/* 段头元数据 = 固定头 + 通道表的内存形态。
|
||
* 这是「待编码的段头」,不是「已写盘的证据」:打包游标只读它、不改它。 */
|
||
typedef struct {
|
||
uint16_t fs_hz; /* 采样率 Hz(从文件读出来的数据,不是编译期常量) */
|
||
uint8_t pts_per_cycle; /* 每周波点数 */
|
||
uint8_t ch_count; /* 通道数(本波 8) */
|
||
uint16_t pts_per_ch; /* 每通道点数(本波 320) */
|
||
uint16_t pre_pts; /* 预触发点数(本波 160;回放画触发光标用) */
|
||
uint8_t trig_src; /* 触发源(wave_trig_src_t) */
|
||
uint8_t trig_elm; /* 触发元件序号(prot_elm 实例下标;手动与定时为 0) */
|
||
uint32_t t0_ms; /* 段首相对上电毫秒 */
|
||
uint32_t wall; /* 对时后的 epoch 秒(未对时 0) */
|
||
uint8_t eng; /* 折算档位(wave_eng_t) */
|
||
uint8_t flags_seq; /* bit s = 语义通道 s 的 ADC_CAL_F_VALID(§6.3) */
|
||
uint16_t clip_mask; /* bit s = 语义通道 s 的 ADC_CAL_F_CLIPPED(V0.1 恒 0xE6) */
|
||
uint32_t frame_seq; /* 段首帧序号(与串口周期行的 frames 对账) */
|
||
wave_ch_desc_t ch[WAVE_ST_CH_MAX]; /* 通道表(前 ch_count 项有效) */
|
||
} wave_seg_meta_t;
|
||
|
||
/* 打包游标阶段(对外只用于「已搬完」判定与观测日志;推进全在 wave_store_pack_next 内部) */
|
||
typedef enum {
|
||
WAVE_PACK_IDLE = 0, /* 未初始化 */
|
||
WAVE_PACK_HEAD = 1, /* 正在产头(固定头 + 通道表 + 头 CRC) */
|
||
WAVE_PACK_DATA = 2, /* 正在产数据区(按通道分列,一列一列产) */
|
||
WAVE_PACK_TAIL = 3, /* 只剩数据区 CRC 两字节 */
|
||
WAVE_PACK_DONE = 4 /* 整段已产完 */
|
||
} wave_pack_phase_t;
|
||
|
||
/* 数据源回调:把「第 a_ch 列、段内第 a_from 点起的 a_cnt 个点」取进 p_dst。
|
||
* 返 回 值:实际拷贝的点数(等于 a_cnt 表示全给到);负 = 错误码 int32_t
|
||
* 只允许读已封存区:本层不提供、也不允许任何跨任务读活缓冲的路径
|
||
* (设计说明 §5.2 第一条陷阱:App/algo/sample_ring.c 的 sample_ring_window() 无临界区,
|
||
* 本波不复用它,跨任务只传「已封存」标志)。
|
||
* 生产实现 = wave_ring_read_cb()(签名一致,可直接传给 wave_store_pack_start)。 */
|
||
typedef int32_t (*wave_reader_fn)(void *p_user, uint8_t a_ch, uint32_t a_from,
|
||
uint32_t a_cnt, int16_t *p_dst);
|
||
|
||
/* 打包游标:段文件字节流的「拉模式」状态机。
|
||
* 为什么拉模式 + 小缓冲:defaultTask 是文件系统唯一写者,落盘窗口几十毫秒量级,
|
||
* 一次心跳最多一段;整段驻留 RAM 既超预算也没必要(设计说明 §7)。
|
||
* 游标不校验写回字节数 —— 那是调用方对着 f_write 做的事(§5.2 第二条陷阱:本移植
|
||
* f_write 簇分配失败会返 FR_OK + 短写、没有错误码,必须靠比对写回字节数判失败)。 */
|
||
typedef struct {
|
||
uint8_t phase; /* wave_pack_phase_t */
|
||
uint8_t ch; /* 数据区当前列 */
|
||
uint16_t head_done; /* 头区已产出字节数(含头 CRC 两字节) */
|
||
uint16_t ch_done; /* 当前列已产出点数 */
|
||
uint8_t tail_done; /* 尾部数据 CRC 已产出字节数(0..2) */
|
||
uint8_t rsv; /* 补齐用,保持结构体偶数字节 */
|
||
uint16_t crc_data; /* 数据区运行 CRC(增量中间值,避免整段驻留) */
|
||
uint32_t total; /* 整段期望字节数 */
|
||
uint32_t sent; /* 已产出字节数(= 下一片的文件偏移) */
|
||
wave_seg_meta_t meta; /* 段头快照(start 时拷入;搬运期间调用方改自己那份不影响) */
|
||
wave_reader_fn p_reader; /* 数据源;NULL = 只产头 */
|
||
void *p_user; /* 数据源上下文 */
|
||
} wave_pack_ctx_t;
|
||
|
||
/* ===== 四、CRC-16/MODBUS(无表位运算) ===== */
|
||
|
||
/*
|
||
* 函数功能:CRC-16/MODBUS 增量计算(输入输出均低位在前)
|
||
* 入口参数:a_crc 上一次的中间值(首次传 WAVE_ST_CRC_INIT)
|
||
* p_data 数据指针(NULL 且 a_len = 0 视为空操作,原样返回 a_crc)
|
||
* a_len 字节数
|
||
* 返 回 值:新的中间值(写文件时低字节在前,见 §6.3 首行) uint16_t
|
||
* 限定条件:任意上下文(含 adcTask 之外的 defaultTask / uiTask);纯计算、无静态状态、
|
||
* 不碰外设、不取临界区
|
||
* 函数说明:为什么自带实现 —— 全仓没有可复用的 CRC-16 表(依据见本文件头注)。
|
||
* 无表 = 零新增表 RAM 与表 Flash;数据区 5120 字节每字节 8 次移位,在 168 MHz
|
||
* 上量级是几十微秒,且只在 defaultTask 的落盘链上出现,不在 1 kHz 采集链上。
|
||
*/
|
||
uint16_t wave_store_crc_update(uint16_t a_crc, const uint8_t *p_data, uint32_t a_len);
|
||
|
||
/*
|
||
* 函数功能:CRC-16/MODBUS 一次算完(从初值起步)
|
||
* 入口参数:p_data 数据指针
|
||
* a_len 字节数
|
||
* 返 回 值:CRC uint16_t
|
||
* 限定条件:任意上下文。p_data 为 NULL 或 a_len = 0 时返回 WAVE_ST_CRC_INIT —— 这是
|
||
* 空输入的定义值,不是「算好的段 CRC」:零长度一律判废,调用方必须先判长度
|
||
* (设计说明 §6.2 末行:长度为 0 → 整段作废)
|
||
*/
|
||
uint16_t wave_store_crc(const uint8_t *p_data, uint32_t a_len);
|
||
|
||
/*
|
||
* 函数功能:CRC 自检 —— 用规范锚点验算实现是否仍是 CRC-16/MODBUS
|
||
* 入口参数:无
|
||
* 返 回 值:WAVE_ST_OK 相符;WAVE_ST_E_FORMAT 不符(实现或编译配置被改坏) int
|
||
* 限定条件:任意上下文;开机或首次落盘前调一次即可,不在每拍路径上
|
||
* 函数说明:锚点 = 串 123456789 的 CRC = 0x4B37(§6.3)。这条是机器可执行的口径锚,
|
||
* 防的是「自算自验永远相符」:只有对已知常数才咬得住(换机、换编译器、
|
||
* 反射方向写反都在这里暴露)。落盘前失败就自弃本段并计入 err(§9 观测行)。
|
||
*/
|
||
int wave_store_crc_selftest(void);
|
||
|
||
/* ===== 五、段头编解码 ===== */
|
||
|
||
/*
|
||
* 函数功能:把段头元数据填成本波默认值(只填格式侧的静态事实)
|
||
* 入口参数:p_meta 输出(调用方持有的结构;本层不返回静态地址)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||
* 限定条件:任意上下文;不碰文件系统
|
||
* 函数说明:填 fs_hz = 1000、pts_per_cycle = 20、ch_count = 8、pts_per_ch = 320、
|
||
* pre_pts = 160,以及通道表的 sem = 下标 + conv / bits 按两片转换器
|
||
* (UA / UN / IA 走 TPAFE 16 位;UB / UC / IB / IC / IN 走片内 ADC 12 位 ——
|
||
* 通道归属口径属主是 docs/硬件平台/ADC通道映射说明.md,本函数只是它的抄本,
|
||
* 单测里对 0xE6 的算术交叉验证会同时钉住这套顺序)。
|
||
* 不填 avail / k_q16 / b_codes / trig_* / t0_ms / wall / eng / flags_seq /
|
||
* clip_mask / frame_seq:那些是封存瞬间的事实,必须由调用方现取现填
|
||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock / wave_ring),本层不链它们。
|
||
*/
|
||
int wave_store_meta_init(wave_seg_meta_t *p_meta);
|
||
|
||
/*
|
||
* 函数功能:把段头元数据编码成段文件头字节流(固定头 + 通道表 + 头 CRC,全小端)
|
||
* 入口参数:p_meta 段头元数据(只读)
|
||
* p_out 输出缓冲
|
||
* a_cap 缓冲容量(须不小于按 ch_count 算出的整头字节数)
|
||
* 返 回 值:不小于 0 = 实际写入字节数;负 = WAVE_ST_E_* int
|
||
* 限定条件:任意上下文;无静态缓冲、可重入;不碰文件系统
|
||
* 函数说明:编码前先自检字段一致性 —— ch_count 为 0 或越界、pts_per_ch = 0、
|
||
* pre_pts 大于 pts_per_ch、表内语义号不等于下标(§6.3「通道表序与列序」那条
|
||
* PC 侧判据在编码侧同样成立)一律拒绝:不把坏头写成看起来像样的文件。
|
||
*/
|
||
int wave_store_head_build(const wave_seg_meta_t *p_meta, uint8_t *p_out, uint32_t a_cap);
|
||
|
||
/*
|
||
* 函数功能:把段文件头字节流解回元数据,并做全套判废检查(魔数 / 版本 / 长度 / 表序 / 头 CRC)
|
||
* 入口参数:p_buf 头字节流
|
||
* a_len 可读字节数
|
||
* p_meta 输出元数据(判废时内容不保证有效,调用方不得消费)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针或缓冲不足;WAVE_ST_E_FORMAT 魔数 / 版本 /
|
||
* 表序不符;WAVE_ST_E_SIZE 零长度或长度与字段矛盾;WAVE_ST_E_CRC 头 CRC 不符 int
|
||
* 限定条件:任意上下文(uiTask 与 defaultTask 都可调)。UI 与 PC 侧必须用同一套判废语义:
|
||
* 解不过就只能显示「该段不可读」,绝不允许退化画一条直线
|
||
* (设计说明 §8.1 空态行:画直线等于伪造证据)
|
||
* 函数说明:a_len 不足「按头里 ch_count 推出来的整头长度」时按缓冲不足返回,不猜没读到
|
||
* 的字节;ver 不等于魔数第三字节、或不等于 WAVE_ST_VER 一律判废。
|
||
*/
|
||
int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t *p_meta);
|
||
|
||
/* ===== 六、整段校验与长度 / 偏移换算(读侧) ===== */
|
||
|
||
/*
|
||
* 函数功能:整段镜像自检 —— 头 + 数据区 + 数据 CRC 三道全过才算「这一段可读」
|
||
* 入口参数:p_img 整段文件镜像(调用方已读进内存)
|
||
* a_len 镜像长度
|
||
* p_meta 输出段头(可为 NULL)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_SIZE 长度为 0 或与期望总长不符;
|
||
* WAVE_ST_E_CRC 任一 CRC 不符;WAVE_ST_E_FORMAT 头本身判废 int
|
||
* 限定条件:任意上下文;只在整段驻留(本波 5250 字节)时用。回放页不要走这条 ——
|
||
* 用 wave_store_head_parse + wave_store_data_off + wave_store_crc_update 分段查,
|
||
* 才符合 §7 的 256 字节读窗预算
|
||
* 函数说明:这是「段文件 CRC 不符时脚本必须拒绝而不是画出东西」那条判据在板侧的同一份
|
||
* 实现,也是本层与 PC 解码器逐字节同解的对照物(两套主机单测互验)。
|
||
*/
|
||
int wave_store_seg_check(const uint8_t *p_img, uint32_t a_len, wave_seg_meta_t *p_meta);
|
||
|
||
/*
|
||
* 函数功能:按头部字段算整段文件应有的字节数(写侧记账与读侧长度判废共用)
|
||
* 入口参数:p_meta 段头元数据
|
||
* 返 回 值:期望字节数(8 路 320 点 = 5250);字段非法(含零长度)返回 0 uint32_t
|
||
* 限定条件:任意上下文
|
||
* 函数说明:返回 0 就是「这一段连长度都不成立」,据此判废。不要拿 0 去开缓冲,
|
||
* 也不要拿 0 去比对 f_write 的写回字节数(那会把失败读成成功)。
|
||
*/
|
||
uint32_t wave_store_seg_size(const wave_seg_meta_t *p_meta);
|
||
|
||
/*
|
||
* 函数功能:整头字节数(固定头 + 通道表 + 头 CRC 两字节)
|
||
* 入口参数:a_ch_count 通道数
|
||
* 返 回 值:字节数;通道数为 0 或越界返回 0 uint32_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint32_t wave_store_head_bytes(uint8_t a_ch_count);
|
||
|
||
/*
|
||
* 函数功能:数据区某一点在段文件里的绝对偏移(按通道分列 → 单通道是一整段连续区)
|
||
* 入口参数:a_ch_count 通道数
|
||
* a_pts_ch 每通道点数
|
||
* a_ch 列号(= 通道表下标 = 语义号,0 到 a_ch_count-1)
|
||
* a_pt 段内点下标(0 到 a_pts_ch-1)
|
||
* 返 回 值:文件偏移;参数非法返回 0xFFFFFFFFu 哨兵(这个值不是任何合法偏移) uint32_t
|
||
* 限定条件:任意上下文
|
||
* 函数说明:回放页据此定位「只读那一路」,配合 wave_store_data_bytes_avail 一次连续读;
|
||
* PC 侧同一算法,两份实现互验。
|
||
*/
|
||
uint32_t wave_store_data_off(uint8_t a_ch_count, uint16_t a_pts_ch,
|
||
uint8_t a_ch, uint32_t a_pt);
|
||
|
||
/*
|
||
* 函数功能:从某点起当前列还能连续读多少字节(不跨列、不越段尾)
|
||
* 入口参数:a_pts_ch 每通道点数
|
||
* a_pt 起始点下标
|
||
* a_want 想读的点数
|
||
* 返 回 值:可连续读字节数 = 2 × 实际可给点数;参数非法返回 0 uint32_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint32_t wave_store_data_bytes_avail(uint16_t a_pts_ch, uint32_t a_pt, uint32_t a_want);
|
||
|
||
/*
|
||
* 函数功能:把段文件读出的小端字节解码成 int16 点序列(按时间序,旧到新)
|
||
* 入口参数:p_raw 原始字节(来自文件,2 × 点数)
|
||
* a_len 字节数(须为偶数,且不大于 a_max_pts × 2)
|
||
* p_out 输出 int16 数组
|
||
* a_max_pts 输出容量(点数)
|
||
* p_pts 输出实际点数(可为 NULL)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 / 奇数字节 / 容量不足 int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:显式按小端逐字节拼装,不依赖宿主字节序,所以板端与 PC 侧同一判据。
|
||
* 片内 ADC 那几路的值本就是 0..4095 的原样正数,不做中点偏置
|
||
* (§6.3「样本编码」行:加偏置等于伪造双极性)。
|
||
*/
|
||
int wave_store_le_decode(const uint8_t *p_raw, uint32_t a_len,
|
||
int16_t *p_out, uint32_t a_max_pts, uint32_t *p_pts);
|
||
|
||
/*
|
||
* 函数功能:把一路的 int16 码值折成定点毫单位(回放页峰值标注用;纯整数,不用 float)
|
||
* 入口参数:p_desc 该路通道表项(k_q16 / b_codes 来自段文件头,不来自当前固件)
|
||
* a_code 原始码值
|
||
* p_milli 输出四舍五入后的毫单位(k_q16 是 µV/码 → 输出单位 mV)(不可为 NULL)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||
* 限定条件:任意上下文(只在回放页与单测里用,不在 1 kHz 采集链上;内部用 int64 中间量,
|
||
* 落在这条路径上时可接受)
|
||
* 函数说明:算式 eng = (code + b) × k × 1e-6,与 App/algo/adc_cal.c 的 adc_cal_apply()
|
||
* 同一模型(§6.3)。输出单位是 mV;单位含义由段头 eng 字段决定(eng = 0 是
|
||
* 引脚域,别读成二次值)。必须用段文件里的 k / b,不许拿当前固件的系数解释
|
||
* 老文件(§12 风险表「标定落地后老文件被解释错」)。
|
||
*/
|
||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||
int32_t *p_milli);
|
||
|
||
/* ===== 七、打包游标(分次搬运,给文件系统唯一写者) ===== */
|
||
|
||
/*
|
||
* 函数功能:初始化打包游标(段头拷进游标,随后逐次拉整段字节流)
|
||
* 入口参数:p_ctx 游标(调用方持有,栈上或静态都行;本层不记它的地址)
|
||
* p_meta 段头元数据(内部拷贝;搬运期间调用方改自己那份不影响本段)
|
||
* p_reader 数据源回调;NULL = 只产头(回放页只校验头 / 主机单测只测头)
|
||
* p_user 数据源上下文(生产传 NULL,wave_ring 是单例不需要)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_FORMAT 段头字段非法;
|
||
* WAVE_ST_E_SIZE 算不出合法长度 int
|
||
* 限定条件:任意上下文(纯计算,无临界区);一段一个游标,搬完再 start 下一段
|
||
* 函数说明:p_reader 为 NULL 而 ch_count 大于 0 时,游标仍能产头,但进数据区那一步
|
||
* 返回 WAVE_ST_E_STATE —— 不静默跳过数据区,否则调用方会写出一个
|
||
* 「长度看着对、内容是空的」段文件(那正是 §8.1 要防的伪造证据形态)。
|
||
*/
|
||
int wave_store_pack_start(wave_pack_ctx_t *p_ctx, const wave_seg_meta_t *p_meta,
|
||
wave_reader_fn p_reader, void *p_user);
|
||
|
||
/*
|
||
* 函数功能:取整段字节流的下一片(分次搬运的唯一推进口)
|
||
* 入口参数:p_ctx 已 start 的游标
|
||
* p_out 输出缓冲
|
||
* a_cap 本片容量(建议 256 字节,见 §7;数据区不切半点,奇数容量会少产 1 字节)
|
||
* p_len 输出本片实际字节数(不可为 NULL)
|
||
* 返 回 值:WAVE_ST_OK 产出本片且还有后续;WAVE_ST_DONE 整段已产完(*p_len 可为 0);
|
||
* 负 = WAVE_ST_E_*(未 start / 容量为 0 / 数据源回调返错 / 游标状态错) int
|
||
* 限定条件:任意上下文,但同一游标不得并发使用(文件系统唯一写者单线程推进)
|
||
* 函数说明:头区字节流与 wave_store_head_build 完全一致(内部同一份写字段函数);
|
||
* **头那一步要求 a_cap 不小于整头字节数**(本波 8 路 = 128),否则返回
|
||
* WAVE_ST_E_PARAM 且不推进游标——不拆着头给,是为了让游标不必存 128 字节
|
||
* 中间态;§7 的 256 字节搬运缓冲天然满足。
|
||
* 数据区 CRC 走增量中间值,整段最后两片是数据 CRC 的低字节、高字节。
|
||
* 调用方每写完一片都必须比对 f_write 写回字节数,短写按失败处理(§5.2)。
|
||
*/
|
||
int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||
uint32_t a_cap, uint32_t *p_len);
|
||
|
||
/*
|
||
* 函数功能:读游标已产出字节数(= 当前文件偏移;供记账、观测与「写没写全」判定)
|
||
* 入口参数:p_ctx 游标
|
||
* 返 回 值:已产出字节数;p_ctx 为 NULL 返回 0 uint32_t
|
||
* 限定条件:任意上下文(只读)
|
||
*/
|
||
uint32_t wave_store_pack_offset(const wave_pack_ctx_t *p_ctx);
|
||
|
||
/*
|
||
* 函数功能:读游标对应整段文件的期望总长(写完与 f_size / 累计写回字节数比对)
|
||
* 入口参数:p_ctx 游标
|
||
* 返 回 值:期望总字节数;p_ctx 为 NULL 返回 0 uint32_t
|
||
* 限定条件:任意上下文(只读)
|
||
*/
|
||
uint32_t wave_store_pack_total(const wave_pack_ctx_t *p_ctx);
|
||
|
||
/* ===== 八、段文件名轮转(只算名字,不碰文件系统) ===== */
|
||
|
||
/*
|
||
* 函数功能:按段号生成段文件路径(严格 8.3 短名:目录 + 前缀 + 两位十进制 + 扩展名)
|
||
* 入口参数:p_out 输出缓冲(容量须不小于 WAVE_ST_PATH_MAX)
|
||
* a_cap 容量
|
||
* a_idx 段号 0 到 WAVE_ST_SEG_MAX-1
|
||
* 返 回 值:不小于 0 = 写入的字符数(不含结束符;当前布局恒为 16,故缓冲至少要 17 字节);
|
||
* WAVE_ST_E_PARAM 空指针或容量不足;
|
||
* WAVE_ST_E_RANGE 段号越界 int
|
||
* 限定条件:任意上下文;纯字符拼装(不用 sprintf,见 CODE-RULE-06 系),逐字节可预期
|
||
* 函数说明:号 0..99 轮转覆盖,共 100 个名字,写满回绕盖最老的(§5.2 第三条)。
|
||
* 本函数不检查文件是否存在、不建目录:那些都是接线道的职责。
|
||
*/
|
||
int wave_store_seg_path(char *p_out, uint32_t a_cap, uint8_t a_idx);
|
||
|
||
/*
|
||
* 函数功能:下一个轮转段号(到 99 后回 0)
|
||
* 入口参数:a_idx 当前段号(越界输入按 0 处理,不猜)
|
||
* 返 回 值:下一个段号 uint8_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint8_t wave_store_next_idx(uint8_t a_idx);
|
||
|
||
/*
|
||
* 函数功能:从文件名或路径反解段号(掉电后靠扫目录重建段账用)
|
||
* 入口参数:p_path 路径或纯文件名(只看最后一段,大小写都收)
|
||
* p_idx 输出段号(不可为 NULL)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_NAME 不是本层命名形态 int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:只认「前缀 + 两位数字 + 扩展名」,其余(目录名、长名、以点开头)一律判不匹配
|
||
* —— 扫目录重建账时不许把陌生名字当录波段;名字/参数类错误码严禁升级成卷操作
|
||
* (docs/架构设计/STM32F4-Base存储架构说明.md §10.1 的同一条教训)。
|
||
*/
|
||
int wave_store_idx_from_name(const char *p_path, uint8_t *p_idx);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* WAVE_STORE_H */
|