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 全过;字节卫生零漂移。
536 lines
23 KiB
C
536 lines
23 KiB
C
/*
|
||
* 模块名称:录波环(故障录波 · 数据面纯逻辑层:滚动环 + 触发冻结状态机 + 已封存区读口)
|
||
* 模块功能:见 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];
|
||
}
|