merge: v2-on-ui into task/screenlink-bench-serial (wave + screenlink)
This commit is contained in:
@@ -60,6 +60,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 常开探针:循环周期 / 工作耗时 / 推流往返 */
|
||||
#include "sb_link.h" /* 屏板链路:帧 B 组帧 / 按键出队 / 统计与离线判定(批 2) */
|
||||
|
||||
@@ -359,6 +369,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)
|
||||
* 入口参数:无
|
||||
@@ -462,6 +929,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