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:
@@ -30,7 +30,7 @@ solution:
|
||||
target-set:
|
||||
- set:
|
||||
images:
|
||||
- project-context: GD32E508_DCU_APP.Debug
|
||||
- project-context: GD32E508_DCU_APP.Release
|
||||
debugger:
|
||||
name: ST-Link@pyOCD
|
||||
clock: 4000000
|
||||
|
||||
@@ -16,6 +16,8 @@ project:
|
||||
- file: ./sys/task_manager.cpp
|
||||
- file: ./sys/task_runner.h
|
||||
- file: ./sys/task_runner.cpp
|
||||
- file: ./sys/cpu_load.h
|
||||
- file: ./sys/cpu_load.cpp
|
||||
- file: ./sys/ota_boot_request.h
|
||||
- file: ./sys/ota_boot_request.cpp
|
||||
- file: ./sys/aes128.h
|
||||
@@ -163,6 +165,9 @@ project:
|
||||
- -DHXTAL_VALUE=8000000U
|
||||
- -gdwarf-5
|
||||
- -D__MICROLIB
|
||||
# 启动文件经 legacy armasm 汇编:消 armclang 驱动"参数未使用"告警 + armasm 弃用告警(A1950W)
|
||||
- -Wno-unused-command-line-argument
|
||||
- -Wa,armasm,--diag_suppress=A1950W
|
||||
Link:
|
||||
- --library_type=microlib
|
||||
- --entry=Reset_Handler
|
||||
|
||||
@@ -137,6 +137,18 @@ enum : uint16_t {
|
||||
ECAT_RXB_OFF_ITEMS = 364U, /**< 帧数组起点,项布局同 ECAT_FRM_OFF_* */
|
||||
};
|
||||
|
||||
/** 输入 PDO 段 5(632..639):设备诊断区(1.5.61 新增,尾部追加)。
|
||||
* 批量接收区最坏情况(全 DLC64 满帧)只用到 364+3×72=580,580..639 永久空闲,
|
||||
* 诊断区锚在 632 起的 8 字节,写侧是 EthercatPdo::process()。 */
|
||||
enum : uint16_t {
|
||||
ECAT_DIAG_OFF_LOAD = 632U, /**< 负载率,小端 uint16,单位 0.1%(1000=100%)。
|
||||
中断 + 非延迟处理段(任务派发/OTA relay)占墙钟比,
|
||||
不含常开轮询与空转;1s 滚动窗口(见 sys/cpu_load.h) */
|
||||
ECAT_DIAG_OFF_RSVD = 634U, /**< 保留 2B */
|
||||
ECAT_DIAG_OFF_SEQ = 636U, /**< 诊断区序号,小端 uint32,每完成一个统计窗口 +1,
|
||||
主站据此判断负载值是否仍在滚动刷新 */
|
||||
};
|
||||
|
||||
/** 单帧数据在输出 PDO / 长帧快照区中的最大字节数(电机 CMD 是 DLC 32)。 */
|
||||
constexpr uint8_t kEcatCanTxDataMax = 32U;
|
||||
/** 段 1 短快照数据区字节数(保持 1.5.53 兼容,勿改)。 */
|
||||
@@ -183,6 +195,9 @@ static_assert(ECAT_TXB_OFF_ITEMS < kEcatPdoOutBytes, "批量发送区起点越
|
||||
/* 批量区至少要装得下一帧满长 CANFD,否则"批量"没有意义 */
|
||||
static_assert(kEcatTxBatchMax >= 1U, "批量发送区装不下一帧 DLC64");
|
||||
static_assert(kEcatRxBatchMax >= 1U, "批量接收区装不下一帧 DLC64");
|
||||
/* 段 5 诊断区必须在批量接收区的最坏占用之外(改批量区布局时一起核对) */
|
||||
static_assert(ECAT_RXB_OFF_ITEMS + (uint16_t)kEcatRxBatchMax * (ECAT_FRM_HDR_SIZE + kEcatCanFdDataMax)
|
||||
<= ECAT_DIAG_OFF_LOAD, "批量接收区最坏占用与段5诊断区重叠");
|
||||
|
||||
/**
|
||||
* @brief EtherCAT ↔ CAN 转发桥。
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include "comm/ethercat_pdo.h"
|
||||
#include "comm/ethercat_can_bridge.h"
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -32,6 +33,26 @@ void EthercatPdo::process()
|
||||
* processOutput 还负责取走主站请求的状态窗口下标,也必须在 buildInput 之前。 */
|
||||
EthercatCanBridge::processOutput(s_out);
|
||||
EthercatCanBridge::buildInput(s_in);
|
||||
fillDiagTail(s_in);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 填充输入 PDO 段 5 设备诊断区(632..639,1.5.61)。
|
||||
* @param[out] in 全长输入过程数据缓冲(buildInput 刚清零+填完段 1~4)。
|
||||
* @note 负载率 1s 滚动一个窗口,seq 随窗口递增——主站只认"seq 在变"的负载值。
|
||||
* 每周期重写 6 字节比"值变化才写"的分支判断更便宜,也不引入隐藏状态。
|
||||
*/
|
||||
void EthercatPdo::fillDiagTail(uint8_t *in)
|
||||
{
|
||||
const uint16_t load = CpuLoad::loadPermille();
|
||||
in[ECAT_DIAG_OFF_LOAD] = (uint8_t)(load & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_LOAD + 1] = (uint8_t)(load >> 8);
|
||||
|
||||
const uint32_t seq = CpuLoad::windowSeq();
|
||||
in[ECAT_DIAG_OFF_SEQ] = (uint8_t)(seq & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_SEQ + 1] = (uint8_t)((seq >> 8) & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_SEQ + 2] = (uint8_t)((seq >> 16) & 0xFFU);
|
||||
in[ECAT_DIAG_OFF_SEQ + 3] = (uint8_t)((seq >> 24) & 0xFFU);
|
||||
}
|
||||
|
||||
void EthercatPdo::writeInputs(uint8_t *esc)
|
||||
|
||||
@@ -77,6 +77,14 @@ public:
|
||||
static void snapshotOutput(uint8_t *dst);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 填充输入 PDO 段 5 设备诊断区(尾部 8 字节,1.5.61)。
|
||||
* @param[out] in 全长输入过程数据缓冲。
|
||||
* @note 布局常量 ECAT_DIAG_OFF_* 见 ethercat_can_bridge.h;当前携带
|
||||
* CPU 负载率(0.1% 单位) + 窗口序号,取自 sys/cpu_load.h。
|
||||
*/
|
||||
static void fillDiagTail(uint8_t *in);
|
||||
|
||||
/** @brief 最近一份主站下发的输出数据。 */
|
||||
static uint8_t s_out[kPdoOutBytes];
|
||||
/** @brief 待交回主站的输入数据。 */
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "sys/config.h"
|
||||
#include "main.h" /* g_app_uptime_sec */
|
||||
#include "sys/cpu_load.h"
|
||||
#include <cstdio>
|
||||
|
||||
extern "C" {
|
||||
@@ -168,13 +169,23 @@ extern "C" {
|
||||
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EXTI0_IRQHandler(void) { EthercatSync::onSync0Exti(); }
|
||||
void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
|
||||
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
|
||||
void EXTI0_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
EthercatSync::onSync0Exti();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
|
||||
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
|
||||
#else
|
||||
void TIMER0_UP_TIMER9_IRQHandler(void) { EthercatSync::onTimerIrq(); }
|
||||
void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
|
||||
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
|
||||
void TIMER0_UP_TIMER9_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
EthercatSync::onTimerIrq();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
|
||||
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
|
||||
#endif
|
||||
|
||||
/* ---- SSC 诊断对象 0x2000 的 C 侧访问 (dc_app.c APPL_InputMapping 每周期拉取) ---- */
|
||||
|
||||
@@ -389,8 +389,8 @@ UINT32 EcatDbg_ReadAlDiag(void)
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈 — 1.5.48 新增, 1.5.49 扩展
|
||||
* (C 链接, task_runner 1s 节流调用)
|
||||
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈 — 1.5.48 新增, 1.5.49/1.5.60 扩展
|
||||
* (C 链接, task_runner 250ms 节流调用)
|
||||
*
|
||||
* 背景:实测"停止回 INIT 后重进 OP"概率性失败——主站 LRW WKC=3/3(输出
|
||||
* 确实写进 SM2),但从站 9s ESM 超时报 0x001B:bEcatFirstOutputsReceived 一直
|
||||
@@ -401,6 +401,13 @@ UINT32 EcatDbg_ReadAlDiag(void)
|
||||
*
|
||||
* 守护(1)0x204 缺位自愈:从站 AL>=SAFEOP 且有输出 PDO 时,SSC 不变式是
|
||||
* "0x204 ⊇ u16ALEventMask"。读实际值比对,缺位则补写并打 WARN。
|
||||
* 1.5.60 加固:期望值 u16ALEventMask 本身也可能缺 SM2 位——下行转换路径
|
||||
* (ecatslv.c AL_ControlInd 尾部)同时清 0x204 的 PDO 事件位、bEscIntEnabled、
|
||||
* bEcatFirstOutputsReceived,恢复全靠 SAFEOP 重入的 StartInputHandler +
|
||||
* SetALEventMask(ecatslv.c:1506);重入没走全时 0x204 丢位。旧判定只比
|
||||
* "actual ⊇ 期望值",而期望值变量在下行路径并不清零——若错的是期望值本身,
|
||||
* 比对恒通过、缺位永远补不回。SM-Synchron+有输出 PDO 的不变式是
|
||||
* u16ALEventMask ⊇ PROCESS_OUTPUT_EVENT(ecatslv.c:959/1086),先补期望再比对。
|
||||
*
|
||||
* 自愈(2)SM2 事件楔死(1.5.49):SAFEOP 下 0x220 bit10 挂起 且 标志未置位
|
||||
* = PDI_Isr 通路楔死。从主循环直接完成 PDI_Isr 该做的事:置位
|
||||
@@ -408,6 +415,15 @@ UINT32 EcatDbg_ReadAlDiag(void)
|
||||
* 协议等价:bit10 挂起 = 主站已写过 SM2 = "收到输出"成立;AL_ControlRes 下一个
|
||||
* MainLoop 周期即完成 SAFEOP→OP。WARN 附带全部内部旗标(bEscInt/bDcSync/
|
||||
* 期望与实际 mask/看门狗状态),下次复现一行日志即可定位楔死路径。
|
||||
*
|
||||
* 自愈(3)OP 态 PDI_Isr 门控(1.5.60):AL==OP 且 bEscIntEnabled==FALSE 不可
|
||||
* 合法出现——门控只在下行转换尾部被清(ecatslv.c:1273),而下行必改 AL 状态;
|
||||
* 守护与 ESM 同在主循环上下文串行,看到"OP 且门关着"即重入路径漏了恢复段。
|
||||
* 此时 PDI_Isr 整体被门控(ecatappl.c:282),PDO_OutputMapping 停调、输入冻结
|
||||
* ——主站侧表现即楔死看门狗盯的窗口回显冻结(2026-09-05 实测 250µs 档每
|
||||
* 20~35s 触发一次重拉,wr 两小时涨 38)。修法:重开门 + 应答挂起的 SM2 事件
|
||||
* (掩码补位由守护(1)在同次调用完成),下一帧 LRW 的 SM2 中断即恢复整条
|
||||
* 过程数据链路,主站看门狗(2s 阈值)从此摸不到触发条件。
|
||||
*---------------------------------------------------------------------------*/
|
||||
UINT32 EcatDbg_GuardAleventMask(void)
|
||||
{
|
||||
@@ -474,7 +490,12 @@ UINT32 EcatDbg_GuardAleventMask(void)
|
||||
evt = HW_GetALEventRegister();
|
||||
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
|
||||
|
||||
/* (1) 0x204 缺位自愈 */
|
||||
/* (1) 0x204 缺位自愈(1.5.60 加固:先补期望值,再比对补实际值) */
|
||||
if ((u16ALEventMask & PROCESS_OUTPUT_EVENT) == 0U) {
|
||||
u16ALEventMask |= PROCESS_OUTPUT_EVENT;
|
||||
printf("[ECAT][AEM] WARN: expected AL mask lacked SM2 bit -> 0x%04X\r\n",
|
||||
(unsigned)u16ALEventMask);
|
||||
}
|
||||
if ((UINT16)(actual & u16ALEventMask) != u16ALEventMask) {
|
||||
const UINT16 before = actual;
|
||||
HW_SetALEventMask(u16ALEventMask);
|
||||
@@ -498,5 +519,25 @@ UINT32 EcatDbg_GuardAleventMask(void)
|
||||
(unsigned)(bDcSyncActive ? 1U : 0U),
|
||||
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
|
||||
}
|
||||
|
||||
/* (3) OP 态 PDI_Isr 门控自愈(1.5.60 新增):OP 下 bEscIntEnabled==FALSE
|
||||
* 不可合法出现——门控只在下行转换尾部被清(ecatslv.c:1273),而下行必改
|
||||
* AL 状态;守护与 ESM 同在主循环上下文串行,看到"OP 且门关着"即 SAFEOP
|
||||
* 重入路径漏了恢复段。此时 PDI_Isr 整体被门控(ecatappl.c:282),
|
||||
* PDO_OutputMapping 停调、输入冻结,只能靠主站看门狗重拉总线。
|
||||
* 修法:重开门 + 应答挂起的 SM2 事件(ISR 非 OP 分支同款读首/末字);
|
||||
* 0x204 掩码补位已由上面(1)在同次调用完成,下一帧 LRW 即恢复。 */
|
||||
if (al == 8U /* OP */ && nPdOutputSize > 0U && bEscIntEnabled == FALSE) {
|
||||
HW_EscReadWord(wdState, ESC_PD_WD_STATE);
|
||||
bEscIntEnabled = TRUE;
|
||||
HW_EscReadWord(dummy, nEscAddrOutputData);
|
||||
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
|
||||
printf("[ECAT][AEM] WARN: OP with PDI_Isr gated, evt=0x%04X "
|
||||
"(bDcSync=%u mask_exp=0x%04X mask_act=0x%04X wd=0x%04X) "
|
||||
"-> gate reopened + event acked\r\n",
|
||||
(unsigned)evt,
|
||||
(unsigned)(bDcSyncActive ? 1U : 0U),
|
||||
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
@@ -887,14 +887,15 @@ UINT8 OBJ_Read( UINT16 index, UINT8 subindex, UINT32 objSize, OBJCONST TOBJECT O
|
||||
p = (CHAR **) pVarPtr;
|
||||
pVarPtr = (UINT16 MBXMEM *)p[subindex-1];
|
||||
|
||||
if((((UINT16)pVarPtr) & 0x1) == 0x1)
|
||||
if((((UINT32)pVarPtr) & 0x1) == 0x1)
|
||||
{
|
||||
/*enum is stored at an odd address*/
|
||||
UINT16 cnt = 0;
|
||||
|
||||
//get last even WORD address
|
||||
/*ECATCHANGE_START(V5.11) SDO3*/
|
||||
pVarPtr = (UINT16 MBXMEM *)(((MEM_ADDR)pVarPtr)& ~(MEM_ADDR)0x1);
|
||||
/*MEM_ADDR=UINT16 会把 32 位指针截成 16 位,必须用完整位宽清 bit0*/
|
||||
pVarPtr = (UINT16 MBXMEM *)(((UINT32)pVarPtr) & ~(UINT32)0x1);
|
||||
/*ECATCHANGE_END(V5.11) SDO3*/
|
||||
|
||||
for(cnt = 0; cnt < (size / 2);cnt++)
|
||||
|
||||
@@ -1025,7 +1025,8 @@ UINT8 SDOS_SdoInfoInd( TSDOINFORMATION MBXMEM *pSdoInfoInd )
|
||||
UINT16 n = OBJ_GetNoOfObjects(i);
|
||||
|
||||
/* copy the number of objects of the list type in the SDO Information response */
|
||||
((UINT16 MBXMEM *) &pSdoInfoInd->CoeHeader)[(SIZEOF_SDOINFOLISTSTRUCT>>1)+i] = SWAPWORD(n);
|
||||
/*(UINT32) 中转:对 packed 成员直接取址强转 UINT16 指针会报 -Waddress-of-packed-member(邮箱缓冲实际偶对齐)*/
|
||||
((UINT16 MBXMEM *)(UINT32)&pSdoInfoInd->CoeHeader)[(SIZEOF_SDOINFOLISTSTRUCT>>1)+i] = SWAPWORD(n);
|
||||
}
|
||||
|
||||
/* size of the mailbox service response */
|
||||
@@ -1045,7 +1046,7 @@ UINT8 SDOS_SdoInfoInd( TSDOINFORMATION MBXMEM *pSdoInfoInd )
|
||||
size = u16SendMbxSize - SIZEOF_SDOINFO - MBX_HEADER_SIZE;
|
||||
}
|
||||
/* initialize pData with the pointer where the fragment has to be copied */
|
||||
pData = &((UINT16 MBXMEM *) &pSdoInfoInd->CoeHeader)[SIZEOF_SDOINFO>>1];
|
||||
pData = &((UINT16 MBXMEM *)(UINT32)&pSdoInfoInd->CoeHeader)[SIZEOF_SDOINFO>>1];
|
||||
/* initialize index with the next index to be sent */
|
||||
index = nSdoInfoIndex;
|
||||
/* decrement the number of fragments to be sent */
|
||||
@@ -1063,7 +1064,7 @@ UINT8 SDOS_SdoInfoInd( TSDOINFORMATION MBXMEM *pSdoInfoInd )
|
||||
size = u16SendMbxSize - SIZEOF_SDOINFOLISTSTRUCT - MBX_HEADER_SIZE;
|
||||
}
|
||||
/* initialize pData with the pointer where the fragment has to be copied */
|
||||
pData = &((UINT16 MBXMEM *) &pSdoInfoInd->CoeHeader)[SIZEOF_SDOINFOLISTSTRUCT>>1];
|
||||
pData = &((UINT16 MBXMEM *)(UINT32)&pSdoInfoInd->CoeHeader)[SIZEOF_SDOINFOLISTSTRUCT>>1];
|
||||
/*Check if List need to be send in fragments*/
|
||||
if( (n<<1) > size)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/critical.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include <cstring>
|
||||
@@ -1593,10 +1594,12 @@ bool CanFd::copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out)
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(0U)`。
|
||||
*/
|
||||
extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); }
|
||||
extern "C" void CAN0_RX0_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(0U); CpuLoad::isrExit(); }
|
||||
|
||||
/** @brief CAN0 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN0_TX_IRQHandler(void) { CanFd::handleTxInterrupt(0U); }
|
||||
extern "C" void CAN0_TX_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(0U); CpuLoad::isrExit(); }
|
||||
|
||||
/**
|
||||
* @brief CAN1 RX0 中断向量入口。
|
||||
@@ -1605,10 +1608,12 @@ extern "C" void CAN0_TX_IRQHandler(void) { CanFd::handleTxInterrupt(0U); }
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(1U)`。
|
||||
*/
|
||||
extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); }
|
||||
extern "C" void CAN1_RX0_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(1U); CpuLoad::isrExit(); }
|
||||
|
||||
/** @brief CAN1 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN1_TX_IRQHandler(void) { CanFd::handleTxInterrupt(1U); }
|
||||
extern "C" void CAN1_TX_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(1U); CpuLoad::isrExit(); }
|
||||
|
||||
/**
|
||||
* @brief CAN2 RX0 中断向量入口。
|
||||
@@ -1617,7 +1622,9 @@ extern "C" void CAN1_TX_IRQHandler(void) { CanFd::handleTxInterrupt(1U); }
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(2U)`。
|
||||
*/
|
||||
extern "C" void CAN2_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(2U); }
|
||||
extern "C" void CAN2_RX0_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(2U); CpuLoad::isrExit(); }
|
||||
|
||||
/** @brief CAN2 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN2_TX_IRQHandler(void) { CanFd::handleTxInterrupt(2U); }
|
||||
extern "C" void CAN2_TX_IRQHandler(void)
|
||||
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(2U); CpuLoad::isrExit(); }
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "device/timer.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/config.h"
|
||||
|
||||
/**
|
||||
@@ -105,4 +106,9 @@ void SchedulerTimer::onInterrupt()
|
||||
* @retval 无返回值。
|
||||
* @note C 向量包装函数,内部转调 `SchedulerTimer::onInterrupt()`。
|
||||
*/
|
||||
extern "C" void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
|
||||
extern "C" void TIMER2_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
SchedulerTimer::onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ extern "C" {
|
||||
#include "sys/config.h"
|
||||
#include "sys/critical.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -878,10 +879,14 @@ void AppSpiCommand::onCsExtiInterrupt()
|
||||
|
||||
extern "C" void SPI0_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
AppSpiCommandBus.onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
extern "C" void EXTI10_15_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
AppSpiCommandBus.onCsExtiInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "device/usart0.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/critical.h"
|
||||
#include "sys/cpu_load.h"
|
||||
|
||||
uint8_t Usart0::s_txBuf[Usart0::kTxBufSize];
|
||||
volatile uint16_t Usart0::s_txHead = 0U;
|
||||
@@ -182,4 +183,9 @@ extern "C" int fputc(int ch, FILE *f) { return Usart0::putChar(ch, f); }
|
||||
* @retval 无返回值。
|
||||
* @note 该 C 包装函数内部转调 `Usart0::onInterrupt()`。
|
||||
*/
|
||||
extern "C" void USART0_IRQHandler(void) { Usart0::onInterrupt(); }
|
||||
extern "C" void USART0_IRQHandler(void)
|
||||
{
|
||||
CpuLoad::isrEnter();
|
||||
Usart0::onInterrupt();
|
||||
CpuLoad::isrExit();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "main.h"
|
||||
#include "sys/initial.h"
|
||||
#include "sys/cpu_load.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/task_runner.h"
|
||||
#include "sys/config.h"
|
||||
@@ -45,8 +46,15 @@ int App::run()
|
||||
APP_LOGI("[APP] >>> TaskRunner ready, entering main loop\r\n");
|
||||
printBanner();
|
||||
|
||||
CpuLoad::init();
|
||||
|
||||
while(1) {
|
||||
/* 负载率测量(1.5.63 定义收窄):runPending 内部按"非延迟工作"分段挂钩
|
||||
* (ISR 全量 + 任务派发 + 电机 OTA relay,见 sys/cpu_load.h);
|
||||
* 常开轮询(SSC MainLoop / 各 poll)与 NOP 空转不计入。
|
||||
* tick() 每圈无条件做 1s 窗口滚动检查——滚动不依赖本圈是否有挂钩段。 */
|
||||
TaskRunner::runPending();
|
||||
CpuLoad::tick();
|
||||
#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0)
|
||||
uint32_t save = RT_CRITICAL_SAVE();
|
||||
if(TaskManager::state().task_flags == 0U) {
|
||||
|
||||
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