Files
STM32F4-Base/App/algo/wave_ring.c
T
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/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 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

536 lines
23 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.
/*
* 模块名称:录波环(故障录波 · 数据面纯逻辑层:滚动环 + 触发冻结状态机 + 已封存区读口)
* 模块功能:见 App/algo/wave_ring.h 的文件头注释块——所有权矩阵、封存握手、跨任务安全、
* 形态钉死与预算全部写在那里,本文件不重抄一遍,只在实现处补「为什么这么写」。
* 本文件是纯逻辑:只 include App/algo/wave_ring.h(它带 wave_store.h),
* 不 include HAL / CMSIS / RTOS 头、不调 FatFs、不建任何 RTOS 对象、不打日志。
* 临界区也不需要:写者只有 adcTask 一个,读者只碰 SEALED 期间冻结的已封存区,
* §5.2 第一条陷阱(无临界区的活缓冲窗口)在本层根本不成立,因为没有那种入口。
* 适用平台:STM32F407ZGTx(Cortex-M4,小端);主机侧单测 test/host/wave_ring/
* 与 test/host/wave_trig/
* 作者:DTU 项目组
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
* 修改记录:
* v1.0 2026-10-29 DTU 项目组 创建
* v1.1 2026-10-29 DTU 项目组 触发挂号改单字节载荷编码(lead 评审:三段写在
* adcTask 抢占下会把段头的触发源 / 元件号录成上一段的旧值,溯源字段作废)
*/
#include "wave_ring.h"
/* ARMCC 的 stdint.h 不提供 NULL,而主机侧 gcc / MSVC 会传递性把它拉进来 ——
* 这个差别主机单测照不出来(2026-10-29 台基线实跑 /keil-build 才暴露:本层
* wave_ring.c 报 22 条 identifier "NULL" 未定义)。与 App/algo/sample_ring.c、App/algo/freq.c
* 同一口径:谁用 NULL 谁自己 include stddef.h,不往公共头里塞(少波及别人的 TU)。 */
#include <stddef.h>
/* 触发挂号的载荷编码(v1.1):挂号量必须**一次单字节存写**才原子,所以把载荷本身编进
* 那一个字节 —— 0 = 无请求,非 0 = (触发源 << 位移) | 元件号。触发源取值 1 到 3 占高 3 位、
* 元件号取 prot_elm 的 picked 位号(uint32 位图 → 0 到 31)占低 5 位,合起来 8 位够用。
* 本层双端编译(板端 ARMCC 与模拟器),不能引 FreeRTOS / CMSIS 的临界区内在函数,
* 所以「把三个字节压成一个字节」是唯一既原子又不依赖平台的写法。 */
#define WAVE_RG_REQ_SHIFT (5u) /* 载荷里触发源的低位置 */
#define WAVE_RG_REQ_ELM_MSK (0x1Fu) /* 载荷里元件号的位掩码(低 5 位) */
#define WAVE_RG_REQ_ELM_MAX (32u) /* 元件号合法上界(不含),picked 位图只有 32 位 */
/* ===== 一、静态对象(全部文件作用域 static,禁堆分配;落位与字节数见头注第七节) ===== */
/* 数据区:按通道分列,8 列 × 320 点 × 2 字节 = 5120 字节(§7 预算的大头)。
* 不许改成 float、不许按帧交错(§12:形态错一次 RAM 翻倍 + 跨步取数)。
* 不放在 CCM:DMA 看不见它,且 .sct 里 RW_CCM 只收 heap_4.o,普通 .bss 天然落 SRAM1 */
static int16_t s_ring[WAVE_RING_CH][WAVE_RING_PTS];
/* 已封存段的段头:latch 之前只有环侧字段有效,latch 之后才是完整段头。
* 这是 defaultTask 与 uiTask 能看到的唯一「段头视图」——内部指针一律不外露 */
static wave_seg_meta_t s_sealed;
/* 观测计数(§9 周期行的 seg= / ovf= / drop= / err= 全从这里取,落盘道不数第二份账) */
static uint32_t s_seg_total; /* 成功 ACK 的段数 */
static uint32_t s_ovf_cnt; /* 被忽略的触发数 */
static uint32_t s_drop_cnt; /* SEALED 窗口丢弃的点数(单位 = 点,一次丢一拍 = 加 8) */
static uint32_t s_err_cnt; /* 落盘失败 + 滞留超时自弃 */
static uint32_t s_last_t0_ms; /* 最近一段段首毫秒 */
static uint32_t s_last_seq; /* 最近一段段首帧序号 */
/* 状态机与索引 */
static uint16_t s_head; /* 下一个写入点(滚动位置) */
static uint16_t s_base; /* 已封存 / 正在录的段首在环内的下标(段内点 0) */
static uint16_t s_post_left; /* 后触发还差多少点封段 */
static uint16_t s_cool_left; /* 冷却剩余拍数 */
static uint16_t s_seal_hold; /* SEALED 已滞留拍数 */
static uint8_t s_state; /* wave_ring_state_t */
/* 触发请求挂号:0 = 无请求;非 0 = (触发源 << WAVE_RG_REQ_SHIFT) | 元件号。
* 刻意只用这一个字节承载「是谁要录波」:旧实现分三个字节写(先写挂号位、后写载荷),
* adcTask 抢在两条指令之间就会把上一段的旧触发源 / 旧元件号抄进本段 —— 而这两个字段是
* 这段记录唯一的溯源信息(现场靠它与 /log/start.log 对表),错一次事后无从发现。
* 单字节存写在 Cortex-M4 上原子,写序不再有意义,所以不需要任何顺序假设或屏障。 */
static uint8_t s_req;
static uint8_t s_trig_src; /* 本段实际用的触发源(钉基址时从请求抄过来) */
static uint8_t s_trig_elm; /* 本段实际用的元件序号 */
static uint8_t s_meta_ready; /* 1 = 段头已 latch */
static uint8_t s_seg_idx; /* 本次上电的轮转下标(跨上电接号由落盘道自持,见头注) */
/* 语义号 → 列号(本波恒等,但把这条恒等式收在一处:二期带 TEMP 时只改这张表,
* 写死下标的调用点会静默错位,走本函数的不会) */
static const int8_t s_col_of_sem[(uint8_t)WAVE_ST_CH_MAX] = {
0, 1, 2, 3, 4, 5, 6, 7, -1
};
/* ===== 二、内部小工具 ===== */
/*
* 函数功能:环内下标 +n 后取模
* 入口参数:a_idx 当前下标;a_step 步长(小于环长)
* 返 回 值:取模后的下标 uint16_t
* 限定条件:仅本文件;a_idx 与 a_step 均在环长量级,无溢出风险
* 函数说明:环长 320 是 2 的幂吗?不是(320 = 64×5),所以老老实实取模,
* 不用位与——写错一次就是「看起来能跑、偶尔串段」的那种 bug。
*/
static uint16_t wave_rg_wrap(uint16_t a_idx, uint16_t a_step) {
uint16_t v = (uint16_t)(a_idx + a_step);
if (v >= (uint16_t)WAVE_RING_PTS) {
v = (uint16_t)(v - (uint16_t)WAVE_RING_PTS);
}
return v;
}
/*
* 函数功能:把已封存段的「环侧字段」初始化(封存瞬间调,标定字段一律清零待 latch)
* 入口参数:a_now_ms 段首毫秒;a_frame_seq 段首帧序号
* 返 回 值:无 void
* 限定条件:仅本文件;只在钉基址那一拍调一次
* 函数说明:先把 ch[] 全部清成「sem = 下标、其余 0」,是为了让**没有 latch** 的段
* 不可能带着上一段的系数被写进文件:sealed() 在未 latch 时本就是 0,
* 但清零让「万一有人绕过握手直接读 s_sealed」也读不出旧真值。
*/
static void wave_rg_seal_reset(uint32_t a_now_ms, uint32_t a_frame_seq) {
uint8_t i;
s_sealed.fs_hz = (uint16_t)WAVE_ST_FS_DEF;
s_sealed.pts_per_cycle = (uint8_t)WAVE_ST_PPC_DEF;
s_sealed.ch_count = (uint8_t)WAVE_RING_CH;
s_sealed.pts_per_ch = (uint16_t)WAVE_RING_PTS;
s_sealed.pre_pts = (uint16_t)WAVE_RING_PRE;
s_sealed.trig_src = s_trig_src;
s_sealed.trig_elm = s_trig_elm;
s_sealed.t0_ms = a_now_ms;
s_sealed.wall = 0u;
s_sealed.eng = (uint8_t)WAVE_ENG_PIN;
s_sealed.flags_seq = 0u;
s_sealed.clip_mask = 0u;
s_sealed.frame_seq = a_frame_seq;
for (i = 0u; i < (uint8_t)WAVE_ST_CH_MAX; i++) {
s_sealed.ch[i].sem = i;
s_sealed.ch[i].conv = 0u;
s_sealed.ch[i].bits = 0u;
s_sealed.ch[i].avail = 0u;
s_sealed.ch[i].k_q16 = 0;
s_sealed.ch[i].b_codes = 0;
}
}
/* ===== 三、初始化与压环 ===== */
/*
* 函数功能:复位环与全部状态
* 入口参数:无
* 返 回 值:无 void
* 限定条件:只能在 adcTask 调,且在开始压环之前(见头文件同函数注释)
* 函数说明:数据区显式清零而不是依赖 .bss 清零:本函数也可能在运行中被重新调
* (显式重配 / 单测复用),留旧数据就等于留下「上一段的尾巴混进下一段」的口子。
*/
void wave_ring_init(void) {
uint8_t c;
uint16_t i;
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
for (i = 0u; i < (uint16_t)WAVE_RING_PTS; i++) {
s_ring[c][i] = 0;
}
}
s_head = 0u;
s_base = 0u;
s_post_left = 0u;
s_cool_left = 0u;
s_seal_hold = 0u;
s_state = (uint8_t)WAVE_RING_ST_IDLE;
s_req = 0u;
s_trig_src = (uint8_t)WAVE_TRIG_NONE;
s_trig_elm = 0u;
s_meta_ready = 0u;
s_seg_idx = 0u;
s_seg_total = 0u;
s_ovf_cnt = 0u;
s_drop_cnt = 0u;
s_err_cnt = 0u;
s_last_t0_ms = 0u;
s_last_seq = 0u;
wave_rg_seal_reset(0u, 0u);
}
/*
* 函数功能:压入一拍的 8 路原始码值
* 入口参数:p_codes 语义序数组(前 8 项);a_n 必须等于 WAVE_RING_CH
* 返 回 值:WAVE_ST_OK 已写入;WAVE_ST_DONE 本拍被丢弃(SEALED 窗口);WAVE_ST_E_PARAM 参数错 int32_t
* 限定条件:只能在 adcTask 调,每拍一次
* 函数说明:写的是「列优先」的 8 个分散地址(每列同一行)——这就是 §6.2 分列形态的代价,
* 换来的是落盘与回放都能按列连续搬,不用跨步长取数。
*/
int32_t wave_ring_push(const int16_t *p_codes, uint8_t a_n) {
uint8_t c;
if ((p_codes == NULL) || (a_n != (uint8_t)WAVE_RING_CH)) {
return WAVE_ST_E_PARAM;
}
if (s_state == (uint8_t)WAVE_RING_ST_SEALED) {
/* 冻结窗口丢样本(§4.2):计数单位是点,一次丢一整拍 = WAVE_RING_CH 点 */
s_drop_cnt += (uint32_t)a_n;
return WAVE_ST_DONE;
}
for (c = 0u; c < (uint8_t)WAVE_RING_CH; c++) {
s_ring[c][s_head] = p_codes[c];
}
s_head = wave_rg_wrap(s_head, 1u);
return WAVE_ST_OK;
}
/* ===== 四、触发、推进、交棒、ACK ===== */
/*
* 函数功能:投递一次录波请求
* 入口参数:a_src 触发源(1 手动 / 2 保护 / 3 bench);a_elm 元件序号(0 到 31)
* 返 回 值:WAVE_ST_OK 受理;WAVE_ST_DONE 被忽略(ovf 已加一);WAVE_ST_E_PARAM 触发源
* 非法或元件号越界 int32_t
* 限定条件:任意上下文(**只写一个字节**,理由见 s_req 声明处)
* 函数说明:这里判「非 IDLE 就拒」而不是「非 IDLE 就排队」——本波不做多段排队(已定裁定),
* 而且排队会把「最近一次故障」降级成「上上次故障」。
* s_req 已挂号时也拒:同一拍内多个元件同时 picked 只该录一段。
* 两条入参校验排在状态判断之前:非法调用是调用方的错,不该记进 ovf 账 ——
* ovf 的语义是「真来了第二次触发」。
* 元件号上界写死在这里:今天没有调用方会越界(picked 是 uint32 位图),这条是给
* 二期加元件的人留的牙 —— 载荷只有 5 位,越界必须当场拒,不许静默截断。
*/
int32_t wave_ring_request(uint8_t a_src, uint8_t a_elm) {
if ((a_src < (uint8_t)WAVE_TRIG_MANUAL) || (a_src > (uint8_t)WAVE_TRIG_BENCH)) {
return WAVE_ST_E_PARAM;
}
if (a_elm >= (uint8_t)WAVE_RG_REQ_ELM_MAX) {
return WAVE_ST_E_PARAM;
}
if ((s_state != (uint8_t)WAVE_RING_ST_IDLE) || (s_req != 0u)) {
s_ovf_cnt++;
return WAVE_ST_DONE;
}
/* 一次存写:触发源与元件号同字节落地。别的上下文要么看到「没请求」、要么看到
* 「完整的这次请求」,不存在读到半份载荷的中间态 */
s_req = (uint8_t)(((uint32_t)a_src << (uint32_t)WAVE_RG_REQ_SHIFT) | (uint32_t)a_elm);
return WAVE_ST_OK;
}
/*
* 函数功能:推进一拍状态机
* 入口参数:a_now_ms 当前相对上电毫秒;a_frame_seq 当前帧序号
* 返 回 值:wave_ring_evt_t(本拍刚封段返回 WAVE_RING_EVT_SEALED) int32_t
* 限定条件:只能在 adcTask 调,每拍在 wave_ring_push 之后一次
* 函数说明:五个分支各管一件事,互不重叠:
* IDLE —— 消费挂号、钉段首基址、转 FREEZE
* FREEZE —— 后触发计点,计满转 SEALED
* SEALED —— 只计滞留,超时自弃(绝不让环永久焊死)
* COOL —— 递减冷却,到 0 回 IDLE
* 段首基址 = 触发那一拍写入点的「往前 160 点」,于是段内点 0 是最老的预触发点、
* 点 160 正好是触发后第一点;后触发的 160 点落在环的另一半(本来就即将被覆盖的
* 最老那 160 槽),所以「冻结」在物理上是**停止滚动覆盖**,不是停止写入。
*/
int32_t wave_ring_tick(uint32_t a_now_ms, uint32_t a_frame_seq) {
int32_t evt = (int32_t)WAVE_RING_EVT_NONE;
switch (s_state) {
case (uint8_t)WAVE_RING_ST_IDLE:
s_cool_left = 0u;
s_seal_hold = 0u;
{
/* 只读一次快照再解码:分两次读就会把两次请求的字段拼成一段的身份 */
uint8_t req = s_req;
if (req != 0u) {
/* 钉基址:本拍的样本已写进 (head-1),段首 = head - 预触发点数 */
s_trig_src = (uint8_t)((uint32_t)req >> (uint32_t)WAVE_RG_REQ_SHIFT);
s_trig_elm = (uint8_t)((uint32_t)req & (uint32_t)WAVE_RG_REQ_ELM_MSK);
s_req = 0u;
s_base = wave_rg_wrap(s_head, (uint16_t)((uint32_t)WAVE_RING_PTS - (uint32_t)WAVE_RING_PRE));
s_post_left = (uint16_t)WAVE_RING_POST;
s_meta_ready = 0u;
s_last_t0_ms = a_now_ms;
s_last_seq = a_frame_seq;
wave_rg_seal_reset(a_now_ms, a_frame_seq);
s_state = (uint8_t)WAVE_RING_ST_FREEZE;
}
}
break;
case (uint8_t)WAVE_RING_ST_FREEZE:
if (s_post_left > 0u) {
s_post_left--;
}
if (s_post_left == 0u) {
/* 封段:此刻起 s_head 正好回到 s_base,再写就覆盖段内点 0 —— 所以 push 开始丢 */
s_state = (uint8_t)WAVE_RING_ST_SEALED;
s_seal_hold = 0u;
evt = (int32_t)WAVE_RING_EVT_SEALED;
}
break;
case (uint8_t)WAVE_RING_ST_SEALED:
/* 兜底:defaultTask 挂住 / 卷没挂载 / 落盘道被更高优先级的事挡住,都不能让环
* 永久冻结——下一次真故障无波可录是比丢一段更贵的代价(lead 裁定) */
s_seal_hold++;
if (s_seal_hold >= (uint16_t)WAVE_SEAL_TIMEOUT_MS) {
s_err_cnt++;
s_meta_ready = 0u;
s_seal_hold = 0u;
s_cool_left = (uint16_t)WAVE_RING_COOL_MS;
s_state = (uint8_t)WAVE_RING_ST_COOL;
}
break;
case (uint8_t)WAVE_RING_ST_COOL:
/* 冷却只在 ACK(或自弃)之后计时:它是二次抑制窗,不是冻结窗 */
if (s_cool_left > 0u) {
s_cool_left--;
}
if (s_cool_left == 0u) {
s_state = (uint8_t)WAVE_RING_ST_IDLE;
}
break;
default:
/* 状态字节被写坏(跑飞 / 越界写)时回到 IDLE 而不是卡死:录波宁可多录也不能停 */
s_state = (uint8_t)WAVE_RING_ST_IDLE;
break;
}
return evt;
}
/*
* 函数功能:封存交棒——把封存瞬间事实锁进段头
* 入口参数:p_cal 封存瞬间事实(不可为 NULL)
* 返 回 值:WAVE_ST_OK 已锁存;WAVE_ST_E_STATE 不在待交棒态;WAVE_ST_E_PARAM 参数错 int32_t
* 限定条件:只能在 adcTask 调,只在 EVT_SEALED 那一拍调一次
* 函数说明:只 copy 封存瞬间的事实,不动环侧字段(点数 / 采样率 / 预触发这些
* 在封段那刻已按 §6.2 定死)。重复调用直接拒,不覆盖已有锁存:
* 半路换系数会让落盘道写进去的头与它已经在搬的数据不同源。
*/
int32_t wave_ring_latch_meta(const wave_seal_cal_t *p_cal) {
uint8_t i;
if (p_cal == NULL) {
return WAVE_ST_E_PARAM;
}
if (p_cal->eng > (uint8_t)WAVE_ENG_PRI) {
return WAVE_ST_E_PARAM;
}
if ((s_state != (uint8_t)WAVE_RING_ST_SEALED) || (s_meta_ready != 0u)) {
return WAVE_ST_E_STATE;
}
s_sealed.eng = p_cal->eng;
s_sealed.flags_seq = p_cal->flags_seq;
s_sealed.clip_mask = p_cal->clip_mask;
s_sealed.wall = p_cal->wall;
for (i = 0u; i < s_sealed.ch_count; i++) {
s_sealed.ch[i].conv = p_cal->ch[i].conv;
s_sealed.ch[i].bits = p_cal->ch[i].bits;
s_sealed.ch[i].avail = p_cal->ch[i].avail;
s_sealed.ch[i].k_q16 = p_cal->ch[i].k_q16;
s_sealed.ch[i].b_codes = p_cal->ch[i].b_codes;
}
s_meta_ready = 1u;
return WAVE_ST_OK;
}
/*
* 函数功能:落盘 ACK
* 入口参数:a_result 0 = 成功;非 0 = 失败(短写 / 打开失败 / CRC 自弃)
* 返 回 值:WAVE_ST_OK 已解冻并起冷却;WAVE_ST_E_STATE 当前不在 SEALED int32_t
* 限定条件:只能由 defaultTask 调,一段一次
* 函数说明:无论成败都解冻 + 起冷却:错误只计数不冻结(环冻着的代价是下一次故障无波可录)。
* 成功才推进轮转号与段计数(失败段留同号,下次重写覆盖,不留半截文件)。
*/
int32_t wave_ring_write_done(int32_t a_result) {
if (s_state != (uint8_t)WAVE_RING_ST_SEALED) {
return WAVE_ST_E_STATE;
}
if (s_meta_ready == 0u) {
/* 没 latch 就没有「段」可 ACK:这时候段头从未就绪,落盘道根本拿不到
* wave_ring_sealed_meta(),绝不可能真写出文件。若在这里放过 ACK,
* 就会白推进轮转号(号 +1 而盘上没那个文件 = 凭空吃掉一个号)并把
* seg_total 报成一个没发生过的数(串口周期行的 seg=NN 从此对不上账)。
* 拒绝而不是记账,把「没交棒就 ACK」变成调用方看得见的错误。
* 本段的出口只有一个:滞留到 WAVE_SEAL_TIMEOUT_MS 自弃(那一路照实计 err)。 */
return WAVE_ST_E_STATE;
}
if (a_result == 0) {
s_seg_idx = wave_store_next_idx(s_seg_idx);
s_seg_total++;
} else {
s_err_cnt++;
}
s_meta_ready = 0u;
s_seal_hold = 0u;
s_cool_left = (uint16_t)WAVE_RING_COOL_MS;
s_state = (uint8_t)WAVE_RING_ST_COOL;
return WAVE_ST_OK;
}
/* ===== 五、状态查询与已封存区读口 ===== */
/*
* 函数功能:查当前状态
* 入口参数:无
* 返 回 值:wave_ring_state_t 取值 uint8_t
* 限定条件:任意上下文(只读单字节)
*/
uint8_t wave_ring_state(void) {
return s_state;
}
/*
* 函数功能:查「有已封存段可读」——跨任务唯一握手量
* 入口参数:无
* 返 回 值:1 = SEALED 且段头已 latch;0 = 不可用 uint8_t
* 限定条件:任意上下文(只读单字节)
*/
uint8_t wave_ring_sealed(void) {
if ((s_state == (uint8_t)WAVE_RING_ST_SEALED) && (s_meta_ready != 0u)) {
return 1u;
}
return 0u;
}
/*
* 函数功能:取已封存段的段头快照
* 入口参数:p_out 输出(整份拷出)
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_STATE 无就绪段 int32_t
* 限定条件:任意上下文(defaultTask / uiTask 只读)
*/
int32_t wave_ring_sealed_meta(wave_seg_meta_t *p_out) {
uint16_t i;
const uint8_t *p_src;
uint8_t *p_dst;
if (p_out == NULL) {
return WAVE_ST_E_PARAM;
}
if (wave_ring_sealed() == 0u) {
return WAVE_ST_E_STATE;
}
/* 逐字节拷出而不是 *p_out = s_sealed:结构体赋值在 ARMCC 上会调 __aeabi_memcpy,
* 这里没必要为一个赋值多拉一层依赖,量也就 136 字节 */
p_src = (const uint8_t *)&s_sealed;
p_dst = (uint8_t *)p_out;
for (i = 0u; i < (uint16_t)sizeof(wave_seg_meta_t); i++) {
p_dst[i] = p_src[i];
}
return WAVE_ST_OK;
}
/*
* 函数功能:按通道读已封存区的连续点窗
* 入口参数:a_ch 列号;a_from 段内起点;a_cnt 期望点数(超出段尾自动夹);p_dst 输出
* 返 回 值:实际点数;WAVE_ST_E_PARAM / WAVE_ST_E_RANGE / WAVE_ST_E_STATE int32_t
* 限定条件:任意上下文,只读已封存区(本层没有任何读活缓冲的入口)
* 函数说明:按 s_base 取模展开,读者看到的是 320 点的线性数组。
*/
int32_t wave_ring_read_ch(uint8_t a_ch, uint16_t a_from, uint16_t a_cnt, int16_t *p_dst) {
uint16_t left;
uint16_t n;
uint16_t idx;
uint16_t i;
if ((p_dst == NULL) || (a_cnt == 0u)) {
return WAVE_ST_E_PARAM;
}
if ((a_ch >= (uint8_t)WAVE_RING_CH) || (a_from >= (uint16_t)WAVE_RING_PTS)) {
return WAVE_ST_E_RANGE;
}
if (wave_ring_sealed() == 0u) {
return WAVE_ST_E_STATE;
}
left = (uint16_t)((uint16_t)WAVE_RING_PTS - a_from);
n = (a_cnt < left) ? a_cnt : left;
idx = wave_rg_wrap(s_base, a_from);
for (i = 0u; i < n; i++) {
p_dst[i] = s_ring[a_ch][idx];
idx = wave_rg_wrap(idx, 1u);
}
return (int32_t)n;
}
/*
* 函数功能:读口的 wave_reader_fn 形态适配(落盘道直接把它交给打包游标)
* 入口参数:p_user 未用;其余同 wave_ring_read_ch(点数与起点是 uint32 形态)
* 返 回 值:实际点数;负 = WAVE_ST_E_* int32_t
* 限定条件:任意上下文,只读已封存区
* 函数说明:32 位入参先夹到 16 位形态再转调,越界一律报错不夹成 0 ——
* 夹成 0 会让落盘道以为「这一列读完了」而写出一个短段。
*/
int32_t wave_ring_read_cb(void *p_user, uint8_t a_ch, uint32_t a_from,
uint32_t a_cnt, int16_t *p_dst) {
(void)p_user;
if (a_cnt > (uint32_t)WAVE_RING_PTS) {
return WAVE_ST_E_PARAM;
}
if (a_from > (uint32_t)WAVE_RING_PTS) {
return WAVE_ST_E_RANGE;
}
return wave_ring_read_ch(a_ch, (uint16_t)a_from, (uint16_t)a_cnt, p_dst);
}
/*
* 函数功能:取观测计数快照
* 入口参数:p_out 输出(不可为 NULL)
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int32_t
* 限定条件:任意上下文(只读)
*/
int32_t wave_ring_get_stat(wave_ring_stat_t *p_out) {
if (p_out == NULL) {
return WAVE_ST_E_PARAM;
}
p_out->state = s_state;
p_out->trig_src = s_trig_src;
p_out->trig_elm = s_trig_elm;
p_out->meta_ready = s_meta_ready;
p_out->seg_idx = s_seg_idx;
/* 对外字段是 8 位:本波后触发最多 160 点,不会溢出;真溢出说明环长被改过,
* 饱和在 255 让它在日志里显眼,而不是绕回 0 假装快封段了 */
p_out->post_left = (s_post_left > 255u) ? 255u : (uint8_t)s_post_left;
p_out->rsv1 = 0u;
p_out->rsv2 = 0u;
p_out->cool_left = s_cool_left;
p_out->seal_hold = s_seal_hold;
p_out->seg_total = s_seg_total;
p_out->ovf_cnt = s_ovf_cnt;
p_out->drop_cnt = s_drop_cnt;
p_out->err_cnt = s_err_cnt;
p_out->last_t0_ms = s_last_t0_ms;
p_out->last_seq = s_last_seq;
return WAVE_ST_OK;
}
/*
* 函数功能:语义通道号 → 本环列号
* 入口参数:a_sem 语义号
* 返 回 值:列号;WAVE_ST_E_RANGE = 该语义不录 int32_t
* 限定条件:任意上下文(纯计算)
*/
int32_t wave_ring_col(uint8_t a_sem) {
if (a_sem >= (uint8_t)WAVE_ST_CH_MAX) {
return WAVE_ST_E_RANGE;
}
if (s_col_of_sem[a_sem] < 0) {
return WAVE_ST_E_RANGE;
}
return (int32_t)s_col_of_sem[a_sem];
}