fix(ssc): 根治邮箱楔死(1.5.59): 几何常量化消灭建立路径, 守护字节序修复

- ecat_def.h: 新增 MBX_SM0/1_START/SIZE 构建期常量(=SII 真值 0x1000/0x1200 各 256B,
  与 gen_eeprom.py/DCU_ESI.xml 三方一致, 改 SII 邮箱尺寸必须同步)
- mailbox.c: MBX_StartMailboxHandler 常量直达缓存, CSR 单次间接读降为交叉校验
  (失配打 [ECAT][MBX] WARN)。根因: INIT→PREOP 单次 CSR 读错值(0x1204/0x1208,
  BUSLAT 余量外的 EXMC 毛刺)被缓存成 u16EscAddrSendMbx → 响应写错址 → 断电才清;
  常量化后错值无进入缓存的路径, 运行期内存踩踏也会在每次 INIT→PREOP 被覆写
- esc_lan9252.cpp: csrRead 双读 CSR_DATA 一致校验+整事务重试(CSR_DATA 为锁存
  寄存器非 FIFO, 连读必一致; 不一致即本拍总线毛刺)。瞬态错读曾致 CheckSmSettings
  以 0x0016/0x001F 打断转换
- dc_app.c: 修复守护/心跳 SWAPWORD 字节序 bug——ESC 寄存器 PDI/CSR 读即主机序
  (与 GetSyncMan/mailbox 裸读一致), 交换使 sm0Len=0x0001<8 恒短路, 1.5.52~1.5.58
  地址失配守护从未生效过; 现降级为运行期内存踩踏兜底并真正可用; ReadAlDiag 同修,
  期间串口日志的 al/evt 均为字节交换错值, 历史日志需交换重读
- task_runner.cpp: 守护注释同步
- 新增 _syntax_check.bat + _syntaxcheck_stubs: 无 AC6 环境用 VS2022 cl /Zs 验 C 文件

真机验收: 140 次 op/start 全成功(40 轮 DC 4ms + 100 轮 DC/free-run 交替),
每轮进 OP、SDO 0x2000:3/4 全成功, 无 0x0016/0x001D/0x001B。
This commit is contained in:
cbb
2026-09-05 17:38:35 +08:00
parent f9ef1b8da2
commit 2ebd4ff1d2
7 changed files with 112 additions and 31 deletions
+11
View File
@@ -0,0 +1,11 @@
@echo off
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1
cd /d "G:\Robot_v2\DCU\DCU_APP\GD32E508_DCU_APP"
set PACKED=/D"STRUCT_PACKED_START=" /D"STRUCT_PACKED_END=" /D"MBX_STRUCT_PACKED_START=" /D"MBX_STRUCT_PACKED_END=" /D"OBJ_STRUCT_PACKED_START=" /D"OBJ_STRUCT_PACKED_END="
set INC=/I. /Icomm\ssc\inc /Icomm\ssc\port /IRTE\_Debug_GD32E508VE /I_syntaxcheck_stubs
echo === mailbox.c ===
cl /nologo /Zs /W0 /TC /std:c11 /utf-8 /Zp1 %PACKED% %INC% comm\ssc\src\mailbox.c
if errorlevel 1 (echo MAILBOX_FAIL) else (echo MAILBOX_OK)
echo === dc_app.c ===
cl /nologo /Zs /W0 /TC /std:c11 /utf-8 /Zp1 %PACKED% %INC% comm\ssc\port\dc_app.c
if errorlevel 1 (echo DC_APP_FAIL) else (echo DC_APP_OK)
@@ -0,0 +1 @@
/* syntax-check stub only: SSC C files never touch device registers directly */
+15 -1
View File
@@ -329,12 +329,26 @@ void EscLan9252::enableEcatIrqOutput()
uint32_t EscLan9252::csrRead(uint16_t addr, uint8_t size)
{
/* DS 12.11.1:写 CSR_CMD(BUSY|R_nW|SIZE|ADDR) 启动,busy 自清后读 CSR_DATA;
* 有效数据总在 CSR_DATA 低位,硬件处理字节对齐。 */
* 有效数据总在 CSR_DATA 低位,硬件处理字节对齐。
* 1.5.59 加固:CSR_DATA 是锁存寄存器(非 FIFO),命令不变时连读两次必一致;
* 不一致即本拍 EXMC 毛刺——历史上这类错读曾让 GetSyncMan 把 0x1204/0x1208
* 缓存成邮箱几何(→SSC 邮箱楔死,根治见 mailbox.c 构建期常量),或让
* CheckSmSettings 以 0x0016/0x001F 瞬态打断状态转换。对错读整事务重跑一次;
* 两轮仍不一致则交出末拍(与旧行为一致的兜底,邮箱几何另有常量兜底)。 */
for (uint8_t attempt = 0U; attempt < 2U; ++attempt)
{
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
const uint32_t d1 = readReg32(ECAT_CSR_DATA_REG);
const uint32_t d2 = readReg32(ECAT_CSR_DATA_REG);
if (d1 == d2)
{
return d1;
}
}
return readReg32(ECAT_CSR_DATA_REG);
}
+24 -20
View File
@@ -374,13 +374,18 @@ void APPL_Application(void)
* AL 诊断快照 — task_runner 5s 心跳用 (C 链接, C++ 侧 extern "C" 声明)
* [19:16]=AL 状态(0x0130 低 4 位) [31:24]=AL 错误码(0x0130 高 8 位)
* [15:0] =AL 事件寄存器(0x0220, bit0=主站 AL 控制请求待处理)
*
* 1.5.59 字节序修正:ESC 寄存器经 PDI/CSR 读出即主机序(LAN9252 CSR 引擎按
* 16-bit 主机总线给出寄存器"值";线上大端只存在于网络侧与 PRAM 原始帧数据),
* 与 SSC 自身 GetSyncMan/mailbox 的裸读约定一致。1.5.48~1.5.58 这里误加了
* SWAPWORD,心跳打印的 al/evt 全是字节交换后的错值——那段时期的串口日志
* (如 evt=0x0400)需按字节交换重新解读后才能当证据用。
*---------------------------------------------------------------------------*/
UINT32 EcatDbg_ReadAlDiag(void)
{
UINT16 alStatus = 0U;
HW_EscReadWord(alStatus, ESC_AL_STATUS_OFFSET);
alStatus = SWAPWORD(alStatus);
return ((UINT32)alStatus << 16) | (UINT32)SWAPWORD(HW_GetALEventRegister());
return ((UINT32)alStatus << 16) | (UINT32)HW_GetALEventRegister();
}
/*---------------------------------------------------------------------------
@@ -420,30 +425,29 @@ UINT32 EcatDbg_GuardAleventMask(void)
return 0xFFFFU; /* 状态不适用:返回哨兵值,调用方不打印 */
}
/* (0) 邮箱 SM 地址/长度失配自愈(2026-09-03 03:00 根治,1.5.52):
/* (0) 邮箱 SM 地址/长度失配兜底(1.5.52 引入,1.5.59 修复字节序并降级为兜底):
*
* 主站侧探针(楔死态)实锤的证据链:
* 历史楔死(1.5.51 及更早,断电才清)的实锤证据链:
* - ESC/SSC 邮箱处理全程活着(注入合法 CoE 请求后 200ms 内 SM1 出响应);
* - 但响应帧头出现在 SM1 start+8 而非 start(且逐会话累积:+4→+8,断电才清零);
* - 网络侧(FPRD)读 SM0/SM1 寄存器 start 恒正确(0x1000/0x1200);
* => SSC 的 u16EscAddrSendMbx(Init→PreOP 时经 GetSyncMan/CSR 间接读)拿到了错值,
* 主站按协议从 0x1200 读 → 帧头永远错位 → SOEM 报 WkcError/MailboxError,
* 且预清理重枚举(重跑 StartMailboxHandler)重读仍错 → 死锁,只有断电恢复。
* 自愈:AL>=PREOP 时用寄存器真值(0x0800/0x0808)校验 SSC 缓存的 start/len,
* 失配即写回 + WARN(旧值/新值一并打印,下次串口复现可确凿定位 CSR 读错还是
* 变量污染——两种成因此写回均有效,写回后下一个邮箱事务即恢复)。
* 变量仅在 Stop/StartMailboxHandler(INIT<->PREOP 迁移,主循环上下文)被写,
* 守护同样在主循环——无并发风险。 */
* => SSC 缓存的 u16EscAddrSendMbx(Init→PreOP 时经 GetSyncMan/CSR 间接读)拿到了错值,
* 主站按协议从 0x1200 读 → 帧头永远错位 → SOEM 报 WkcError/MailboxError。
* 1.5.59 根治:mailbox.c 的 MBX_StartMailboxHandler 改用构建期常量直达
* (ecat_def.h:MBX_SM0/1_START/SIZE),CSR 错值从此没有进入缓存的路径——
* 楔死无建立点,本守护降级为"运行期内存踩踏"的最后兜底:AL>=PREOP 时用
* 寄存器真值校验 SSC 缓存的 start/len,失配即写回 + WARN。
*
* ★ 1.5.59 字节序修复:ESC 寄存器经 PDI/CSR 读出即主机序(与 SSC 自身
* GetSyncMan/mailbox 路径一致)。1.5.52~1.5.58 这里误加了 SWAPWORD——
* sm0Len 被换成 0x0001 < 8,下面的失配判定恒短路,守护自上线起从未真正
* 执行过("自愈"名存实亡,楔死实际靠重枚举重读碰运气恢复)。 */
if (al >= 2U && bMbxRunning) {
UINT16 sm0Start = 0U, sm0Len = 0U, sm1Start = 0U, sm1Len = 0U;
HW_EscReadWord(sm0Start, ESC_SYNCMAN_REG_OFFSET);
HW_EscReadWord(sm0Len, ESC_SYNCMAN_REG_OFFSET + 2U);
HW_EscReadWord(sm1Start, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER);
HW_EscReadWord(sm1Len, ESC_SYNCMAN_REG_OFFSET + SIZEOF_SM_REGISTER + 2U);
sm0Start = SWAPWORD(sm0Start);
sm0Len = SWAPWORD(sm0Len);
sm1Start = SWAPWORD(sm1Start);
sm1Len = SWAPWORD(sm1Len);
if (sm0Len >= 8U && sm1Len >= 8U
&& (u16EscAddrReceiveMbx != sm0Start || u16EscAddrSendMbx != sm1Start
|| u16ReceiveMbxSize != sm0Len || u16SendMbxSize != sm1Len)) {
@@ -464,16 +468,17 @@ UINT32 EcatDbg_GuardAleventMask(void)
return 0xFFFFU; /* 以下仅 SAFEOP+ 适用 */
}
evt = SWAPWORD(HW_GetALEventRegister());
/* 1.5.59:PDI 寄存器读即主机序,不再 SWAPWORD——1.5.48~1.5.58 交换出的
* evt/actual 是错值,0x204 缺位判定恒真(每 250ms 盲写一次 mask,值凑巧
* 正确故未暴露),SM2 事件判定则对在错位上。详见上方字节序修复说明。 */
evt = HW_GetALEventRegister();
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
actual = SWAPWORD(actual);
/* (1) 0x204 缺位自愈 */
if ((UINT16)(actual & u16ALEventMask) != u16ALEventMask) {
const UINT16 before = actual;
HW_SetALEventMask(u16ALEventMask);
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
actual = SWAPWORD(actual);
printf("[ECAT][AEM] WARN: AL Event Mask lost bits, actual=0x%04X expect=0x%04X -> restored 0x%04X\r\n",
(unsigned)before, (unsigned)u16ALEventMask, (unsigned)actual);
}
@@ -482,7 +487,6 @@ UINT32 EcatDbg_GuardAleventMask(void)
* (OP 态下 LRW 每周期置位/清除 bit10 属正常,不能误报) */
if (al == 4U /* SAFEOP */ && (evt & 0x0400U) != 0U && bEcatFirstOutputsReceived == FALSE) {
HW_EscReadWord(wdState, ESC_PD_WD_STATE);
wdState = SWAPWORD(wdState);
bEcatFirstOutputsReceived = TRUE;
HW_EscReadWord(dummy, nEscAddrOutputData);
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
+23
View File
@@ -708,6 +708,29 @@
#define MAX_MBX_READ_ADDRESS 0x1FFF
#endif
/* 1.5.59 根治邮箱楔死:本板邮箱几何构建期常量。
* 取值 = gen_eeprom.py 的 MBX_RX_OFFSET/SIZE(0x1000/256) 与 MBX_TX_OFFSET/SIZE
* (0x1200/256),即 DCU_ESI.xml <Sm> 与 ESC 上电自 SII 加载的寄存器真值;主站
* config_init 也只是照抄同一份 SII。MBX_StartMailboxHandler 以此直达缓存,
* 不再信任单次 CSR 间接读(错值曾以 0x1204/0x1208 进入缓存 → 所有 SDO 响应
* 写错址 → 断电才解的楔死),CSR 读仅作交叉校验(mailbox.c 打 WARN)。
* ★ 改 SII 邮箱几何必须同步改这里(gen_eeprom.py 顶部注释同样有约束)。★ */
#ifndef MBX_SM0_START
#define MBX_SM0_START 0x1000
#endif
#ifndef MBX_SM0_SIZE
#define MBX_SM0_SIZE 0x0100
#endif
#ifndef MBX_SM1_START
#define MBX_SM1_START 0x1200
#endif
#ifndef MBX_SM1_SIZE
#define MBX_SM1_SIZE 0x0100
#endif
/*-----------------------------------------------------------------------------------------
------
------ PDO Timing (DC sync parameters, unit: ns)
+30 -5
View File
@@ -133,6 +133,8 @@ V4.07 ECAT 1: The sources for SPI and MCI were merged (in ecat_def.h<br>
#include "ecatcoe.h"
#include <stdio.h> /* printf: SM 几何交叉校验 WARN(1.5.59,见 MBX_StartMailboxHandler) */
/*--------------------------------------------------------------------------------------
------
------ internal Types and Defines
@@ -282,24 +284,47 @@ void MBX_Init(void)
UINT16 MBX_StartMailboxHandler(void)
{
UINT16 result = 0;
/* get address of the receive mailbox sync manager (SM0) */
/* 1.5.59 根治"邮箱楔死"(响应帧头落在 SM1 start+4/+8、逐会话漂移,断电才清):
* 根因链 = INIT→PREOP 时经 GetSyncMan/CSR 单次间接读(EXMC HBI)把 SM0/SM1
* 几何缓存进 u16EscAddr* 与 u16*MbxSize 等变量;总线毛刺给出的"合法"错值
* (0x1204/0x1208 均为偶地址且在检查范围内)即被缓存,此后所有 SDO 响应写错址,
* 主站按协议从 0x1200 读永远错位,重枚举重读仍错 → 只有断电能解。本板邮箱
* 几何是 SII 固定值(gen_eeprom.py 的 MBX_RX/TX_OFFSET/SIZE,ESC 上电自行
* 加载,主站 config_init 也只是照抄),改为构建期常量(ecat_def.h:
* MBX_SM0/1_START/SIZE)直达缓存,CSR 读降级为交叉校验:失配打 WARN(说明
* ESC 寄存器被非常规改写,需查)但缓存仍取常量——CSR 错值从此没有进入
* 缓存的路径,楔死无建立点;运行期内存踩踏也会在每次 INIT→PREOP 被常量覆写。
* 注意:ESC 寄存器经 PDI/CSR 读出即主机序(与 GetSyncMan 既有路径一致,勿加
* SWAPWORD;PRAM 帧数据才是线上大端,那边交换是对的)。 */
/*ECATCHANGE_START(V5.11) HW1*/
TSYNCMAN ESCMEM * pSyncMan = (TSYNCMAN ESCMEM *)GetSyncMan(MAILBOX_WRITE);
/*ECATCHANGE_END(V5.11) HW1*/
if (pSyncMan->PhysicalStartAddress != MBX_SM0_START || pSyncMan->Length != MBX_SM0_SIZE)
{
printf("[ECAT][MBX] WARN: SM0 reg 0x%04X/%u != build-time 0x%04X/%u -> keep build-time\r\n",
(unsigned)pSyncMan->PhysicalStartAddress, (unsigned)pSyncMan->Length,
(unsigned)MBX_SM0_START, (unsigned)MBX_SM0_SIZE);
}
/* store size of the receive mailbox */
u16ReceiveMbxSize = pSyncMan->Length;
u16ReceiveMbxSize = MBX_SM0_SIZE;
/* store the address of the receive mailbox */
u16EscAddrReceiveMbx = pSyncMan->PhysicalStartAddress;
u16EscAddrReceiveMbx = MBX_SM0_START;
/* get address of the send mailbox sync manager (SM1) */
/*ECATCHANGE_START(V5.11) HW1*/
pSyncMan =(TSYNCMAN ESCMEM *) GetSyncMan(MAILBOX_READ);
/*ECATCHANGE_END(V5.11) HW1*/
if (pSyncMan->PhysicalStartAddress != MBX_SM1_START || pSyncMan->Length != MBX_SM1_SIZE)
{
printf("[ECAT][MBX] WARN: SM1 reg 0x%04X/%u != build-time 0x%04X/%u -> keep build-time\r\n",
(unsigned)pSyncMan->PhysicalStartAddress, (unsigned)pSyncMan->Length,
(unsigned)MBX_SM1_START, (unsigned)MBX_SM1_SIZE);
}
/* store the size of the send mailbox */
u16SendMbxSize = pSyncMan->Length;
u16SendMbxSize = MBX_SM1_SIZE;
/* store the address of the send mailbox */
u16EscAddrSendMbx = pSyncMan->PhysicalStartAddress;
u16EscAddrSendMbx = MBX_SM1_START;
// HBu 02.05.06: it should be checked if there are overlaps in the sync manager areas
if ((u16EscAddrReceiveMbx + u16ReceiveMbxSize) > u16EscAddrSendMbx && (u16EscAddrReceiveMbx < (u16EscAddrSendMbx + u16SendMbxSize)))
+3
View File
@@ -295,6 +295,9 @@ extern "C" uint32_t EcatDbg_GuardAleventMask(void);
* 1.5.52:周期 1s→250ms——新加的邮箱地址自愈(1.5.52 根治 SSC 侧
* u16EscAddrSendMbx 错位楔死,详见 dc_app.c)必须快过主站 SDO 超时
* (~700ms),否则自愈永远落在主站放弃之后,形同虚设。
* 1.5.59:邮箱楔死已根治(mailbox.c 几何常量化,CSR 错值无进入缓存路径),
* 本守护降级为运行期内存踩踏兜底;且修复了 1.5.52~1.5.58 的 SWAPWORD
* 字节序 bug——那段时期本守护的失配判定恒短路,实际从未生效过。
*/
static void ethercatAleMaskGuard()
{