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协议栈增加新的诊断数据区
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file cpu_load.cpp
|
||||
* @brief CPU 负载率测量实现(1.5.61,度量定义与并发协议见 cpu_load.h)。
|
||||
*
|
||||
* 热路径(beginWork/endWork/isrEnter/isrExit/tick)放进 .ramfunc——它们会被包进
|
||||
* EXTI0(Sync0, preempt=0) 等延迟敏感的 ISR 包装,与 EthercatSync::onSync0Exti
|
||||
* 同款 section 属性,避免 flash 等待态给 Sync0 入口添抖动。1s 一次的窗口
|
||||
* 结算(含 64 位除法)留在普通 flash 函数里。
|
||||
*/
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
volatile uint32_t CpuLoad::s_depth = 0U;
|
||||
volatile uint32_t CpuLoad::s_stamp = 0U;
|
||||
volatile uint32_t CpuLoad::s_busy = 0U;
|
||||
volatile uint32_t CpuLoad::s_winStart = 0U;
|
||||
volatile uint16_t CpuLoad::s_loadPerm = 0U;
|
||||
volatile uint32_t CpuLoad::s_seq = 0U;
|
||||
|
||||
/** @brief 区间并集的共享进入点。0→1 者负责盖时间戳;STREX 失败方重试时深度
|
||||
* 已被抢占者修正,不会再重复盖戳。 */
|
||||
__attribute__((always_inline)) static inline void cpuLoadEnter()
|
||||
{
|
||||
uint32_t d;
|
||||
do {
|
||||
d = __LDREXW(&CpuLoad::s_depth);
|
||||
if (d == 0U) {
|
||||
CpuLoad::s_stamp = DWT->CYCCNT;
|
||||
__DMB();
|
||||
}
|
||||
} while (__STREXW(d + 1U, &CpuLoad::s_depth) != 0U);
|
||||
__DMB();
|
||||
}
|
||||
|
||||
/** @brief 区间并集的共享退出点。1→0 者把区间累进 s_busy(同样走独占访问,
|
||||
* 防"ISR 恰好插在主循环退出的读-改-写之间"丢累加)。 */
|
||||
__attribute__((always_inline)) static inline void cpuLoadLeave()
|
||||
{
|
||||
const uint32_t end = DWT->CYCCNT;
|
||||
const uint32_t start = CpuLoad::s_stamp;
|
||||
uint32_t d;
|
||||
do {
|
||||
d = __LDREXW(&CpuLoad::s_depth);
|
||||
} while (__STREXW(d - 1U, &CpuLoad::s_depth) != 0U);
|
||||
if (d == 1U) {
|
||||
const uint32_t delta = end - start;
|
||||
uint32_t b;
|
||||
do {
|
||||
b = __LDREXW(&CpuLoad::s_busy);
|
||||
} while (__STREXW(b + delta, &CpuLoad::s_busy) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
void CpuLoad::init()
|
||||
{
|
||||
/* DWT 已由 EscLan9252::init 使能;这里幂等兜底(万一 EtherCAT 初始化被裁剪)。 */
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
s_depth = 0U;
|
||||
s_busy = 0U;
|
||||
s_winStart = DWT->CYCCNT;
|
||||
s_loadPerm = 0U;
|
||||
s_seq = 0U;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::beginWork()
|
||||
{
|
||||
cpuLoadEnter();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::isrEnter()
|
||||
{
|
||||
cpuLoadEnter();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::isrExit()
|
||||
{
|
||||
cpuLoadLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 窗口结算(普通 flash 函数,1s 才进一次)。
|
||||
* @note s_busy 用独占访问换出:窗口边界上 ISR 的在途累加要么算进本窗口、
|
||||
* 要么算进下窗口,不丢。
|
||||
*/
|
||||
static void cpuLoadRollWindow(uint32_t now)
|
||||
{
|
||||
const uint32_t elapsed = now - CpuLoad::s_winStart; /* 调用方已保证 >= 1s */
|
||||
uint32_t busy;
|
||||
do {
|
||||
busy = __LDREXW(&CpuLoad::s_busy);
|
||||
} while (__STREXW(0U, &CpuLoad::s_busy) != 0U);
|
||||
CpuLoad::s_winStart = now;
|
||||
|
||||
uint32_t perm = (uint32_t)(((uint64_t)busy * 1000ULL) / (uint64_t)elapsed);
|
||||
if (perm > 1000U) {
|
||||
perm = 1000U;
|
||||
}
|
||||
CpuLoad::s_loadPerm = (uint16_t)perm;
|
||||
CpuLoad::s_seq++;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::endWork()
|
||||
{
|
||||
cpuLoadLeave();
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline)) void CpuLoad::tick()
|
||||
{
|
||||
const uint32_t now = DWT->CYCCNT;
|
||||
if ((uint32_t)(now - s_winStart) >= SystemCoreClock) {
|
||||
cpuLoadRollWindow(now);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @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 */
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "main.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
volatile uint32_t SysTickTimer::s_ms = 0U;
|
||||
|
||||
@@ -93,4 +94,9 @@ bool SysTickTimer::initMs()
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `SysTickTimer::onInterrupt()`。
|
||||
*/
|
||||
extern "C" void SysTick_Handler(void) { SysTickTimer::onInterrupt(); }
|
||||
extern "C" void SysTick_Handler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
SysTickTimer::onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "sys/ota_boot_request.h"
|
||||
#include "sys/motor_ota_relay.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/task_runner.h"
|
||||
#include "comm/ethercat_slave.h"
|
||||
#include "comm/ethercat_sync.h"
|
||||
@@ -565,15 +566,26 @@ void TaskRunner::runPending()
|
||||
/* 0xB0 进 BOOT:必须每圈检查,不能只靠 1Hz TASK_HEARTBEAT。
|
||||
* 否则 Flash 写/复位被拖到下一秒,ESP32 HELLO 一直 role=app 超时。 */
|
||||
if (OtaBootRequest::isRebootPending()) {
|
||||
CpuLoad::beginWork();
|
||||
OtaBootRequest::process();
|
||||
CpuLoad::endWork();
|
||||
}
|
||||
|
||||
/* 电机 OTA relay:活跃时每圈推进状态机(等 ACK / 超时 / 发下一包)。
|
||||
* 非活跃时立即返回,开销可忽略。 */
|
||||
/* 负载率挂钩(1.5.63):relay 活跃时才计 busy——isActive() 是廉价枚举判断,
|
||||
* 空闲路径零挂钩开销;计数段覆盖等 ACK/超时/发包的真实工作。 */
|
||||
const bool motorOtaActive = MotorOtaRelay::isActive();
|
||||
if (motorOtaActive) {
|
||||
CpuLoad::beginWork();
|
||||
}
|
||||
MotorOtaRelay::process();
|
||||
/* 数据发送推进:主循环每圈调用,按 SysTick 钟表控制每包间隔
|
||||
* (替代原靠 tick100us 计数 + TASK_MOTOR_OTA_TX 的方式,消除计数器竞争)。 */
|
||||
MotorOtaRelay::onTxTick();
|
||||
if (motorOtaActive) {
|
||||
CpuLoad::endWork();
|
||||
}
|
||||
|
||||
/* 2026-08 请求驱动改造:0xC1 CAN 反馈 / 0xC5 电机状态 / 0xC4 ADC 已改为
|
||||
* ESP32 周期查询(主动按铃推送全部移除,避免 OTA 期间悬空按铃刷屏)。
|
||||
@@ -593,6 +605,9 @@ void TaskRunner::runPending()
|
||||
const uint32_t pending = TaskManager::takePending();
|
||||
if(pending == 0U) return;
|
||||
|
||||
/* 负载率挂钩(1.5.63):派发的周期任务属"非延迟工作",整段计入 busy;
|
||||
* 前面的常开轮询(Watchdog/poll/MainLoop/guard)不计入,见 sys/cpu_load.h。 */
|
||||
CpuLoad::beginWork();
|
||||
if(pending & (1UL << TASK_ADC_VREF_SAMPLE)) taskAdcVrefSample();
|
||||
if(pending & (1UL << TASK_ADC_TEMP_SAMPLE)) taskAdcTempSample();
|
||||
if(pending & (1UL << TASK_ADC_PROCESS)) taskAdcProcess();
|
||||
@@ -600,4 +615,5 @@ void TaskRunner::runPending()
|
||||
if(pending & (1UL << TASK_CAN_TX_SEND)) taskCanTxSend();
|
||||
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
|
||||
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
|
||||
CpuLoad::endWork();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user