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:
@@ -106,6 +106,15 @@
|
||||
* 不并进 cal: 行是因为 9 个码值会把该行推到 168 字符、
|
||||
* 撞 DBG_CON_LINE_MAX(192) 的静默截断边界(见该行注释)。
|
||||
* 无新增跨任务状态、无新增外设访问,栈 +36 B(局部码值数组)
|
||||
* v3.4 2026-10-29 DTU 项目组 故障录波数据面接线(task-2):新增 2c——每拍把 8 路
|
||||
* 语义原码压进 wave_ring 并 tick 推进;EVT_SEALED 那一拍现取
|
||||
* 折算/有效性/墙钟事实组 wave_seal_cal_t 并 latch_meta 恰好
|
||||
* 一次(握手次序照 App/algo/wave_ring.h 头注唯一合法次序);
|
||||
* 心跳末尾加 §9 唯一周期行 wave:(字段逐字,取数
|
||||
* wave_ring_get_stat);新增 2d——bench 定周期自抓,只在
|
||||
* DTU_BENCH 且 WAVE_BENCH_AUTOCAP 非 0 的双层编译门内存在,
|
||||
* 默认关闭 + 5 分钟时长上限 + 启动一次性告警(§10 红线);
|
||||
* adc_task_init 内接 wave_ring_init(init 禁日志约束不破)
|
||||
*/
|
||||
|
||||
#include "adc_task.h"
|
||||
@@ -123,6 +132,8 @@
|
||||
#include "console_log.h"
|
||||
#include "sys_log.h" /* SYSLOG_SELFTEST:采样故障留痕(仅故障才写) */
|
||||
#include "task_prof.h" /* 常开耗时探针:单拍耗时 */
|
||||
#include "wave_ring.h" /* 故障录波环(task-2 数据面接线):push/tick/latch_meta/get_stat */
|
||||
#include "sys_clock.h" /* 封存交棒的 wall 字段:sys_clock_get() epoch 秒(UINT32_MAX=未对时,写 0) */
|
||||
|
||||
#define DBG_TAG "[ADC_TASK]"
|
||||
#include "dbg_log.h"
|
||||
@@ -132,6 +143,17 @@
|
||||
* 消费节拍改由 TIM3 ISR 置位 ADC_WAKE_FLAG 提供,与生产同源,不再自定周期) */
|
||||
#define ADC_TASK_REPORT_MS 10000U
|
||||
|
||||
/* bench 定周期自抓(§10 红线「默认关闭 + 有时长上限 + 启动打一次告警」的落点):
|
||||
* WAVE_BENCH_AUTOCAP 默认 0——产品档连外层 DTU_BENCH 都没有,双层编译门保证
|
||||
* 产品镜像里 2d 段整段不存在;台架确实要定周期刷段时才本地改 1 重编译,
|
||||
* 用完立刻回 0(每 2 s 一段 = 每 5 分钟一轮 NAND 写,长挂白烧写次数,dhara
|
||||
* 坏块记账还是持久代价)。宏形态照 App/algo/adc_cal.c 的 adc_cal_prot_blocked():
|
||||
* 联合条件放 #if,未定义宏恒为假,不把 cppcheck 引到旁路上。
|
||||
* 周期/上限取自 §10 的算式:2 s/段、300 s 自动停。 */
|
||||
#define WAVE_BENCH_AUTOCAP 0
|
||||
#define WAVE_BENCH_PERIOD_MS 2000u
|
||||
#define WAVE_BENCH_MAX_MS 300000u
|
||||
|
||||
/* 统计:已消费帧数 / 无新帧次数(首拍必为无新帧,之后应稳定为 0 增量)。
|
||||
* 原始码值缓冲由 语义取数与有效性层 App/algo/adc_input.c 持有(消费侧按语义取数,不写死下标) */
|
||||
static uint32_t s_frames_consumed;
|
||||
@@ -157,6 +179,20 @@ static uint8_t s_rail_syslogged;
|
||||
* 200 ms(与 v2.6 修"重复采样污染 DFT 窗"同族误差,勿"优化"成拍计数) */
|
||||
static uint32_t s_meas_ms;
|
||||
|
||||
/* 故障录波环接线状态(task-2;状态机与计数全在 App/algo/wave_ring.c,这里只搬运):
|
||||
* s_wave_codes - 本拍 8 路语义原码(文件级 static,不上单拍的栈——本任务静态
|
||||
* 最坏链余量按 1024 B ISR 底线核)
|
||||
* s_wave_cal - EVT_SEALED 拍的交棒结构(同理不放局部:整块 ~160 B) */
|
||||
static int16_t s_wave_codes[WAVE_RING_CH];
|
||||
static wave_seal_cal_t s_wave_cal;
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
/* bench 自抓编排:on = 已过写者就绪门开始跑;off = 到时自动停(init 重置) */
|
||||
static uint8_t s_wave_bench_on;
|
||||
static uint8_t s_wave_bench_off;
|
||||
static uint32_t s_wave_bench_t0;
|
||||
static uint32_t s_wave_bench_next;
|
||||
#endif
|
||||
|
||||
|
||||
/* 消费者就绪门:由 adcTask 自己在进入等待前置 1;单写者 = 本任务,ISR 只读。
|
||||
* ⚠ 为什么必须有这道门:TIM3 在 app_main_init()(Src/main.c:140)里就
|
||||
@@ -288,6 +324,13 @@ int adc_task_init(void) {
|
||||
s_meas_ms = 0u;
|
||||
adc_health_init(); /* 内含 采样通道自检 贴轨观察器复位(rail_init) */
|
||||
(void)meas_init(); /* 遥测快照双区/序号复位(幂等;发布状态属本任务,init 无日志) */
|
||||
wave_ring_init(); /* 录波环纯清 RAM(无内核依赖;本函数在开闸前跑,init 禁日志不破) */
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
s_wave_bench_on = 0u;
|
||||
s_wave_bench_off = 0u;
|
||||
s_wave_bench_t0 = 0u;
|
||||
s_wave_bench_next = 0u;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -454,6 +497,103 @@ void adc_task_func(void *arg) {
|
||||
adc_health_report(&h_st);
|
||||
}
|
||||
|
||||
/* 2c) 故障录波环泵送(task-2 数据面接线;状态机与计数全在 App/algo/wave_ring.c,
|
||||
* 本段只做搬运)。每拍取 8 路语义**原码**压环 + tick 推进;触发那拍的样本
|
||||
* 落在段内点 WAVE_RING_PRE-1(点序 lead 钉死)。只存原码不存折算工程量——
|
||||
* §6.3 裁定:样本域 = 原始码,折算系数进段头,回放/PC 侧统一换算。
|
||||
* 某路取不到(开机头几拍)保持上值(与 adc: 心跳行同一兜底口径)。
|
||||
* tick 返 EVT_SEALED 的那拍必须**同拍** latch meta(头注唯一合法次序),
|
||||
* 漏了 = 2 s 后环自弃计 err,defaultTask 根本不会见到这一段。 */
|
||||
{
|
||||
uint8_t w;
|
||||
int32_t wev;
|
||||
|
||||
for (w = 0u; w < WAVE_RING_CH; w++) {
|
||||
int32_t wc = 0;
|
||||
|
||||
if (adc_input_raw((adc_semantic_t)w, &wc) == 0) {
|
||||
s_wave_codes[w] = (int16_t)wc;
|
||||
}
|
||||
}
|
||||
(void)wave_ring_push(s_wave_codes, WAVE_RING_CH);
|
||||
wev = wave_ring_tick(now, s_frames_consumed);
|
||||
|
||||
if (wev == WAVE_RING_EVT_SEALED) {
|
||||
uint8_t c;
|
||||
|
||||
/* 封存瞬间事实(交棒结构 s_wave_cal 为文件级 static,不上栈):
|
||||
* eng / VALID 位图 / CLIPPED 位图 / 墙钟 / 逐路 conv+bits+avail+k+b。
|
||||
* eng 口径 = adc_cal.h 注③ 的 8 路量纲标签(判据源
|
||||
* App/app/ui_data_feed.c feed_eng(),与 prot_blocked 的三路口径**不是**
|
||||
* 一件事):8 路电气量全 VALID 才是二次值;V0.1 无 CT 变比,一次值不可达。
|
||||
* wall 未对时写 0(sys_clock_get 失败 = UINT32_MAX,不许拿上电毫秒冒充)。
|
||||
* k 取 adc_cal_k_q16() int32 原值;b 的 float 回乘在本域无损
|
||||
* (|b| 限幅 8192 << 2^24,float32 精确表示整数;禁用浮点只针对 k)。 */
|
||||
s_wave_cal.flags_seq = 0u;
|
||||
s_wave_cal.clip_mask = 0u;
|
||||
for (c = 0u; c < WAVE_RING_CH; c++) {
|
||||
const uint32_t f = adc_cal_flags((adc_semantic_t)c);
|
||||
const adc_channel_desc_t *d = adc_input_desc((adc_semantic_t)c);
|
||||
|
||||
if ((f & ADC_CAL_F_VALID) != 0u) {
|
||||
s_wave_cal.flags_seq |= (uint8_t)(1u << c);
|
||||
}
|
||||
if ((f & ADC_CAL_F_CLIPPED) != 0u) {
|
||||
s_wave_cal.clip_mask |= (uint16_t)(1u << c);
|
||||
}
|
||||
s_wave_cal.ch[c].conv = (d != NULL) ? (uint8_t)d->source
|
||||
: (uint8_t)ADC_SRC_STM32;
|
||||
s_wave_cal.ch[c].bits = (s_wave_cal.ch[c].conv == (uint8_t)ADC_SRC_TPAFE)
|
||||
? 16u : 12u;
|
||||
s_wave_cal.ch[c].avail = (uint8_t)f;
|
||||
s_wave_cal.ch[c].k_q16 = adc_cal_k_q16((adc_semantic_t)c);
|
||||
s_wave_cal.ch[c].b_codes = (int32_t)adc_cal_b_codes((adc_semantic_t)c);
|
||||
}
|
||||
s_wave_cal.eng = (s_wave_cal.flags_seq == 0xFFu)
|
||||
? (uint8_t)WAVE_ENG_SEC : (uint8_t)WAVE_ENG_PIN;
|
||||
{
|
||||
uint32_t wall = sys_clock_get();
|
||||
|
||||
s_wave_cal.wall = (wall == UINT32_MAX) ? 0u : wall;
|
||||
}
|
||||
/* 只在 EVT_SEALED 拍调一次;返错不补救——环 2 s 超时自弃兜底,
|
||||
* 这里补第二行日志就违反 §9「不在已留痕处重复锚点」 */
|
||||
(void)wave_ring_latch_meta(&s_wave_cal);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2d) bench 定周期自抓(§10 红线落点:默认关闭 + 时长上限 + 启动一次性告警)。
|
||||
* 编译门 = DTU_BENCH 且 WAVE_BENCH_AUTOCAP 非 0,产品镜像无本段。
|
||||
* 节拍:每 WAVE_BENCH_PERIOD_MS 投一次 bench 请求(环侧冷却/去重自管,
|
||||
* 被丢的计入 ovf——这是 bench 的预期观测面,不是故障)。
|
||||
* 计时起点等写者就绪门(v2.1 理由:就绪前直发会与 defaultTask 存储
|
||||
* 日志字节级交错);到时自动停 + 一次性 STOP 行,之后不再投任何请求。
|
||||
* ⚠ 这两条 WARN/STOP 是一次性生命周期日志,不是周期行,§9 一条线约束不破 */
|
||||
#if defined(DTU_BENCH) && (WAVE_BENCH_AUTOCAP != 0)
|
||||
if (s_wave_bench_off == 0u) {
|
||||
if (s_wave_bench_on == 0u) {
|
||||
if (dbg_console_writer_ready() != 0u) {
|
||||
s_wave_bench_on = 1u;
|
||||
s_wave_bench_t0 = now;
|
||||
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
||||
/* [WARN] 进正文(本仓无 WARN 级;用 ERR 级会带 [ERR] 误触 @test 禁用标记) */
|
||||
DBG_INFO("[WARN] wave bench autocap ON: 1 seg / %u ms, auto-stop %u s (BENCH BUILD ONLY)",
|
||||
(unsigned int)WAVE_BENCH_PERIOD_MS,
|
||||
(unsigned int)(WAVE_BENCH_MAX_MS / 1000u));
|
||||
}
|
||||
} else if ((uint32_t)(now - s_wave_bench_t0) >= WAVE_BENCH_MAX_MS) {
|
||||
s_wave_bench_off = 1u;
|
||||
DBG_INFO("wave bench autocap STOP after %u s",
|
||||
(unsigned int)(WAVE_BENCH_MAX_MS / 1000u));
|
||||
} else if ((uint32_t)(now - s_wave_bench_next) >= WAVE_BENCH_PERIOD_MS) {
|
||||
s_wave_bench_next = now + WAVE_BENCH_PERIOD_MS;
|
||||
(void)wave_ring_request((uint8_t)WAVE_TRIG_BENCH, 0u);
|
||||
} else {
|
||||
/* 本拍未到周期点:无事可做(比较用无符号差值,回绕安全) */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 3) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走
|
||||
* 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错
|
||||
* (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。
|
||||
@@ -610,6 +750,46 @@ void adc_task_func(void *arg) {
|
||||
(long)(ms.freq_hz * 100.0f),
|
||||
(unsigned)ms.freq_valid);
|
||||
}
|
||||
|
||||
/* 录波环周期行(docs/子系统设计/故障录波设计说明.md §9 逐字模板,
|
||||
* 字段名与顺序**不许动**——PC/台架脚本按行名 grep)。全链路只加这一条
|
||||
* 周期行(每条 DBG ≈90 B Flash + 216 B 栈,§9 明令不加第二条):
|
||||
* seg = 累计成功 ACK 的段数(wave_ring_stat.seg_total,不是轮转号)
|
||||
* armed= 1 表示此刻有段在飞(FREEZE/SEALED;IDLE/COOL 为 0——
|
||||
* COOL 只挡新触发不录新段,画进 armed 会让台架误读)
|
||||
* src = 触发源 M 手动 / P 保护 / T bench 定时,- = 本 boot 未触发过
|
||||
* err = 落盘失败 + SEALED 超时自弃(环内 err_cnt 收口,落盘道不数
|
||||
* 第二份账);ovf = ACK 前重复触发与冷却期丢弃数;
|
||||
* drop = 冻结窗口丢弃的录波点数(单位=点)
|
||||
* 判读:bench 自抓一轮后 seg_total 应增长;err 涨而 seg 不涨 =
|
||||
* 落盘道在丢段,查 defaultTask 侧 [WAVE] 失败行 */
|
||||
{
|
||||
wave_ring_stat_t wst;
|
||||
|
||||
(void)wave_ring_get_stat(&wst);
|
||||
{
|
||||
const char *sc;
|
||||
|
||||
if (wst.trig_src == (uint8_t)WAVE_TRIG_MANUAL) {
|
||||
sc = "M";
|
||||
} else if (wst.trig_src == (uint8_t)WAVE_TRIG_PROTECT) {
|
||||
sc = "P";
|
||||
} else if (wst.trig_src == (uint8_t)WAVE_TRIG_BENCH) {
|
||||
sc = "T";
|
||||
} else {
|
||||
sc = "-";
|
||||
}
|
||||
DBG_INFO("wave: seg=%lu armed=%u src=%s pts=%u err=%lu ovf=%lu drop=%lu",
|
||||
(unsigned long)wst.seg_total,
|
||||
(unsigned)((wst.state == (uint8_t)WAVE_RING_ST_FREEZE) ||
|
||||
(wst.state == (uint8_t)WAVE_RING_ST_SEALED)) ? 1u : 0u,
|
||||
sc,
|
||||
(unsigned int)WAVE_RING_PTS,
|
||||
(unsigned long)wst.err_cnt,
|
||||
(unsigned long)wst.ovf_cnt,
|
||||
(unsigned long)wst.drop_cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
report_at = now + ADC_TASK_REPORT_MS;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
* v1.3 2026-10-25 DTU 项目组 写回缓存空闲 flush 接线:主循环 syslog 排空
|
||||
* 之后调 vol_idle_sync_poll()(卷空闲周期 CTRL_SYNC 兜底,
|
||||
* 模块 = App/fs/vol_idle_sync.[ch],道F 批 2)
|
||||
* v1.4 2026-10-29 DTU 项目组 故障录波落盘道(task-2):vol_idle_sync_poll 之后
|
||||
* 新增 wave_store_drain()——一次心跳整段搬完(lead 裁③:
|
||||
* 一次 try 锁内 pack_next 256 B 分片 + 完整 open...close),
|
||||
* 持锁耗时 >50 ms 补一条 WARN、峰值 ms 记本层静态;
|
||||
* 轮转号自持(启动扫 /wave 重建,成功 ACK 才推进,lead 裁(b));
|
||||
* 失败 = 先 unlink 半成品再 write_done(-1)(lead 裁①);
|
||||
* 卷未就绪 / 环未封存静默跳过(§5.1 所有权 + §9 一条线约束)
|
||||
* v1.5 2026-10-29 DTU 项目组 Lead 复核补第四道并发陷阱(本道与 adcTask 的抢占窗):
|
||||
* 搬段中途 adcTask 可能正好走到 SEALED 滞留自弃(2 s)甚至
|
||||
* 已封出下一段——CRC 是对"读到的字节"算的,半新半旧的段照样
|
||||
* 自洽,只能靠写完关档后复检环侧身份(握手量 + 段首 t0/帧序)
|
||||
* 判废:判废即 unlink、不 ACK、号不推进,失败按既有单条留痕
|
||||
*/
|
||||
|
||||
#include "default_task.h"
|
||||
@@ -53,6 +65,8 @@
|
||||
#include "task_prof.h" /* 各任务最坏耗时上报 */
|
||||
#include "vol_lock.h"
|
||||
#include "vol_idle_sync.h" /* 写回缓存空闲 flush 兜底(App/fs/vol_idle_sync.h) */
|
||||
#include "wave_ring.h" /* 录波环消费侧:sealed/sealed_meta/read_cb/write_done(task-2) */
|
||||
#include "wave_store.h" /* 段打包游标 + 段号轮转纯函数(落盘道本体,task-2) */
|
||||
#include "task_handles.h" /* 任务句柄与属性块的引用契约(定义处仍是 Src/freertos.c) */
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
@@ -72,6 +86,14 @@
|
||||
* (自检已上报、卷保留、不重格式化),等下次上电重新 resume */
|
||||
#define STORAGE_BRINGUP_RETRIES 3u
|
||||
|
||||
/* 录波段落盘持锁耗时告警线(lead 裁③i):一次心跳整段 5250 B(约 21 次 256 B
|
||||
* f_write),设计 §4.2 预估落盘窗「几十 ms」;越过本线说明本段在占用 defaultTask
|
||||
* 喂狗链前的最慢一段,按事件补一条 WARN(不是周期行),并把历史峰值留在本层
|
||||
* 静态里供收工报告核喂狗余量(vol_lock.h 铁律「等锁即毁喂狗链」的前例在案) */
|
||||
#define WAVE_DRAIN_HELD_WARN_MS 50u
|
||||
/* 搬运缓冲(§7 的 256 B;wave_store_pack_next 头阶段要求 cap >= 128,lead 转告(d)) */
|
||||
#define WAVE_DRAIN_CHUNK_B 256u
|
||||
|
||||
/* Private typedef -------------------------------------------------------------*/
|
||||
typedef struct {
|
||||
const char *p_name;
|
||||
@@ -97,6 +119,7 @@ static void stack_probe_poll(void);
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
static void default_task_net_cfg_save_notify(void);
|
||||
static FRESULT storage_bring_up(FATFS *fs);
|
||||
static void wave_store_drain(void);
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -301,6 +324,10 @@ void default_task_func(void *argument) {
|
||||
* 一次 f_close 同型的同步段。放在 drain 之后:本拍若写过日志,close
|
||||
* 已同步过,这里随即 clean 短路——漏网脏页的驻留窗由周期封顶 */
|
||||
vol_idle_sync_poll();
|
||||
/* 录波段落盘(task-2):卷空闲 + 环已封存才搬,就绪门/try 锁全在函数内;
|
||||
* 一次心跳最多一段(lead 裁③)。放在 idle sync 之后:本拍若刚写过录波段,
|
||||
* 下一拍空闲 flush 会兜住残留脏页,语义与 syslog 排空同位 */
|
||||
wave_store_drain();
|
||||
#endif
|
||||
/* 栈水位观测:首次 5s 后报告,此后每 2s 采样、出现更深峰值才打印 */
|
||||
task_wd_seg(TASK_WD_SEG_STACK);
|
||||
@@ -565,6 +592,248 @@ static FRESULT storage_bring_up(FATFS *fs) {
|
||||
}
|
||||
#endif /* !TEST_SUITE_STORAGE */
|
||||
|
||||
/*
|
||||
* 函数功能:故障录波落盘道——把已封存的 wave_ring 段整段搬进 /wave(task-2)
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅 defaultTask 主循环调用(vol_idle_sync_poll 之后);内部绝不阻塞——
|
||||
* try 锁 + 有界段长(5250 B / 256 B ≈ 21 次 f_write,设计 §4.2 预估几十 ms),
|
||||
* 绝不把喂狗点做成等待态(vol_lock.h 使用约定:defaultTask 一律 try)
|
||||
* 函数说明:所有权按 docs/子系统设计/故障录波设计说明.md §5.1——环的生产者是
|
||||
* adcTask,本道是唯一落盘消费者;跨任务握手只认 wave_ring_sealed()。
|
||||
* 本道承担五条口径:
|
||||
* ① 打开一律 FA_WRITE|FA_OPEN_APPEND:本移植 FA_OPEN_APPEND 不含 FA_WRITE
|
||||
* (docs/架构设计/STM32F4-Base存储架构说明.md §10.1 陷阱一),少写位
|
||||
* f_write 恒 FR_DENIED;
|
||||
* ② 每次 f_write 比对写回字节数:本移植簇分配失败返 FR_OK+短写无错误码
|
||||
* (同处陷阱二),短写一律判失败;
|
||||
* ③ 轮转号由本道自持(lead 转告(b)):ring.seg_idx 只是本 boot 本地计数,
|
||||
* 盘上权威下标 = 首次持锁扫 /wave 用 wave_store_idx_from_name() 重建
|
||||
* (取已存在最大号 +1,空目录 = 0,陌生名字不入账),写成功才
|
||||
* wave_store_next_idx() 推进;失败不推进、重试同号;
|
||||
* ④ 成败都必须 wave_ring_write_done() ACK——ACK 前环一直冻结;err 账
|
||||
* 由 ring 的 err_cnt 单点收口(§9),本道不数第二份(ACK 前环已
|
||||
* 超时自弃的那次例外:skip 掉 ACK,不去补第二笔账);
|
||||
* ⑤ 锁跨越一次完整 open...close(vol_lock.h 铁律),持锁耗时超
|
||||
* WAVE_DRAIN_HELD_WARN_MS 按事件补一条 WARN(§9 唯一周期行不在本道),
|
||||
* 历史峰值 s_wheld_ms_max 供核 defaultTask 喂狗余量(lead 裁③)。
|
||||
* 失败处置(lead 裁①):开过文件才 unlink 半成品(未开成的失败不动盘,
|
||||
* 绝不拿未初始化的路径误删旧段);unlink 失败照旧 ACK(-1)——最坏留
|
||||
* 一个坏段文件,PC 侧被 CRC 判废,不会画成证据。
|
||||
* 静默跳过(不刷屏):卷未就绪 / 环未封存 / try 锁没到(下轮再来,
|
||||
* 环侧 2 s 超时自弃是兜底账,本道不为此打日志)。
|
||||
* (上面五条口径就是本函数功能「把已封存段整段搬进 /wave」的逐条展开,
|
||||
* 细则出处 docs/子系统设计/故障录波设计说明.md §5.1 与 §10)
|
||||
*/
|
||||
static void wave_store_drain(void) {
|
||||
static FIL s_wfil; /* static:FF_FS_TINY=0 时 FIL ~528 B,
|
||||
* 绝不上栈(FS_VOL_PROBE 同一理由) */
|
||||
static uint8_t s_wbuf[WAVE_DRAIN_CHUNK_B];
|
||||
static wave_pack_ctx_t s_wpack;
|
||||
static wave_seg_meta_t s_wmeta;
|
||||
static char s_wpath[WAVE_ST_PATH_MAX];
|
||||
static DIR s_wdir;
|
||||
static FILINFO s_wfno;
|
||||
static uint8_t s_wnext_idx; /* 本道自持的下一个段号 0..99 */
|
||||
static uint8_t s_wscan_done; /* 1 = 本 boot 已扫过 /wave */
|
||||
static uint8_t s_wdir_ready; /* 1 = /wave 已建或已存在 */
|
||||
static uint8_t s_wcrc_state; /* 0 未验 1 过 2 败(败=永久自弃,不烧垃圾段) */
|
||||
static uint32_t s_wheld_ms_max; /* 持锁搬段历史峰值 ms(收工报告核余量用) */
|
||||
uint32_t t0_ms;
|
||||
uint32_t held_ms;
|
||||
uint32_t written = 0u;
|
||||
uint32_t len = 0u;
|
||||
uint32_t iter;
|
||||
uint8_t opened = 0u;
|
||||
uint8_t skip_ack = 0u; /* 1 = 环已自行了断本段,本道不 ACK 不补账 */
|
||||
wave_ring_stat_t wst2; /* 关档后复检环侧身份用(v1.5) */
|
||||
const char *why = NULL;
|
||||
FRESULT fr;
|
||||
UINT bw;
|
||||
int rc;
|
||||
|
||||
/* 就绪门(同 vol_idle_sync_poll 的四级门风格) */
|
||||
if (sys_evtHandle == NULL) {
|
||||
return;
|
||||
}
|
||||
if ((osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) == 0u) {
|
||||
return;
|
||||
}
|
||||
/* 封存门:只搬已封存段(唯一跨任务握手量,§5.1) */
|
||||
if (wave_ring_sealed() == 0u) {
|
||||
return;
|
||||
}
|
||||
/* 空闲门:try 锁;卷被 syslog/配置/FTP 占住 → 本轮放弃,不排队不打日志 */
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return;
|
||||
}
|
||||
t0_ms = (uint32_t)xTaskGetTickCount();
|
||||
|
||||
/* CRC 锚点自检每 boot 一次(落盘前失败 = 整族失败,永久自弃免得白烧写次数) */
|
||||
if (s_wcrc_state == 0u) {
|
||||
s_wcrc_state = (wave_store_crc_selftest() == WAVE_ST_OK) ? 1u : 2u;
|
||||
}
|
||||
if (s_wcrc_state == 2u) {
|
||||
why = "crc selftest FAIL";
|
||||
}
|
||||
if ((why == NULL) && (s_wdir_ready == 0u)) {
|
||||
fr = f_mkdir(WAVE_ST_DIR);
|
||||
if ((fr == FR_OK) || (fr == FR_EXIST)) {
|
||||
s_wdir_ready = 1u;
|
||||
} else {
|
||||
why = "mkdir /wave";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (s_wscan_done == 0u)) {
|
||||
FRESULT rr;
|
||||
|
||||
fr = f_opendir(&s_wdir, WAVE_ST_DIR);
|
||||
if (fr == FR_OK) {
|
||||
uint8_t found = 0u;
|
||||
uint8_t mx = 0u;
|
||||
|
||||
for (;;) {
|
||||
uint8_t ix;
|
||||
|
||||
rr = f_readdir(&s_wdir, &s_wfno);
|
||||
if ((rr != FR_OK) || (s_wfno.fname[0] == 0)) {
|
||||
break;
|
||||
}
|
||||
if (wave_store_idx_from_name(s_wfno.fname, &ix) == WAVE_ST_OK) {
|
||||
if ((found == 0u) || (ix > mx)) {
|
||||
mx = ix;
|
||||
}
|
||||
found = 1u;
|
||||
}
|
||||
}
|
||||
(void)f_closedir(&s_wdir);
|
||||
if (rr == FR_OK) {
|
||||
/* max+1 即下一写号;回绕号复用由 PC 侧 t0/frame_seq 对账兜底,
|
||||
* 目录里没有的事实不去猜更多(陌生名字一律不入账) */
|
||||
s_wnext_idx = (found != 0u) ? wave_store_next_idx(mx) : 0u;
|
||||
s_wscan_done = 1u;
|
||||
} else {
|
||||
why = "readdir /wave";
|
||||
}
|
||||
} else {
|
||||
why = "opendir /wave";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (s_wscan_done != 0u)) {
|
||||
if (wave_ring_sealed_meta(&s_wmeta) != WAVE_ST_OK) {
|
||||
/* sealed 检查与此读之间环 2 s 超时自弃(err 已由环计过):
|
||||
* 本道不 ACK、不补账、不打日志,放锁走人 */
|
||||
skip_ack = 1u;
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
/* lead 转告(e):E_FORMAT/E_SIZE = 坏段头,一律当落盘失败丢段计 err,
|
||||
* 不当参数错吞掉 */
|
||||
rc = wave_store_pack_start(&s_wpack, &s_wmeta, wave_ring_read_cb, NULL);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
why = "pack_start";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
if (wave_store_seg_path(s_wpath, (uint32_t)sizeof(s_wpath),
|
||||
s_wnext_idx) <= 0) {
|
||||
why = "seg_path";
|
||||
} else {
|
||||
fr = f_open(&s_wfil, s_wpath, FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr != FR_OK) {
|
||||
why = "f_open";
|
||||
} else {
|
||||
opened = 1u;
|
||||
}
|
||||
}
|
||||
}
|
||||
iter = 0u;
|
||||
while ((why == NULL) && (opened != 0u)) {
|
||||
rc = wave_store_pack_next(&s_wpack, s_wbuf,
|
||||
(uint32_t)sizeof(s_wbuf), &len);
|
||||
if ((rc != WAVE_ST_OK) && (rc != WAVE_ST_DONE)) {
|
||||
why = "pack_next";
|
||||
break;
|
||||
}
|
||||
if (len != 0u) {
|
||||
bw = 0u;
|
||||
fr = f_write(&s_wfil, s_wbuf, (UINT)len, &bw);
|
||||
if ((fr != FR_OK) || (bw != (UINT)len)) {
|
||||
why = "f_write short"; /* ②:FR_OK+短写 = 本移植独有失败形态 */
|
||||
break;
|
||||
}
|
||||
written += len;
|
||||
}
|
||||
if (rc == WAVE_ST_DONE) {
|
||||
break;
|
||||
}
|
||||
if (++iter > 32u) { /* 5250/256 = 21 片;游标卡死时的有界保险 */
|
||||
why = "pack stall";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opened != 0u) {
|
||||
fr = f_close(&s_wfil);
|
||||
if ((why == NULL) && (skip_ack == 0u) && (fr != FR_OK)) {
|
||||
why = "f_close";
|
||||
}
|
||||
}
|
||||
if ((why == NULL) && (skip_ack == 0u) && (opened != 0u)) {
|
||||
if (written != wave_store_pack_total(&s_wpack)) {
|
||||
why = "len mismatch"; /* 长度等式(§6.3):半截文件骗不过这条 */
|
||||
}
|
||||
}
|
||||
/* v1.5(lead 复核):adcTask 优先级高于本道,搬段中途它可能正好把滞留 2 s 的本段
|
||||
* 自弃(甚至已经封出下一段)。数据区的 CRC 是对"本道读到的字节"算的,半新半旧的
|
||||
* 段一样自洽 → 关档后必须复检:握手量没了、或段首身份不是本段,一律判废。
|
||||
* 判废走既有失败路:unlink 本道开过的文件 + write_done(-1)(此时多半回 E_STATE,
|
||||
* 环侧的 err 已由自弃计过,这里不重复补账)+ 段号不推进(同号重写覆盖)。 */
|
||||
if ((why == NULL) && (skip_ack == 0u)) {
|
||||
if ((wave_ring_sealed() == 0u) || (wave_ring_get_stat(&wst2) != WAVE_ST_OK)
|
||||
|| (wst2.last_t0_ms != s_wmeta.t0_ms) || (wst2.last_seq != s_wmeta.frame_seq)) {
|
||||
why = "ring abandoned";
|
||||
}
|
||||
}
|
||||
if (skip_ack == 0u) {
|
||||
/* 失败留痕边沿锁存(搬进本块,lead 裁方案 A):
|
||||
* ① 它是 static,初值只在编译期发生一次、不是每次进块重置;
|
||||
* ② 它是"上一拍是否处于失败态"的边沿锁存,所以必须跨调用存活;
|
||||
* ③ 单独放这里而不在函数入口那 12 连排台账里,因为 variableScope
|
||||
* 检查要求把局部量收进真正覆盖全部使用点的最小块(本仓刻意开了 style)。 */
|
||||
static uint8_t s_wfail_latch; /* 一个 episode 只留一条痕 */
|
||||
|
||||
if (why == NULL) {
|
||||
(void)wave_ring_write_done(0);
|
||||
s_wnext_idx = wave_store_next_idx(s_wnext_idx); /* 成功才推进(转告(b)) */
|
||||
s_wfail_latch = 0u;
|
||||
} else {
|
||||
if (opened != 0u) {
|
||||
(void)f_unlink(s_wpath); /* 只清本道开过的文件;unlink 失败照旧 ACK */
|
||||
}
|
||||
(void)wave_ring_write_done(-1);
|
||||
if (s_wfail_latch == 0u) {
|
||||
s_wfail_latch = 1u; /* 持续失败不刷屏;成功一次重新武装 */
|
||||
DBG_ERROR("[WAVE] seg drop: %s (idx=%u written=%lu)", why,
|
||||
(unsigned)s_wnext_idx, (unsigned long)written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 持锁耗时台账(lead 裁③i/ii):超线按事件补一条 WARN(原先这里静默占锁,
|
||||
* 就是「等锁即毁喂狗链」前例的同族黑洞);峰值进静态供收工报告 */
|
||||
held_ms = (uint32_t)xTaskGetTickCount() - t0_ms;
|
||||
if (held_ms > s_wheld_ms_max) {
|
||||
s_wheld_ms_max = held_ms;
|
||||
}
|
||||
if ((skip_ack == 0u) && (held_ms > (uint32_t)WAVE_DRAIN_HELD_WARN_MS)) {
|
||||
/* 本仓 dbg_log 无 WARN 级(宏只有 ERROR/INFO/DEBUG,ERR 级自动带 [ERR] = @test
|
||||
* 禁用标记):[WARN] 按 console_log.c 先例进正文,慢不是错(真失败另有 seg drop) */
|
||||
DBG_INFO("[WAVE][WARN] seg held %lu ms (idx=%u)",
|
||||
(unsigned long)held_ms, (unsigned)s_wnext_idx);
|
||||
}
|
||||
vol_lock_release(); /* 成败/跳过都放锁:锁泄漏比丢一段录波重一百倍 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:"保存网络配置"请求通知(注册给 net_cfg 的保存交接;
|
||||
* 2026-09-25 自 Src/freertos.c 迁出,逻辑零改动)
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
行为不变:判定式、调用位置(采样恢复分支内、巡检/压板
|
||||
之前)、边沿日志与 SOE 分型全部照旧;s_prev_cal_lock
|
||||
仍只作边沿日志用,**不是**判据缓存
|
||||
* v1.4 2026-10-29 DTU 项目组 故障录波触发接线(task-2,lead 裁②):
|
||||
* s_log_record_edge() 的 picked 上升沿分支补一次
|
||||
* wave_ring_request()——与 SYSLOG_START 同一事件点
|
||||
* (设计 §4.1「同源,避免两处各自判定」),判据本体
|
||||
* (prot_elm_run/判定逻辑)零改动;一拍只请求一次,
|
||||
* elm = 本拍最低位上升元件
|
||||
*/
|
||||
#include "protect_task.h"
|
||||
|
||||
@@ -62,6 +68,7 @@
|
||||
#include "prot_set.h"
|
||||
#include "soe_store.h" /* B9:动作/复归边沿 → SOE 投递(零 FS,队列异步) */
|
||||
#include "sys_log.h" /* task-10:事故 / 保护告警 / 保护启动 三类文本记录 */
|
||||
#include "wave_ring.h" /* task-2:picked 上升沿 → 录波请求(与 SYSLOG_START 同源同点) */
|
||||
#include "task_watchdog.h"
|
||||
#include "tpafe5160.h" /* tpafe5160_frame_seq():帧序号(BUSY EXTI 写) */
|
||||
|
||||
@@ -151,8 +158,24 @@ static void s_log_active_edge(uint32_t active) {
|
||||
static void s_log_record_edge(uint32_t active, uint32_t pick) {
|
||||
uint32_t p_chg = pick ^ s_prev_pick;
|
||||
uint32_t a_chg = active ^ s_prev_active;
|
||||
const uint32_t p_rise = p_chg & pick; /* picked 0->1 的元件位集合 */
|
||||
uint32_t i;
|
||||
|
||||
/* task-2 补(第 3 类边沿的用途):picked 上升沿同点请求录波。一拍只调一次——
|
||||
* 多元件同拍启动录同一段波就够(环本就不重入,后续请求由 ring 计 ovf,不当
|
||||
* 失败);元件号取最低位上升元件(位号最小 = 表中靠前,速断段优先于定时限段)。
|
||||
* 返回值刻意忽略:本函数在 protectTask 的最坏栈链上(B9 写进 DoD 的硬线),
|
||||
* 不为此加日志;err/ovf 两本账全在串口 wave: 周期行与环 stat 里
|
||||
* (docs/子系统设计/故障录波设计说明.md §9)。 */
|
||||
if (p_rise != 0u) {
|
||||
uint32_t e = 0u;
|
||||
|
||||
while (((p_rise >> e) & 1u) == 0u) {
|
||||
e++; /* p_rise != 0 → 必在 32 位内命中,无越界 */
|
||||
}
|
||||
(void)wave_ring_request((uint8_t)WAVE_TRIG_PROTECT, (uint8_t)e);
|
||||
}
|
||||
|
||||
for (i = 0u; (i < (uint32_t)PE_COUNT) && (((p_chg | a_chg) != 0u)); i++) {
|
||||
const uint32_t bit = 1u << i;
|
||||
|
||||
|
||||
@@ -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