feat(wave): 故障录波数据面、接线与单通道回放页(软件闭环交付)
数据面(App/algo,纯逻辑,双端编译) - wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask 唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃 兜底——下一次真故障无波可录比丢一段更贵 - wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带 锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度 - 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式 - 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样 纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉 - test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条 自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合 - 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定) 接线(任务层) - adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图), 段头按最低有效位精确记账,不经浮点回算 - adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实; 台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行 - defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到 关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms 补一条告警并记历史峰值(喂狗余量可核) - Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留 超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的, 半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进 - Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到 上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数; 段文件字节布局零改动,三份合成段哈希逐字节不变 - protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体 零改动 人机(160×160 单色屏) - 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器 得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30 - 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与 垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、 单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据 - ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、 手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口 - 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有 60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形 - 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变 - 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏 22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、 仪器用已在跑的单轮工作耗时探针,不新加埋点 台账与文档 - 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936 字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名; 散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建 重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置) - 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据); §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径 风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」 - 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项 - 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划 索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动) - 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件 形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记 「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场 - 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行 227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退 为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与 体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账 同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道 编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串 生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
This commit is contained in:
@@ -57,6 +57,16 @@
|
||||
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
|
||||
#include "net_latency_test.h" /* M3 探针:ui_loop(生产循环实测周期) */
|
||||
#include "task_watchdog.h"
|
||||
/* 故障录波回放道(UI 道):录波注入表执行侧需要的四份头。
|
||||
* wave_ring.h 给触发投递(request 任意上下文可调、非阻塞);
|
||||
* wave_store.h 给段格式解析与定点 mV 换算(唯一口径,页面不复述算式);
|
||||
* vol_lock.h + ff.h:/wave 只读遍历按 soe_store.c 读者同款纪律——
|
||||
* try 锁非阻塞(卷忙本轮按「无段」处理,uiTask 绘制节拍绝不等待)、
|
||||
* 锁跨完整文件操作序列、小窗口分次读、整段 5250B 绝不驻留 RAM */
|
||||
#include "ff.h"
|
||||
#include "vol_lock.h"
|
||||
#include "wave_ring.h"
|
||||
#include "wave_store.h"
|
||||
#include "task_prof.h" /* P2 常开探针:循环周期 / 工作耗时 / 推流往返 */
|
||||
|
||||
/*
|
||||
@@ -355,6 +365,463 @@ static int ui_task_soe_get(uint32_t idx, ui_w_soe_rec_t *p_out) {
|
||||
|
||||
static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get };
|
||||
|
||||
/* ═══════════════ 故障录波注入表执行侧(UI 道,2026-10-29)═══════════════
|
||||
* 纪律逐条对齐 soe_store.c 读者(vol_lock try / 锁跨完整文件序列 / 小窗口):
|
||||
* - 卷忙(vol_lock_wait(0) 返 0)一律按「本轮无段」处理——uiTask 是
|
||||
* LVGL + 推流节拍的所有者,等锁 = 停摆,宁可空一帧也不阻塞(lead 裁定);
|
||||
* - 段完整性判定只信 wave_store 自己的判据(尺寸等式 / magic+ver / 双 CRC /
|
||||
* sem==index),本胶水不读 avail/flags 做二次裁断(§6.3 与 2026-10-29
|
||||
* 「VALID+CLIPPED 非矛盾」裁定同源:可用性不是完整性);
|
||||
* - 整段 5250B 不驻留:256B 静态窗口分次读,头区一次、数据区流式过 CRC。
|
||||
* uiTask 阻塞上界(IWDG 800ms 窗、uiTask 在 required_mask 内;写法对齐上方
|
||||
* 维护通道那段上界,同文件先例):
|
||||
* - 第零项是「本块不等待」:vol_lock_wait(0) 拿不到锁即按「本轮无段」
|
||||
* 返回——不等锁 = 零阻塞,它本身就是上界的一项,不是只保心跳判读;
|
||||
* - 一次渲染实际发生的 I/O(进回放页首绘、或按键后重绘那帧)三件叠加:
|
||||
* ① 表重扫 = 1 次 f_opendir + 至多 WAVE_ST_SEG_MAX(100)次 f_readdir,
|
||||
* 收尾是 100 元插入排序(纯内存);
|
||||
* ② 段头装载 = 仅未缓存段才发生:1 次 f_open + 头区 1 次 128B 读 +
|
||||
* head_parse 内的头 CRC + 数据区 20 次 256B 流式读 + 尾 2B 一次
|
||||
* = 最坏 22 次小读;5120B 逐字节 CRC 与 sem 表校验是纯计算,
|
||||
* 168MHz 下微秒到毫秒量级;缓存命中零卷操作;
|
||||
* ③ 窗口装载 = 1 次 f_open + 1 次 f_lseek + 1 次 80B 读(40 点一窗),
|
||||
* 解码 40 点 + 逐点 code_to_milli 峰值扫描 40 次(int64 乘除,
|
||||
* 微秒级)——不是全段 8×320 扫描,回放页一屏只碰一个通道一窗;
|
||||
* - 纯内存不碰卷的路径(评审据此看最坏的落点):manual_req 只投一个
|
||||
* 请求字节;已缓存段的上下切道、左右切段的段号表环游;空态与坏态
|
||||
* 直接返回。所以最坏只落在「第一次进回放页 / 第一次切到未缓存段」
|
||||
* 这类一次性动作上,同段内连续按键不重复付 ②。
|
||||
* 没有实测数,上面是与落盘道同一把尺的**推算**:落盘道的告警线是
|
||||
* 50 ms / 21 次写,读路径无簇分配、不写 FAT 元数据,只会更便宜,故按
|
||||
* 同量级不高于它估——一次渲染最坏一窗 ≈ 段表 + 一段全验 + 一窗,仍远
|
||||
* 小于 800ms。实测归台架,仪器就用本文件已经在跑的 task_prof 单轮
|
||||
* 工作耗时探针,不必新加埋点。
|
||||
* 段序口径(V0.1 已知限制,终报点名):目录重建后按段号数值排序环游,
|
||||
* 盘上写满 100 段翻转后「数值序 = 时间序」不再严格成立——每屏顶行都带
|
||||
* 段号自证身份,查事故先看号再看序;翻转窗口排序升级留给台架结论。
|
||||
*/
|
||||
static uint8_t s_wave_idx[WAVE_ST_SEG_MAX]; /* 盘上现存段号升序表 */
|
||||
static uint8_t s_wave_cnt; /* 表内条数 */
|
||||
static uint8_t s_wave_scanned; /* 表是否有效(manual_req 后作废) */
|
||||
static uint8_t s_wave_cached_idx; /* 段头缓存属于哪一段 */
|
||||
static uint8_t s_wave_cache_ok; /* 该段已验完整(头+数据 CRC) */
|
||||
static wave_seg_meta_t s_wave_meta; /* 已验段的头(172B,唯一驻留) */
|
||||
static uint8_t s_wave_buf[256u]; /* 读窗:数据区流式 256B/次(lead 裁定
|
||||
* 2026-10-29 第 1 条·第一条:128B 抬 256B,
|
||||
* f_read 次数对半砍,RAM 只多 128B;
|
||||
* 不选 100 位「已验」位图——位图的作废纪律
|
||||
* 一旦漏做,「CRC 不符必须拒收」这条台架
|
||||
* 判据对旧段号会静默失效,多 128B 没这个面)*/
|
||||
|
||||
/*
|
||||
* 函数功能:重扫 /wave 目录,把现存段号收集成升序表
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 扫描完成(条数见 s_wave_cnt);-1 - 卷忙本轮跳过 int
|
||||
* 限定条件:uiTask;一次 f_opendir + 有界 f_readdir,全程锁内
|
||||
* 函数说明:目录不存在(从未录过波)与目录空都归「0 段」,不是错误;
|
||||
* 名字不合形态的文件(E_NAME)静默跳过——那是别的道写进来的
|
||||
* 东西,回放页不替它背书也不报警
|
||||
*/
|
||||
static int ui_task_wave_scan(void) {
|
||||
static DIR dir; /* static:同 FIL 理由,压栈深 */
|
||||
FILINFO finfo;
|
||||
uint8_t idx;
|
||||
uint8_t i;
|
||||
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -1;
|
||||
}
|
||||
s_wave_cnt = 0u;
|
||||
if (f_opendir(&dir, WAVE_ST_DIR) == FR_OK) {
|
||||
for (;;) {
|
||||
if (f_readdir(&dir, &finfo) != FR_OK || finfo.fname[0] == '\0') {
|
||||
break;
|
||||
}
|
||||
if (finfo.fattrib & AM_DIR) {
|
||||
continue;
|
||||
}
|
||||
if (wave_store_idx_from_name(finfo.fname, &idx) == 0) {
|
||||
if (s_wave_cnt < WAVE_ST_SEG_MAX) {
|
||||
s_wave_idx[s_wave_cnt++] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
f_closedir(&dir);
|
||||
}
|
||||
vol_lock_release();
|
||||
/* 插入排序:条数上限 100,不引 qsort 的 stdlib 依赖 */
|
||||
for (i = 1u; i < s_wave_cnt; i++) {
|
||||
uint8_t j;
|
||||
uint8_t tmp;
|
||||
|
||||
tmp = s_wave_idx[i];
|
||||
j = i;
|
||||
while ((j > 0u) && (s_wave_idx[j - 1u] > tmp)) {
|
||||
s_wave_idx[j] = s_wave_idx[j - 1u];
|
||||
j--;
|
||||
}
|
||||
s_wave_idx[j] = tmp;
|
||||
}
|
||||
s_wave_scanned = 1u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:确保段表可用(必要时重扫)
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 表可用;-1 - 卷忙 int
|
||||
* 限定条件:uiTask 绘制/按键链
|
||||
*/
|
||||
static int ui_task_wave_table(void) {
|
||||
if (s_wave_scanned == 0u) {
|
||||
return ui_task_wave_scan();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:表内查段号下标
|
||||
* 入口参数:a_idx - 段号;a_pos - 出参下标
|
||||
* 返回值:1 - 命中;0 - 不在表内 uint8_t
|
||||
* 限定条件:表已可用(先过 ui_task_wave_table)
|
||||
*/
|
||||
static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) {
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0u; i < s_wave_cnt; i++) {
|
||||
if (s_wave_idx[i] == a_idx) {
|
||||
*a_pos = i;
|
||||
return 1u;
|
||||
}
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读段并做完整性判定(头 CRC 由 head_parse 判,数据 CRC 流式判),
|
||||
* 成功则把段头留在 s_wave_meta 供 seg_win 免重读
|
||||
* 入口参数:a_idx - 段号
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE(卷忙/文件没了)/ UI_WAVE_BAD(判废) int
|
||||
* 限定条件:uiTask;锁跨 open→close 完整序列;头 128B 一次、数据区 256B
|
||||
* 分窗流式(共约 1 + 20 次 f_read),整段不驻留
|
||||
* 耗时账(lead 裁定 2026-10-29 第 1 条·第三条并存):未缓存段的完整性
|
||||
* 判定最坏约 21 次小读 + 5120B 逐位 CRC,全部在卷锁内、uiTask 上;
|
||||
* IWDG 800ms 窗内余量几何**实测毫秒归台架**(终报点名待测项)。
|
||||
* 缓存只留一段:左右切段 A→B→A 会把 B 的校验再来一遍——接受该
|
||||
* 成本的代价换「CRC 不符必须拒收」永远现算,不做跨键存的位图
|
||||
* 函数说明:缓存命中(同段且已验)直接返 OK——左右切段回到上一段、上下切
|
||||
* 通道都靠它把重复的 5250B 全扫压成一次;文件读中途消失(被别的
|
||||
* 写者轮转掉)按 NONE 处理,页面下轮自然重扫
|
||||
*/
|
||||
static int ui_task_wave_load_head(uint8_t a_idx) {
|
||||
static FIL f; /* static:FIL = 528B,理由同 soe_store_read */
|
||||
char path[WAVE_ST_PATH_MAX]; /* 容量 = WAVE_ST_PATH_MAX(17 字节口径,
|
||||
* 自己声明路径缓冲不得写死 16) */
|
||||
FRESULT fr;
|
||||
UINT br = 0u;
|
||||
int rc;
|
||||
|
||||
if ((s_wave_cache_ok != 0u) && (s_wave_cached_idx == a_idx)) {
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
if (wave_store_seg_path(path, (uint32_t)sizeof(path), a_idx) != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return UI_WAVE_NONE; /* 卷忙 = 本轮按无段处理(不阻塞 uiTask) */
|
||||
}
|
||||
rc = UI_WAVE_NONE;
|
||||
if (f_open(&f, path, FA_READ) == FR_OK) {
|
||||
uint32_t size;
|
||||
uint32_t total;
|
||||
uint32_t n;
|
||||
|
||||
size = (uint32_t)f_size(&f);
|
||||
total = 0u;
|
||||
rc = UI_WAVE_BAD;
|
||||
/* 第一步:头区(首窗 128B = 当前形态整头);head_parse 自带判废 */
|
||||
n = (size < (uint32_t)WAVE_ST_HEAD_SZ_CUR) ? size
|
||||
: (uint32_t)WAVE_ST_HEAD_SZ_CUR;
|
||||
fr = f_read(&f, s_wave_buf, n, &br);
|
||||
if ((fr == FR_OK) && (br == n)) {
|
||||
if (wave_store_head_parse(s_wave_buf, n, &s_wave_meta) == 0) {
|
||||
total = wave_store_seg_size(&s_wave_meta);
|
||||
}
|
||||
}
|
||||
/* 第二步:尺寸等式(零长/长度与头部字段矛盾 = 整段判废,§6.3) */
|
||||
if ((total != 0u) && (size == total)) {
|
||||
uint32_t off;
|
||||
uint16_t crc;
|
||||
uint8_t tail[2];
|
||||
|
||||
/* 第三步:数据区流式 CRC(128B 窗;CRC 覆盖不含尾两字节自身) */
|
||||
crc = WAVE_ST_CRC_INIT;
|
||||
off = wave_store_head_bytes(s_wave_meta.ch_count);
|
||||
rc = UI_WAVE_BAD;
|
||||
while ((off + 2u) < size) {
|
||||
n = (size - 2u - off < (uint32_t)sizeof(s_wave_buf))
|
||||
? (size - 2u - off) : (uint32_t)sizeof(s_wave_buf);
|
||||
fr = f_read(&f, s_wave_buf, n, &br);
|
||||
if ((fr != FR_OK) || (br != n)) {
|
||||
rc = UI_WAVE_BAD;
|
||||
break;
|
||||
}
|
||||
crc = wave_store_crc_update(crc, s_wave_buf, n);
|
||||
off += n;
|
||||
}
|
||||
/* 第四步:读回存储值比对(低字节在前) */
|
||||
if ((rc == UI_WAVE_BAD) && (f_lseek(&f, size - 2u) == FR_OK) &&
|
||||
(f_read(&f, tail, 2u, &br) == FR_OK) && (br == 2u)) {
|
||||
if (crc == (uint16_t)(tail[0] | ((uint16_t)tail[1] << 8))) {
|
||||
rc = UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
} else if (total == 0u) {
|
||||
rc = UI_WAVE_BAD; /* seg_size 返 0 = 头字段自相矛盾,判废 */
|
||||
} else {
|
||||
rc = UI_WAVE_BAD; /* 长度与头部字段不符,判废 */
|
||||
}
|
||||
(void)f_close(&f);
|
||||
}
|
||||
vol_lock_release();
|
||||
if (rc == UI_WAVE_OK) {
|
||||
s_wave_cached_idx = a_idx;
|
||||
s_wave_cache_ok = 1u;
|
||||
} else {
|
||||
s_wave_cache_ok = 0u;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:录波触发页唯一写出口——投一次手动触发请求
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 已受理;非零 - 拒收(未 IDLE/冷却中/参数),页面显示 ERR int
|
||||
* 限定条件:uiTask(request 本身任意上下文可调,这里只是转发不等待)
|
||||
* 函数说明:受理 = ring 进冻结窗,成段最快也在约一秒后由默认任务落盘;
|
||||
* 顺手作废段表缓存,页面下次取数会看到新段
|
||||
*/
|
||||
static int ui_task_wave_manual_req(void) {
|
||||
int r = wave_ring_request((uint8_t)WAVE_TRIG_MANUAL, 0u);
|
||||
|
||||
if (r == 0) {
|
||||
s_wave_scanned = 0u;
|
||||
s_wave_cache_ok = 0u; /* lead 裁定 2026-10-29 第 3 条:段表与段头缓存
|
||||
* 一起作废——同名文件会被轮转重写,旧头字段
|
||||
* (pts/pre/k/b/逐路 avail)不作废就会顶着新段
|
||||
* 的号用旧尺子。单槽缓存本身 = 已知限制:
|
||||
* 只挡「本段验过不再重验」,多段来回切会重验 */
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取盘上「数值最大段号」(回放页开屏落点)
|
||||
* 入口参数:a_idx - 出参段号
|
||||
* 返回值:UI_WAVE_OK / NONE(无段或卷忙本轮)/ BAD 本件不产(表只含现存文件)
|
||||
* 函数说明:口径 = 数值最大,**不是时间最新**(lead 裁定 2026-10-29):
|
||||
* 回绕后数值序不等于时间序,页顶显示的段号即自证;真做时间序要
|
||||
* 逐段开文件读 frame_seq/t0,一百次开档不在回放页的账上,本波不做
|
||||
*/
|
||||
static int ui_task_wave_seg_first(uint8_t *a_idx) {
|
||||
if (ui_task_wave_table() != 0) {
|
||||
return UI_WAVE_NONE; /* 卷忙 = 本轮无段(同一条不阻塞纪律) */
|
||||
}
|
||||
if (s_wave_cnt == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
*a_idx = s_wave_idx[s_wave_cnt - 1u];
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:环游取邻段(a_dir:-1 更早 / +1 更新;段号数值序环回)
|
||||
* 入口参数:a_cur - 当前段号;a_out - 出参邻段段号
|
||||
* 返回值:UI_WAVE_OK / NONE(表不可用或已无邻段可去) int
|
||||
* 限定条件:uiTask;当前段已被轮转覆盖时先重扫一次再定位
|
||||
*/
|
||||
static int ui_task_wave_seg_around(uint8_t a_cur, int8_t a_dir, uint8_t *a_out) {
|
||||
uint8_t pos = 0u;
|
||||
|
||||
if (ui_task_wave_table() != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (ui_task_wave_find(a_cur, &pos) == 0u) {
|
||||
if ((ui_task_wave_scan() != 0) || (s_wave_cnt == 0u) ||
|
||||
(ui_task_wave_find(a_cur, &pos) == 0u)) {
|
||||
/* 当前段没了(翻转覆盖):退回首段,画面由调用侧重载 */
|
||||
if (s_wave_cnt == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
*a_out = s_wave_idx[0u];
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
if (s_wave_cnt == 1u) {
|
||||
*a_out = s_wave_idx[0u]; /* 单段:原地(用户看到的仍是这段) */
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
if (a_dir > 0) {
|
||||
*a_out = s_wave_idx[(uint8_t)((pos + 1u) % s_wave_cnt)];
|
||||
} else {
|
||||
*a_out = s_wave_idx[(uint8_t)((pos + s_wave_cnt - 1u) % s_wave_cnt)];
|
||||
}
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:切到段号表里的前一段(LR 左键的落点,数值升序表 → 「前」= 号更小)
|
||||
* 入口参数:a_cur 当前段号;a_out 输出切换后的段号(越界/空表不写)
|
||||
* 返 回 值:UI_WAVE_OK 已给出;UI_WAVE_NONE 盘上一段都没有 int
|
||||
* 限定条件:仅 uiTask 上下文(段号表是本任务私有缓存)
|
||||
* 函数说明:只是 ui_task_wave_seg_around 的方向包装,存在的理由是 ops 表要两个
|
||||
* 独立函数指针,不写第二份查找逻辑。
|
||||
*/
|
||||
static int ui_task_wave_seg_prev(uint8_t a_cur, uint8_t *a_out) {
|
||||
return ui_task_wave_seg_around(a_cur, -1, a_out);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:切到段号表里的后一段(LR 右键;数值升序表 → 「后」= 号更大)
|
||||
* 入口参数:a_cur 当前段号;a_out 输出切换后的段号(越界/空表不写)
|
||||
* 返 回 值:UI_WAVE_OK 已给出;UI_WAVE_NONE 盘上一段都没有 int
|
||||
* 限定条件:仅 uiTask 上下文(同上)
|
||||
* 函数说明:与 seg_prev 共用 ui_task_wave_seg_around,切段即触发一次懒加载;
|
||||
* 未缓存段的读盘 + 流式 CRC 也发生在这条调用链上(单轮上界见本
|
||||
* 文件「故障录波注入表执行侧」块首注释的 uiTask 阻塞上界段)。
|
||||
*/
|
||||
static int ui_task_wave_seg_next(uint8_t a_cur, uint8_t *a_out) {
|
||||
return ui_task_wave_seg_around(a_cur, 1, a_out);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读段头(含双 CRC 完整性判定——判定发生在 ui_task_wave_load_head)
|
||||
* 入口参数:a_idx - 段号;a_eng/a_trig/a_clip - 段头三字段原值
|
||||
* 返回值:UI_WAVE_OK / NONE / BAD int
|
||||
* 限定条件:uiTask;命中缓存零卷操作
|
||||
*/
|
||||
static int ui_task_wave_seg_head(uint8_t a_idx, uint8_t *a_eng,
|
||||
uint8_t *a_trig, uint8_t *a_clip) {
|
||||
int rc = ui_task_wave_load_head(a_idx);
|
||||
|
||||
if (rc != UI_WAVE_OK) {
|
||||
return rc;
|
||||
}
|
||||
*a_eng = s_wave_meta.eng;
|
||||
*a_trig = s_wave_meta.trig_src;
|
||||
*a_clip = (uint8_t)(s_wave_meta.clip_mask & 0xFFu);
|
||||
return UI_WAVE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读固定显示窗(触发点前后各一周波)的码值与峰值
|
||||
* 入口参数:a_idx - 段号;a_ch - 通道号;a_codes - UI_WAVE_WIN_PTS 个码值
|
||||
* (已并入段头 b_codes);a_peak_milli - 窗内绝对值最大点的 mV
|
||||
* 返回值:UI_WAVE_OK / NONE(段头未验过/卷忙)/ BAD(数据区读不齐) int
|
||||
* 限定条件:uiTask;先段头后窗口(未过完整性判定的段不供数据,页面显示
|
||||
* 「该段不可读」而不是半截波形——判废优先于展示)
|
||||
* 函数说明:窗口起点用段头 pre_pts 现算(§8.2「数据不是编译常量」:
|
||||
* 起点 = pre_pts - 窗长一半,与光标居中是同一个 20 的一半,
|
||||
* 改窗宽只改 ui_data.h 的 UI_WAVE_WIN_PTS 一处);峰值的 mV
|
||||
* 换算逐点走 wave_store_code_to_milli —— 全仓唯一口径,
|
||||
* 这里不重抄公式(§6.3;PC 道同一条款)
|
||||
*/
|
||||
static int ui_task_wave_seg_win(uint8_t a_idx, uint8_t a_ch,
|
||||
int16_t *a_codes, int32_t *a_peak_milli) {
|
||||
static FIL f;
|
||||
char path[WAVE_ST_PATH_MAX];
|
||||
UINT br = 0u;
|
||||
uint32_t off;
|
||||
uint32_t want;
|
||||
uint32_t start;
|
||||
uint32_t got_pts = 0u;
|
||||
int16_t raw[UI_WAVE_WIN_PTS];
|
||||
int32_t milli;
|
||||
int rc;
|
||||
|
||||
if (ui_task_wave_load_head(a_idx) != UI_WAVE_OK) {
|
||||
return UI_WAVE_BAD; /* 段头坏了/读不到:窗口无从谈起 */
|
||||
}
|
||||
if (a_ch >= s_wave_meta.ch_count) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
if ((s_wave_meta.pts_per_ch < UI_WAVE_WIN_PTS) ||
|
||||
(s_wave_meta.pre_pts < (UI_WAVE_WIN_PTS / 2u))) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
start = (uint32_t)s_wave_meta.pre_pts - (UI_WAVE_WIN_PTS / 2u);
|
||||
off = wave_store_data_off(s_wave_meta.ch_count, s_wave_meta.pts_per_ch,
|
||||
a_ch, start);
|
||||
if (off == 0xFFFFFFFFu) {
|
||||
return UI_WAVE_BAD;
|
||||
}
|
||||
want = wave_store_data_bytes_avail(s_wave_meta.pts_per_ch, (uint16_t)start,
|
||||
UI_WAVE_WIN_PTS);
|
||||
if (want != UI_WAVE_WIN_PTS * 2u) {
|
||||
return UI_WAVE_BAD; /* 窗口贴不出完整 40 点:显式不可读 */
|
||||
}
|
||||
if (wave_store_seg_path(path, (uint32_t)sizeof(path), a_idx) != 0) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return UI_WAVE_NONE;
|
||||
}
|
||||
rc = UI_WAVE_BAD;
|
||||
if (f_open(&f, path, FA_READ) == FR_OK) {
|
||||
if ((f_lseek(&f, off) == FR_OK) &&
|
||||
(f_read(&f, s_wave_buf, want, &br) == FR_OK) && (br == want)) {
|
||||
if ((wave_store_le_decode(s_wave_buf, want, raw, UI_WAVE_WIN_PTS,
|
||||
&got_pts) == 0) &&
|
||||
(got_pts == UI_WAVE_WIN_PTS)) {
|
||||
rc = UI_WAVE_OK;
|
||||
}
|
||||
}
|
||||
(void)f_close(&f);
|
||||
}
|
||||
vol_lock_release();
|
||||
if (rc == UI_WAVE_OK) {
|
||||
uint8_t i;
|
||||
int32_t peak = 0;
|
||||
|
||||
for (i = 0u; i < UI_WAVE_WIN_PTS; i++) {
|
||||
int32_t v;
|
||||
|
||||
v = (int32_t)raw[i] + s_wave_meta.ch[a_ch].b_codes;
|
||||
if (v > 32767L) {
|
||||
v = 32767L;
|
||||
} else if (v < (-32768L)) {
|
||||
v = -32768L;
|
||||
}
|
||||
a_codes[i] = (int16_t)v;
|
||||
/* 显示与度量同一把尺(「谁被显示谁被度量」,§6.3):
|
||||
* a_codes 画的是 raw+b,而 wave_store_code_to_milli 的公式是
|
||||
* (a_code + b_codes) × k_q16 / 65536000 取整(App/algo/
|
||||
* wave_store.c:567 实测口径)——传 raw 进去,b 已由唯一口径
|
||||
* 自己加过,PK 数与屏上波形天然同尺,这里不再加第二遍
|
||||
* (加两遍才是错);clamp 只护 int16 画布,b 的 V0.1 恒 0 */
|
||||
if (wave_store_code_to_milli(&s_wave_meta.ch[a_ch], raw[i],
|
||||
&milli) == 0) {
|
||||
if ((milli < 0 ? -milli : milli) >
|
||||
(peak < 0 ? -peak : peak)) {
|
||||
peak = milli;
|
||||
}
|
||||
}
|
||||
}
|
||||
*a_peak_milli = peak;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 录波通道函数表(六件齐全;ui_data_wave_bind 拒绝残表,缺件就是装配错) */
|
||||
static const ui_data_wave_ops_t s_wave_ops = {
|
||||
ui_task_wave_manual_req,
|
||||
ui_task_wave_seg_first,
|
||||
ui_task_wave_seg_prev,
|
||||
ui_task_wave_seg_next,
|
||||
ui_task_wave_seg_head,
|
||||
ui_task_wave_seg_win,
|
||||
};
|
||||
|
||||
/*
|
||||
* 函数功能:推流往返边界的心跳续命钩(F3,注册给 ui_net_sink)
|
||||
* 入口参数:无
|
||||
@@ -431,6 +898,12 @@ void ui_task_func(void *argument) {
|
||||
if (ui_data_maint_bind(&s_maint_ops) != 0) {
|
||||
DBG_ERROR("UI maint bind: FAIL");
|
||||
}
|
||||
/* 录波通道注入(故障录波道 UI 侧,2026-10-29):手动录波触发页与回放页
|
||||
* 的唯一数据入口,同样先于 ui_menu_init。未注入窗口里六件按「无录波段」
|
||||
* 空态渲染(不画线、不崩);残表 = 装配错,启动日志必须可见 */
|
||||
if (ui_data_wave_bind(&s_wave_ops) != 0) {
|
||||
DBG_ERROR("UI wave bind: FAIL");
|
||||
}
|
||||
/* 快照层复位:必须先于 ui_menu_init(首帧之前任何页面被打开都按"无数据"
|
||||
* 渲染 "--",不会读到上电残留)——UI_数据供给设计说明.md §5.3 */
|
||||
ui_data_init();
|
||||
|
||||
Reference in New Issue
Block a user