4a4ad6bc5f
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名, 不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体 run_boot_test.py 的真实调用。 - AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改 「编译(/keil-build)」「串口回归(/serial-boot-test)」 - keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree 方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围) - 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入 等 41 行历史证据保留旧名 - gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑 - 顺手补并行方案同行漏网计数 51→52 钩子 验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52 钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
982 lines
38 KiB
C
982 lines
38 KiB
C
/*
|
||
* 模块名称:录波段格式层实现(故障录波 · 段文件字节布局编解码 + 打包游标 + 段名轮转)
|
||
* 模块功能:见 App/algo/wave_store.h 的文件头注释块——口径属主、职责边界、跨任务安全、
|
||
* 二期不返工的约束全部写在那里,本文件不重复一遍。
|
||
* 本文件是纯逻辑:只 include App/algo/wave_store.h 与 C 标准库的 stdint/stddef,
|
||
* 不 include HAL / CMSIS / RTOS 头、不调 FatFs、不碰文件系统、不打日志。
|
||
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_store/
|
||
* 作者:DTU 项目组
|
||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||
* 修改记录:
|
||
* v1.0 2026-10-29 DTU 项目组 创建
|
||
*/
|
||
|
||
#include "wave_store.h"
|
||
|
||
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
|
||
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 /keil-build 才暴露:本层
|
||
* wave_store.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
|
||
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
|
||
#include <stddef.h>
|
||
|
||
/* 打包游标一次最多从数据源取多少个点(128 字节栈上临时区,落盘道在 defaultTask 上跑,
|
||
* 不在 1 kHz 采集链上;取批是为了把「每点一次回调」压成「每 64 点一次回调」) */
|
||
#define WAVE_ST_BATCH (64u)
|
||
/* 数据区点下标非法时的内部哨兵(对外一律走 wave_store_data_off 的 0xFFFFFFFFu) */
|
||
#define WAVE_ST_BAD_OFF (0xFFFFFFFFu)
|
||
|
||
/* ===== 内部:小端读写(显式移位拼装,不依赖宿主字节序,板端与 PC 侧同一判据) ===== */
|
||
|
||
/*
|
||
* 函数功能:写 16 位小端
|
||
* 入口参数:p 目标位置(至少 2 字节);v 值
|
||
* 返 回 值:无 void
|
||
* 限定条件:调用方保证 p 可写 2 字节
|
||
*/
|
||
static void wave_st_put16(uint8_t *p, uint16_t v) {
|
||
p[0] = (uint8_t)(v & 0xFFu);
|
||
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:写 32 位小端
|
||
* 入口参数:p 目标位置(至少 4 字节);v 值
|
||
* 返 回 值:无 void
|
||
* 限定条件:调用方保证 p 可写 4 字节
|
||
*/
|
||
static void wave_st_put32(uint8_t *p, uint32_t v) {
|
||
p[0] = (uint8_t)(v & 0xFFu);
|
||
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||
p[2] = (uint8_t)((v >> 16) & 0xFFu);
|
||
p[3] = (uint8_t)((v >> 24) & 0xFFu);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读 16 位小端
|
||
* 入口参数:p 源位置(至少 2 字节)
|
||
* 返 回 值:值 uint16_t
|
||
* 限定条件:调用方保证 p 可读 2 字节
|
||
*/
|
||
static uint16_t wave_st_get16(const uint8_t *p) {
|
||
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读 32 位小端
|
||
* 入口参数:p 源位置(至少 4 字节)
|
||
* 返 回 值:值 uint32_t
|
||
* 限定条件:调用方保证 p 可读 4 字节
|
||
*/
|
||
static uint32_t wave_st_get32(const uint8_t *p) {
|
||
return ((uint32_t)p[0]) | (((uint32_t)p[1]) << 8) |
|
||
(((uint32_t)p[2]) << 16) | (((uint32_t)p[3]) << 24);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:把 int16 写成小端两字节
|
||
* 入口参数:p 目标位置;v 码值
|
||
* 返 回 值:无 void
|
||
* 限定条件:调用方保证 p 可写 2 字节
|
||
* 函数说明:先转 uint16 再移位——负数右移是实现定义行为,转成无符号后行为确定。
|
||
*/
|
||
static void wave_st_put_code(uint8_t *p, int16_t v) {
|
||
wave_st_put16(p, (uint16_t)v);
|
||
}
|
||
|
||
/* ===== 内部:段头字段一致性自检(编码前与解码后各跑一次,同一把尺子) ===== */
|
||
|
||
/*
|
||
* 函数功能:校验段头元数据的**几何与表序**是否自相矛盾
|
||
* 入口参数:p_meta 段头元数据(非空)
|
||
* 返 回 值:WAVE_ST_OK = 合法;WAVE_ST_E_SIZE 长度类不成立(零通道 / 零点数 / 预触发超段长);
|
||
* WAVE_ST_E_FORMAT 档位 / 触发源 / 表序非法 int
|
||
* 限定条件:p_meta 由调用方保证非空
|
||
* 函数说明:为什么编码侧也要跑:写一个「看起来像样的坏段文件」比拒写糟得多——落盘道会
|
||
* 把 CRC 与长度都算自洽的垃圾写进 NAND,等到回放页或 PC 脚本才发现,那时
|
||
* 现场已经没了(§8.1「画成一条直线等于伪造证据」是同一条忌讳的另一半)。
|
||
*/
|
||
static int wave_st_meta_check(const wave_seg_meta_t *p_meta) {
|
||
uint8_t i;
|
||
|
||
/* 零通道 / 零点数:这一段连长度都不成立(§6.2 末行「长度为 0 判废」) */
|
||
if ((p_meta->ch_count == 0u) || (p_meta->ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (p_meta->pts_per_ch == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
/* 预触发点数不能大于一段的点数;后触发 = pts_per_ch - pre_pts,允许为 0(手动触发
|
||
* 撞上落盘窗口等极端情形),但不允许负数——那是把段长算错 */
|
||
if (p_meta->pre_pts > p_meta->pts_per_ch) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
/* 采样率与每周波点数是数据,但为 0 的数据没法解释(回放与 PC 都要拿它做除数) */
|
||
if ((p_meta->fs_hz == 0u) || (p_meta->pts_per_cycle == 0u)) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
/* 与 PC 侧 reader 的拒绝清单对齐(谁都不能产出一段「自己写得出、对面读不了」的文件):
|
||
* 每周波不足 2 点连一个周期都描不出来;段长不足一个整周期则基波算法无从取窗。 */
|
||
if (p_meta->pts_per_cycle < 2u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (p_meta->pts_per_ch < (uint32_t)p_meta->pts_per_cycle) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (p_meta->eng > (uint8_t)WAVE_ENG_PRI) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
if (p_meta->trig_src > (uint8_t)WAVE_TRIG_BENCH) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
/* 表序:第 i 项的语义号必须等于下标(§6.3「通道表序与列序」行)——
|
||
* 数据区第 i 列归第 i 项解释,错一位就是拿 IB 的波形配 IC 的系数 */
|
||
for (i = 0u; i < p_meta->ch_count; i++) {
|
||
if (p_meta->ch[i].sem != i) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
if (p_meta->ch[i].conv > (uint8_t)1u) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
/* 位宽留个合理域:本波只有 16(TPAFE)与 12(片内 ADC),但格式层不写死这两个值,
|
||
* 只挡明显不成立的(0 位、超过 32 位)——二期换转换器不必改这里 */
|
||
if ((p_meta->ch[i].bits == 0u) || (p_meta->ch[i].bits > 32u)) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
}
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* ===== CRC-16/MODBUS ===== */
|
||
|
||
/*
|
||
* 函数功能:CRC-16/MODBUS 增量计算
|
||
* 入口参数:a_crc 上一次的中间值(首次传 WAVE_ST_CRC_INIT)
|
||
* p_data 数据(NULL 视为空操作)
|
||
* a_len 字节数
|
||
* 返 回 值:新的中间值 uint16_t
|
||
* 限定条件:任意上下文;纯计算
|
||
* 函数说明:反射形态 = 右移、异或 0xA001、初值 0xFFFF、异或输出 0;
|
||
* 写进文件时低字节在前(§6.3)。无表实现的理由见 wave_store.h 头注。
|
||
*/
|
||
uint16_t wave_store_crc_update(uint16_t a_crc, const uint8_t *p_data, uint32_t a_len) {
|
||
uint16_t crc = a_crc;
|
||
uint32_t i;
|
||
|
||
if (p_data == NULL) {
|
||
return crc;
|
||
}
|
||
for (i = 0u; i < a_len; i++) {
|
||
uint8_t b = p_data[i]; /* 身兼两职:本路字节 + 下面内层 8 次的计数器 */
|
||
|
||
crc = (uint16_t)(crc ^ (uint16_t)b);
|
||
for (b = 0u; b < 8u; b++) {
|
||
if ((crc & 0x0001u) != 0u) {
|
||
crc = (uint16_t)((crc >> 1) ^ WAVE_ST_CRC_POLY);
|
||
} else {
|
||
crc = (uint16_t)(crc >> 1);
|
||
}
|
||
}
|
||
}
|
||
/* 异或输出恒 0:写成宏引用,换异或输出时只改一处 */
|
||
return (uint16_t)(crc ^ WAVE_ST_CRC_XOROUT);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:CRC-16/MODBUS 一次算完
|
||
* 入口参数:p_data 数据;a_len 字节数
|
||
* 返 回 值:CRC;空输入返回初值定义 uint16_t
|
||
* 限定条件:任意上下文。零长度的判废由调用方做(见 wave_store.h 同函数注释)
|
||
*/
|
||
uint16_t wave_store_crc(const uint8_t *p_data, uint32_t a_len) {
|
||
return wave_store_crc_update(WAVE_ST_CRC_INIT, p_data, a_len);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:CRC 实现自检(对规范锚点,不是自算自验)
|
||
* 入口参数:无
|
||
* 返 回 值:WAVE_ST_OK 相符;WAVE_ST_E_FORMAT 不符 int
|
||
* 限定条件:任意上下文;开机或首次落盘前一次,不在每拍路径上
|
||
* 函数说明:锚点串「123456789」的 CRC-16/MODBUS = 0x4B37(§6.3)。字面量必须纯 ASCII,
|
||
* 所以这里用显式字节数组而不是把中文塞进字符串。
|
||
*/
|
||
int wave_store_crc_selftest(void) {
|
||
static const uint8_t s_anchor[9] = {
|
||
(uint8_t)0x31u, (uint8_t)0x32u, (uint8_t)0x33u,
|
||
(uint8_t)0x34u, (uint8_t)0x35u, (uint8_t)0x36u,
|
||
(uint8_t)0x37u, (uint8_t)0x38u, (uint8_t)0x39u
|
||
};
|
||
uint16_t got;
|
||
|
||
got = wave_store_crc(s_anchor, (uint32_t)sizeof(s_anchor));
|
||
if (got != WAVE_ST_CRC_CHECK) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* ===== 段头编解码 ===== */
|
||
|
||
/* 语义通道的转换器归属与位宽(0 = TPAFE5160,1 = 片内 ADC1;口径属主是
|
||
* docs/硬件平台/ADC通道映射说明.md,本表是其抄本,改硬件接线时两处一起改)。
|
||
* 第 9 项 TEMP 本波不录入段文件(ch_count 恒 8),仍按实际归属填,
|
||
* 免得将来放开 TEMP 时这里是个 0。 */
|
||
static const uint8_t s_sem_conv[(uint8_t)WAVE_ST_CH_MAX] = {
|
||
(uint8_t)0u, (uint8_t)1u, (uint8_t)1u,
|
||
(uint8_t)0u, (uint8_t)0u, (uint8_t)1u, (uint8_t)1u, (uint8_t)1u, (uint8_t)1u
|
||
};
|
||
static const uint8_t s_sem_bits[(uint8_t)WAVE_ST_CH_MAX] = {
|
||
16u, 12u, 12u,
|
||
16u, 16u, 12u, 12u, 12u, 12u
|
||
};
|
||
|
||
/*
|
||
* 函数功能:把段头元数据填成本波默认值(只填格式侧静态事实)
|
||
* 入口参数:p_meta 输出
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:填三样格式侧的静态事实:语义号 = 下标(§6.3 表序口径,也是 PC 侧判据)、
|
||
* conv / bits 按两片转换器的归属(下面那张表的口径属主是
|
||
* docs/硬件平台/ADC通道映射说明.md 的抄本,本层刻意不带 adc_input.h,
|
||
* 这样格式层在主机侧不需要任何假件就能编译;单测里 0xE6 的算术交叉验证
|
||
* 会同时钉住这套顺序,抄错就会红)。
|
||
* 不填 avail / k_q16 / b_codes:那些是封存瞬间的事实,必须由调用方现取现填
|
||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock),本层不链它们;
|
||
* 调用方忘填 k_q16 与 avail 不会让编码失败(0 是合法整数),
|
||
* 但会让 PC 侧把波形解释成一条 0 线——所以单测里有一条专门检查
|
||
* 「填了系数的往返」与「没填系数时段的形状仍然正确、只是幅值归零」。
|
||
*/
|
||
int wave_store_meta_init(wave_seg_meta_t *p_meta) {
|
||
uint8_t i;
|
||
|
||
if (p_meta == NULL) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
p_meta->fs_hz = (uint16_t)WAVE_ST_FS_DEF;
|
||
p_meta->pts_per_cycle = (uint8_t)WAVE_ST_PPC_DEF;
|
||
p_meta->ch_count = (uint8_t)WAVE_ST_CH_NUM;
|
||
p_meta->pts_per_ch = (uint16_t)WAVE_ST_PTS_DEF;
|
||
p_meta->pre_pts = (uint16_t)WAVE_ST_PRE_DEF;
|
||
p_meta->trig_src = (uint8_t)WAVE_TRIG_NONE;
|
||
p_meta->trig_elm = 0u;
|
||
p_meta->t0_ms = 0u;
|
||
p_meta->wall = 0u;
|
||
p_meta->eng = (uint8_t)WAVE_ENG_PIN;
|
||
p_meta->flags_seq = 0u;
|
||
p_meta->clip_mask = 0u;
|
||
p_meta->frame_seq = 0u;
|
||
for (i = 0u; i < (uint8_t)WAVE_ST_CH_MAX; i++) {
|
||
p_meta->ch[i].sem = i;
|
||
p_meta->ch[i].conv = s_sem_conv[i];
|
||
p_meta->ch[i].bits = s_sem_bits[i];
|
||
p_meta->ch[i].avail = 0u;
|
||
p_meta->ch[i].k_q16 = 0;
|
||
p_meta->ch[i].b_codes = 0;
|
||
}
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:内部——把固定头 + 通道表写进缓冲(不含头 CRC)
|
||
* 入口参数:p_meta 段头;p_out 缓冲(容量须 >= 头 CRC 覆盖区)
|
||
* 返 回 值:写入字节数(= 头 CRC 覆盖区长度) uint32_t
|
||
* 限定条件:p_meta 已过 wave_st_meta_check、p_out 容量已核过
|
||
*/
|
||
static uint32_t wave_st_head_raw(const wave_seg_meta_t *p_meta, uint8_t *p_out) {
|
||
uint8_t i;
|
||
const uint32_t tbl_n = (uint32_t)p_meta->ch_count * WAVE_ST_CH_BYTES;
|
||
|
||
p_out[WAVE_ST_OFF_MAGIC] = (uint8_t)WAVE_ST_MAGIC0;
|
||
p_out[WAVE_ST_OFF_MAGIC + 1u] = (uint8_t)WAVE_ST_MAGIC1;
|
||
/* 魔数第三字节 = 版本号:与 ver 字段同源同值(§6.2 第 0 行 + §6.3 判废口径) */
|
||
p_out[WAVE_ST_OFF_MAGIC + 2u] = (uint8_t)WAVE_ST_VER;
|
||
p_out[WAVE_ST_OFF_VER] = (uint8_t)WAVE_ST_VER;
|
||
wave_st_put16(&p_out[WAVE_ST_OFF_FS_HZ], p_meta->fs_hz);
|
||
p_out[WAVE_ST_OFF_PTS_CYC] = p_meta->pts_per_cycle;
|
||
p_out[WAVE_ST_OFF_CH_CNT] = p_meta->ch_count;
|
||
wave_st_put16(&p_out[WAVE_ST_OFF_PTS_CH], p_meta->pts_per_ch);
|
||
wave_st_put16(&p_out[WAVE_ST_OFF_PRE_PTS], p_meta->pre_pts);
|
||
p_out[WAVE_ST_OFF_TRIG_SRC] = p_meta->trig_src;
|
||
p_out[WAVE_ST_OFF_TRIG_ELM] = p_meta->trig_elm;
|
||
wave_st_put32(&p_out[WAVE_ST_OFF_T0_MS], p_meta->t0_ms);
|
||
wave_st_put32(&p_out[WAVE_ST_OFF_WALL], p_meta->wall);
|
||
p_out[WAVE_ST_OFF_ENG] = p_meta->eng;
|
||
p_out[WAVE_ST_OFF_FLAGS] = p_meta->flags_seq;
|
||
wave_st_put16(&p_out[WAVE_ST_OFF_CLIP], p_meta->clip_mask);
|
||
wave_st_put32(&p_out[WAVE_ST_OFF_SEQ], p_meta->frame_seq);
|
||
|
||
for (i = 0u; i < p_meta->ch_count; i++) {
|
||
uint32_t pos = (uint32_t)WAVE_ST_OFF_TBL + ((uint32_t)i * WAVE_ST_CH_BYTES);
|
||
|
||
p_out[pos + WAVE_ST_CH_OFF_SEM] = p_meta->ch[i].sem;
|
||
p_out[pos + WAVE_ST_CH_OFF_CONV] = p_meta->ch[i].conv;
|
||
p_out[pos + WAVE_ST_CH_OFF_BITS] = p_meta->ch[i].bits;
|
||
p_out[pos + WAVE_ST_CH_OFF_AVAIL] = p_meta->ch[i].avail;
|
||
/* int32 系数按小端原样落 4 字节:右移到 24 位前把符号扩展掉,
|
||
* 保证板端 ARMCC 与主机 CRT 写出同一串字节 */
|
||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_KQ16], (uint32_t)p_meta->ch[i].k_q16);
|
||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_BCODE], (uint32_t)p_meta->ch[i].b_codes);
|
||
}
|
||
return (uint32_t)WAVE_ST_OFF_TBL + tbl_n;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:编码段头(固定头 + 通道表 + 头 CRC)
|
||
* 入口参数:p_meta 段头元数据;p_out 输出缓冲;a_cap 容量
|
||
* 返 回 值:>= 0 写入字节数;负 = WAVE_ST_E_* int
|
||
* 限定条件:任意上下文;无静态缓冲、可重入
|
||
* 函数说明:头 CRC 覆盖 [0, 30+12×ch_count)、自身紧随其后、低字节在前(§6.3)。
|
||
*/
|
||
int wave_store_head_build(const wave_seg_meta_t *p_meta, uint8_t *p_out, uint32_t a_cap) {
|
||
uint32_t crc_pos;
|
||
uint16_t crc;
|
||
int rc;
|
||
|
||
if ((p_meta == NULL) || (p_out == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
rc = wave_st_meta_check(p_meta);
|
||
if (rc != WAVE_ST_OK) {
|
||
return rc;
|
||
}
|
||
crc_pos = WAVE_ST_HEAD_CRC_POS(p_meta->ch_count);
|
||
if (a_cap < (crc_pos + 2u)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
(void)wave_st_head_raw(p_meta, p_out);
|
||
crc = wave_store_crc(p_out, crc_pos);
|
||
/* 低字节在前(§6.3 首行):put16 本来就是小端,这里不额外翻转 */
|
||
wave_st_put16(&p_out[crc_pos], crc);
|
||
return (int)(crc_pos + 2u);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:解码 + 全套判废(魔数 / 版本 / 缓冲长度 / 几何 / 表序 / 头 CRC)
|
||
* 入口参数:p_buf 头字节流;a_len 可读字节数;p_meta 输出
|
||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_*(见 wave_store.h) int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:顺序刻意是「先验魔数与版本,再算整头长度,再验 CRC,最后验语义」——
|
||
* 前两步决定我们**有没有资格**按这份布局去数偏移;反过来先按 ch_count 算长度,
|
||
* 一个被写坏的 ch_count 就能让我们读到缓冲外面去。
|
||
*/
|
||
int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t *p_meta) {
|
||
uint8_t ch_count;
|
||
uint32_t crc_pos;
|
||
uint32_t head_sz;
|
||
uint16_t want;
|
||
uint16_t got;
|
||
int rc;
|
||
wave_seg_meta_t tmp;
|
||
|
||
if ((p_buf == NULL) || (p_meta == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
/* 固定头是 30 + 1 字节通道数才能算出整头长度,至少得读到偏移 7 */
|
||
if (a_len < ((uint32_t)WAVE_ST_OFF_CH_CNT + 1u)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
if ((p_buf[WAVE_ST_OFF_MAGIC] != (uint8_t)WAVE_ST_MAGIC0) ||
|
||
(p_buf[WAVE_ST_OFF_MAGIC + 1u] != (uint8_t)WAVE_ST_MAGIC1)) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
/* ver 必须等于魔数第三字节、且等于本层认识的版本(§6.2 末行) */
|
||
if ((p_buf[WAVE_ST_OFF_VER] != p_buf[WAVE_ST_OFF_MAGIC + 2u]) ||
|
||
(p_buf[WAVE_ST_OFF_VER] != (uint8_t)WAVE_ST_VER)) {
|
||
return WAVE_ST_E_FORMAT;
|
||
}
|
||
ch_count = p_buf[WAVE_ST_OFF_CH_CNT];
|
||
head_sz = wave_store_head_bytes(ch_count);
|
||
if (head_sz == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (a_len < head_sz) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
crc_pos = WAVE_ST_HEAD_CRC_POS(ch_count);
|
||
want = wave_st_get16(&p_buf[crc_pos]);
|
||
got = wave_store_crc(p_buf, crc_pos);
|
||
if (want != got) {
|
||
return WAVE_ST_E_CRC;
|
||
}
|
||
|
||
/* 头 CRC 过了才开始解释字段:下面填的是一份可交回调用方的完整元数据 */
|
||
tmp.fs_hz = wave_st_get16(&p_buf[WAVE_ST_OFF_FS_HZ]);
|
||
tmp.pts_per_cycle = p_buf[WAVE_ST_OFF_PTS_CYC];
|
||
tmp.ch_count = ch_count;
|
||
tmp.pts_per_ch = wave_st_get16(&p_buf[WAVE_ST_OFF_PTS_CH]);
|
||
tmp.pre_pts = wave_st_get16(&p_buf[WAVE_ST_OFF_PRE_PTS]);
|
||
tmp.trig_src = p_buf[WAVE_ST_OFF_TRIG_SRC];
|
||
tmp.trig_elm = p_buf[WAVE_ST_OFF_TRIG_ELM];
|
||
tmp.t0_ms = wave_st_get32(&p_buf[WAVE_ST_OFF_T0_MS]);
|
||
tmp.wall = wave_st_get32(&p_buf[WAVE_ST_OFF_WALL]);
|
||
tmp.eng = p_buf[WAVE_ST_OFF_ENG];
|
||
tmp.flags_seq = p_buf[WAVE_ST_OFF_FLAGS];
|
||
tmp.clip_mask = wave_st_get16(&p_buf[WAVE_ST_OFF_CLIP]);
|
||
tmp.frame_seq = wave_st_get32(&p_buf[WAVE_ST_OFF_SEQ]);
|
||
for (ch_count = 0u; ch_count < (uint8_t)WAVE_ST_CH_MAX; ch_count++) {
|
||
tmp.ch[ch_count].sem = 0u;
|
||
tmp.ch[ch_count].conv = 0u;
|
||
tmp.ch[ch_count].bits = 0u;
|
||
tmp.ch[ch_count].avail = 0u;
|
||
tmp.ch[ch_count].k_q16 = 0;
|
||
tmp.ch[ch_count].b_codes = 0;
|
||
}
|
||
for (ch_count = 0u; ch_count < p_buf[WAVE_ST_OFF_CH_CNT]; ch_count++) {
|
||
uint32_t pos = (uint32_t)WAVE_ST_OFF_TBL + ((uint32_t)ch_count * WAVE_ST_CH_BYTES);
|
||
tmp.ch[ch_count].sem = p_buf[pos + WAVE_ST_CH_OFF_SEM];
|
||
tmp.ch[ch_count].conv = p_buf[pos + WAVE_ST_CH_OFF_CONV];
|
||
tmp.ch[ch_count].bits = p_buf[pos + WAVE_ST_CH_OFF_BITS];
|
||
tmp.ch[ch_count].avail = p_buf[pos + WAVE_ST_CH_OFF_AVAIL];
|
||
tmp.ch[ch_count].k_q16 = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_KQ16]);
|
||
tmp.ch[ch_count].b_codes = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_BCODE]);
|
||
}
|
||
rc = wave_st_meta_check(&tmp);
|
||
if (rc != WAVE_ST_OK) {
|
||
return rc;
|
||
}
|
||
*p_meta = tmp;
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* ===== 长度 / 偏移换算 ===== */
|
||
|
||
/*
|
||
* 函数功能:整头字节数
|
||
* 入口参数:a_ch_count 通道数
|
||
* 返 回 值:字节数;通道数非法返回 0 uint32_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint32_t wave_store_head_bytes(uint8_t a_ch_count) {
|
||
if ((a_ch_count == 0u) || (a_ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||
return 0u;
|
||
}
|
||
return WAVE_ST_HEAD_BYTES(a_ch_count);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按头部字段算整段应有字节数
|
||
* 入口参数:p_meta 段头
|
||
* 返 回 值:期望字节数;字段非法返回 0 uint32_t
|
||
* 限定条件:任意上下文
|
||
* 函数说明:等式 = 32 + 12×ch_count + pts_per_ch×ch_count×2 + 2(§6.3;
|
||
* 8 路 320 点 = 5250)。落盘道拿它比对 f_write 写回字节数与 f_size。
|
||
*/
|
||
uint32_t wave_store_seg_size(const wave_seg_meta_t *p_meta) {
|
||
if (p_meta == NULL) {
|
||
return 0u;
|
||
}
|
||
if ((p_meta->ch_count == 0u) || (p_meta->ch_count > (uint8_t)WAVE_ST_CH_MAX)) {
|
||
return 0u;
|
||
}
|
||
if (p_meta->pts_per_ch == 0u) {
|
||
return 0u;
|
||
}
|
||
return WAVE_ST_SEG_TOTAL(p_meta->ch_count, p_meta->pts_per_ch);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:数据区某点的文件绝对偏移
|
||
* 入口参数:a_ch_count 通道数;a_pts_ch 每通道点数;a_ch 列号;a_pt 段内点下标
|
||
* 返 回 值:偏移;非法返回 0xFFFFFFFFu 哨兵 uint32_t
|
||
* 限定条件:任意上下文
|
||
* 函数说明:按通道分列 → 偏移 = 整头长 + (列 × 点数 + 点下标) × 2。单通道整列是连续的,
|
||
* 这正是 §6.2「只读那一路」能一次连续读的原因。
|
||
*/
|
||
uint32_t wave_store_data_off(uint8_t a_ch_count, uint16_t a_pts_ch,
|
||
uint8_t a_ch, uint32_t a_pt) {
|
||
uint32_t head_sz = wave_store_head_bytes(a_ch_count);
|
||
|
||
if ((head_sz == 0u) || (a_pts_ch == 0u) || (a_ch >= a_ch_count)) {
|
||
return WAVE_ST_BAD_OFF;
|
||
}
|
||
if (a_pt >= (uint32_t)a_pts_ch) {
|
||
return WAVE_ST_BAD_OFF;
|
||
}
|
||
return head_sz + (((uint32_t)a_ch * (uint32_t)a_pts_ch) + a_pt) * 2u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从某点起当前列还能连续读多少字节
|
||
* 入口参数:a_pts_ch 每通道点数;a_pt 起始点下标;a_want 想读点数
|
||
* 返 回 值:可连续读字节数(参数非法返回 0) uint32_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint32_t wave_store_data_bytes_avail(uint16_t a_pts_ch, uint32_t a_pt, uint32_t a_want) {
|
||
uint32_t left;
|
||
uint32_t n;
|
||
|
||
if ((a_pts_ch == 0u) || (a_want == 0u)) {
|
||
return 0u;
|
||
}
|
||
if (a_pt >= (uint32_t)a_pts_ch) {
|
||
return 0u;
|
||
}
|
||
left = (uint32_t)a_pts_ch - a_pt;
|
||
n = (a_want < left) ? a_want : left;
|
||
return n * 2u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:小端字节 → int16 点序列
|
||
* 入口参数:p_raw 原始字节;a_len 字节数;p_out 输出;a_max_pts 容量;p_pts 实际点数(可空)
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 / 奇数字节 / 容量不足 int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:奇数字节按参数错处理而不是「丢掉尾巴」——半截样本被静默吞掉,
|
||
* 回放页画出来的波形会比真的少最后一个点,而这种误差正好最难查。
|
||
*/
|
||
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) {
|
||
uint32_t n;
|
||
uint32_t i;
|
||
|
||
if ((p_raw == NULL) || (p_out == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
if (((a_len & 0x00000001u) != 0u) || (a_len > (a_max_pts * 2u))) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
n = a_len / 2u;
|
||
for (i = 0u; i < n; i++) {
|
||
/* 先攒成 uint16 再转 int16:补码回绕在 C 里是实现定义但两大编译器一致,
|
||
* 而这里需要的正是「按补码位型解释」——TPAFE 的 16 位补码就是这么存的 */
|
||
p_out[i] = (int16_t)wave_st_get16(&p_raw[i * 2u]);
|
||
}
|
||
if (p_pts != NULL) {
|
||
*p_pts = n;
|
||
}
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:码值 → 定点毫单位(mV)
|
||
* 入口参数:p_desc 通道表项(必须来自段文件头);a_code 码值;p_milli 输出 mV
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||
* 限定条件:任意上下文(只在回放页与单测用,不在 1 kHz 采集链上;用 int64 中间量)
|
||
* 函数说明:模型 eng = (code + b) × k × 1e-6(§6.3,与 adc_cal_apply() 同式),
|
||
* k = k_q16 / 65536 µV/码 → mV = (code + b) × k_q16 / 65536000,四舍五入。
|
||
* 中间量最大约 (32767 + 8192) × 5.28e7 ≈ 2.2e12,int64 宽裕。
|
||
*/
|
||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||
int32_t *p_milli) {
|
||
int64_t num;
|
||
int64_t den = 65536000;
|
||
int64_t v;
|
||
|
||
if ((p_desc == NULL) || (p_milli == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
num = ((int64_t)a_code + (int64_t)p_desc->b_codes) * (int64_t)p_desc->k_q16;
|
||
if (num >= 0) {
|
||
v = (num + (den / 2)) / den;
|
||
} else {
|
||
v = -(((-num) + (den / 2)) / den);
|
||
}
|
||
*p_milli = (int32_t)v;
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* ===== 整段校验 ===== */
|
||
|
||
/*
|
||
* 函数功能:整段镜像自检(头 + 数据区 + 数据 CRC)
|
||
* 入口参数:p_img 镜像;a_len 长度;p_meta 输出段头(可空)
|
||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_*(见 wave_store.h) int
|
||
* 限定条件:任意上下文;整段驻留时用(本波 5250 字节)
|
||
* 函数说明:三条判废一条都不能省:长度 0 或不等、头 CRC、数据 CRC。
|
||
* 期望总长用**头里的字段**算,不用编译期常量——文件是自己说它多长,
|
||
* 与镜像实际长度不一致就是坏段(§6.2 末行)。
|
||
*/
|
||
int wave_store_seg_check(const uint8_t *p_img, uint32_t a_len, wave_seg_meta_t *p_meta) {
|
||
wave_seg_meta_t meta;
|
||
uint32_t want_total;
|
||
uint32_t data_off;
|
||
uint32_t data_len;
|
||
uint16_t want;
|
||
uint16_t got;
|
||
int rc;
|
||
|
||
if (p_img == NULL) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
if (a_len == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
rc = wave_store_head_parse(p_img, a_len, &meta);
|
||
if (rc != WAVE_ST_OK) {
|
||
return rc;
|
||
}
|
||
want_total = wave_store_seg_size(&meta);
|
||
if (want_total == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (a_len != want_total) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
data_off = wave_store_head_bytes(meta.ch_count); /* 数据区起点 = 整头字节数 */
|
||
data_len = a_len - 2u - data_off;
|
||
if (data_len == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
want = wave_st_get16(&p_img[a_len - 2u]);
|
||
got = wave_store_crc(&p_img[data_off], data_len);
|
||
if (want != got) {
|
||
return WAVE_ST_E_CRC;
|
||
}
|
||
if (p_meta != NULL) {
|
||
*p_meta = meta;
|
||
}
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* ===== 打包游标 ===== */
|
||
|
||
/*
|
||
* 函数功能:初始化打包游标
|
||
* 入口参数:p_ctx 游标;p_meta 段头;p_reader 数据源(可空 = 只产头);p_user 上下文
|
||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_* int
|
||
* 限定条件:任意上下文(纯计算);一段一个游标
|
||
* 函数说明:段头整份拷进游标——搬运窗口几十毫秒,期间调用方完全可以去改它自己那份
|
||
* (比如 adcTask 又跑了几拍),本段的字节不能受影响。
|
||
*/
|
||
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) {
|
||
int rc;
|
||
|
||
if ((p_ctx == NULL) || (p_meta == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
rc = wave_st_meta_check(p_meta);
|
||
if (rc != WAVE_ST_OK) {
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_IDLE;
|
||
return rc;
|
||
}
|
||
p_ctx->total = wave_store_seg_size(p_meta);
|
||
if (p_ctx->total == 0u) {
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_IDLE;
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_HEAD;
|
||
p_ctx->ch = 0u;
|
||
p_ctx->head_done = 0u;
|
||
p_ctx->ch_done = 0u;
|
||
p_ctx->tail_done = 0u;
|
||
p_ctx->rsv = 0u;
|
||
p_ctx->crc_data = WAVE_ST_CRC_INIT;
|
||
p_ctx->sent = 0u;
|
||
p_ctx->p_reader = p_reader;
|
||
p_ctx->p_user = p_user;
|
||
p_ctx->meta = *p_meta;
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取整段字节流的下一片
|
||
* 入口参数:p_ctx 游标;p_out 缓冲;a_cap 容量;p_len 实际产出字节数
|
||
* 返 回 值:WAVE_ST_OK 还有后续;WAVE_ST_DONE 整段产完;负 = WAVE_ST_E_* int
|
||
* 限定条件:任意上下文,同一游标不得并发使用
|
||
* 函数说明:三段推进——头(一次给完,要求容量 >= 整头)、数据区(按列连续、整样本切,
|
||
* 绝不切半个 int16,也不跨列,便于回调按列读)、尾 CRC 两字节。
|
||
* 数据 CRC 边产边算(增量中间值),所以全程不需要整段驻留 RAM。
|
||
*/
|
||
int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||
uint32_t a_cap, uint32_t *p_len) {
|
||
uint32_t n = 0u;
|
||
|
||
if ((p_ctx == NULL) || (p_out == NULL) || (p_len == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
*p_len = 0u;
|
||
if ((p_ctx->phase == (uint8_t)WAVE_PACK_IDLE) || (a_cap == 0u)) {
|
||
return WAVE_ST_E_STATE;
|
||
}
|
||
if (p_ctx->phase == (uint8_t)WAVE_PACK_DONE) {
|
||
return WAVE_ST_DONE;
|
||
}
|
||
|
||
/* --- 头:整头一次产完(接线索按 §7 给 256 字节缓冲,足够 128 字节整头) --- */
|
||
if (p_ctx->phase == (uint8_t)WAVE_PACK_HEAD) {
|
||
uint32_t head_sz;
|
||
int rc;
|
||
|
||
head_sz = wave_store_head_bytes(p_ctx->meta.ch_count);
|
||
if (head_sz == 0u) {
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
if (a_cap < head_sz) {
|
||
/* 不拆着头给:半截头意味着游标要存 128 字节中间态,纯浪费 */
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
rc = wave_store_head_build(&p_ctx->meta, p_out, a_cap);
|
||
if (rc < 0) {
|
||
return rc;
|
||
}
|
||
n = (uint32_t)rc;
|
||
p_ctx->head_done = (uint16_t)n;
|
||
p_ctx->sent += n;
|
||
*p_len = n;
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_DATA;
|
||
return WAVE_ST_OK;
|
||
}
|
||
|
||
/* --- 数据区:按列推进,一次最多一批 --- */
|
||
if (p_ctx->phase == (uint8_t)WAVE_PACK_DATA) {
|
||
int16_t batch[WAVE_ST_BATCH];
|
||
|
||
if (p_ctx->p_reader == NULL) {
|
||
/* 没有数据源却进了数据区:宁可报错也不产空字节。
|
||
* 产空字节会写出「长度对、内容是 0」的段文件,回放页会画出一条直线——
|
||
* 那正是 §8.1 明令禁止的伪造证据形态。 */
|
||
return WAVE_ST_E_STATE;
|
||
}
|
||
while ((n < a_cap) && (p_ctx->ch < p_ctx->meta.ch_count)) {
|
||
uint32_t room_pts = (a_cap - n) / 2u;
|
||
uint32_t col_left = (uint32_t)p_ctx->meta.pts_per_ch - (uint32_t)p_ctx->ch_done;
|
||
uint32_t take;
|
||
int32_t got;
|
||
uint32_t i;
|
||
|
||
if (col_left == 0u) {
|
||
p_ctx->ch++;
|
||
p_ctx->ch_done = 0u;
|
||
continue;
|
||
}
|
||
if (room_pts == 0u) {
|
||
break;
|
||
}
|
||
take = (room_pts < col_left) ? room_pts : col_left;
|
||
if (take > (uint32_t)WAVE_ST_BATCH) {
|
||
take = (uint32_t)WAVE_ST_BATCH;
|
||
}
|
||
got = p_ctx->p_reader(p_ctx->p_user, p_ctx->ch, (uint32_t)p_ctx->ch_done,
|
||
take, batch);
|
||
if (got < 0) {
|
||
return (int)got;
|
||
}
|
||
if ((uint32_t)got != take) {
|
||
/* 数据源给不满就是数据源的问题(越界 / 未封存),不拿半批凑长度 */
|
||
return WAVE_ST_E_STATE;
|
||
}
|
||
uint32_t base_n = n;
|
||
for (i = 0u; i < take; i++) {
|
||
wave_st_put_code(&p_out[n], batch[i]);
|
||
n += 2u;
|
||
}
|
||
p_ctx->crc_data = wave_store_crc_update(p_ctx->crc_data, &p_out[base_n], take * 2u);
|
||
p_ctx->ch_done = (uint16_t)((uint32_t)p_ctx->ch_done + take);
|
||
}
|
||
if (p_ctx->ch >= p_ctx->meta.ch_count) {
|
||
/* 最后一列的最后一点产出后,「列走完了」要到下一次进循环才发现,
|
||
* 所以这一拍可能一个字节都没产。这时候不许当参数错报出去:整段就差
|
||
* 最后两片 CRC,报 E_PARAM 会让落盘道把一次真故障判成落盘失败(白丢)。
|
||
* 让它落到下面的尾段分支,在同一次调用里把尾产完。 */
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_TAIL;
|
||
}
|
||
p_ctx->sent += n;
|
||
*p_len = n;
|
||
if (n > 0u) {
|
||
return WAVE_ST_OK;
|
||
}
|
||
if (p_ctx->phase != (uint8_t)WAVE_PACK_TAIL) {
|
||
return WAVE_ST_E_PARAM; /* 容量连一个整样本都装不下 */
|
||
}
|
||
}
|
||
|
||
/* --- 尾:数据区 CRC,低字节在前 --- */
|
||
if (p_ctx->phase == (uint8_t)WAVE_PACK_TAIL) {
|
||
if (p_ctx->tail_done == 0u) {
|
||
p_out[n] = (uint8_t)(p_ctx->crc_data & 0xFFu);
|
||
n++;
|
||
p_ctx->tail_done = 1u;
|
||
}
|
||
if ((p_ctx->tail_done == 1u) && (n < a_cap)) {
|
||
p_out[n] = (uint8_t)((p_ctx->crc_data >> 8) & 0xFFu);
|
||
n++;
|
||
p_ctx->tail_done = 2u;
|
||
p_ctx->phase = (uint8_t)WAVE_PACK_DONE;
|
||
}
|
||
p_ctx->sent += n;
|
||
*p_len = n;
|
||
return (p_ctx->phase == (uint8_t)WAVE_PACK_DONE) ? WAVE_ST_DONE : WAVE_ST_OK;
|
||
}
|
||
return WAVE_ST_E_STATE;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:游标已产出字节数
|
||
* 入口参数:p_ctx 游标
|
||
* 返 回 值:字节数;空指针返回 0 uint32_t
|
||
* 限定条件:任意上下文(只读)
|
||
*/
|
||
uint32_t wave_store_pack_offset(const wave_pack_ctx_t *p_ctx) {
|
||
return (p_ctx == NULL) ? 0u : p_ctx->sent;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:游标对应整段的期望总长
|
||
* 入口参数:p_ctx 游标
|
||
* 返 回 值:字节数;空指针返回 0 uint32_t
|
||
* 限定条件:任意上下文(只读)
|
||
*/
|
||
uint32_t wave_store_pack_total(const wave_pack_ctx_t *p_ctx) {
|
||
return (p_ctx == NULL) ? 0u : p_ctx->total;
|
||
}
|
||
|
||
/* ===== 段文件名轮转(只算名字,不碰文件系统) ===== */
|
||
|
||
/*
|
||
* 函数功能:内部——把两位十进制写进缓冲
|
||
* 入口参数:p 目标(至少 2 字节);a_idx 0..99
|
||
* 返 回 值:无 void
|
||
* 限定条件:a_idx 已由调用方限制在 0..99
|
||
*/
|
||
static void wave_st_put_idx2(char *p, uint8_t a_idx) {
|
||
p[0] = (char)('0' + (a_idx / 10u));
|
||
p[1] = (char)('0' + (a_idx % 10u));
|
||
}
|
||
|
||
/*
|
||
* 函数功能:按段号生成段文件路径
|
||
* 入口参数:p_out 缓冲;a_cap 容量;a_idx 段号 0..99
|
||
* 返 回 值:>= 0 写入字符数(不含结束符);WAVE_ST_E_PARAM / WAVE_ST_E_RANGE int
|
||
* 限定条件:任意上下文;纯字符拼装(不用格式化函数),板端与 PC 侧逐字节一致
|
||
* 函数说明:形态 = 目录 / 前缀 + 两位十进制 + 扩展名,严格 8.3(§5.2 第三条)。
|
||
*/
|
||
int wave_store_seg_path(char *p_out, uint32_t a_cap, uint8_t a_idx) {
|
||
static const char s_dir[] = WAVE_ST_DIR;
|
||
static const char s_pfx[] = WAVE_ST_PFX;
|
||
static const char s_ext[] = WAVE_ST_EXT;
|
||
uint32_t pos = 0u;
|
||
uint32_t i;
|
||
char buf[WAVE_ST_PATH_MAX];
|
||
|
||
if (p_out == NULL) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
if (a_idx >= (uint8_t)WAVE_ST_SEG_MAX) {
|
||
return WAVE_ST_E_RANGE;
|
||
}
|
||
for (i = 0u; i < ((uint32_t)sizeof(s_dir) - 1u); i++) {
|
||
buf[pos++] = s_dir[i];
|
||
}
|
||
buf[pos++] = '/';
|
||
for (i = 0u; i < ((uint32_t)sizeof(s_pfx) - 1u); i++) {
|
||
buf[pos++] = s_pfx[i];
|
||
}
|
||
wave_st_put_idx2(&buf[pos], a_idx);
|
||
pos += 2u;
|
||
for (i = 0u; i < ((uint32_t)sizeof(s_ext) - 1u); i++) {
|
||
buf[pos++] = s_ext[i];
|
||
}
|
||
if (pos >= (uint32_t)sizeof(buf)) {
|
||
/* 宏与拼装长度漂移时的兜底:宁可给不出名字,也绝不把结束符写到别人缓冲外 */
|
||
return WAVE_ST_E_SIZE;
|
||
}
|
||
buf[pos] = '\0';
|
||
/* 容量必须容得下结束符:写成 a_cap < pos 会放过「刚好差一个字节」的调用方,
|
||
* 于是把 p_out[pos] 写到别人缓冲外面(越界写在板子上是静默的) */
|
||
if (a_cap <= pos) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
for (i = 0u; i <= pos; i++) {
|
||
p_out[i] = buf[i];
|
||
}
|
||
return (int)pos;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:下一个轮转段号
|
||
* 入口参数:a_idx 当前段号(越界按 0 处理)
|
||
* 返 回 值:下一个段号 uint8_t
|
||
* 限定条件:任意上下文
|
||
*/
|
||
uint8_t wave_store_next_idx(uint8_t a_idx) {
|
||
if (a_idx >= (uint8_t)WAVE_ST_SEG_MAX) {
|
||
return 0u;
|
||
}
|
||
if (a_idx >= ((uint8_t)WAVE_ST_SEG_MAX - 1u)) {
|
||
return 0u;
|
||
}
|
||
return (uint8_t)(a_idx + 1u);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:内部——大小写无关地比对一个字符
|
||
* 入口参数:a 待测字符(视作 ASCII);b 期望字符(取自段名宏)
|
||
* 返 回 值:1 相同;0 不同 uint8_t
|
||
* 限定条件:两侧都是 ASCII;非字母字符原样比
|
||
* 函数说明:**两侧都**归一化成小写再比。只归一化一侧是个真踩过一次的坑——
|
||
* 段名宏 WAVE_ST_PFX / WAVE_ST_EXT 是大写形态(FatFs 8.3 短名就是大写),
|
||
* 只把待测串转小写等于「小写 vs 大写」永不相等,反解会拒绝掉全部合法段名
|
||
* (单测 用例:首轮 100% 拒绝就是这个)。盘上大小写与宏写法
|
||
* 谁都不该去猜,故两侧各归一一次。
|
||
*/
|
||
static uint8_t wave_st_ci_eq(char a, char b) {
|
||
char x = a;
|
||
char y = b;
|
||
|
||
if ((x >= 'A') && (x <= 'Z')) {
|
||
x = (char)(x + ('a' - 'A'));
|
||
}
|
||
if ((y >= 'A') && (y <= 'Z')) {
|
||
y = (char)(y + ('a' - 'A'));
|
||
}
|
||
return (x == y) ? 1u : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从文件名或路径反解段号
|
||
* 入口参数:p_path 路径或纯文件名;p_idx 输出
|
||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_NAME 不是本层命名形态 int
|
||
* 限定条件:任意上下文
|
||
* 函数说明:只认 basename 上的「wave + 两位数字 + .bin」(大小写都收)。
|
||
* 不匹配就返回错误,**绝不**把陌生名字当录波段——扫目录重建段账时误认一个
|
||
* 别的文件,等于把别人的文件当波形解释(名字/参数类错误码严禁升级成卷操作,
|
||
* 存储架构说明 §10.1 同一条教训)。
|
||
*/
|
||
int wave_store_idx_from_name(const char *p_path, uint8_t *p_idx) {
|
||
static const char s_pfx[] = WAVE_ST_PFX;
|
||
static const char s_ext[] = WAVE_ST_EXT;
|
||
const char *base = p_path;
|
||
const char *q = p_path;
|
||
uint32_t i;
|
||
uint32_t len = 0u;
|
||
uint8_t hi;
|
||
uint8_t lo;
|
||
|
||
if ((p_path == NULL) || (p_idx == NULL)) {
|
||
return WAVE_ST_E_PARAM;
|
||
}
|
||
/* 找 basename:支持反斜杠形态(PC 侧脚本拼出来的路径可能被原样传进来) */
|
||
while (*q != '\0') {
|
||
if ((*q == '/') || (*q == '\\')) {
|
||
base = q + 1;
|
||
}
|
||
q++;
|
||
}
|
||
q = base;
|
||
while ((*q != '\0') && (len < (uint32_t)WAVE_ST_PATH_MAX)) {
|
||
q++;
|
||
len++;
|
||
}
|
||
if ((*q != '\0') || (len != ((uint32_t)sizeof(s_pfx) - 1u) + 2u +
|
||
((uint32_t)sizeof(s_ext) - 1u))) {
|
||
return WAVE_ST_E_NAME;
|
||
}
|
||
for (i = 0u; i < ((uint32_t)sizeof(s_pfx) - 1u); i++) {
|
||
if (wave_st_ci_eq(base[i], s_pfx[i]) == 0u) {
|
||
return WAVE_ST_E_NAME;
|
||
}
|
||
}
|
||
hi = (uint8_t)base[i];
|
||
lo = (uint8_t)base[i + 1u];
|
||
if ((hi < '0') || (hi > '9') || (lo < '0') || (lo > '9')) {
|
||
return WAVE_ST_E_NAME;
|
||
}
|
||
for (i = (i + 2u); i < len; i++) {
|
||
if (wave_st_ci_eq(base[i], s_ext[i - ((uint32_t)sizeof(s_pfx) - 1u) - 2u]) == 0u) {
|
||
return WAVE_ST_E_NAME;
|
||
}
|
||
}
|
||
*p_idx = (uint8_t)(((hi - '0') * 10u) + (lo - '0'));
|
||
return WAVE_ST_OK;
|
||
}
|