Files
STM32F4-Base/App/task/ui_task.c
T
wandering1 c4edf352fe refactor(UI): 记录页改注入式 reader,layer-deps 基线 6->3(道B task-6)
B-6 分层收紧(UI_数据供给设计说明.md §7.2 契约原样落地):
- ui_w_records:删 #include "sys_log.h",改 ui_rec_reader_t 只读函数表 +
  ui_w_records_bind() 模块级注入(sys_log 读 API 本为全局,函数表共享、
  实例读盘缓存仍各自独享——切页陈旧行防御不变);log_id 换不透明编号
  UI_W_REC_LOG_RUN/SELFTEST;缓冲容量宏本地化 UI_W_REC_TS/DESC_LEN
  (20/29,与 sys_log 契约下限同宽;读失败退化空行防御保留)
- ui_page_runlog / ui_page_selftest:去 sys_log include,实例改不透明编号
- 两端薄包装适配层(编号→syslog_id_t 显式映射,错误码 0/-1/-2 直通不改写):
  App/task/ui_task.c 与 simulator/main.c(无头+窗口两个 init 点),均在
  ui_menu_init 之前 ui_w_records_bind;未绑定窗口组件按空态防御不读野
- scripts/check_layer_deps.py:BASELINE 6→3(--prune 实测恰 3 条 UI 记录
  边消失后写回;剩 NETCFG×2 + lftpd_io 与 UI 无关)——道B 独占收紧口径
- 文档同步:门禁说明 layer-deps.md §1/§6/§9 与 README 计数 6→3;
  UI_数据供给设计说明.md §7.2 加落地注记、§14"计划削减"改"已兑现"
- 首提被 id-check 拦下:注释/文档里自造"批卡编号"(B-6/道B-3)命中
  "未登记前缀"增量拦截——已全数改回 task-6/道B 普通指称(编号不回收、
  禁自造,教训入本提交);id-check 复跑 RC=0(S10 告警为存量非本改动)

验证:
- 模拟器 0 警告;菜单 8 状态 + 内容页 11 页黄金参考逐字节 PASS
  (记录页数据流经 reader 后像素与基线一致,无基线更新提交)
- cppcheck/coding-standard/bsp-err/font-charset/no-mojibake 单跑全绿;
  layer-deps 复跑:6 处反向 include = 豁免 3 / 基线内 3 / 新增 0,
  --prune 显示"基线无需收紧(3 条存量全部仍成立)"
- doc-links --all 的 35 处告警全为本仓检出路径含空格误扫 SDL2 自带 md
  (环境问题、非本改动引入;提交门禁按暂存文件名过滤不受影响)
- @build 0 Error 0 Warning(终树复跑 BUILD_OK);uvprojx 无 UV4 抖动、
  无新文件不涉注册;其余 38 钩全绿(ui-golden/font-charset/coding-standard/
  bsp-err/id-check/cppcheck…单跑复验 RC=0)

增量(A/B 同树 .map,基线 = 合入 c1b425d 后 HEAD):
- Flash:Code +104 B、RO +8 B(两端薄包装代码 + reader 函数表 const)
- RAM:RW +4 B(s_reader 指针)/ ZI +4 B(两页实例 struct 枚举改 uint8
  本应 -8 B,链接段对齐后净 +4 B);RW+ZI 合计 +8 B

gate: only mem-budget/mem-numbers pending lead ledger backfill
- pre-commit run --all-files 实测仅这两条失败(台账/RAM 文档 vs .map 漂移,
  根因即上列 Code +104/RO +8/RW +4/ZI +4),其余 38 钩全绿;按 Lead 裁决
  (task-4 口径"以后照此办理",含 Flash/RAM 漂移同例外)--no-verify 提交,
  合并后请主树回填台账(scripts/ram_baseline.py --write 由 Lead 侧执行)
- 手工自查通过:5 个 BOM 文件 BOM=EF BB BF 齐全、docs/py/main.c 保持无 BOM,
  全部 10 文件 CR=0、U+FFFD=0
- 台账增量供回填:Flash Code +104 B、RO +8 B;RAM RW +4 B、ZI +4 B
2026-10-25 05:21:05 +08:00

214 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:UI Task
* 模块功能:UI 主任务,等待网络就绪后初始化显示/菜单/推流并周期刷新
* 适用平台:STM32F407 系列(LVGL + CH395F 以太网)
* 作者:王建锋
* 创建日期:2026-08-30
* 修改记录:
* v1.1 2026-09-14 王建锋 新增待机显示页初始化(ui_standby_init,空闲 60s 切入)
* v1.2 2026-10-10 王建锋 任务耗时测量埋点:主循环加常开探针
* (循环周期 / 单轮工作耗时 / ui_net_sink_poll 往返),
* 用于把"心跳缺席"拆成 饿死 / 阻塞在往返 / 自身拖长
* 三种互斥机理(判读见 docs/子系统设计/看门狗设计说明.md §11.4)
* v1.3 2026-10-10 王建锋 解耦心跳(docs/子系统设计/看门狗设计说明.md §11.3):把 task_wd_beat
* (TASK_WD_UI) 提到 ui_net_sink_poll() **之前**。原写法心跳
* 在推流往返之后 → 心跳周期 = 渲染 + 一次 netTask 往返 + 10ms,
* 实测 uiw≈uis≈50ms(心跳被 netTask 往返主导),心跳实际测的是
* "netTask 响应性"而非"UI 活着"。提到往返之前后,心跳测的是
* "UI 自己被调度到了"。注:若往返**永久**阻塞,本改仍不能救
* (心跳只发一次)→ 真要彻底去掉依赖须走事件驱动 accept
* (NET_EVENT_CONNECTED 回调),待复现快照确认阻塞机理后再定。
*/
#include "ui_task.h"
#include <string.h>
#include "cmsis_os.h"
#include "sys_events.h"
#include "ui_port.h"
#include "ui_menu.h"
#include "ui_net_sink.h"
#include "ui_standby.h"
#include "ui_w_records.h" /* task-6 记录 reader 注入点(UI 页面不再 include sys_log.h) */
#include "sys_log.h"
#include "ui_tokens.h" /* UI 设计令牌:循环周期 UI_TASK_TICK_MS / 待机阈值 UI_STANDBY_IDLE_MS */
#include "sys_clock.h"
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
#include "net_latency_test.h" /* M3 探针:ui_loop(生产循环实测周期) */
#include "task_watchdog.h"
#include "task_prof.h" /* P2 常开探针:循环周期 / 工作耗时 / 推流往返 */
/*
* 函数功能:待机屏底部时钟提供回调(平台差异:MCU = sys_clock_get_str)
* 入口参数:buf - 时间串输出缓冲 char *
* len - buf 容量 size_t
* 返回值:无
*/
static void ui_task_time_cb(char *buf, size_t len) {
if (sys_clock_get_str(buf, len) == NULL && len > 0u) {
buf[0] = '\0'; /* 墙钟无效:空串(待机屏时钟行留白) */
}
}
/*
* 函数功能:记录 reader 适配(task-6 注入)——UI 不透明编号 0/1 → syslog_id_t 映射
* 入口参数:log_id - UI 记录源编号(UI_W_REC_LOG_*) uint8_t
* 返回值:记录条数;未知编号返回 0(页面退化为空态) uint16_t
* 限定条件:uiTask 上下文同步调用(记录页绘制路径,与合并前直调同形)
*/
static uint16_t ui_task_rec_count(uint8_t log_id) {
if (log_id == UI_W_REC_LOG_RUN) {
return (uint16_t)syslog_count(SYSLOG_RUN);
}
if (log_id == UI_W_REC_LOG_SELFTEST) {
return (uint16_t)syslog_count(SYSLOG_SELFTEST);
}
return 0u;
}
/*
* 函数功能:记录 reader 适配(task-6 注入)——读记录字段直通 sys_log(错误码不改写)
* 入口参数:log_id/idx/ts/ts_len/desc/desc_len - 契约见 ui_w_records.h ui_rec_reader_t
* 返回值:0 成功;-1 编号/序号越界(未知编号同 -1);-2 读失败 int
* 限定条件:同上
*/
static int ui_task_rec_get(uint8_t log_id, uint32_t idx, char *ts, uint32_t ts_len,
char *desc, uint32_t desc_len) {
if (log_id == UI_W_REC_LOG_RUN) {
return syslog_get_record(SYSLOG_RUN, idx, ts, (size_t)ts_len, desc, (size_t)desc_len);
}
if (log_id == UI_W_REC_LOG_SELFTEST) {
return syslog_get_record(SYSLOG_SELFTEST, idx, ts, (size_t)ts_len, desc, (size_t)desc_len);
}
return -1;
}
static const ui_rec_reader_t s_rec_reader = { ui_task_rec_count, ui_task_rec_get };
#define DBG_TAG "[UI_TASK]"
#include "dbg_log.h"
/*
* 函数功能:等待网络就绪事件
* 入口参数:无
* 返回值:无
* 限定条件:sys_evtHandle 为 NULL 时直接返回
* 函数说明:无限期等待 SYS_EVT_NET_READY 标志
*/
static void ui_task_wait_net_ready(void) {
if (sys_evtHandle != NULL) {
/* osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒)。
* 2026-10-02 台架修复:等待改 100ms 分片 + 每片喂心跳(与 timeSync/netTask/
* ftpTask 入口同模式)——IWDG 必需掩码含 UI 位,原 osWaitForever 无心跳:
* IWDG 启动(存储就绪)时网络未必就绪 → 掩码 800ms 不可达 → 停喂 → 复位循环。
* 100ms 分片 = 抽样窗,纯任务睡眠零队列开销。 */
for (;;) {
uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
osFlagsWaitAny | osFlagsNoClear, 100u);
if ((r & osFlagsError) == 0u) {
break; /* NET_READY 已置位(状态位,未被消费) */
}
task_wd_beat(TASK_WD_UI); /* 看门狗:分片喂心跳(屏障等待态) */
}
}
}
/*
* 函数功能:UI 主任务入口,初始化显示/菜单/推流并进入刷新主循环
* 入口参数:argument - FreeRTOS 任务参数(未使用) void *
* 返回值:无
* 限定条件:FreeRTOS 已启动
* 函数说明:1. 等待 SYS_EVT_NET_READY 后依次初始化 ui_port/ui_menu/ui_net_sink
* 2. 主循环周期调用 ui_port_step 与 ui_net_sink_poll 刷新界面
*/
void ui_task_func(void *argument) {
(void)argument;
task_wd_bind(TASK_WD_UI); /* FTL 心跳路由:登记本任务句柄(入口一次) */
/* 等待网络就绪期间不可能喂狗:本任务此时尚未进入主循环,而就绪位由 netTask 置位。
* 该段若长期不成立,看门狗会复位——这是期望行为(网络 bring-up 卡死须暴露)。
* 注意本任务在 required_mask 内,主循环跑起来后须保证每 100ms 窗口内至少喂一次。 */
DBG_INFO("UI task: started, wait net ready");
ui_task_wait_net_ready();
DBG_INFO("UI task: net ready, init port");
DBG_INFO("UI task: ui_port_init enter");
if (ui_port_init() != 0) {
DBG_ERROR("UI port init: FAIL");
for (;;) { osDelay(1000); }
}
DBG_INFO("UI port init: OK");
/* 记录 reader 注入:必须在 ui_menu_init 之前(记录页在此前不可能被打开,
* 但契约把"启动期先绑定"写死;未绑定窗口组件按空态防御——见 ui_w_records.h) */
ui_w_records_bind(&s_rec_reader);
DBG_INFO("UI task: ui_menu_init enter");
if (ui_menu_init() != 0) {
DBG_ERROR("UI menu init: FAIL");
for (;;) { osDelay(1000); }
}
DBG_INFO("UI menu init: OK");
if (ui_standby_init(UI_STANDBY_IDLE_MS, ui_task_time_cb) != 0) {
DBG_ERROR("UI standby init: FAIL");
} else {
DBG_INFO("UI standby: ready (idle=60s)");
}
if (ui_net_sink_start(UI_NET_SINK_PORT) != 0) {
DBG_ERROR("UI net sink: start FAIL");
} else {
DBG_INFO("UI net sink: started (port %u)", (unsigned)UI_NET_SINK_PORT);
}
#if defined(TEST_LATENCY_PROBE)
/* M3 探针 ui_loop:主循环实测周期(= LVGL 渲染 + 推流轮询 + osDelay(10))。
* 每轮开头先结算"上一轮到此轮"的间隔,再重置标记 → 得到真实循环周期。
* 单写者(本任务)/单读者(testTask),样本经 lat_probe_add 落静态缓冲。 */
lat_tmark_t tm_loop;
uint8_t tm_loop_valid = 0U;
#endif
/* 常开探针:TEST_LATENCY_PROBE 只在 CH395F 套件构建下打开,
* 且样本消费者是 testTask(产品构建里不跑)→ 产品构建拿不到任何 UI 周期数据。
* 故此处独立埋点,恒定生效、只写本模块的静态计数,由 defaultTask 周期上报。 */
while (1) {
/* 循环周期用 enter/exit 测:exit 结算"上轮起点 → 本轮起点",enter 记本轮起点。
* 首轮 exit 时尚未 enter 过 → 内部直接返回,不会拿 0 当起点。
* 选 enter/exit 而非"自己存一个 prev"的额外收益:两次循环之间段内标志保持为 1
* → 报告可读出"本轮已进行多久"。**这正是'uiTask 是否被饿住'的直接证据**:
* 若该值在两次报告间持续增长而 uic 不动,就是被调度饿死/阻塞,而非渲染拖长。 */
task_prof_exit(TASK_PROF_UI_PERIOD);
task_prof_enter(TASK_PROF_UI_PERIOD);
const uint32_t prof_t0 = task_prof_cyc(); /* 单轮工作耗时基准 */
#if defined(TEST_LATENCY_PROBE)
if (tm_loop_valid != 0U) {
lat_probe_add_seg("ui_loop", lat_mark_us(&tm_loop));
}
lat_mark(&tm_loop);
tm_loop_valid = 1U;
#endif
ui_port_step(UI_TASK_TICK_MS);
/* 心跳**先于**推流往返发出(判读口径 docs/子系统设计/看门狗设计说明.md §11.3):UI 心跳测的是
* "UI 自身被调度到了",而非"netTask 回执快慢"。原写法心跳在 sink 之后,
* 致实测 uiw≈uis≈50ms(心跳被 netTask 往返主导)。提到往返之前后,
* 即使往返偶发变慢,心跳也不被其拖垮。
* ⚠ 若往返**永久**阻塞(xTaskNotifyWait portMAX_DELAY 不返回),本改只能发
* 一次心跳,救不了 → 彻底解耦须走事件驱动 accept,见文件头修改记录 v1.3。 */
task_wd_beat(TASK_WD_UI);
/* 推流轮询段单独 enter/exit:它内部经 net_accept → net_wait_result 走到
* xTaskNotifyWait(portMAX_DELAY) 的**无限期阻塞**。若 uiTask 永久卡在这里,
* 样本永不结算,只有 task_prof 的"仍在段内已 X us"能证明它卡在何处 */
task_prof_enter(TASK_PROF_UI_SINK);
ui_net_sink_poll();
task_prof_exit(TASK_PROF_UI_SINK);
/* 单轮工作耗时(不含 osDelay):与循环周期对比即得"调度延迟" */
task_prof_sample(TASK_PROF_UI_WORK, task_prof_cyc() - prof_t0);
osDelay(UI_TASK_TICK_MS);
}
}