Files
STM32F4-Base/App/algo/wave_store.h
T
wandering1 ae09c5737a docs: 删任务规划两份已完成文档,数据可信内容迁入算法层/硬件平台/登记簿
任务规划目录从 5 个文件收敛到 3 个:只留导航(README)、待办(登记簿)、
暂缓专项(暂缓-RS485)。

删除的两个文件都不再有现行读者:
- UI补齐.md 执行清单 2026-10-28 清空、上板判据 2026-11-06 台架轮已复核;
  叶数口径迁 docs/UI设计/菜单页面设计.md §3.9,三条内容页硬规矩迁
  docs/UI设计/内容页架构.md §7.1,两条未闭环待办迁登记簿 §7/§8。
- 数据可信.md 不是完成记录,而是被约 130 处代码头注释引用的设计约束,
  因此按内容分三处落位后删除,不整篇丢弃。

数据可信内容的新归属(旧节号 → 新家):
- §2/§4 设计口径 → docs/算法/采集与算法层设计说明.md 新增 §13(13 小节),
  §7 前置决策表 → §13.12 已定案的前置决策。
- §3 台阶与波次退出判据 → docs/硬件平台/ADC通道映射说明.md 新增 §9。
- §5 任务卡 → docs/任务规划/登记簿.md §7(拆成变比确认 / 两点标定两行)。

引用改写 34 文件 77 处路径引用 + 14 文件 24 处昵称引用 + 双重节号 35 处,
覆盖代码头注释、测试、生成器与文档;归档区历史路径改纯文本提及。
四个 SVG 生成器文案同步并重跑,产物随脚本同批提交。

同批处理两件事:
- 9 个 UTF-16 LE 证据文件转 UTF-8。end-of-file-fixer 按字节判末尾换行,
  UTF-16 的换行是 0A 00、末字节 00,钩子会追加裸 0x0a 使文件变奇数长度,
  8 个已实际损坏。转码后逐文件比对 HEAD 原文验证正文零丢失。
- 修回 App/cfg/cfg_file.c 被剥掉的 UTF-8 BOM(补 3 字节,未整文件重写)。

验证:/keil-build 0 错误 0 警告(Code=255160);check_doc_links --all
--sections-strict 221 篇全通过、无法判定 0;pre-commit 全量全 Passed。
2026-11-09 16:13:57 +08:00

492 lines
31 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.
/*
* 模块名称:录波段格式层(故障录波 · 段文件字节布局编解码 + 打包游标 + 段名轮转)
* 模块功能:把「一段录波」在内存字节缓冲与结构化元数据之间逐字节翻译,并给落盘道
* 提供分次搬运的打包游标。口径唯一属主 = 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 §13.3) */
#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 */