fix(ui/screenlink): 按键应答扣到按键后画面,反馈典型 250-450 ms 降至 70-135 ms

按键召唤答的是按键前的旧帧:uiTask 还没消化键答案就发走了,新画面
只能等下一次周期召唤(280 ms/轮)才带得出——真机反馈 250-450 ms 的
主构成(根因账本 docs/子系统设计/屏板链路设计说明.md §7.1)。

改动(用户批准范围 A+B+C;屏板固件未动,决策三不变):
- sb_link:按键召唤改为只入队不起发;uiTask 同拍重绘并组帧成功后交
  凭据(sb_link_key_rendered),TIM3 1 kHz 的 sb_link_ms_tick 以 ≤1 ms
  放行新帧。140 ms 截止(SB_KEY_RESP_MAX_MS,+59.6 ms 线上 <205 ms
  屏板放弃线)保底发旧帧,"一召一答"契约不破;线忙时同旧丢单、
  不留扣件。起发两中断源(USART1 IDLE/TIM3)共用 sb_tx_kick,
  "判忙→占位"在 PRIMASK 临界区原子完成。
- ui_task:排空注键挪到组帧之前,注键→即派即绘→组帧同拍闭环;
  组帧返回 BSP_OK 才交重绘凭据(BUSY 拍帧还没反映按键)。
- ui_port:注键单槽改 8 深环(连按不再互相覆盖,满丢最旧+计数);
  新增 ui_port_service_keys:同拍 lv_indev_read + lv_refr_now,
  省掉 indev ≤20 ms + 刷新 ≤20 ms 两段等待。
- ui_menu_view:菜单移动选中只失效新旧两行带(黄金参考零像素变化),
  滚动/异常回退整框失效。
- simulator:窗口循环 sleep 扣除本轮已耗时(原先无条件睡满
  lv_timer_handler 返回值,叠加 Windows Sleep 15.6 ms 粒度放大观感)。
- 串口 TX 上报新增 dfok/dfold(凭据放行新帧数/超时保底旧帧数):
  dfold 连涨 = uiTask 被饿 ≥140 ms 的直接现场证据。

验证:test/host/sb_link 164/164(新增 18 条:不立刻起发/凭据放行/
140 ms 截止保底/忙时不留扣件/陈旧凭据不放行等边界);菜单 8 状态 +
内容页 66 张黄金像素 PASS;Keil @build 0 错误 0 警告(两次);
BOM 99 文件无漂移、代码规范、doc-links 本次文件零问题、cppcheck PASS。
文档:屏板规划文档已被主干收编进设计说明,延迟账本落
docs/子系统设计/屏板链路设计说明.md §7.1,并同步 §4 口径、§8 接口、
§9.1 单测台账 146→164、§10 坑账本补条。

RAM 台账(分支不跑 --write,并行分支提交约定):本笔盖戳行
RW_IRAM1 94,336 B (72.0%) / RW_CCM 57,376 B (87.9%),较上一台账行
+4,400 B——为距上次台账构建以来分支累计差(含批 3 状态灯等未盖戳
提交),非全部本笔引入;本次自身增量按对象计为 ui_port.o +12 B
(8 深键环+丢计数+indev 指针)与 sb_link/o 数十字节级。
gate: only mem-budget/mem-numbers pending lead ledger backfill
(合并落主干后 Lead 统一 --write 回填受管区)。
This commit is contained in:
2026-10-30 09:31:29 +08:00
parent 36f6948544
commit 7708f12c10
13 changed files with 592 additions and 59 deletions
+38 -15
View File
@@ -998,9 +998,15 @@ void ui_task_func(void *argument) {
ui_data_feed_tick();
ui_port_step(UI_TASK_TICK_MS);
/* ---- 屏板链路(批 2):帧 B 组帧 + 按键上行,全在任务上下文 ----
* 组帧紧跟渲染:本轮画出来的就是本轮组进去的那一帧。
* 排在推流往返**之前**,是因为那个往返最坏能等 ≈4 s(LCDMEM 应答 3204 B),
/* ---- 屏板链路(批 2 + 2026-10-30 按键延迟整治):帧 B 组帧 + 按键上行,全在任务上下文 ----
* 本段顺序是**先注键、后组帧**(原先反过来):按键召唤由 sb_link 扣住不发
* (延迟应答,判据见 sb_link.h 的 SB_KEY_RESP_MAX_MS),这一拍必须完成
* "出键→即派即绘→组帧",sb_link_ms_tick() 在 ≤1 ms 内放行的那一答带的才是
* **按键之后**的新内容;旧顺序下按键召唤答的必然是按键前的旧帧,新内容
* 要等下一次召唤才带得出(平均 ~140 ms、最坏 280 ms,账本见
* docs/子系统设计/屏板链路设计说明.md §7.1)。
* "本轮画出来的就是本轮组进去的那一帧"的承诺因此覆盖到按键。
* 排在推流往返**之前**不变:那个往返最坏能等 ≈4 s(LCDMEM 应答 3204 B),
* 排在它后面会让屏板画面跟着网络一起卡(同一份显存、两个出口,出口不能互相拖)。
* 显存直接取活帧缓冲:不留 3200 B 影子副本,sb_link 内部与帧里的点阵段 memcmp。
* LED 掩码(批 3):每拍现采四路状态源经 sb_led_map_eval 折算——四个源
@@ -1012,6 +1018,7 @@ void ui_task_func(void *argument) {
uint32_t led_mask;
bsp_err_t sb_ret;
uint8_t board_key;
uint8_t key_injected = 0u;
led_in.prot_active = prot_elm_active_mask();
led_in.prot_pick = prot_elm_pick_mask();
@@ -1019,6 +1026,28 @@ void ui_task_func(void *argument) {
led_in.iec104_run = iec104_serv_link_run();
led_mask = sb_led_map_eval(&led_in, osKernelGetTickCount());
/* 逐键必到:一次取一个,直到队列空。9/10 不映射(决策四待定),丢弃并留痕。
* 走 ui_port_queue_key 而不是 ui_net_sink_inject_key:后者只是 7003 后端
* 对同一个函数的转发(见 ui_net_sink_backend_mcu.c),多绕一层还得让
* App 去 include 推流后端头,没必要。两条入口最终进的是同一个输入队列。 */
while ((board_key = sb_link_key_take()) != 0u) {
uint8_t ui_key = ui_task_board_key_map(board_key);
if (ui_key != 0u) {
ui_port_queue_key(ui_key);
key_injected = 1u;
} else {
DBG_INFO("board key %u unmapped (RESET/FACTORY pending decision)",
(unsigned)board_key);
}
}
/* 有键的这一拍:立刻派发 + 立刻重绘(省 indev 轮询 ≤20 ms +
* 刷新定时器 ≤20 ms 两段干等),随后组帧才能把新画面装进本拍的答案。
* 没键的拍 service_keys 是空操作,一拍不多花。 */
if (key_injected != 0u) {
ui_port_service_keys();
}
sb_ret = sb_link_frame_update(ui_port_get_framebuffer(), led_mask);
/* BSP_ERR_BUSY 是常态(上一帧还在 59.6 ms 的发送窗口里),不必留痕;
@@ -1029,18 +1058,12 @@ void ui_task_func(void *argument) {
DBG_ERROR("screenlink frame update ret=%d", sb_ret);
}
/* 逐键必到:一次取一个,直到队列空。9/10 不映射(决策四待定),丢弃并留痕。
* 走 ui_port_queue_key 而不是 ui_net_sink_inject_key:后者只是 7003 后端
* 对同一个函数的转发(见 ui_net_sink_backend_mcu.c),多绕一层还得让
* App 去 include 推流后端头,没必要。两条入口最终进的是同一个输入队列。 */
while ((board_key = sb_link_key_take()) != 0u) {
uint8_t ui_key = ui_task_board_key_map(board_key);
if (ui_key != 0u) {
ui_port_queue_key(ui_key);
} else {
DBG_INFO("board key %u unmapped (RESET/FACTORY pending decision)",
(unsigned)board_key);
}
/* 给 sb_link 交"键已画进画面、且帧内容与活显存一致"的凭据
* (延迟应答的放行钥匙):**只在组帧返回 BSP_OK 时交**——BUSY 意味着这一拍没能
* 把新画面组进帧缓冲,凭据必须留给组成功的那一拍;扣住的召唤自会
* 等下一个 OK 拍或按 140 ms 上限发旧帧保底。 */
if ((key_injected != 0u) && (sb_ret == BSP_OK)) {
sb_link_key_rendered();
}
}