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:
2026-10-10 19:06:06 +08:00
parent 28bd391035
commit 3b1d0308d8
9 changed files with 623 additions and 14 deletions
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 271,440 B (265.08 KB) | 1,048,576 B (1024 KB) | 25.9% |
| RW_IRAM1 | 90,936 B (88.80 KB) | 131,072 B (128 KB) | 69.4% |
| ER_IROM1 | 272,436 B (266.05 KB) | 1,048,576 B (1024 KB) | 26.0% |
| RW_IRAM1 | 91,328 B (89.19 KB) | 131,072 B (128 KB) | 69.7% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| **Flash (ROM 合计)** | **272,228 B (265.8 KB)** | 1,048,576 B (1024 KB) | **26.0%** |
| **Flash (ROM 合计)** | **273,236 B (266.8 KB)** | 1,048,576 B (1024 KB) | **26.1%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -4,7 +4,9 @@
> → 连续 8 次(800ms)缺席 → 停喂 IWDG → 复位。
> **背景**:L2 相量层落地(commit `3762c03`)后**新出现**的间歇性回归:L2 版 3/10 次运行失败,
> 改前基线(`git stash` 重建)0/6。
> **状态**:**分析完成,处置待确认**(2026-10-10)。本文只做归因,未改动生产代码。
> **状态**:**分析完成,P0/P1/P2 处置已落地并上板验证**(2026-10-10)。P0(adc 压环条件 +
> 截止期重同步)修复后累计 22 次启动 0/22 复现;P1-4a 已解耦 UI 心跳与 netTask 往返;P2 常开
> 探针已上线。详见 §8「处置落地」。
> **关联**:
> [`../待处理内容.md`](../待处理内容.md)(**N-17** 心跳缺口登记)、
> [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md)(`required_mask` / 抽样窗 / 长耗时白名单)、
@@ -216,16 +218,29 @@ next += ADC_TASK_BEAT_MS; /* 固定 +1ms,不随实际进度修正 */
(a) `netTask` 回执延迟(§4.2),还是
(b) `defaultTask` 长段(syslog NAND 写 / 配置保存),还是
(c) adcTask 超时突发(§4.4)。
**补充(2026-10-10,P2 测量后):22 次上板启动(P0 修复后,含本分析前的 12 次
+ 复跑 10 次)**全部 0 复现停滞**——健康运行里最大的单一非让出占用是
`defaultTask` 控制台排空 `defd≈48 ms`、ADC 轮询 `adcp≈4 ms`,**没有任何任务
接近 800 ms**。故 800 ms 是异常长尾,靠健康剖面无法指认,须靠复现时的 `[PROF]/[PROF2]`
现场快照;但 P0 修复已把回归压到 0/22(原 L2 基线 3/10),暂不再阻塞推进。
2. **`adc1_err=288` 的自身根因未查。** 2 ms 超时对 7.4 µs 的转换而言异常巨大,
指向 ADC 被反复 `Start/Stop` 或 EOC/OVR 状态异常;需专项测量,不要与本条混谈。
3. **复位归因存疑。** 日志报 `reset cause: software (NVIC_SystemReset) (CSR=0x14000000)`,
即 `SFTRSTF|PINRSTF`;而按设计"停喂 → IWDG 自然溢出"应留下 `IWDGRSTF`。
本次复位可能来自**测试台架**(ST-LINK 拉 NRST → `PINRSTF`)而非看门狗,需与台架流程对齐。
3. **复位归因已澄清为非诊断项。** 失败日志报 `reset cause: software (NVIC_SystemReset)
(CSR=0x14000000)`(即 `SFTRSTF|PINRSTF`);但 2026-10-10 的健康启动**同样**报
`software (NVIC_SystemReset)` 且判定 PASS——说明本台架上电复位本就走软件复位路径
(或烧录工具经软件复位),**不能据此区分"IWDG 溢出"还是"台架拉 NRST"**。停摆的权威
证据仍是 `[ERR][WD] task stall` 行本身,而非复位原因寄存器。
4. **`[STACK]` 周期从 2.0 s 拉到 4.05 s**(17:36:19.902 → 23.951,标称 `STACK_PROBE_PERIOD_MS=2000`)
值得注意,但**不能作为证据**:控制台缓冲满时会**丢行**,该报告也可能只是被丢了。
5. **`idle` 的物理含义**:`idle=649/20001` 意味着任务轮询略快于帧产生(相位漂移),
反映 `osDelayUntil`(1 ms 内核 tick)与 TIM6(1 kHz 硬件)之间的**时钟源差异**,
本身不是故障,但会让 §5 的缺陷持续发生。
6. **启动阶段 `MISS start snap=0x67` 是良性的(2026-10-10 新增)。** `[WD] armed` 在
bring-up 完成后、UI 首跳之前武装,而 `required_mask` 静态含 UI 位 → 武装后到 UI 首跳
之间必现一次 `MISS start 0x67`。健康运行(`FATFS mounted≈0.43s`)在 `UI net sink:
started` 处再出现一次后**自行恢复**;失败运行也是同一良性 episode 起头,真正的锁死是
运行中段(t=10585)的第二次 episode。**故该 episode 本身不是故障判据**,只作"UI 尚未
起跳"的旁证。
---
@@ -258,6 +273,35 @@ next += ADC_TASK_BEAT_MS; /* 固定 +1ms,不随实际进度修正 */
**P2-6/7 先测(一次 @test 即可拿到区分性数据)→ 按数据定 P1 的方案 → P0 顺路一起修 → 复跑 10 次确认 0/10。**
不建议直接上 P1-3(提优先级)了事:那会**掩盖** §4.2 与 §4.4 两个真实缺陷。
### 处置落地(2026-10-10,按"先测量后修"顺序执行完毕)
- **P2(测量)= 已落地。** 新增常开探针模块 `App/util/task_prof.c/.h`:测各任务循环周期 /
单轮耗时 / 推流往返 / ADC 轮询 / 控制台排空 / syslog 落盘 / net_poll 的最坏值,并在**心跳
位图 1→0 边沿**裸 UART 直发一份现场快照(`[PROF]`/`[PROF2]` 两行)。接线覆盖 ui / adc /
default / net 四个任务,已在 uvprojx 登记。
- **P2 数据结论(决定 P1 的依据):**
- 健康运行 `uiw≈uis≈50 ms` → **UI 单轮耗时几乎等于推流往返耗时**,证实 §4.2:心跳被
`netTask` 往返主导,实际测的是"netTask 响应性"而非"UI 活着"。
- 健康运行里 `uisn=0`(推流段无"仍在段内"积压)→ UI **未被卡在往返**,阻塞机理排除。
- 最大单一非让出占用 `defd≈48ms`(控制台排空)/ `adcp≈4ms`(ADC 轮询),无任务接近 800ms。
- **P1(结构性)= 已落地 P1-4a。** `App/task/ui_task.c`:把 `task_wd_beat(TASK_WD_UI)`
**提到 `ui_net_sink_poll()` 之前**(v1.3)。心跳从此测"UI 自己被调度到",不再被 netTask
往返拖垮。**未上 P1-3(提优先级)**:doc 明确警告它会掩盖 §4.2/§4.4。**P1-5(ADC 超时整拍
跳过)暂缓**:数据表明 `adcp≈4ms` 已受限,且 P0-2 的 `osDelayUntil` 重同步已解除自旋死锁,
暂无必要改动采样时序(避免引入丢帧风险)。**若日后复现且快照指向"永久阻塞在往返"**,
再升级到事件驱动 accept(`NET_EVENT_CONNECTED` 回调,无客户端时不走阻塞 `net_accept`)。
- **P0(缺陷修复)= 已落地并验证。** `App/task/adc_task.c`:
- P0-1(§5):仅 `r==1` 时压环 + 重算。上板直接印证:修复后 `frames=29924 idle=63
ring=29924` → **`ring == frames`**(修前 `ring = frames + idle` 污染相量窗);
`adc1_err` 从失败日志的 **288** 降到 **1**。
- P0-2(§4.3):`osDelayUntil` 返回值重同步,落后即一次性拉回基准,解除"永久不阻塞→
Normal 级空转抢占 uiTask"的死锁路径。
- **验证:** P0 修复后累计 22 次上板启动 **0/22** 复现停滞(原 L2 基线 3/10);
P0+P1 后再复跑 10 次确认(见 `prof_batch4`)。构建 0 错误 0 警告
(`Code=256276 RO=16160 RW=800 ZI=139760`)。
- **遗留(不在本次处置范围):** §7.2 `adc1_err` 自身根因、§7.4 `[STACK]` 周期漂移、§4.4 的
ADC 忙等未彻底改 DMA(仅以 P0-2 + 暂缓 P1-5 管控)。如复现重现,优先回头查 §7.2。
---
## 变更记录