Files
DCU_APP/GD32E508_DCU_APP/sys/cpu_load.h
T
cbb 70daa4b0c6 feat(DCU_APP): 添加CPU负载监控和EtherCAT诊断功能
- 在DCU_APP项目中添加cpu_load.h和cpu_load.cpp模块用于CPU负载率监测
- 修改csolution配置将构建目标从Debug切换为Release
- 在EtherCAT PDO协议栈中新增段5诊断区(632-639字节),包含负载率和序列号
- 在所有关键中断服务程序中添加CpuLoad::isrEnter()/isrExit()钩子用于准确统计中断占用
- 增强EtherCAT同步中断处理,改进AL事件掩码守护机制,修复OP状态下PDI中断门控问题
- 解决SSC源码中指针地址对齐警告和打包成员访问警告问题
- 在主循环中集成CPU负载率采样tick()调用,实现1秒滚动窗口统计

BREAKING CHANGE: 构建配置从Debug改为Release,EtherCAT协议栈增加新的诊断数据区
2026-09-05 20:07:22 +08:00

98 lines
4.6 KiB
C++
Raw 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.
/**
* @file cpu_load.h
* @brief CPU 负载率测量(1.5.63 定义收窄,机制沿用 1.5.61)。
*
* 度量定义:**中断 + 非延迟处理段(任务派发 / OTA 中继等请求驱动工作)占墙钟
* 时间的比值**,1s 滚动窗口,分辨率 0.1%(permille)。
*
* 为什么不计入主循环常开轮询:本架构在 EtherCAT 实时模式(CONFIG_MAIN_LOOP_WFI_ENABLE=0)
* 下主循环空闲时是 NOP 忙等,且 runPending() 每圈无条件跑 SSC MainLoop 轮询与各
* poll() 检查——把这部分计入"busy"的话负载率恒 ≈99%,失去参考价值(1.5.61 实机
* 实测 99.4%,其中绝大部分是自由运转的轮询而非在处理事件)。收窄后的定义回答
* 的问题变成"CPU 有多少时间在响应事件",随真实负载(CAN 流量、OTA 中继、
* DC 模式 Sync0 过程数据搬运)增长;常开轮询成本与空转时间不计入。
*
* 计入项(全走同一深度计数器求**区间并集**,嵌套不重不漏):
* - 全部挂钩 ISR:TIMER2(100µs 调度)、SysTick、EXTI0/1/2(EtherCAT Sync0/PDI/
* Sync1)、TIMER0(DC 定时档)、CAN0-2 RX+TX、SPI0+EXTI10_15、USART0。
* DC 模式下 Sync0_Isr 的 EXMC 过程数据搬运由此计入——这是大头,不能漏。
* - 主循环非延迟段:TaskManager 派发的周期任务、0xB0 进 BOOT 处理、
* 电机 OTA relay 推进(isActive() 门控,空闲时零挂钩开销)。
* 不计入项:SSC MainLoop 轮询、AppSpiCommandBus.poll()、看门狗喂狗等常开检查
* (free-run 调试模式下 PDO 应用跑在主循环轮询路径,不计入;生产走 DC 模式,
* 等价工作在 Sync0 ISR 内已计入)。
*
* 并发协议(无关中断、无临界区):enter/exit 用 LDREX/STREX 原子改深度,
* 任一优先级可抢占任意上下文而不破坏计数——STREX 失败方重试时会看到已修正的
* 深度并补上时间戳。已知残余误差:exit 在读时间戳与递减深度之间被高优先级
* 中断插入时,该次中断时长漏计(几 cycle 窗口,对 0.1% 分辨率可忽略)。
*
* @note DWT 已由 EscLan9252::init 使能;init() 幂等兜底,在主循环启动前调用。
*/
#ifndef SYS_CPU_LOAD_H
#define SYS_CPU_LOAD_H
#include <stdint.h>
/**
* @brief CPU 负载率测量。
* @note 全静态类,与 SysTickTimer 同风格。
*/
class CpuLoad {
public:
/**
* @brief 使能 DWT CYCCNT 并复位统计状态。
* @note 幂等;须在主循环启动前调用一次。
*/
static void init();
/** @brief 主循环圈起点:进入"busy"区间。与 endWork() 成对,每圈各一次。 */
static void beginWork();
/**
* @brief 主循环圈终点:退出"busy"区间。
* @note 与 beginWork() 成对;窗口滚动不在这里(begin/end 可能整圈不执行),
* 见 tick()。
*/
static void endWork();
/**
* @brief 主循环每圈无条件调用:检查 1s 窗口是否滚动。
* @note 只做一次 CYCCNT 读 + 比较(命中才进 flash 侧结算函数),滚动不再
* 依赖"本圈恰好有挂钩段执行",空闲系统 seq 也会按秒推进。
*/
static void tick();
/** @brief 中断入口挂钩:与 isrExit() 成对,包住 ISR 主体。 */
static void isrEnter();
/** @brief 中断出口挂钩。 */
static void isrExit();
/**
* @brief 最近一个完整窗口的负载率。
* @return 0..1000,单位 0.1%(1000=满载);窗口未滚动过时为 0。
*/
static uint16_t loadPermille() { return s_loadPerm; }
/**
* @brief 已完成的统计窗口数(自 init 起,单调递增回绕)。
* @note 随负载率一起进 EtherCAT 输入 PDO 段 5,主站据此判断数据是否仍在刷新。
*/
static uint32_t windowSeq() { return s_seq; }
public:
/* 静态成员开成 public:cpu_load.cpp 里 .ramfunc 的文件级内联热路径
* (cpuLoadEnter/cpuLoadLeave/cpuLoadRollWindow) 直接触碰它们,
* 经成员函数绕一道反而多一层调用。勿在模块外写这些状态。 */
static volatile uint32_t s_depth; /**< busy 区间嵌套深度 */
static volatile uint32_t s_stamp; /**< 深度 0→1 时刻的 CYCCNT 快照 */
static volatile uint32_t s_busy; /**< 当前窗口内累计 busy 周期数 */
static volatile uint32_t s_winStart; /**< 当前窗口起点 CYCCNT */
static volatile uint16_t s_loadPerm; /**< 最近一个完整窗口的负载率(0.1%) */
static volatile uint32_t s_seq; /**< 完成滚动的窗口计数 */
};
#endif /* SYS_CPU_LOAD_H */