diff --git a/App/task/ui_task.c b/App/task/ui_task.c index 6f4ece2a..425eaad9 100644 --- a/App/task/ui_task.c +++ b/App/task/ui_task.c @@ -419,9 +419,12 @@ static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get * 解码 40 点 + 逐点 code_to_milli 峰值扫描 40 次(int64 乘除, * 微秒级)——不是全段 8×320 扫描,回放页一屏只碰一个通道一窗; * - 纯内存不碰卷的路径(评审据此看最坏的落点):manual_req 只投一个 - * 请求字节;已缓存段的上下切道、左右切段的段号表环游;空态与坏态 - * 直接返回。所以最坏只落在「第一次进回放页 / 第一次切到未缓存段」 - * 这类一次性动作上,同段内连续按键不重复付 ②。 + * 请求字节;wave_enter 只翻两个作废旗标(#93 F1,不取锁不开盘); + * 已缓存段的上下切道、左右切段的段号表环游;空态与坏态直接返回。 + * 所以最坏落在「进回放页(#93 F1 起每次开页都先作废、首帧付一次 ①, + * 总额仍封死在首帧那一帧)/ 第一次切到未缓存段」这类一次性动作上, + * 同段内连续按键不重复付 ②;空态再探盘的按键付 ①(方案 B 自带的 + * 语义——盘真 0 段时每次再探重扫正是该付的,成本有界在空态按键数)。 * 没有实测数,上面是与落盘道同一把尺的**推算**:落盘道的告警线是 * 50 ms / 21 次写,读路径无簇分配、不写 FAT 元数据,只会更便宜,故按 * 同量级不高于它估——一次渲染最坏一窗 ≈ 段表 + 一段全验 + 一窗,仍远 @@ -433,7 +436,8 @@ static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get */ 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_scanned; /* 表是否有效(manual_req / + * wave_enter 后作废,#93 F1) */ static uint8_t s_wave_cached_idx; /* 段头缓存属于哪一段 */ static uint8_t s_wave_cache_ok; /* 该段已验完整(头+数据 CRC) */ static wave_seg_meta_t s_wave_meta; /* 已验段的头(172B,唯一驻留) */ @@ -567,6 +571,18 @@ static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) { return 0u; } +/* + * 判废原因码(#93 F2:ui_task_wave_load_head 五类 BAD 出口收敛成一条 + * 串口留痕,reject= 字段取值即本组宏)。值域是判读表与 PC 侧互证的 + * 接口的一部分:只增不改义。1 收「拿不到一个解析得动的头」的一切 + * (头窗读败 = 无从解析,与 head_parse 拒收、段长字段自相矛盾同族)。 + */ +#define UI_WAVE_REJ_HEAD 1u /* 头窗读败 / head_parse 拒收 / seg_size 自相矛盾 */ +#define UI_WAVE_REJ_LEN 2u /* 长度等式不符(文件尺寸 != 头算出的整段尺寸) */ +#define UI_WAVE_REJ_WIN 3u /* 数据区流式读窗失败 */ +#define UI_WAVE_REJ_TAIL 4u /* 尾 2B(存储 CRC)读失败 */ +#define UI_WAVE_REJ_CRC 5u /* 数据 CRC 不符(留痕带 stored/calc,可与 PC 侧 wave_read REJECT 行对值互证) */ + /* * 函数功能:读段并做完整性判定(头 CRC 由 head_parse 判,数据 CRC 流式判), * 成功则把段头留在 s_wave_meta 供 seg_win 免重读 @@ -581,7 +597,10 @@ static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) { * 成本的代价换「CRC 不符必须拒收」永远现算,不做跨键存的位图 * 函数说明:缓存命中(同段且已验)直接返 OK——左右切段回到上一段、上下切 * 通道都靠它把重复的 5250B 全扫压成一次;文件读中途消失(被别的 - * 写者轮转掉)按 NONE 处理,页面下轮自然重扫 + * 写者轮转掉)按 NONE 处理,页面下轮自然重扫;五类判废(reject= + * 原因码见上方 UI_WAVE_REJ_* 组)在本函数收尾统一收敛成一条 + * DBG_ERROR——只有串口在手的远程轮也要有判废可查面(#93 F2, + * 电平与不判重两条裁定记录在设计说明 §10 的 2026-11-07 节) */ static int ui_task_wave_load_head(uint8_t a_idx) { static FIL f; /* static:FIL = 528B,理由同 soe_store_read */ @@ -591,6 +610,11 @@ static int ui_task_wave_load_head(uint8_t a_idx) { FRESULT ofr; /* 开档 FR 码:开档失败留痕用 */ UINT br = 0u; int rc; + uint8_t reject = 0u; /* 判废原因码 UI_WAVE_REJ_*(#93 F2 留痕; + * 0 = 没走到任何判废点。BAD 时必非 0) */ + uint16_t crc_stored = 0u; /* 仅 reject=UI_WAVE_REJ_CRC 时有值:与 PC 侧 + * wave_read REJECT 行互证的 stored/calc 对 */ + uint16_t crc_calc = 0u; if ((s_wave_cache_ok != 0u) && (s_wave_cached_idx == a_idx)) { return UI_WAVE_OK; @@ -641,6 +665,7 @@ static int ui_task_wave_load_head(uint8_t a_idx) { fr = f_read(&f, s_wave_buf, n, &br); if ((fr != FR_OK) || (br != n)) { rc = UI_WAVE_BAD; + reject = UI_WAVE_REJ_WIN; break; } crc = wave_store_crc_update(crc, s_wave_buf, n); @@ -649,14 +674,27 @@ static int ui_task_wave_load_head(uint8_t a_idx) { /* 第四步:读回存储值比对(低字节在前) */ 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))) { + uint16_t stored = (uint16_t)(tail[0] | ((uint16_t)tail[1] << 8)); + + if (crc == stored) { rc = UI_WAVE_OK; + } else if (reject == 0u) { + /* 窗读全程跑完还对不上才是真判废(stored/calc 对值得留 + * 给 wave_read 互证);窗已判废(REJ_WIN)时 crc 是半截 + * 值,既不覆原因也不留这对比值——免得互证对上一个假数 */ + reject = UI_WAVE_REJ_CRC; + crc_calc = crc; + crc_stored = stored; } + } else if (reject == 0u) { + reject = UI_WAVE_REJ_TAIL; /* 尾 2B 定位/读失败 */ } } else if (total == 0u) { rc = UI_WAVE_BAD; /* seg_size 返 0 = 头字段自相矛盾,判废 */ + reject = UI_WAVE_REJ_HEAD; } else { rc = UI_WAVE_BAD; /* 长度与头部字段不符,判废 */ + reject = UI_WAVE_REJ_LEN; } (void)f_close(&f); } @@ -671,6 +709,21 @@ static int ui_task_wave_load_head(uint8_t a_idx) { * 没有任何视觉面——必须留痕(10-31 半轮栽就栽在这类静默面上) */ DBG_ERROR("ui wave head idx=%u open fail fr=%d", (unsigned)a_idx, (int)ofr); + } else if (rc == UI_WAVE_BAD) { + /* #93 F2:五类判废出口(头/长度等式/窗/尾/CRC)收敛成这一条 + * 留痕——有画面在手判废有画面自证,只有串口在手的远程无人轮 + * 判废不留痕就是事后黑箱。BAD 不写 cache_ok = 每次进坏段都 + * 付一次完整校验,留痕与按键、画面一一对应,刻意**不判重** + * ——判废是低频人工动作,逐次留痕正是取证要的东西(电平 + * ERROR + 不判重两条裁定见设计说明 §10 的 2026-11-07 节)。 + * reject= 原因码表与「reject!=5 时两个 CRC 字段恒 0x0000」 + * 判读口径见手册 §3;reject=5 的 stored/calc 与 PC 侧 + * wave_read 的 REJECT 行同名同值、可对值互证。[ERR] 会命中 + * /serial-boot-test 的 Forbidden 清单 → 坏段轮里按「预期内」 + * 点名放行(同手册),正常盘上本行一条都不会出现 */ + DBG_ERROR("ui wave head idx=%u reject=%u stored=0x%04X calc=0x%04X", + (unsigned)a_idx, (unsigned)reject, + (unsigned)crc_stored, (unsigned)crc_calc); } } return rc; @@ -698,6 +751,29 @@ static int ui_task_wave_manual_req(void) { return r; } +/* + * 函数功能:回放页开页 / 空态再探盘的作废钩子——作废段表与段头缓存 + * (#93 F1 方案 A/B;与 manual_req 受理支同一条作废纪律,字段 + * 契约见 ui_data.h wave_enter 注) + * 入口参数:无 + * 返回值:无 + * 限定条件:uiTask(页面框架 init_cb / key_cb 链都跑在本任务上;作废的 + * 是本任务私有缓存,无锁无竞态);纯内存零 I/O,开页链不破 + * 函数说明:只翻旗标不重扫——重扫仍懒在 ui_task_wave_table() 首次取数。 + * manual_req 的作废盖「本机自己新落段」,本件盖「别的写者 + * (FTP STOR)在上次开页之后投放的新段」:11-02 把钉表防抖立 + * 成纪律时把这两种情形一起钉死了,开页因此看不见新段(#93 F1 + * 发现面)。页内普通按键**不调本件** = 防抖纪律原样保留;空 + * 态左右键调(方案 B)只在盘真 0 段时逐次重探——「新段可能已 + * 落」这条注释里写了一直没机制兑现的语义从此有兑现。残留纪律 + * (手册 §4 / 设计说明 §10 留痕):**页开着时**投放的段要再开 + * 一次页才可见,页开着追新段本波不做 + */ +static void ui_task_wave_enter(void) { + s_wave_scanned = 0u; + s_wave_cache_ok = 0u; +} + /* * 函数功能:取盘上「数值最大段号」(回放页开屏落点) * 入口参数:a_idx - 出参段号 @@ -903,9 +979,10 @@ static int ui_task_wave_seg_win(uint8_t a_idx, uint8_t a_ch, return rc; } -/* 录波通道函数表(六件齐全;ui_data_wave_bind 拒绝残表,缺件就是装配错) */ +/* 录波通道函数表(七件齐全;ui_data_wave_bind 拒绝残表,缺件就是装配错) */ static const ui_data_wave_ops_t s_wave_ops = { ui_task_wave_manual_req, + ui_task_wave_enter, ui_task_wave_seg_first, ui_task_wave_seg_prev, ui_task_wave_seg_next, @@ -1014,7 +1091,7 @@ void ui_task_func(void *argument) { DBG_ERROR("UI maint bind: FAIL"); } /* 录波通道注入(故障录波道 UI 侧,2026-10-29):手动录波触发页与回放页 - * 的唯一数据入口,同样先于 ui_menu_init。未注入窗口里六件按「无录波段」 + * 的唯一数据入口,同样先于 ui_menu_init。未注入窗口里七件按「无录波段」 * 空态渲染(不画线、不崩);残表 = 装配错,启动日志必须可见 */ if (ui_data_wave_bind(&s_wave_ops) != 0) { DBG_ERROR("UI wave bind: FAIL"); diff --git a/Drivers/BSP/UI/ui_data.c b/Drivers/BSP/UI/ui_data.c index e3572c22..7b1cb37f 100644 --- a/Drivers/BSP/UI/ui_data.c +++ b/Drivers/BSP/UI/ui_data.c @@ -1090,14 +1090,14 @@ uint8_t ui_data_comm_pending(void) { /* ── 通道六:故障录波(wave)注入与包装 ───────────────────────────────────── * 包装层零判据:未注入统一按「无录波段」态返(页面显式空态文案,绝不 - * 画平线——设计 §8.1 空态行);六件的参数校验只到指针级,语义判定 + * 画平线——设计 §8.1 空态行);七件的参数校验只到指针级,语义判定 * (卷忙/段损坏)全在执行侧,与通道四/五同一条分层纪律。 */ static const ui_data_wave_ops_t *s_wave = NULL; /* * 函数功能:注入录波执行侧函数表(启动期一次) - * 入口参数:ops - 六件齐;NULL = 撤销注入 + * 入口参数:ops - 七件齐;NULL = 撤销注入 * 返回值:0 - 已注入/已撤销;-1 - 残表(存量不动,理由逐字同 comm bind) int * 限定条件:uiTask 启动期,先于 ui_menu_init() */ @@ -1106,7 +1106,8 @@ int ui_data_wave_bind(const ui_data_wave_ops_t *a_ops) { s_wave = NULL; return 0; } - if ((a_ops->manual_req == NULL) || (a_ops->seg_first == NULL) || + if ((a_ops->manual_req == NULL) || (a_ops->wave_enter == NULL) || + (a_ops->seg_first == NULL) || (a_ops->seg_prev == NULL) || (a_ops->seg_next == NULL) || (a_ops->seg_head == NULL) || (a_ops->seg_win == NULL)) { return -1; @@ -1123,6 +1124,13 @@ int ui_data_wave_manual_req(void) { return s_wave->manual_req(); } +/* 函数功能:开页 / 空态再探盘的作废钩子(未注入 = 什么都不做,零副作用) void */ +void ui_data_wave_enter(void) { + if (s_wave != NULL) { + s_wave->wave_enter(); + } +} + /* 函数功能:录波通道三态查询的统一未注入兜底(返 NONE 显空态,不崩) int */ static int wp_wave_none(void) { return UI_WAVE_NONE; diff --git a/Drivers/BSP/UI/ui_data.h b/Drivers/BSP/UI/ui_data.h index cb02c5f1..66942b6b 100644 --- a/Drivers/BSP/UI/ui_data.h +++ b/Drivers/BSP/UI/ui_data.h @@ -947,14 +947,20 @@ uint8_t ui_data_comm_pending(void); #define UI_WAVE_BAD 2 /* - * 录波执行侧注入表(六件齐全;ui_data_wave_bind 拒绝残表) - * 返回值约定:除 manual_req 外,各件返回 UI_WAVE_OK / NONE / BAD 三态; - * seg_win 的出参仅在返 UI_WAVE_OK 时可读(通道五同一条出参契约)。 + * 录波执行侧注入表(七件齐全;ui_data_wave_bind 拒绝残表) + * 返回值约定:除 manual_req 与 wave_enter(两件是动作位,不产三态)外, + * 各件返回 UI_WAVE_OK / NONE / BAD 三态;seg_win 的出参仅在返 + * UI_WAVE_OK 时可读(通道五同一条出参契约)。 */ typedef struct { /* 投一次手动录波请求。返回值:0 - 已受理;非零 - 拒收(含未落段通道 * 不可用),页面据此走确认链的 ERR 支 */ int (*manual_req)(void); + /* 回放页开页 / 空态再探盘时作废段表与段头缓存(#93 F1 方案 A/B 的 + * 作废钩子;纯内存零 I/O——重扫仍懒在首次取数,开页链零 I/O 不破; + * 页内按键不调它(空态再探除外),11-02 钉表防抖纪律原样保留)。 + * 返回值:无 */ + void (*wave_enter)(void); /* 取盘上数值最大的段号(回放页开屏落点;左右键从此起环游)。 * 回绕后本序不保证时间最新,页顶段号即自证;时间序要靠段头 * frame_seq/t0,代价是 100 次开文件,本波不做(lead 裁定 2026-10-29) */ @@ -980,7 +986,7 @@ typedef struct { /* * 函数功能:注入录波执行侧函数表(启动期一次;形状对齐 ui_data_comm_bind) - * 入口参数:ops - 函数表(六件都不得为 NULL);NULL = 撤销注入(宿主测试用) + * 入口参数:ops - 函数表(七件都不得为 NULL);NULL = 撤销注入(宿主测试用) * 返回值:0 - 已注入/已撤销;-1 - 残表(存量不动) int * 限定条件:uiTask 启动期、先于 ui_menu_init() */ @@ -992,6 +998,13 @@ int ui_data_wave_bind(const ui_data_wave_ops_t *a_ops); */ int ui_data_wave_manual_req(void); +/* + * 函数功能:回放页开页 / 空态再探盘时作废段表与段头缓存(#93 F1 方案 + * A/B 的作废钩子;纯内存零 I/O,重扫懒在首帧取数) + * 返回值:无(未注入 = 什么都不做,页面取数行为不变) void + */ +void ui_data_wave_enter(void); + /* * 函数功能:取数值最大段号(排序口径见 ui_data.c 同名胶水注释:回绕后本序 * 不保证时间最新,页顶段号即自证;真时间序要逐段读 frame_seq/t0, diff --git a/Drivers/BSP/UI/ui_page_wave_play.c b/Drivers/BSP/UI/ui_page_wave_play.c index f72aa4a0..a25322ec 100644 --- a/Drivers/BSP/UI/ui_page_wave_play.c +++ b/Drivers/BSP/UI/ui_page_wave_play.c @@ -12,7 +12,10 @@ * 空态显式文案「无录波段」/「该段不可读」——画成一条直线等于 * 伪造证据(§8.1 空态行,此判据优先于「页面上总得画点东西」)。 * 键位:上下切通道、左右切段(key_intercept=1 才能把 LEFT 从 - * 「关页」手里要来);ESC 关页。 + * 「关页」手里要来);ESC 关页。页的**缓存作废面**:本文件不持 + * 任何缓存,开页(pg_init)与空态再探盘(pg_key IDLE 支)各调 + * 一次注入表的 wave_enter 作废钩子(#93 F1 方案 A/B,纯内存 + * 零 I/O)——开页后盘上新落盘 / 新投放的段即随重扫进表。 * 取数纪律:本文件零 App include——段号/段头/窗口码值/峰值 mV 全部经 * ui_data.h 录波注入表进来(波形算式与定点换算在执行侧,页面 * 连 b_codes 都不碰,收到的码值已并偏移);首帧绘制才懒取数 @@ -33,6 +36,10 @@ * 本页 key_cb 返回 true(接管键),框架不再替本页失效重绘,状态变了 * 屏上却纹丝不动——该病在枚举修复前整页恒空、从未有键可切,故随 * 2026-11-02 回放页修复首次上板才暴露(真机实证见 PR #67)。 + * v1.2 2026-11-07 DTU 项目组 #93 F1:开页(pg_init)与空态左右键 + * 再探(pg_key IDLE 支)各调注入表新增的 wave_enter 作废钩子—— + * 开机后新落盘/新投放的段重开页即见、空态再探不再被钉表短路; + * 本页像素形态与键位语义不变(黄金参考不刷),重扫仍懒在首帧。 */ /* 头文件包含区 */ @@ -320,14 +327,18 @@ static const char *wp_eng_word(uint8_t a_eng) { } /* - * 函数功能:开页复位(框架 init_cb)——通道回到 UA、数据回到未取态 + * 函数功能:开页复位(框架 init_cb)——通道回到 UA、数据回到未取态, + * 并调执行侧作废钩子丢掉钉住的段表/段头缓存(#93 F1 方案 A: + * 开机后新落盘的段开合一次页即进表;作废是纯内存动作,首帧 + * 懒取数链原样,开页链零 I/O 不破) * 入口参数:无 * 返回值:无 - * 限定条件:框架开页链内;只清状态零 I/O(懒取数在首帧绘制) + * 限定条件:框架开页链内;只清状态与翻作废旗标、零 I/O(懒取数在首帧绘制) */ static void pg_init(void) { s_state = WP_ST_IDLE; s_ch = 0u; + ui_data_wave_enter(); } /* @@ -406,6 +417,7 @@ static bool pg_key(lv_key_t a_key) { case LV_KEY_LEFT: case LV_KEY_RIGHT: if (s_state == WP_ST_IDLE) { + ui_data_wave_enter(); /* #93 F1 方案 B:先作废钉表,再探才探得出新段 */ wp_ensure(); /* 空态下按左右 = 再探一次盘(新段可能已落) */ ui_content_page_invalidate(); return true; diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index c8cda4c8..417f5cbd 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 285,716 B (279.02 KB) | 1,048,576 B (1024 KB) | 27.2% | +| ER_IROM1 | 285,940 B (279.24 KB) | 1,048,576 B (1024 KB) | 27.3% | | RW_IRAM1 | 94,752 B (92.53 KB) | 131,072 B (128 KB) | 72.3% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **287,788 B (281.0 KB)** | 1,048,576 B (1024 KB) | **27.4%** | +| **Flash (ROM 合计)** | **288,012 B (281.3 KB)** | 1,048,576 B (1024 KB) | **27.5%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index 3a8299d7..9843a1b8 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 285,716 B | 1,048,576 B | 27.2% | 参考 | +| ER_IROM1 (Flash) | 285,940 B | 1,048,576 B | 27.3% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 94,752 B | 131,072 B | 72.3% | PASS | diff --git a/simulator/main.c b/simulator/main.c index 5eb95baf..7de794aa 100644 --- a/simulator/main.c +++ b/simulator/main.c @@ -853,9 +853,15 @@ static int sim_wave_seg_win(uint8_t a_idx, uint8_t a_ch, int16_t *a_codes, return UI_WAVE_OK; } -/* 录波通道假表(与真机 s_wave_ops 同形状六件) */ +/* 函数功能:开页 / 空态再探盘作废钩子假桩(#93 F1:模拟器假表没有段表 / + * 段头缓存可作废,空做 = 行为不变;补这件与真机 s_wave_ops 同形状七件) void */ +static void sim_wave_enter(void) { +} + +/* 录波通道假表(与真机 s_wave_ops 同形状七件) */ static const ui_data_wave_ops_t s_wave_ops = { sim_wave_manual_req, + sim_wave_enter, sim_wave_seg_first, sim_wave_seg_prev, sim_wave_seg_next,