Files
DCU_APP/GD32E508_DCU_APP/sys/cpu_load.h
T
cbb 5fd986299c feat(comm): 1.6.51 CAN 空通道自动静默 + PDO 诊断 + OTA 布局共用头
变更点:
1. CAN-FD(device/CANFD.cpp、sys/config.h)
   - 发送统一 FD+BRS(DLC≤8 不再回退经典 CAN);OTA 期仍按通道压制 BRS
   - 空通道自动识别:连续 5 个 1Hz 窗口无有效帧且有错误 → 静默上报;有帧即恢复
   - 三路 CAN 默认全开;RX parse budget 48→64
2. EtherCAT 桥 / PDO
   - 段1 增 txtxid/rx00ff;版本应答 0x00FF 粘滞发布,避免被 CONFIG 冲掉
   - STATUS 禁止写入 xrx 快照;processSeq 新鲜度救援
3. OTA / 配置
   - sys/config.h include 仓库内 shared/ota_layout.h(与 BOOT 共用,不再依赖仓外路径)
   - ota_boot_request 对齐配置页 word 布局
4. docs/audit_ecat_spi_wifi_ota_compatibility.md:ECAT/SPI/WiFi/OTA 兼容性审计
5. .gitignore:忽略 .trae/、docs/traework-import、评审过程稿
2026-09-20 22:58:09 +08:00

111 lines
4.9 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 派发任务、BOOT、OTA relay、新鲜度救援、
* free-run 下 **1ms 节流** 的过程数据应用(有 CpuLoad 挂钩)。
* - PDI/Sync0 ISR 全程(含 SM 类型下每拍 Process/映射)——负载随 PDO Hz 升。
* 不计入:MainLoop/SSC/poll 空转。
* 性能约束:PDI 热路径不得「凡 AL 事件就 Process/InputMapping」——8kHz 时
* 单拍超 ~120µs 墙会掉速;输出用 12B 探针,process 兜底读带新鲜度门。
*
* 并发协议(无关中断、无临界区):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>
#ifdef __cplusplus
extern "C" {
#endif
/** C 侧挂钩(ecatappl.c / dc_app.c)。与 beginWork/endWork 同一深度计数器。 */
void CpuLoad_Enter(void);
void CpuLoad_Leave(void);
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
/**
* @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 /* __cplusplus */
#endif /* SYS_CPU_LOAD_H */