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、评审过程稿
This commit is contained in:
cbb
2026-09-20 22:58:09 +08:00
parent 63af85e7be
commit 5fd986299c
32 changed files with 2592 additions and 781 deletions
+80 -115
View File
@@ -11,10 +11,7 @@
* 0x6000 Input Data (从站→主站, 384 bytes)
* 0x2000 SYNC Diag (只读: sync0/sync1/escirq/uptime, 主站 SDO 轮询看 DC 通路)
*
* 1.5.55:为电机控制台把过程数据从 32B/32B 扩到 64B/384B。
* 0x7000/0x6000/0x1600/0x1A00 由 OBJCODE_REC 改为 OBJCODE_ARR —— 关键原因见
* 下方 Entry Descriptions 注释:REC 会逐下标索引 pEntryDesc[Subindex],48 个
* 条目就得写 49 项描述符;ARR 由 OBJ_GetEntryDesc 复用 pEntryDesc[1],只需 2 项。
* 0x7000/0x6000/0x1600/0x1A00 为 OBJCODE_ARR(原因见下方 Entry Descriptions 注释)。
*/
#include "ecat_def.h"
@@ -27,7 +24,11 @@
#include <string.h>
#include <stddef.h> /* offsetof:下方布局守卫断言使用 */
#include <stdio.h> /* printf:0x204 守护 WARN 日志(1.5.48) */
#include <stdio.h> /* printf:0x204 守护 WARN 日志 */
/* CPU 负载挂钩(sys/cpu_load.cpp,与 /api/app/load 同口径) */
extern void CpuLoad_Enter(void);
extern void CpuLoad_Leave(void);
/*---------------------------------------------------------------------------
* el9800appl.h 桩 include (SSC 内部需要)
@@ -39,18 +40,13 @@
/*---------------------------------------------------------------------------
* PDO 数据缓冲
*
* 1.5.56 扩到 DPRAM 硬上限:输出 48×64bit=384B,输入 80×64bit=640B(非对称)。
* 1.6.1 输入瘦身 640→496(62×64bit):批量接收条目数据 64→32B + 诊断区前移,
* 依据与推导见 comm/ethercat_pdo_size.h 的 1.6.1 注。
* 1.6.15 输入 496→600(75×64bit):批量接收改定长覆盖窗口 5 槽(每槽带 u32
* seqlock,槽长 44B),诊断区随之后移到 584。推导见该头文件的 1.6.15 注。
* 长度的唯一真源是 comm/ethercat_pdo_size.h;本文件用 _Static_assert 与之绑定,
* 改一处漏一处会编译失败。为何硬上限是 out+in≤1024B 见该头文件的推导。
*---------------------------------------------------------------------------*/
/** 输出/输入 PDO 的 64bit 条目数。 */
#define PDO_OUT_ENTRIES 48u /* 48×8 = 384 B */
#define PDO_IN_ENTRIES 75u /* 75×8 = 600 B (1.6.15 覆盖窗口,原 62×8=496) */
#define PDO_IN_ENTRIES 75u /* 75×8 = 600 B (覆盖窗口) */
/** 输出数据对象 0x7000 (主站→从站, RxPDO) */
typedef struct OBJ_STRUCT_PACKED_START {
@@ -69,7 +65,7 @@ TOBJ6000;
static TOBJ7000 s_outputObj = {PDO_OUT_ENTRIES, {0}};
static TOBJ6000 s_inputObj = {PDO_IN_ENTRIES, {0}};
/* 布局守卫(1.5.42):SSC 的 SDO 单条目访问按"si0 占 16bit,其后按 BitLength 紧排"
/* 布局守卫:SSC 的 SDO 单条目访问按"si0 占 16bit,其后按 BitLength 紧排"
* 计算偏移(OBJ_GetEntryOffset),故 UINT64 数组必须紧跟在 UINT16 之后落在字节 2。
* OBJ_STRUCT_PACKED_START 曾被留空导致其落在字节 8、SDO 读写错位,加断言防回归。 */
_Static_assert(offsetof(TOBJ7000, u64Data) == 2, "TOBJ7000 未紧排:检查 OBJ_STRUCT_PACKED_START");
@@ -98,7 +94,7 @@ typedef struct OBJ_STRUCT_PACKED_START {
UINT32 u32Sync1; /* sub2 */
UINT32 u32EscIrq; /* sub3 */
UINT32 u32UptimeS; /* sub4 */
/* 1.6.0 剖分埋点(sub5..12,DWT 周期/计数累加,主站 1s 差分;定位完即移除) */
/* 剖分埋点(sub5..12,DWT 周期/计数累加,主站 1s 差分) */
UINT32 u32PdiCyc; /* sub5: PDI_Isr 全函数累计 */
UINT32 u32OutCyc; /* sub6: PDO_OutputMapping(输出探针/全量读) */
UINT32 u32FreeRunCyc; /* sub7: MainLoop free-run 块(SM 同步下恒 0) */
@@ -118,14 +114,15 @@ typedef struct OBJ_STRUCT_PACKED_START {
UINT32 u32DsSeg1; /* sub18: 段1(0..31) 桥状态+短快照 变化字节合计 */
UINT32 u32DsSeg2; /* sub19: 段2(32..543) 电机整轮密排 */
UINT32 u32DsSeg3; /* sub20: 段3(544..583) 长帧快照 */
UINT32 u32DsSeg4; /* sub21: 已删(1.6.21 恒 0) */
UINT32 u32DsSeg4; /* sub21: 已删(恒 0) */
UINT32 u32DsSeg5; /* sub22: 段5(584..599) 诊断区+保留 */
UINT32 u32DsCompareCyc; /* sub23: 比对自身累计 DWT 周期(u32 粒度,不含快照拷贝) */
UINT32 u32OpFreshRescues; /* sub24: OP 输入新鲜度救援次数(主站网页看,不打串口) */
} OBJ_STRUCT_PACKED_END
TOBJ2000;
static TOBJ2000 s_syncDiag = {23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static TOBJ2000 s_syncDiag = {24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/* 同上:UINT32 首条目必须落在字节 2(未紧排时为 4)。 */
_Static_assert(offsetof(TOBJ2000, u32Sync0) == 2, "TOBJ2000 未紧排:检查 OBJ_STRUCT_PACKED_START");
@@ -135,7 +132,7 @@ extern UINT32 EcatDbg_GetSync0(void);
extern UINT32 EcatDbg_GetSync1(void);
extern UINT32 EcatDbg_GetEscIrq(void);
extern UINT32 EcatDbg_GetUptimeSec(void);
/* 1.6.0 剖分埋点:ecatappl.c 累加器 + ethercat_sync.cpp 的 DWT 读 */
/* 剖分埋点:ecatappl.c 累加器 + ethercat_sync.cpp 的 DWT 读 */
extern UINT32 EcatDbg_Dwt(void);
extern volatile UINT32 g_dbg_pdiCyc;
extern volatile UINT32 g_dbg_outCyc;
@@ -149,7 +146,7 @@ extern volatile UINT32 g_dbg_appCyc;
/* [DIFF-STATS] 差分写统计窗口值(comm/ethercat_diff_stats.cpp),out[0..10] 对应 sub13..23 */
void EcatDiffStats_GetWindow(UINT32 *out);
/* 1.6.33:OP 输入新鲜度 — APPL_InputMapping 每次成功刷新就自增 */
/* OP 输入新鲜度 — APPL_InputMapping 每次成功刷新就自增 */
static volatile UINT32 s_inMapSeq = 0U;
static UINT32 s_inMapSeqSeen = 0U;
static UINT32 s_inStaleBaseCyc = 0U;
@@ -159,11 +156,11 @@ static UINT32 s_opFreshRescues = 0U;
* 不能用 UINT32 重复声明。 */
extern uint32_t SystemCoreClock;
/* 过程数据收发模块 (comm/ethercat_pdo.cpp 的 extern "C" shim, 1.5.54)。
* 1.5.53 时这里直接调 EcatCan_* 转发桥;1.5.54 把"一个 EtherCAT 周期怎么收发"
* 收拢进 EthercatPdo 类,本文件退回纯 SSC 移植层:只留对象字典 + 三行转发。 */
/* 过程数据收发模块 (comm/ethercat_pdo.cpp 的 extern "C" shim)。
* 本文件为纯 SSC 移植层:只留对象字典 + 三行转发。 */
extern void EcatPdo_ReadOutputs(const UINT8 *esc);
extern void EcatPdo_Process(void);
extern uint32_t EcatPdo_ProcessSeq(void);
extern void EcatPdo_WriteInputs(UINT8 *esc);
/* 0x2502 用:进 BOOT 的异步请求(sys/ota_boot_request.cpp 的 extern "C" shim)。
@@ -216,7 +213,7 @@ static TOBJ_RXPDO_MAPPING sRxPDOMap = {PDO_OUT_ENTRIES, {
PDO_MAP_8(0x7000, 24), PDO_MAP_8(0x7000, 32), PDO_MAP_8(0x7000, 40)
}};
/* 0x1A00: 映射 0x6000:01..75, 每个 64 bits(1.6.15 覆盖窗口 62→75:9×8 + 3) */
/* 0x1A00: 映射 0x6000:01..75, 每个 64 bits(9×8 + 3) */
static TOBJ_TXPDO_MAPPING sTxPDOMap = {PDO_IN_ENTRIES, {
PDO_MAP_8(0x6000, 0), PDO_MAP_8(0x6000, 8), PDO_MAP_8(0x6000, 16),
PDO_MAP_8(0x6000, 24), PDO_MAP_8(0x6000, 32), PDO_MAP_8(0x6000, 40),
@@ -231,16 +228,11 @@ _Static_assert(sizeof(sTxPDOMap.aEntries) / 4 == PDO_IN_ENTRIES, "0x1A00 条目
/*---------------------------------------------------------------------------
* Entry Descriptions
*
* 重要(1.5.42 修正): OBJ_GetEntryDesc()(objdef.c:554) 只对 OBJCODE_ARR 复用
* OBJ_GetEntryDesc()(objdef.c:554) 只对 OBJCODE_ARR 复用
* pEntryDesc[1] 描述所有子索引; 对 OBJCODE_REC 是逐下标索引 pEntryDesc[Subindex]。
* 因此 REC 对象的描述数组必须给满(Sub0..SubN 共 N+1 项), 否则 SDO Info 越界读到
* 相邻常量数组的垃圾, 主站 config_map 报 "Unsupported access to an object" /
* "Attempt to read to a write only object"(实测 0x1600/0x1A00 的 sub0/3/4 报错
* 而 sub1/2 侥幸通过, 正是越界特征)。
*
* 1.5.55: 输入 PDO 扩到 48 条目后, 继续用 REC 就得写 49 项描述符(且 0x1600/0x1A00
* 与 0x7000/0x6000 条目数不同, 不能再共用一个数组)。这些条目类型完全一致, 正是
* ARR 的语义, 故统一改为 OBJCODE_ARR —— 描述数组固定 2 项, 由 SSC 复用 [1]。
* 相邻常量数组的垃圾。
* 条目类型完全一致时用 OBJCODE_ARR —— 描述数组固定 2 项, 由 SSC 复用 [1]。
* 注意: 可被 PDO 映射的数据子条目必须带 OBJACCESS_RxPDOMAPPING/TxPDOMAPPING,
* 否则主站校验 0x1600/0x1A00 的映射目标时会拒绝(官方 sampleappl.h 同此)。
*---------------------------------------------------------------------------*/
@@ -270,13 +262,13 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asInputEntryDesc[] = {
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING}
};
/* SYNC 诊断 0x2000: REC, sub0..sub23 共 24 项, 纯只读不参与 PDO 映射,
* 改子索引数无需重刷 SII/EEPROM。sub5..12 为 1.6.0 剖分埋点(DWT 周期/计数
* 累加,定位负载构成后移除);sub13..23 为 [DIFF-STATS] 差分写取证仪表。
/* SYNC 诊断 0x2000: REC, sub0..sub24 共 25 项, 纯只读不参与 PDO 映射,
* 改子索引数无需重刷 SII/EEPROM。sub5..12 为剖分埋点(DWT 周期/计数累加);sub13..23 为 [DIFF-STATS] 差分写取证仪表;
* sub24 = OP 输入新鲜度救援次数(不打串口,网页 /api/sync_diag 读)。
* 行数必须与 TOBJ2000 字段一一对应:少一行不会编译报错,只会让 SDO 从错误的
* 偏移取值 —— 故逐行标号便于核对。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* sub0 : 子索引个数 = 23 */
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* sub0 : 子索引个数 = 24 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub1 : SYNC0 计数 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub2 : SYNC1 计数 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub3 : ESC IRQ 计数 */
@@ -300,6 +292,7 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub21 : [DIFF-STATS] 段4 变化字节 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub22 : [DIFF-STATS] 段5 变化字节 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub23 : [DIFF-STATS] 比对自身 DWT 周期 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* sub24 : OP 输入新鲜度救援次数 */
};
/*---------------------------------------------------------------------------
@@ -394,8 +387,8 @@ TOBJECT OBJMEM ApplicationObjDicList[] = {
{NULL, NULL, 0x6000, {DEFTYPE_UNSIGNED64, PDO_IN_ENTRIES | (OBJCODE_ARR << 8)},
asInputEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路; sub5..8 为 1.6.0 剖分埋点) */
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 23 | (OBJCODE_REC << 8)},
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 24 | (OBJCODE_REC << 8)},
asSyncDiagEntryDesc, aName0x2000, &s_syncDiag, NULL, NULL, 0x0000},
/* 0x2502: 引导程序交接请求(写 1 = 回 BOOT 等灌写;见 ObjBootReqWrite 注) */
@@ -413,7 +406,7 @@ TOBJECT OBJMEM * VARMEM ApplicationObjDic = ApplicationObjDicList;
*---------------------------------------------------------------------------*/
UINT16 APPL_GenerateMapping(UINT16 *pInputSize, UINT16 *pOutputSize)
{
*pInputSize = ECAT_PDO_IN_BYTES; /* 75×UINT64 = 600 bytes (1.6.15) */
*pInputSize = ECAT_PDO_IN_BYTES; /* 75×UINT64 = 600 bytes */
*pOutputSize = ECAT_PDO_OUT_BYTES; /* 48×UINT64 = 384 bytes */
return 0;
}
@@ -429,12 +422,12 @@ UINT16 APPL_StopMailboxHandler(void) { return 0; }
*---------------------------------------------------------------------------*/
UINT16 APPL_StartInputHandler(UINT16 *pIntMask)
{
/* 1.5.44:此处**不能**覆写 *pIntMask。
/* 此处**不能**覆写 *pIntMask。
*
* pIntMask 指向 SSC 全局 u16ALEventMask,StartInputHandler() 已按同步模式算好:
* free-run + 有输出 PDO 时它是 PROCESS_OUTPUT_EVENT(0x0400)(ecatslv.c:959/1090),
* 即 SM2 收到输出这一事件才是 SM-Synchron 模式的同步节拍。
* 旧代码写死 0x0800(PROCESS_INPUT_EVENT) 把 0x0400 顶掉了,而随后的
* 若写死 0x0800(PROCESS_INPUT_EVENT) 会把 0x0400 顶掉,而随后的
* SetALEventMask() 只做“或”,于是寄存器 0x204 永远缺 SM2 位:
* 输出到达不再产生 IRQ,PDI_Isr 只能被主站“读输入”事件带起来,
* 输入刷新因此永远落在主站读之后 —— SM3 是 3-buffer 缓冲模式,
@@ -482,7 +475,7 @@ void APPL_InputMapping(UINT16 *pInputData)
s_syncDiag.u32Sync1 = EcatDbg_GetSync1();
s_syncDiag.u32EscIrq = EcatDbg_GetEscIrq();
s_syncDiag.u32UptimeS = EcatDbg_GetUptimeSec();
/* 1.6.0 剖分埋点快照(sub5..12) */
/* 剖分埋点快照(sub5..12) */
s_syncDiag.u32PdiCyc = g_dbg_pdiCyc;
s_syncDiag.u32OutCyc = g_dbg_outCyc;
s_syncDiag.u32FreeRunCyc = g_dbg_freerunCyc;
@@ -491,6 +484,7 @@ void APPL_InputMapping(UINT16 *pInputData)
s_syncDiag.u32InWriteCyc = g_dbg_inWriteCyc;
s_syncDiag.u32AlCyc = g_dbg_alCyc;
s_syncDiag.u32AppCyc = g_dbg_appCyc;
s_syncDiag.u32OpFreshRescues = s_opFreshRescues;
/* [DIFF-STATS] 差分写统计快照(sub13..23):一次取全 11 个窗口值,
* 取的都是"上一个已完成的 1s 窗口",故 SDO 读不会拿到半累积值。 */
@@ -510,12 +504,12 @@ void APPL_InputMapping(UINT16 *pInputData)
s_syncDiag.u32DsCompareCyc = ds[10];
}
/* 1.5.54:先让 EthercatPdo 把本周期的输入数据写进 0x6000 的对象存储,
/* 先让 EthercatPdo 把本周期的输入数据写进 0x6000 的对象存储,
* 再交给 ESC。经由对象存储中转(而非直接写 pInputData)是为了让主站
* SDO 读 0x6000 时看到与过程数据一致的内容。 */
EcatPdo_WriteInputs((UINT8 *)&s_inputObj.u64Data[0]);
memcpy(pInputData, &s_inputObj.u64Data[0], ECAT_PDO_IN_BYTES);
/* 1.6.33:OP 输入新鲜度不变量的活性计数。每次真正走到输入映射就自增,
/* OP 输入新鲜度不变量的活性计数。每次真正走到输入映射就自增,
* 供 EcatDbg_OpInputFreshness 判断 PDI 路径是否还活着。 */
++s_inMapSeq;
}
@@ -534,11 +528,9 @@ void APPL_OutputMapping(UINT16 *pOutputData)
*---------------------------------------------------------------------------*/
void APPL_Application(void)
{
/* 1.5.54:应用逻辑全部搬进 EthercatPdo::process()(当前实现是 PDO↔CAN 转发)。
*
* 原 Phase 2 的 32B 回环已被取代:回环的验证价值由输入 PDO 的 seq 回显
* (ECAT_CAN_RX_OFF_ACK)承担——主站写 seq=N 后应在下一周期读回 ack=N,
* 同样能证明 输出→应用→输入 整条过程数据链路通。 */
/* 应用逻辑在 EthercatPdo::process()(当前实现是 PDO↔CAN 转发)。
* 不 CpuLoad 包裹:SM/DC 下本函数在 PDI/Sync0 ISR 内已被 isrEnter/Exit
* 计入;free-run 下若主循环每圈调用,再包会抬高 load_perm。 */
EcatPdo_Process();
}
@@ -547,11 +539,9 @@ void APPL_Application(void)
* [19:16]=AL 状态(0x0130 低 4 位) [31:24]=AL 错误码(0x0130 高 8 位)
* [15:0] =AL 事件寄存器(0x0220, bit0=主站 AL 控制请求待处理)
*
* 1.5.59 字节序修正:ESC 寄存器经 PDI/CSR 读出即主机序(LAN9252 CSR 引擎按
* ESC 寄存器经 PDI/CSR 读出即主机序(LAN9252 CSR 引擎按
* 16-bit 主机总线给出寄存器"值";线上大端只存在于网络侧与 PRAM 原始帧数据),
* 与 SSC 自身 GetSyncMan/mailbox 的裸读约定一致。1.5.48~1.5.58 这里误加了
* SWAPWORD,心跳打印的 al/evt 全是字节交换后的错值——那段时期的串口日志
* (如 evt=0x0400)需按字节交换重新解读后才能当证据用。
* 与 SSC 自身 GetSyncMan/mailbox 的裸读约定一致,勿加 SWAPWORD。
*---------------------------------------------------------------------------*/
UINT32 EcatDbg_ReadAlDiag(void)
{
@@ -561,41 +551,29 @@ UINT32 EcatDbg_ReadAlDiag(void)
}
/*---------------------------------------------------------------------------
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈 — 1.5.48 新增, 1.5.49/1.5.60 扩展
* AL Event Mask (0x204) 守护 + SM2 事件楔死自愈
* (C 链接, task_runner 250ms 节流调用)
*
* 背景:实测"停止回 INIT 后重进 OP"概率性失败——主站 LRW WKC=3/3(输出
* 确实写进 SM2),但从站 9s ESM 超时报 0x001B:bEcatFirstOutputsReceived 一直
* 没被置位。该标志只在 PDI_Isr 收到 SM2 事件(AL Event 0x220 bit10)时置位。
* 01:03 串口日志钉死现场:evt=0x0400 在 0x220 挂起 10s 无人处理(PDI_Isr 的
* SM2 分支从未执行),escirq 2.6/s 证明 EXTI 只在 AL Control 事件时跳动——
* 即 PDI_Isr 在跑但 SM2 分支被某个内部旗标挡住(bEscIntEnabled 门控)。
*
* 守护(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),先补期望再比对。
* 先补期望值再比对——下行转换路径(ecatslv.c AL_ControlInd 尾部)同时清
* 0x204 的 PDO 事件位、bEscIntEnabled、bEcatFirstOutputsReceived;若期望值
* 本身缺 SM2 位,旧判定恒通过、缺位永远补不回。SM-Synchron+有输出 PDO 的
* 不变式是 u16ALEventMask ⊇ PROCESS_OUTPUT_EVENT(ecatslv.c:959/1086)。
*
* 自愈(2)SM2 事件楔死(1.5.49):SAFEOP 下 0x220 bit10 挂起 且 标志未置位
* 自愈(2)SM2 事件楔死:SAFEOP 下 0x220 bit10 挂起 且 标志未置位
* = PDI_Isr 通路楔死。从主循环直接完成 PDI_Isr 该做的事:置位
* bEcatFirstOutputsReceived + 读 SM2 首/末字应答事件(ecatappl.c:317-320 同款)。
* 协议等价:bit10 挂起 = 主站已写过 SM2 = "收到输出"成立;AL_ControlRes 下一个
* MainLoop 周期即完成 SAFEOP→OP。WARN 附带全部内部旗标(bEscInt/bDcSync/
* 期望与实际 mask/看门狗状态),下次复现一行日志即可定位楔死路径。
* 协议等价:bit10 挂起 = 主站已写过 SM2 = "收到输出"成立。
* WARN 附带全部内部旗标(bEscInt/bDcSync/期望与实际 mask/看门狗状态),
* 下次复现一行日志即可定位楔死路径。
*
* 自愈(3)OP 态 PDI_Isr 门控(1.5.60):AL==OP 且 bEscIntEnabled==FALSE 不可
* 自愈(3)OP 态 PDI_Isr 门控: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 阈值)从此摸不到触发条件。
* 此时 PDI_Isr 整体被门控(ecatappl.c:282),PDO_OutputMapping 停调、输入冻结。
* 修法:重开门 + 应答挂起的 SM2 事件(掩码补位由守护(1)在同次调用完成),
* 下一帧 LRW 的 SM2 中断即恢复整条过程数据链路。
*---------------------------------------------------------------------------*/
UINT32 EcatDbg_GuardAleventMask(void)
{
@@ -613,23 +591,11 @@ UINT32 EcatDbg_GuardAleventMask(void)
return 0xFFFFU; /* 状态不适用:返回哨兵值,调用方不打印 */
}
/* (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。
* 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,下面的失配判定恒短路,守护自上线起从未真正
* 执行过("自愈"名存实亡,楔死实际靠重枚举重读碰运气恢复)。 */
/* (0) 邮箱 SM 地址/长度失配兜底:
* 邮箱几何构建期常量化(mailbox.c 直达 ecat_def.h:MBX_SM0/1_START/SIZE),
* CSR 错值无进入缓存的路径;本守护为运行期内存踩踏的最后兜底:
* AL>=PREOP 时用寄存器真值校验 SSC 缓存的 start/len,失配即写回 + WARN。
* ESC 寄存器经 PDI/CSR 读出即主机序,勿加 SWAPWORD。 */
if (al >= 2U && bMbxRunning) {
UINT16 sm0Start = 0U, sm0Len = 0U, sm1Start = 0U, sm1Len = 0U;
HW_EscReadWord(sm0Start, ESC_SYNCMAN_REG_OFFSET);
@@ -656,13 +622,11 @@ UINT32 EcatDbg_GuardAleventMask(void)
return 0xFFFFU; /* 以下仅 SAFEOP+ 适用 */
}
/* 1.5.59:PDI 寄存器读即主机序,不再 SWAPWORD——1.5.48~1.5.58 交换出的
* evt/actual 是错值,0x204 缺位判定恒真(每 250ms 盲写一次 mask,值凑巧
* 正确故未暴露),SM2 事件判定则对在错位上。详见上方字节序修复说明。 */
/* PDI 寄存器读即主机序,勿加 SWAPWORD。 */
evt = HW_GetALEventRegister();
HW_EscReadWord(actual, ESC_AL_EVENTMASK_OFFSET);
/* (1) 0x204 缺位自愈(1.5.60 加固:先补期望值,再比对补实际值) */
/* (1) 0x204 缺位自愈:先补期望值,再比对补实际值 */
if ((u16ALEventMask & PROCESS_OUTPUT_EVENT) == 0U) {
u16ALEventMask |= PROCESS_OUTPUT_EVENT;
printf("[ECAT][AEM] WARN: expected AL mask lacked SM2 bit -> 0x%04X\r\n",
@@ -692,11 +656,11 @@ UINT32 EcatDbg_GuardAleventMask(void)
(unsigned)u16ALEventMask, (unsigned)actual, (unsigned)wdState);
}
/* (3) OP 态 PDI_Isr 门控自愈(1.5.60 新增):OP 下 bEscIntEnabled==FALSE
/* (3) OP 态 PDI_Isr 门控自愈:OP 下 bEscIntEnabled==FALSE
* 不可合法出现——门控只在下行转换尾部被清(ecatslv.c:1273),而下行必改
* AL 状态;守护与 ESM 同在主循环上下文串行,看到"OP 且门关着"即 SAFEOP
* 重入路径漏了恢复段。此时 PDI_Isr 整体被门控(ecatappl.c:282),
* PDO_OutputMapping 停调、输入冻结,只能靠主站看门狗重拉总线。
* PDO_OutputMapping 停调、输入冻结。
* 修法:重开门 + 应答挂起的 SM2 事件(ISR 非 OP 分支同款读首/末字);
* 0x204 掩码补位已由上面(1)在同次调用完成,下一帧 LRW 即恢复。 */
if (al == 8U /* OP */ && nPdOutputSize > 0U && bEscIntEnabled == FALSE) {
@@ -715,18 +679,16 @@ UINT32 EcatDbg_GuardAleventMask(void)
}
/*---------------------------------------------------------------------------
* OP 输入新鲜度不变量 — 1.6.33
* OP 输入新鲜度不变量
*
* 结构洞(用户实锤:1ms 也复现,复位 GD32 APP 才好):
* SM-Synchron 稳态下 MainLoop 的 free-run 分支被
* (!bEscIntEnabled || !bEcatFirstOutputsReceived) && !bDcSyncActive
* 整段跳过,输入 PDO **只**由 PDI_Isr 的 PDO_InputMapping 刷新。
* PDI 通路一停(门控/掩码/EXTI),ESC 仍答 WKC、AL 仍 OP,主站读到的是
* 冻住的旧缓冲 —— 网页绿灯+高 Hz、CPU/CAN 全像没解析。既有 1.5.60 只救
* "门关着",救不了"门开着但 EXTI 不来"。
* 冻住的旧缓冲。
*
* 根治:把「OP ⇒ 输入必须在 T ms 内被重写」做成不变量。
* - 正常路径:PDI_Isr 每拍自增 s_inMapSeq
* 不变量:「OP ⇒ 输入必须在 T ms 内被重写」。
* - 正常路径:Process 每拍自增 seq
* - 异常路径:seq 停涨超过阈值 → 主循环强制 PDO_InputMapping
* (屏中断防双写)+ 重开门/应答 SM2,主站侧 gseq/rx 立刻恢复前进
* 不依赖主站重拉、不依赖人工复位。MainLoop 若本身死锁则本函数也停 ——
@@ -736,7 +698,9 @@ void EcatDbg_OpInputFreshness(void)
{
const UINT8 al = (UINT8)(nAlStatus & 0x0FU);
const UINT32 now = EcatDbg_Dwt();
const UINT32 seq = s_inMapSeq;
/* 活性看 processSeq(Process 真填过输入),不能看 s_inMapSeq。
* InputMapping 可在 Process 停摆时继续拷陈旧 s_in,seq 照涨 → 救援永不触发。 */
const UINT32 seq = EcatPdo_ProcessSeq();
if (al != 8U || nPdInputSize == 0U) {
s_inMapSeqSeen = seq;
@@ -759,7 +723,9 @@ void EcatDbg_OpInputFreshness(void)
}
/* 强制刷新:先重开门/首输出标志(门关着时 PDI 永远不会回来),
* 再在 EXMC 互斥窗口(仅屏蔽 EXTI0/1/2)内做一次输入映射,保证不关全局中断(Preempt 0 CAN RX 不受影响)。 */
* 再在 EXMC 互斥窗口(仅屏蔽 EXTI0/1/2)内做一次输入映射,保证不关全局中断(Preempt 0 CAN RX 不受影响)。
* 必须先 EcatPdo_Process() 再 PDO_InputMapping()。
* Process 先填好 buildInput+fillDiagTail,InputMapping 再发布,救援才有内容可写。 */
{
UINT16 dummy = 0U;
const UINT16 evt = HW_GetALEventRegister();
@@ -771,19 +737,18 @@ void EcatDbg_OpInputFreshness(void)
HW_EscReadWord(dummy, nEscAddrOutputData);
HW_EscReadWord(dummy, (UINT16)(nEscAddrOutputData + nPdOutputSize - 2U));
}
/* 救援路径计入 busy(低频;与 ISR/任务派发同一口径)。
* 不要把 MainLoop 整段包进 CpuLoad——见 task_runner / cpu_load.h。 */
CpuLoad_Enter();
const uint32_t prevMask = EscExmcBus_MaskEnter();
EcatPdo_Process();
PDO_InputMapping();
EscExmcBus_MaskExit(prevMask);
CpuLoad_Leave();
++s_opFreshRescues;
s_inMapSeqSeen = s_inMapSeq;
s_inMapSeqSeen = EcatPdo_ProcessSeq();
s_inStaleBaseCyc = EcatDbg_Dwt();
printf("[ECAT][FRESH] WARN: OP input frozen (seq=%lu) evt=0x%04X "
"(gate=%u first=%u dc=%u) -> forced PDO_InputMapping #%lu\r\n",
(unsigned long)seq, (unsigned)evt,
(unsigned)(bEscIntEnabled ? 1U : 0U),
(unsigned)(bEcatFirstOutputsReceived ? 1U : 0U),
(unsigned)(bDcSyncActive ? 1U : 0U),
(unsigned long)s_opFreshRescues);
/* 不打串口:计数经 0x2000:24 / /api/sync_diag 上到网页,避免 5ms 刷屏。 */
}
}
+3 -5
View File
@@ -3,8 +3,6 @@
* @brief DCU EtherCAT 应用层接口声明
*
* 提供 SSC 所需的 APPL_ 回调函数声明。
* Phase 1: 桩实现,仅保证编译通过。
* Phase 3: 接入实际 PDO 映射(DCU 传感器输入 / 控制输出)。
*/
#ifndef COMM_SSC_PORT_DC_APP_H
#define COMM_SSC_PORT_DC_APP_H
@@ -33,17 +31,17 @@ void APPL_InputMapping(UINT16 *pInputData);
void APPL_OutputMapping(UINT16 *pOutputData);
void APPL_Application(void);
/* 1.6.33:OP 输入新鲜度不变量(task_runner 每圈调用;内部廉价,仅冻结时强制重写) */
/* OP 输入新鲜度不变量(task_runner 每圈调用;内部廉价,仅冻结时强制重写) */
void EcatDbg_OpInputFreshness(void);
UINT32 EcatDbg_OpFreshRescues(void);
/* 1.6.34: HBI/EXMC 事务互斥(仅屏蔽 EXTI0/1/2,不关全局中断) */
/* HBI/EXMC 事务互斥(仅屏蔽 EXTI0/1/2,不关全局中断) */
uint32_t EscExmcBus_MaskEnter(void);
void EscExmcBus_MaskExit(uint32_t prev);
uint32_t HW_EscExmcMaskEnter(void);
void HW_EscExmcMaskExit(uint32_t prev);
/* 1.6.34: 复位输出 sequence 状态机 */
/* 复位输出 sequence 状态机 */
void EcatCan_ResetSequenceState(void);
#ifdef __cplusplus
+17 -29
View File
@@ -144,22 +144,17 @@
#endif
/** 看门狗由定时器中断检查。
* 1.5.47:1→0。ECAT_TIMER_INT=1 要求端口提供调用 ECAT_CheckTimer() 的 1ms 硬件
* 定时器 ISR——本工程从未实现,导致 ECAT_CheckTimer 在整个固件里零调用者:
* (a) EsmTimeoutCounter 永不递减 → 任何 pending 状态迁移(SAFEOP→OP 等)永不
* 超时,从站会无限期忽略一切 AL Control(实测"进 PREOP 失败: AL=SAFEOP
* code=0x0000"的卡死就是它);
* (b) DC_CheckWatchdog/PLL 计数器(i16WaitForPllRunningCnt)不前进 → 若 ESC
* 残留 DC 激活位(0x0981,上个 DC 会话遗留、跨重连不清),从站按 DC 同步
* 模式进 SAFEOP→OP,OP 等待 PLL 序列永远不满足 → SAFEOP+0x0000 永久挂起
* (00:27 复测日志: 等待期轨迹恒 SAFEOP+0x0000,LRW 每 7ms 也救不回)。
* 改 0 后 MainLoop 按 DWT CYCCNT 轮询(hw_gd32exmc.cpp 的 HW_GetTimerValue/
* HW_ClearTimerValue 本就是为此写的死代码),每 1ms 调一次 ECAT_CheckTimer。 */
* ECAT_TIMER_INT=0:MainLoop 按 DWT CYCCNT 轮询(hw_gd32exmc.cpp 的
* HW_GetTimerValue/HW_ClearTimerValue),每 1ms 调一次 ECAT_CheckTimer。
* 若置 1 则要求端口提供调用 ECAT_CheckTimer() 的 1ms 硬件定时器 ISR——
* 本工程无该 ISR,ECAT_CheckTimer 零调用者会导致 EsmTimeoutCounter 永不递减
* (状态迁移永不超时)及 DC PLL 计数器不前进(残留 DC 激活位时 SAFEOP+0x0000
* 永久挂起)。 */
#ifndef ECAT_TIMER_INT
#define ECAT_TIMER_INT 0
#endif
/** 1.5.47:DWT CYCCNT @180MHz 的每毫秒 tick 数(ECAT_TIMER_INT=0 时 MainLoop 用)。 */
/** DWT CYCCNT @180MHz 的每毫秒 tick 数(ECAT_TIMER_INT=0 时 MainLoop 用)。 */
#ifndef ECAT_TIMER_INC_P_MS
#define ECAT_TIMER_INC_P_MS 180000U
#endif
@@ -535,13 +530,10 @@
------
------ Struct Packing
------
------ 1.5.42 修正:此前三对宏全部留空,注释误以为"AC6 不需要"。实际这些宏就是 SSC
------ 用来紧排结构体的唯一手段——SSC 的 SDO 读写按"si0 占 16bit,其后各条目按
------ BitLength 紧排"计算字节偏移(OBJ_GetEntryOffset, objdef.c:609),留空则结构体
------ 按编译器自然对齐,与该假设不符。armclang 实测:{UINT16; UINT64[4]} 的 UINT64
------ 落在字节 8 而非 2(编译期断言验证),{UINT16; UINT32 x4} 落在字节 4 而非 2,
------ 于是 0x7000/0x6000/0x2000 的 SDO 单条目访问全部读写错位内存;SSC 内部
------ 0x1C3x(TSYNCMANPAR)/0x10F1 等同样受影响。
------ 这些宏是 SSC 紧排结构体的唯一手段——SSC 的 SDO 读写按"si0 占 16bit,
------ 其后各条目按 BitLength 紧排"计算字节偏移(OBJ_GetEntryOffset, objdef.c:609),
------ 留空则结构体按编译器自然对齐,与该假设不符,导致 SDO 单条目访问
------ 全部读写错位内存;SSC 内部 0x1C3x/0x10F1 等同样受影响。
------ AC6/armclang 支持 GCC 风格 __attribute__((packed)),置于 struct 关键字之后
------ (即 START 位置)对整个结构体生效。
-----------------------------------------------------------------------------------------*/
@@ -590,11 +582,8 @@
------
------ PDO Size (需与 DCU_ESI.xml / gen_eeprom.py / dc_app.c 三处一致)
------
------ 1.5.56 扩到 DPRAM 硬上限:64B/384B → 384B/640B。
------ 输出 384B = 48×64bit:兼容单帧区 48B + CANFD 批量发送区 336B
------ 输入 640B = 80×64bit:桥状态 32B + 电机窗口 288B + 长帧 40B + 批量接收区 280B
------ 上限依据:邮箱占 0x1000-0x13FF,过程数据区仅 0x1400-0x1FFF=3072B,
------ SM2/SM3 均 3-buffer 实占 3×size ⇒ out+in ≤ 1024B。384+640 恰好用满。
------ 上限依据:邮箱占 0x1000-0x13FF,过程数据区仅 0x1400-0x1FFF=3072B,
------ SM2/SM3 均 3-buffer 实占 3×size ⇒ out+in ≤ 1024B。
------ 推导与实测(SII 余量/EXMC 耗时)见 comm/ethercat_pdo_size.h。
------
------ 这两个宏是**编译期硬门限**,必须先于其它改动生效:
@@ -604,12 +593,12 @@
------ 忘改这里会在进 SAFEOP 时报 AL 错误,而不是静默截断。
-----------------------------------------------------------------------------------------*/
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。1.5.56: 0x40 → 0x180 = 384B */
/** 最大输出 PDO 字节数(RxPDO,主站→从站)。0x180 = 384B */
#ifndef MAX_PD_OUTPUT_SIZE
#define MAX_PD_OUTPUT_SIZE 0x180
#endif
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。1.5.56: 0x140 → 0x280 = 640B */
/** 最大输入 PDO 字节数(TxPDO,从站→主站)。0x280 = 640B */
#ifndef MAX_PD_INPUT_SIZE
#define MAX_PD_INPUT_SIZE 0x280
#endif
@@ -710,12 +699,11 @@
#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)。
* 不信任单次 CSR 间接读,CSR 读仅作交叉校验(mailbox.c 打 WARN)。
* ★ 改 SII 邮箱几何必须同步改这里(gen_eeprom.py 顶部注释同样有约束)。★ */
#ifndef MBX_SM0_START
#define MBX_SM0_START 0x1000
+11 -17
View File
@@ -4,8 +4,7 @@ Generate LAN9252 EEPROM binary image (ESI/SII) for GD32E508 DCU.
Outputs a C header file with the EEPROM content as a byte array.
Layout per ETG.1000.6 section 5.4 "SII coding" (Table 16/17, verified against the
local spec copy ETG1000_6_V1i0i3; 1.5.27 rewrite of the previous generator whose
layout was shifted by 2 words from word 4 onwards and used XOR instead of CRC-8):
local spec copy ETG1000_6_V1i0i3):
Word 0x00 PDI Control (byte0=PDI type 0x8D HBI 16-bit indexed,
byte1=ESC Configuration 0x00)
@@ -62,7 +61,6 @@ MBX_TX_SIZE = 256
# Process Data configuration
#
# 1.5.56 扩到 DPRAM 硬上限 64/384 -> 384/640(需要一周期合并多条 CANFD 帧)。
# 必须与 comm/ethercat_pdo_size.h 的 ECAT_PDO_OUT_BYTES / ECAT_PDO_IN_BYTES 一致
# ——固件侧有 _Static_assert 守着,但 EEPROM 这一侧无法编译期校验,只能靠这里对齐。
#
@@ -70,10 +68,8 @@ MBX_TX_SIZE = 256
# 邮箱 SM0/SM1 各 256B 固定占 0x1000-0x13FF,过程数据区只剩 0x1400-0x1FFF=3072B。
# SM2/SM3 都是 3-buffer 模式(control 0x64 / 0x20,bit1=0),实际占用 3×size:
# SM2 384B -> 0x1400-0x187F (384×3 = 1152B)
# SM3 600B -> 0x1880-0x1F67 (600×3 = 1800B) ← 1.6.15,DPRAM 留 120B 余量
# (384+600)×3 = 2952 ≤ 3072 = 过程数据区全长。历史上 1.5.56 曾取 640B
# ((384+640)×3 = 3072 恰好用满),1.6.1 瘦身到 496B 换 CPU,1.6.15 回到 600B
# 换批量区容量——推导与取舍见 comm/ethercat_pdo_size.h。
# SM3 600B -> 0x1880-0x1F67 (600×3 = 1800B), DPRAM 留 120B 余量
# (384+600)×3 = 2952 ≤ 3072 = 过程数据区全长。
# SSC 在 ecatslv.c:708-721 按 (start + size*buffer) 做重叠检查,撞了会在
# PREOP->SAFEOP 返回 ALSTATUSCODE_INVALIDSMOUTCFG,不是静默出错。
# 要再更大只能压邮箱(拖慢 SDO/FoE)或改 1-buffer(失去读写并发保护),见
@@ -81,14 +77,12 @@ MBX_TX_SIZE = 256
PD_OUT_OFFSET = 0x1400 # SM2: RxPDO (Master -> Slave outputs)
PD_OUT_SIZE = 384 # 48×64bit
PD_IN_OFFSET = 0x1880 # SM3: TxPDO (Slave -> Master inputs)
PD_IN_SIZE = 600 # 75×64bit (1.6.15 覆盖窗口;1.6.1 曾瘦身到 496=62×64bit,更早 640=80×64bit)
# 1.6.1 瘦身 640→496:批量接收条目数据 64→32B + 段5诊断区前移到 484。
# 1.6.15 496→600:批量接收改**定长覆盖窗口** 5 槽(每槽 12B 头含 u32 seqlock
# + 32B 数据 = 44B,共 220B,落在 364..583),段5诊断区 484→584。
# 动机:旧"环形队列+变长紧排"容量 3 帧/拍,现场 4000 帧/s 下每拍恒丢 1 帧,
# 且饱和态喂出了 1.6.9~1.6.13 追的错标/拼接竞态。5 槽 = 5000 帧/s@1ms。
PD_IN_SIZE = 600 # 75×64bit
# 批量接收为**定长覆盖窗口** 5 槽(每槽 12B 头含 u32 seqlock
# + 32B 数据 = 44B,共 220B,落在 364..583),段5诊断区 584。
# 5 槽 = 5000 帧/s@1ms。
# SM3 实占 3×600=1800B (0x1880-0x1F67),out+in=984 ≤ 1024 硬上限,留 120B 余量;
# 依据见 comm/ethercat_pdo_size.h 的 1.6.1 / 1.6.15 注。
# 依据见 comm/ethercat_pdo_size.h。
# 改后必须重烧 EEPROM 并断电冷启动。
# 3-buffer 占用与 DPRAM 上限自检:改尺寸/偏移时立刻在生成阶段拦住,
@@ -107,7 +101,7 @@ assert PD_OUT_SIZE % 8 == 0 and PD_IN_SIZE % 8 == 0, \
# 0x8D=HBI 16-bit). 0x05 is a reserved value on LAN9252 and disables the HBI PDI.
PDI_CONTROL = 0x8D
# Word 0 high byte = ESC Configuration register (0x0141): all zero
# (the old 0x0C hit "NOT set by EEPROM" DC bits - removed in 1.5.27).
# (0x0C would hit "NOT set by EEPROM" DC bits).
ESC_CONFIG = 0x00
# Word 1 low byte = PDI Configuration register (0x0150) HBI-mode bits: ALE
# qual / RD_WR mode / CS / RD / WR / ALE polarities. 0x00 = normal active-low
@@ -119,7 +113,7 @@ PDI_CONFIG = 0x00
# SYNC1 Output=1, SYNC0 Map=1, SYNC0 Output=1, both Driver/Polarity=00
# (push-pull ACTIVE LOW, DS p247). Active-low pulses pair with the firmware's
# falling-edge EXTI; with 0x00 the SYNC0/1 pins stay LATCH inputs and a DC
# SYNC0 event would never reach PB0 -> process data dead in DC mode (1.5.28).
# SYNC0 event would never reach PB0 -> process data dead in DC mode.
# Same value as the official LAN9252-EVB-HBIXML.xml ConfigData 890E80CC...
SYNC_LATCH_CFG = 0xCC
# Word 2 = Pulse Length of SyncSignals register (0x0982), units of 10ns
@@ -353,7 +347,7 @@ for row in range(0, EEPROM_SIZE, 16):
chunk = eeprom[row:row+16]
hex_str = ", ".join(f"0x{b:02X}" for b in chunk)
if all(b == 0xFF for b in chunk):
# 1.5.28: emit the real 0xFF bytes. A comment-only padding row leaves
# Emit the real 0xFF bytes. A comment-only padding row leaves
# those array elements zero-initialized (C static init) instead of
# the erased-0xFF state, and the firmware burns the whole array.
if row % 256 == 0:
@@ -3,13 +3,13 @@
* @brief SSC V5.11 硬件 port 层 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
*
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
* 转发到 EscLan9252 的间接访问端口(1.5.39 按 DS 12.11 修正访问模型):
* 转发到 EscLan9252 的间接访问端口:
* - EtherCAT 核心 CSR(0x0000-0x0FFF:AL Status/Event、SyncManager、LED 等)
* → ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300) 间接端口;
* - 过程 RAM(0x1000-0x1FFF:邮箱 0x1000+、PDO 0x1400+)
* → PRAM FIFO(0x000-0x03C 数据口 + 0x308-0x314 地址/命令)。
* 1.5.37 及之前直读/直写 HBI 索引(误以为索引空间统一可寻址),核心 CSR 全部
* 读 0/写丢弃——SSC 对主站 AL Control 完全失聪,主站恒读 AL=INIT。
* 核心 CSR 不在 HBI 直接空间,直读/直写会使核心 CSR 全部
* 读 0/写丢弃,SSC 对主站 AL Control 完全失聪。
*
* 并发保护(不变):
* - HW_EscRead/HW_EscWrite 等主循环函数:内部经 EscExmcBus::maskEnter/Exit
@@ -229,8 +229,7 @@ void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
/**
* @brief 禁用指定 SM 通道 (清 SM Activate 寄存器 0x0806+8n bit0)。
* @param channel SM 通道号 (0-15)
* @note 1.5.39:偏移由 +4 改为 +6——+4 是 SM Control(操作模式字段),
* Activate(bit0=SM Enable)在 +6;旧值从未真正生效过(访问层全空)。
* @note SM Activate 在偏移 +6(bit0=SM Enable);+4 是 SM Control(操作模式字段)。
*/
void HW_DisableSyncManChannel(UINT8 channel)
{