5a9988b557
清掉的(读者无从解析、且所指文档已不存在) - 已删任务文档的编号: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
301 lines
12 KiB
C
301 lines
12 KiB
C
/*
|
||
* 模块名称: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);
|
||
}
|