fix(ui): Task #32 P0/P1/P2 — 解耦 UI 心跳、修 adc 压环与截止期、加测量探针
Task #32 UI 看门狗停摆(docs/问题记录/UI任务看门狗停摆分析.md) P0 adc_task.c(已上板验证): - 仅 r==1 才压环+重算;上板印证 ring==frames(修前 ring=frames+idle) - osDelayUntil 返回值重同步,落后拉回基准,解除永久空转死锁 P1-4a ui_task.c v1.3: task_wd_beat 提到 ui_net_sink_poll 之前 → 心跳测"UI 活着"而非"netTask 响应",解开 §4.2 心跳依赖 → 未上 P1-3(提优先级会掩盖 §4.2/§4.4);P1-5 暂缓 P2 新增 App/util/task_prof.c/.h: DWT 常开探针,测各任务周期/耗时/ 推流往返/ADC 轮询/控制台排空/syslog 落盘/net_poll 最坏值;心跳位图 1→0 边沿裸 UART 直发 [PROF]/[PROF2] 快照;uvprojx 已登记 doc: 分析 doc §7 补良性启动 episode 与复位非诊断项;§8 加处置落地 验证: P0+P1 累计 32 次上板启动 0/32 复现(原 L2 基线 3/10),构建 0/0
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 模块名称:Task Profiler(任务耗时最坏值探针)
|
||||
* 模块功能:用内核 DWT 周期计数器(CYCCNT,168 MHz 主频下 168 周期 = 1 us)为
|
||||
* 各任务主循环的**最坏耗时 / 执行次数 / "此刻是否仍在某段内"** 提供
|
||||
* 常开、低开销的观测面,经控制台单写者输出 `[PROF]` 行。
|
||||
* 用途:把"某任务心跳缺席"这一结果拆成三种互斥机理,供归因定位。
|
||||
* 适用平台:STM32F407ZGTx(仅 MCU 端:依赖 DWT 与 HAL 时基;模拟器/主机单测
|
||||
* 不编译本模块)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-10
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-10 王建锋 创建(Task #32 的 P2 测量埋点)
|
||||
*
|
||||
* 设计背景(docs/问题记录/UI任务看门狗停摆分析.md §8 P2):
|
||||
* uiTask 偶发心跳缺席导致 IWDG 复位,但"占用者"无法从既有日志判定。
|
||||
* 同一现象至少对应三种互斥机理,观测上必须能区分:
|
||||
* ① 被调度饿死 → 循环周期长,而段内耗时短;
|
||||
* ② 阻塞在网络往返 → `ui_net_sink_poll()` 段内耗时长;若永久阻塞,
|
||||
* 该段样本**永不结算**,只有"此刻仍在段内已 X us"能证明;
|
||||
* ③ 自身渲染拖长 → 单轮工作耗时长。
|
||||
* 故每个探针记录 max / count / last,并对可疑段记录 enter/exit。
|
||||
*
|
||||
* ⚠ 开销:一次采样 = 1 次 DWT 读 + 数次比较/存储(< 20 周期);对 1 ms 节拍
|
||||
* (168000 周期)可忽略。无动态分配、无内核对象、无临界区、无阻塞。
|
||||
* ⚠ 单写者假设:同一探针 ID 只由**一个任务**写入(见下表),报告由
|
||||
* defaultTask 读取。32 位对齐读写在 Cortex-M4 上原子,读侧不会撕裂。
|
||||
*
|
||||
* 探针 ID 写入者 含义(enter/exit = 可读出"此刻仍在段内已多久")
|
||||
* UI_PERIOD uiTask 上轮循环起点 → 本轮循环起点(enter/exit)
|
||||
* UI_WORK uiTask 本轮起点 → 心跳前(含 LVGL 渲染 + 推流轮询)
|
||||
* UI_SINK uiTask ui_net_sink_poll() 单次往返(enter/exit)
|
||||
* ADC_BEAT adcTask 单拍起点 → 心跳后(含取数 + 压环 + 相量 + 心跳日志)
|
||||
* ADC_POLL adcTask adc_input_poll() 单次(enter/exit)——隔离 ADC 超时成本
|
||||
* DEF_DRAIN defaultTask dbg_console_drain()(enter/exit)——控制台忙等发送
|
||||
* DEF_SYSLOG defaultTask syslog_drain()(enter/exit)——NAND/FTL 写
|
||||
* NET_POLL netTask net_poll()(enter/exit)——CH395F 驱动轮询
|
||||
*
|
||||
* 后四个探针用于**指认占用者**:UI 心跳缺席时,谁的最坏耗时在同一窗口抬起来,
|
||||
* 谁就是挤占 CPU 的 Normal 任务(见 docs/问题记录/UI任务看门狗停摆分析.md §7.1)。
|
||||
*/
|
||||
|
||||
#ifndef TASK_PROF_H
|
||||
#define TASK_PROF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 探针 ID(顺序即内部槽位下标,新增只能追加) */
|
||||
typedef enum {
|
||||
TASK_PROF_UI_PERIOD = 0, /* uiTask 循环周期 */
|
||||
TASK_PROF_UI_WORK = 1, /* uiTask 单轮工作耗时(不含 osDelay) */
|
||||
TASK_PROF_UI_SINK = 2, /* ui_net_sink_poll() 往返耗时 */
|
||||
TASK_PROF_ADC_BEAT = 3, /* adcTask 单拍耗时 */
|
||||
TASK_PROF_ADC_POLL = 4, /* adc_input_poll() 耗时(隔离 ADC 超时成本) */
|
||||
TASK_PROF_DEF_DRAIN = 5, /* defaultTask 控制台排空耗时 */
|
||||
TASK_PROF_DEF_SYSLOG = 6, /* defaultTask syslog 落盘耗时 */
|
||||
TASK_PROF_NET_POLL = 7, /* netTask net_poll() 耗时(CH395F 驱动) */
|
||||
TASK_PROF_ID_COUNT
|
||||
} task_prof_id_t;
|
||||
|
||||
/*
|
||||
* 函数功能:读 DWT 周期计数器(懒使能;基准为"任意",仅供取差值)
|
||||
* 入口参数:无
|
||||
* 返回值:当前 CYCCNT 值(周期数)
|
||||
* 限定条件:任意上下文可调(幂等;纯寄存器读,不创建任何内核对象)
|
||||
* 函数说明:首次调用置 CoreDebug->DEMCR.TRCENA 与 DWT->CTRL.CYCCNTENA。
|
||||
* 刻意**不清零** CYCCNT:本模块只用差值,清零无意义,且会与
|
||||
* console_log.c 的同名惰性使能互相干扰。DWT 使能失败时恒返回 0,
|
||||
* 采样侧把 0 判为"不可用"(见 task_prof_sample)。
|
||||
*/
|
||||
uint32_t task_prof_cyc(void);
|
||||
|
||||
/*
|
||||
* 函数功能:记录一次耗时样本(周期数)
|
||||
* 入口参数:id - 探针 ID
|
||||
* cyc - 耗时(周期数;0 或超出合理上界视为不可用/计数器被重置)
|
||||
* 返回值:无
|
||||
* 限定条件:单写者(每个 ID 只由一个任务调用)
|
||||
* 函数说明:更新 count / last / max。`cyc == 0`(DWT 未使能)或
|
||||
* `cyc >= TASK_PROF_GLITCH_CYC`(计数器中途被重置造成的无符号回绕)
|
||||
* 只累加 glitch 计数、不污染 max——否则一次计数器重置就会把
|
||||
* 最坏值抬到 ~2^32 周期,掩盖真实数据。
|
||||
*/
|
||||
void task_prof_sample(task_prof_id_t id, uint32_t cyc);
|
||||
|
||||
/*
|
||||
* 函数功能:标记"进入某段"(用于观测"此刻仍在段内已多久")
|
||||
* 入口参数:id - 探针 ID
|
||||
* 返回值:无
|
||||
* 限定条件:单写者;须与 task_prof_exit 成对
|
||||
* 函数说明:记录进入时刻并置"在段内"标志。若该任务此后永久阻塞在该段,
|
||||
* 样本永不结算,只有报告侧的"仍在段内"字段能证明它卡在哪里。
|
||||
*/
|
||||
void task_prof_enter(task_prof_id_t id);
|
||||
|
||||
/*
|
||||
* 函数功能:结算一段耗时(与 task_prof_enter 成对)
|
||||
* 入口参数:id - 探针 ID
|
||||
* 返回值:无
|
||||
* 限定条件:单写者;须与 task_prof_enter 成对
|
||||
* 函数说明:清"在段内"标志 → 以 enter 时刻为起点记一次样本。
|
||||
* **未 enter 过(in_seg == 0)时直接返回**——这使"先 exit 再 enter"的
|
||||
* 周期测量写法(如 uiTask 的循环周期)在首轮不会拿 0 当起点,
|
||||
* 从而不产生一次虚假的超大样本。
|
||||
*/
|
||||
void task_prof_exit(task_prof_id_t id);
|
||||
|
||||
/*
|
||||
* 函数功能:周期性打印 `[PROF]` / `[PROF2]` 行(内部节流:首次 5 s 后,此后每 2 s)
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:仅 defaultTask 主循环调用(唯一串口写者;静态行缓冲复用)
|
||||
* 函数说明:整行一次 `dbg_console_write_line` 入环形缓冲(行级原子)。
|
||||
* `[PROF]` = uiTask/adcTask 任务级(周期 / 工作 / 往返 / 单拍);
|
||||
* `[PROF2]` = 占用者段级(defaultTask 排空与落盘、netTask 驱动轮询、
|
||||
* adcTask ADC 单次),用于指认"谁把 CPU 占住了"。
|
||||
* 用**静态**行缓冲而非栈缓冲:本函数挂在 defaultTask 的最坏调用链上,
|
||||
* 栈上再放一个 192 B 行缓冲会挤占该任务的栈预算。
|
||||
*/
|
||||
void task_prof_report(void);
|
||||
|
||||
/*
|
||||
* 函数功能:停摆现场快照——同 `[PROF]` + `[PROF2]` 两行,走**裸 UART 直发**
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:仅 defaultTask 调用(此时它就是唯一串口写者,无并发)
|
||||
* 函数说明:用于"某任务心跳刚由'到过'变为'缺席'"的瞬间。缺席窗口内环形缓冲
|
||||
* 可能积压,裸直发保证这两行能出去(与 task_wd_poll 的 `MISS start`
|
||||
* 标记同款理由)。
|
||||
*/
|
||||
void task_prof_report_raw(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TASK_PROF_H */
|
||||
Reference in New Issue
Block a user