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:
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user