Files
wandering1 5a9988b557 chore(注释): 清掉源码里不可检索的过程编码
清掉的(读者无从解析、且所指文档已不存在)
- 已删任务文档的编号:Task #32、其内部 §5 / §4.3、P1-5、P0-2、P1-4a、
  "方案 a"、"Lead 批准 2026-10-12"
- 正文里当代词用的文件版本号:"(v2.7 起为 AboveNormal)""已随 v2.0 事件
  驱动改造删除""(v2.0:DMA 消费)""见文件头 v1.3 说明"等 → 改写成陈述现状
- 测试与脚本同类:adc_input 桩头"v2.0:DMA 模型"、stub/adc.h 的 v2.0、
  check_cubemx_survival.py 段头的"2026-10-10 v2.8"、run_all_suites.py 的"方案 A"

刻意保留
- 可检索登记簿锚点:P-AUDIT 真条目(P0-1/P1-8/P2-1/P2-9/P3-11)、S6/S11、
  E-31/E-44、N-18/N-23/N-32、B1/B5/B7/B8、L1-x~L5-x 功能号
- 文件头修改记录块内的 vN.M(规范 V1.0 §2.1/§2.2 要求保留该块;块内编号
  在本文件内自洽可读)

顺带纠正注释与代码已经不一致的过时事实
- 仍把 TIM6 写成现行节拍源:Src/main.c 的 include 注释与回调注释、
  Src/freertos.c 的 adcTask 块 → 改 TIM3;adc_input.h 修订条目里"ADC1 只认
  TIM2/TIM3/TIM8 的 TRGO"不准确 → 换成指向判据(ADC采样节拍设计说明 §3.2)
- App/algo/phasor.c 双区注释称"唯一消费者是 adcTask、为后续保护任务预留",
  实际 protectTask 已跨上下文读(prot_elm_run -> phasor_get)→ 改为"读者跨
  adcTask 心跳与 protectTask 判据两个上下文,勿改加锁、勿取消双区",并同步
  修正 docs/算法/采集与算法层设计说明.md 同一句
- adc_tick_irq_notify() 的"拒绝 ISR 内计算"理由仍写"内含 2 ms 忙等"(DMA 化
  后已无忙等)→ 换成三条真实理由:任务级共享数据 / 计算量 / 生成区回调
- 裸 §5(指代不明)与"规划 §1.6 / 规划 §4"昵称式 → 写成完整路径紧邻式

字节卫生(Edit 会静默剥 BOM)
- App/task/ui_task.c 与 Drivers/BSP/UI/ui_net_sink.c 被剥掉 BOM 两次,均按
  3 字节 EF BB BF 前缀补回并复验;全部 20 个文件 CR=0、U+FFFD=0、无行尾空白

门禁
- @build 0 错误 0 警告,且 Program Size 与清理前逐项相同
  (Code=213356 RO=18580 RW=1184 ZI=131480)→ 纯注释改动、中文未被误重编码
- ui-golden 首跑红:新工作树缺被 gitignore 的黄金参考 simulator/tests/ref/
  (不入库、只在主工作树生成过);从主工作树复制该基线后复跑通过 → 注释改动
  零像素影响
- 全套 pre-commit 在这 20 个文件上 Passed(coding-standard / cppcheck /
  6 个主机单测 / doc-links / task-table / mem-numbers / cubemx-survival /
  registration-check / mem-budget / font-charset / ui-golden / id-check)
- id-check 可判定比例 82.71% -> 82.88%(删掉的是"形似 P-AUDIT 但查无定义"的
  假锚点),悬空引用 863 -> 854
2026-10-21 11:39:53 +08:00

141 lines
7.3 KiB
C
Raw Permalink 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.
/*
* 模块名称:Task Profiler(任务耗时最坏值探针)
* 模块功能:用内核 DWT 周期计数器(CYCCNT,168 MHz 主频下 168 周期 = 1 us)为
* 各任务主循环的**最坏耗时 / 执行次数 / "此刻是否仍在某段内"** 提供
* 常开、低开销的观测面,经控制台单写者输出 `[PROF]` 行。
* 用途:把"某任务心跳缺席"这一结果拆成三种互斥机理,供归因定位。
* 适用平台:STM32F407ZGTx(仅 MCU 端:依赖 DWT 与 HAL 时基;模拟器/主机单测
* 不编译本模块)
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建:常开任务耗时探针
*
* 设计背景(docs/子系统设计/看门狗设计说明.md §11.4):
* 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/子系统设计/看门狗设计说明.md §11.2 第 4 步)。
*/
#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 */