/* * 模块名称:录波环(故障录波 · 数据面纯逻辑层:滚动环 + 触发冻结状态机 + 已封存区读口) * 模块功能:见 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 /* 触发挂号的载荷编码(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]; }