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

301 lines
12 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(任务耗时最坏值探针)实现
* 模块功能:接口见 task_prof.h。本文件只做三件事:
* ① 惰性使能 DWT CYCCNT 并读值;② 维护每探针的 count/max/last/glitch
* 与"在段内"标志;③ 把全部字段格式化成一整行 `[PROF]` 交给控制台单写者。
* 适用平台:STM32F407ZGTx(仅 MCU 端)
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建:常开任务耗时探针
*
* 设计依据:docs/子系统设计/看门狗设计说明.md §11.4(探针字段语义与实测基线)
*/
#include "task_prof.h"
#include <stdio.h>
#include "main.h" /* DWT / CoreDebug / SystemCoreClock / HAL_GetTick() */
#include "console_log.h" /* dbg_console_write_line / dbg_console_write_raw */
/* Private defines -----------------------------------------------------------*/
/* 报告节奏:首次延后(bring-up 深链期间不占串口),此后周期一行。
* 2 s 与 defaultTask 的 [STACK] 报告同节奏,便于两条序列交叉比对。 */
#define TASK_PROF_FIRST_MS 5000u
#define TASK_PROF_PERIOD_MS 2000u
/* 合理耗时上界(周期数)。超过即判为"CYCCNT 中途被重置"造成的无符号回绕
* (差值 ~2^32),丢弃而不污染 max。取 10 s 对应周期数:远高于真实最坏段
* 耗时(停摆窗口 < 2.5 s 即复位),又远低于 2^32,不会误杀真实数据。
* 168 MHz 下 10 s = 1.68e9 < 2^32,本身不溢出。 */
#define TASK_PROF_GLITCH_CYC (SystemCoreClock * 10u)
/* Private types -------------------------------------------------------------*/
typedef struct {
volatile uint32_t count; /* 已结算样本数 */
volatile uint32_t max; /* 最坏耗时(周期数) */
volatile uint32_t last; /* 最近一次耗时(周期数) */
volatile uint32_t glitch; /* 丢弃的样本数(DWT 不可用 / 计数器被重置) */
volatile uint32_t t_start; /* 进段时刻(周期数;仅 enter/exit 类探针使用) */
volatile uint32_t in_seg; /* 1 = 此刻仍在段内(读侧据此算"已停留多久") */
} task_prof_slot_t;
/* Private variables ---------------------------------------------------------*/
static task_prof_slot_t s_prof[TASK_PROF_ID_COUNT];
static uint8_t s_dwt_ok; /* 1 = DWT CYCCNT 已使能 */
static uint8_t s_prof_reported; /* 1 = 已首次报告(节流状态) */
static uint32_t s_prof_last; /* 上次报告时刻(ms) */
static char s_prof_line[DBG_CON_LINE_MAX]; /* 报告行缓冲(仅 defaultTask 用) */
/* Private function prototypes -----------------------------------------------*/
static uint32_t prof_cyc_to_us(uint32_t cyc);
static void prof_send(int n, uint32_t raw);
static void prof_emit(uint32_t raw);
/*
* 函数功能:周期数 → 微秒
* 入口参数:cyc - 周期数
* 返回值:微秒数(SystemCoreClock 低于 1 MHz 时返回 0)
* 限定条件:任意上下文可调(纯算术)
* 函数说明:MicroLIB 的 snprintf 不支持 %f → 一律以整数微秒上报。
*/
static uint32_t prof_cyc_to_us(uint32_t cyc) {
uint32_t per_us = SystemCoreClock / 1000000u;
if (per_us == 0u) {
return 0u;
}
return cyc / per_us;
}
/*
* 函数功能:把已格式化在静态行缓冲里的行按指定通道发出
* 入口参数:n - snprintf 返回的长度(<= 0 = 格式化失败,丢弃)
* raw - 0 = 入环形缓冲;非 0 = 裸 UART 直发
* 返回值:无
* 限定条件:仅 defaultTask 调用(唯一串口写者)
* 函数说明:长度按静态缓冲容量夹取,防 snprintf 截断后越界。
*/
static void prof_send(int n, uint32_t raw) {
const unsigned cap = (unsigned)sizeof(s_prof_line);
unsigned len;
if (n <= 0) {
return;
}
len = ((unsigned)n >= cap) ? (cap - 1u) : (unsigned)n;
if (raw != 0u) {
dbg_console_write_raw(s_prof_line, len);
} else {
(void)dbg_console_write_line(s_prof_line, len);
}
}
/*
* 函数功能:格式化并输出 `[PROF]` + `[PROF2]` 两行(静态行缓冲,零栈开销)
* 入口参数:raw - 0 = 入环形缓冲(周期报告);非 0 = 裸 UART 直发(停摆现场)
* 返回值:无
* 限定条件:仅 defaultTask 调用(唯一串口写者,无并发;故可复用静态缓冲)
* 函数说明:字段含义(括号内为打印单位):
* [PROF] t HAL tick(ms)
* uic/uip/uipn/uil uiTask 循环次数 / 周期最坏 / 本轮已进行 / 周期最近(us)
* uiw/uis/uisn uiTask 单轮工作最坏 / 推流往返最坏 / 仍在推流段内(us)
* adc/adcm/adcn adcTask 单拍次数 / 单拍最坏 / 仍在拍内(us)
* [PROF2] defd/defs defaultTask 控制台排空最坏 / syslog 落盘最坏(us)
* netp/netn netTask 驱动轮询最坏 / 仍在轮询内(us)
* adcp/adcq adcTask ADC 单次最坏 / 仍在 ADC 内(us)
* 两行的 gl 丢弃样本数(DWT 不可用或计数器被重置)
* "仍在段内"为 0 表示不在该段内——某任务若永久阻塞在某段,该段的
* count 不再增长,只有这个字段能证明它卡在哪里。
*/
static void prof_emit(uint32_t raw) {
const uint32_t t = HAL_GetTick();
const uint32_t now = task_prof_cyc();
uint32_t uipn = 0u;
uint32_t uisn = 0u;
uint32_t adcn = 0u;
uint32_t netn = 0u;
uint32_t adcq = 0u;
uint32_t gl = 0u;
uint32_t i;
int n;
if (s_prof[TASK_PROF_UI_PERIOD].in_seg != 0u) {
uipn = now - s_prof[TASK_PROF_UI_PERIOD].t_start;
}
if (s_prof[TASK_PROF_UI_SINK].in_seg != 0u) {
uisn = now - s_prof[TASK_PROF_UI_SINK].t_start;
}
if (s_prof[TASK_PROF_ADC_BEAT].in_seg != 0u) {
adcn = now - s_prof[TASK_PROF_ADC_BEAT].t_start;
}
if (s_prof[TASK_PROF_NET_POLL].in_seg != 0u) {
netn = now - s_prof[TASK_PROF_NET_POLL].t_start;
}
if (s_prof[TASK_PROF_ADC_POLL].in_seg != 0u) {
adcq = now - s_prof[TASK_PROF_ADC_POLL].t_start;
}
for (i = 0u; i < (uint32_t)TASK_PROF_ID_COUNT; i++) {
gl += s_prof[i].glitch;
}
/* 行 1:任务级(uiTask / adcTask) */
n = snprintf(s_prof_line, sizeof(s_prof_line),
"[PROF] t=%lu uic=%lu uip=%lu uipn=%lu uil=%lu uiw=%lu uis=%lu "
"uisn=%lu adc=%lu adcm=%lu adcn=%lu gl=%lu\r\n",
(unsigned long)t,
(unsigned long)s_prof[TASK_PROF_UI_PERIOD].count,
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_UI_PERIOD].max),
(unsigned long)prof_cyc_to_us(uipn),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_UI_PERIOD].last),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_UI_WORK].max),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_UI_SINK].max),
(unsigned long)prof_cyc_to_us(uisn),
(unsigned long)s_prof[TASK_PROF_ADC_BEAT].count,
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_ADC_BEAT].max),
(unsigned long)prof_cyc_to_us(adcn),
(unsigned long)gl);
prof_send(n, raw);
/* 行 2:段级(占用者指认:defaultTask 排空/落盘、netTask 驱动、adcTask ADC) */
n = snprintf(s_prof_line, sizeof(s_prof_line),
"[PROF2] t=%lu defd=%lu defs=%lu netp=%lu netn=%lu adcp=%lu "
"adcq=%lu gl=%lu\r\n",
(unsigned long)t,
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_DEF_DRAIN].max),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_DEF_SYSLOG].max),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_NET_POLL].max),
(unsigned long)prof_cyc_to_us(netn),
(unsigned long)prof_cyc_to_us(s_prof[TASK_PROF_ADC_POLL].max),
(unsigned long)prof_cyc_to_us(adcq),
(unsigned long)gl);
prof_send(n, raw);
}
/*
* 函数功能:读 DWT 周期计数器(懒使能;见 task_prof.h 的契约说明)
* 入口参数:无
* 返回值:当前 CYCCNT 值(周期数)
* 限定条件:任意上下文可调(幂等;纯寄存器读写)
* 函数说明:只置使能位、**不清零** CYCCNT——本模块只用差值,清零无意义,
* 且会与 console_log.c 的同名惰性使能互相干扰(该模块对 CYCCNT
* 清零后,在途的差值会变成一次无符号回绕,由 glitch 守卫丢弃)。
*/
uint32_t task_prof_cyc(void) {
if (s_dwt_ok == 0u) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
if ((DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0u) {
s_dwt_ok = 1u;
}
}
return DWT->CYCCNT;
}
/*
* 函数功能:记录一次耗时样本(周期数)
* 入口参数:id - 探针 ID;cyc - 耗时(周期数)
* 返回值:无
* 限定条件:单写者(每个 ID 只由一个任务调用)
* 函数说明:不可用样本(cyc 为 0 或超出合理上界)只累加 glitch,不更新
* max/last/count——否则一次计数器重置就会把最坏值抬到 ~2^32 周期。
*/
void task_prof_sample(task_prof_id_t id, uint32_t cyc) {
task_prof_slot_t *p;
if ((uint32_t)id >= (uint32_t)TASK_PROF_ID_COUNT) {
return;
}
p = &s_prof[id];
if ((cyc == 0u) || (cyc >= TASK_PROF_GLITCH_CYC)) {
p->glitch++;
return;
}
p->count++;
p->last = cyc;
if (cyc > p->max) {
p->max = cyc;
}
}
/*
* 函数功能:标记"进入某段"(观测"此刻仍在段内已多久")
* 入口参数:id - 探针 ID
* 返回值:无
* 限定条件:单写者;须与 task_prof_exit 成对
* 函数说明:写序为先 t_start 后 in_seg——读侧见 in_seg 为 1 时 t_start 必已有效。
*/
void task_prof_enter(task_prof_id_t id) {
task_prof_slot_t *p;
if ((uint32_t)id >= (uint32_t)TASK_PROF_ID_COUNT) {
return;
}
p = &s_prof[id];
p->t_start = task_prof_cyc();
p->in_seg = 1u;
}
/*
* 函数功能:结算一段耗时(与 task_prof_enter 成对)
* 入口参数:id - 探针 ID
* 返回值:无
* 限定条件:单写者;须与 task_prof_enter 成对
* 函数说明:先清"在段内"标志再结算,避免读侧把已结算的段误报为"仍在段内"。
* **未 enter 过则直接返回**:让"先 exit 再 enter"的周期测量写法
* 在首轮不把 0 当作起点(否则会记下一次虚假的超大样本)。
*/
void task_prof_exit(task_prof_id_t id) {
uint32_t t0;
if ((uint32_t)id >= (uint32_t)TASK_PROF_ID_COUNT) {
return;
}
if (s_prof[id].in_seg == 0u) {
return;
}
t0 = s_prof[id].t_start;
s_prof[id].in_seg = 0u;
task_prof_sample(id, task_prof_cyc() - t0);
}
/*
* 函数功能:周期性打印 [PROF] 行(内部节流)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 主循环调用
* 函数说明:首次 5 s 后报告(bring-up 深链已跑过),此后每 2 s 一行。
*/
void task_prof_report(void) {
const uint32_t now = HAL_GetTick();
if (s_prof_reported == 0u) {
if (now < TASK_PROF_FIRST_MS) {
return;
}
s_prof_reported = 1u;
} else if ((uint32_t)(now - s_prof_last) < TASK_PROF_PERIOD_MS) {
return;
}
s_prof_last = now;
prof_emit(0u);
}
/*
* 函数功能:停摆现场快照(同一 [PROF] 行,走裸 UART 直发)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 调用(此时它就是唯一串口写者,无并发)
* 函数说明:用于"首次心跳缺席"瞬间;缺席窗口内环形缓冲可能积压,
* 裸直发保证这一行能出去。
*/
void task_prof_report_raw(void) {
prof_emit(1u);
}