diff --git a/GD32E508_DCU_APP/comm/esc_lan9252.cpp b/GD32E508_DCU_APP/comm/esc_lan9252.cpp index f55c121..500781b 100644 --- a/GD32E508_DCU_APP/comm/esc_lan9252.cpp +++ b/GD32E508_DCU_APP/comm/esc_lan9252.cpp @@ -407,7 +407,7 @@ uint8_t EscLan9252::pmDrive(uint8_t op) bool EscLan9252::init() { - s_busReady = false; /* 1.5.27:s_busReady 语义 = init 完整成功(EXMC+探针+BYTE_TEST) */ + s_busReady = false; /* 1.6.40:s_busReady = EXMC+BYTE_TEST 往返通过(IDX 残值不挡) */ s_coreReady = false; /* 1.5.39:s_coreReady 语义 = EEPROM 加载完成、EtherCAT 核心使能 */ /* 使能 DWT 周期计数器,供 hw_gd32exmc/SSC 执行时间测量 */ @@ -426,36 +426,56 @@ bool EscLan9252::init() return false; } - /* HBI 存活探针(数据手册 9.5.4.2):先直接读 HBI_IDX_0(offset 0,不写任何东西), - * 其复位默认值 = 0x1234。RST# 释放后 ESC 需完成内部启动与 EEPROM 加载, - * 故按手册建议轮询(此前为单次读,复位后立即读可能仍在启动中)。 - * 若超时仍 != 0x1234(通常是 0x0000/0xFFFF 或上次写入的回显), - * 说明 LAN9252 完全没有驱动总线——CS 未拉低、CHIP_MODE strap 非 00(三端口=SPI only) - * 或芯片未出复位/未上电,而不是 EEPROM 问题(HBI 在 EEPROM 空白时也工作)。 */ - uint16_t idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET); - for (uint32_t waited = 0U; - idx0 != 0x1234U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS; - waited += CONFIG_ESC_HBI_POLL_STEP_MS) { - SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS); + /* 1.6.40: 放行判据换到能兜住的那侧——BYTE_TEST 完整往返(索引写+数据低/高 + * 字读)才证明 HBI 通路通;HBI_IDX_0 只证明「芯片刚出过复位」(复位默认值 + * 0x1234,DS 9.5.4.2),证明不了通路。warm jump(BOOT 刷完直跳)时 BOOT 的 + * bootEcatDeinit 最后一条 HBI 写把索引寄存器留在 0x0054(IRQ_CFG),而跳转前 + * 那发硬复位已被证明会把 warm jump 打死(BOOT 1.1.0.18 已短路)——于是这条 + * 「读复位默认值」的探针对 warm 路径必然假阴性;cold 路径芯片自带 POR 天然 + * 0x1234,与那发复位有没有落地无关——这正是「reset 能起、刷完起不来」的根因。 + * BYTE_TEST 每次访问前都会重写索引,与复位态无关。 + * 结构:BYTE_TEST 为唯一放行门;不过才补一发 RST# 再探一轮(此刻 SSC 尚未 + * MainInit,二次复位零代价;s_resetDone 只保护运行期重复 init,不挡 init 内 + * 重试)。两轮 BYTE_TEST 仍不通 = 总线物理不通,维持降级。 + * warm 残值(如 0x0054)跳过 0x1234 轮询,避免无意义 300ms 等待 + 二次复位。 */ + uint16_t idx0 = 0U; + uint32_t byteTest = 0U; + for (uint8_t attempt = 0U; attempt < 2U; ++attempt) { + if (attempt > 0U) { + printf("[ECAT][ESC] probe#%u fail (idx0=0x%04X byte_test=0x%08lX), 补发 RST# 重探\r\n", + (unsigned)attempt, (unsigned)idx0, (unsigned long)byteTest); + hardwareReset(); + } idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET); + /* 仅在「像复位中/未驱动」时轮询 0x1234;warm 残值(非 0/0xFFFF/0x1234)说明 + * 芯片活着且带着上次索引,直接测 BYTE_TEST,不空等。 */ + const bool idxLooksIdle = (idx0 == 0x0000U) || (idx0 == 0xFFFFU); + if (idxLooksIdle || idx0 == 0x1234U) { + for (uint32_t waited = 0U; + idx0 != 0x1234U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS; + waited += CONFIG_ESC_HBI_POLL_STEP_MS) { + SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS); + idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET); + } + } + byteTest = readReg32(LAN9252_REG_BYTE_TEST); + if (byteTest == LAN9252_BYTE_TEST_VALUE) { + break; + } } - if (idx0 != 0x1234U) { - printf("[ECAT][ESC] init: FAIL, HBI_IDX_0=0x%04X (expect 0x1234) -> LAN not driving bus\r\n", - (unsigned)idx0); + + if (byteTest != LAN9252_BYTE_TEST_VALUE) { + printf("[ECAT][ESC] init: FAIL, BYTE_TEST=0x%08lX (expect 0x87654321), idx0=0x%04X -> LAN not driving bus\r\n", + (unsigned long)byteTest, (unsigned)idx0); printf("[ECAT][ESC] hint: check CS(PD7) pulse, CHIP_MODE0/1 straps=00, RST#/power\r\n"); return false; } - - /* 再读 BYTE_TEST(0x064)确认接口完全就绪,默认 0x87654321(小端)。 - * 该值只有 HBI_DATA_0 偏移正确(EXMC 16-bit 地址左移 1 位)才可能读到。 */ - const uint32_t byteTest = readReg32(LAN9252_REG_BYTE_TEST); - if (byteTest != LAN9252_BYTE_TEST_VALUE) { - printf("[ECAT][ESC] init: FAIL, BYTE_TEST=0x%08lX (expect 0x87654321)\r\n", - (unsigned long)byteTest); - printf("[ECAT][ESC] hint: IDX probe passed but DATA access failed -> check HBI_DATA_0 offsets (0x40000/0x60000)\r\n"); - return false; + if (idx0 != 0x1234U) { + printf("[ECAT][ESC] init: WARN idx0=0x%04X != 0x1234, BYTE_TEST ok -> 复位态未知但通路可用,放行\r\n", + (unsigned)idx0); } - printf("[ECAT][ESC] init: OK, HBI alive (IDX=0x1234, BYTE_TEST=0x87654321)\r\n"); + printf("[ECAT][ESC] init: OK, HBI alive (idx0=0x%04X, BYTE_TEST=0x87654321)\r\n", + (unsigned)idx0); s_busReady = true; /* 等待 PDI 就绪、EtherCAT 核心可交互(1.5.37 新增,1.5.39 改经 CSR 间接端口读—— diff --git a/GD32E508_DCU_APP/comm/esc_lan9252.h b/GD32E508_DCU_APP/comm/esc_lan9252.h index 56fbf96..fcf6141 100644 --- a/GD32E508_DCU_APP/comm/esc_lan9252.h +++ b/GD32E508_DCU_APP/comm/esc_lan9252.h @@ -92,8 +92,10 @@ public: /** @brief 过程 RAM(0x1000-0x1FFF)经 PRAM 写 FIFO 写入 len 字节。 */ static void pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len); - /** @brief EXMC 总线 + HBI_IDX_0 探针 + BYTE_TEST 全部通过(init 成功)后为 true。 - * @note 与 init() 返回值一致,可作运行期查询。 */ + /** @brief EXMC 总线 + BYTE_TEST 往返探针通过(init 成功)后为 true。 + * @note 1.6.40 起 HBI_IDX_0 复位默认值降级为诊断读数,不再参与放行 + * (它只证明"芯片刚出过复位",证明不了 HBI 通路)。 + * 与 init() 返回值一致,可作运行期查询。 */ static bool isBusReady() { return s_busReady; } /** @brief EEPROM(SII) 加载完成、EtherCAT 核心使能(1.5.39)。 @@ -123,7 +125,7 @@ private: * SSC 以 0x001B 拖回 SAFEOP。 */ static void configPdWatchdog(); - static bool s_busReady; /**< init 完整成功(EXMC+IDX 探针+BYTE_TEST)后置位。 */ + static bool s_busReady; /**< init 成功(EXMC+BYTE_TEST 往返,1.6.40;IDX 残值不挡)后置位。 */ static bool s_coreReady; /**< EEPROM 加载完成、EtherCAT 核心使能后置位(1.5.39)。 */ static bool s_resetDone; /**< hardwareReset 已执行门控(防二次复位清掉 SSC 配置)。 */ static bool s_pmPhyDown; /**< pmDrive 进 D3 时两 PHY 已被置 PowerDown,唤醒后需恢复。 */ diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index fb9efe7..c067337 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -161,13 +161,17 @@ constexpr uint32_t APP_FW_VERSION_MINOR = 6U; * (2) CoE 0x100A 从 SSC 占位 "1.0" 改成真版本串(DEVICE_SW_VERSION_LEN 0x3→0x10、 * acSoftwareversion 显式 LEN+1 防越界读、initial.cpp 起板时写),主站刷完可以在 * 总线上自证"新 APP 起来了、是哪一版",不必再靠串口或 SPI PING 身份块。 */ - /* 1.6.39:按用户裁定,跳转/启动链回退到 git 已提交基线(对齐 BOOT 46adcb7 - * = 1.1.0.14)——startup_gd32e50x_cl.s / system_gd32e50x.c / initial.cpp+h / - * systick.cpp / hardfault.cpp 全部回到 HEAD:裸机诊断回到 115200、无 - * __enable_irq、无 BKP 面包屑、无 haltWithWatchdog 兜底、无 3M 化。 - * 业务改动全部保留(CAN/PDO/桥/睡眠/task_manager/0x100A 版本上报)。 - * 未提交的 1.6.35~38 实验备份在 G:/Robot_v2/DCU/_revert_backup_20260916/。 */ -constexpr uint32_t APP_FW_VERSION_PATCH = 39U; /* 1.6.33: OP 输入新鲜度不变量(EcatDbg_OpInputFreshness)——SM-Synchron 下输入 PDO 只靠 PDI_Isr,通路一停主站读到冻缓冲(WKC/AL 仍正常=假连接);现 OP 内输入停更>5ms 由主循环强制 PDO_InputMapping+重开门。根治"连接看起来正常但 CPU/CAN 不更新、复位 GD32 才好"。 */ /* 1.6.32: SPI CS EXTI 提到抢占级0(见 app_spi_command configureExti),根治 CS release 被 PDI/RT_CRITICAL 延迟导致的 RX 撕裂(0xE8) *//* /* 1.6.28:ESC 睡眠挂起门控(EscLan9252::pmSuspended)。pmDrive 进 D1-D3 置位后全固件 HBI 消费者静默——task_runner 停调 MainLoop/AL 守护/AL 心跳(MainLoop 每圈 HBI 读 0x220,睡眠中任何 HBI 访问含读都把芯片打回 D0,2026-09-12 实测休眠 ~20s 即被主站"复活":芯片毫秒级被 GD32 自己叫醒、SM 失效后主站 wedge 重建 OP);ethercat_sync 三 EXTI ISR 与 timer Sync0 路径清 pending 即返。解除:pmDrive 唤醒成功/op0 读到 D0 自愈/nrstDrive 释放复位/hardwareReset。配套主站侧 ecat_native /api/pm/hold 楔死看门狗豁免。 */ /* A/B 结束: 恢复 1.6.25 稀疏+差分写(用户裁定, 主站侧已有后台恢复兜底) */ /* A/B C组: SPARSE=0+DIFF=0, 回 1.6.21 恒全量 600B 写 ESC(查开机楔死) */ /* 1.6.25:P1 输入 PDO 差分写 ESC(三区:段1 桥状态/段2 电机密排/段3+5 快照诊断;变化后 3-buffer 冲刷协议,稳定段跳过;全跳过写末 2 字节完成 SM)。P2 csrRead 分级:verify 参数,SM/AL/邮箱默认双读,诊断/MII busy/init 轮询单读。开关 CONFIG_ECAT_DIFF_ESC_WRITE。 */ /* 1.6.24:pmDrive 进 D1/D2/D3 移除读回验证。板级实测:进入睡眠后任何 HBI 访问(含读,手册 6.3.4.2 只说写唤醒,芯片实际按主机 CS 活动判定)都会 <50ms 触发唤醒回 D0——读回验证自证失败,改为置信构造返回值(READY=0|SLEEP_EN=1|MODE=op);「读状态」语义改为"若在睡眠则主动打破睡眠"。 */ /* 1.6.23:LAN9252 芯片级电源管理诊断路径。EscLan9252::pmDrive()(0xA2 action 30~34:30 读状态/31 进 D1/32 进 D2/33 进 D3/34 手动唤醒),按 DS 6.3 写 PMT_CTRL(0x084) PM_MODE+PM_SLEEP_EN(分两次写);D3 前置两 PHY General Power Down(经 ESC 核心 MII 管理 0x0510-0x0514,PHY A 地址 0/2 运行期判定),唤醒=HBI 写 BYTE_TEST 后轮询 BYTE_TEST/READY(DWT CYCCNT 忙等,50µs 步进、上限 10ms——0xA2 在 CS EXTI ISR 上下文执行,禁用 SysTickTimer::delayMs 会死等;HBI 段 maskEnter 屏 EXTI0/1 防 SYNC0 突入)。唤醒后自动恢复 PHY 上电(自协商需数秒)。休眠期间 EtherCAT 通信中断属预期,主站 wkc 报错。 */ /* 1.6.22:P1 稀疏写 ESC。PDO_InputMapping 在密排布局下按 count 只写「段1+段2有效区(0..40+count*56)」与「段3+段5(544..599)」,跳过空槽区,省 HW_EscWriteIsr 的 PRAM 字节。开关 CONFIG_ECAT_SPARSE_ESC_WRITE=1;非密排/count 撑满自动回退整包。总长 600B/SII/主站解码不变。A/B:置 0 重编即 1.6.21 写侧。 */ /* 1.6.21:段 2 改「一帧=总线电机整轮状态」。用户裁定旧需求不对:不要 win_base 滑窗扫 27 槽,也不要段 4 历史帧覆盖窗反复重发。实测病理:接 3 个电机网页却看到 5 个状态帧——固定 5 槽里空槽 node_id=0 也占位,主站不滤就多报。本版:(1) 段 2 密排,count=本拍实际电机数(0..9),空节点不占位,槽序按 node_id 升序,同号多通道取 seq 最新;主站 `for i in 0..count` 按槽内 node_id 自动识别,无需滑窗。(2) 删段 4 覆盖窗口(fillRxWindow/RxWinSlot 整套),非 STATUS 仍走段 1/3 最近一帧。(3) 差分填充:节点集合与各 seq 未变时整段复用影子,跳过 MotorState 重读与逐槽 48B 拷贝。(4) 输入 PDO 总长仍 600B,SII 无需重刷;段 2 扩到 32..543、段 3 前移到 544、段 5 仍 584。**主站解码必须同步**(decode_input/transport-ecat.js:count 密排 + 段 3 偏移 320→544)。 */ + /* 1.6.40:HBI 起板探针放行判据从「HBI_IDX_0==0x1234」换成「BYTE_TEST 往返」, + * 根治 warm jump 后自检「降级 ethercat / EXMC 总线未就绪」+ 0xA2 全回 0xE1。 + * 实测链(2026-09-16):BOOT 1.1.0.18 短路跳转前 RST# 后,刷完 warm jump 必现 + * fault_codes[10]=1、module_mask 缺 bit10/11;reset(芯片不掉电)却正常。 + * 根因不在 BOOT 发没发复位:cold 路径芯片自带 POR、IDX_0 天然 0x1234,与 + * PB10 那发有没有落地无关;warm 路径 BOOT 的 bootEcatDeinit 最后一条 HBI 写 + * 把索引寄存器留在 0x0054(IRQ_CFG),「读复位默认值」的探针对 warm 必然假阴性。 + * 本版:BYTE_TEST(索引写+数据读完整往返)为唯一放行门;任一判据不过补一发 + * RST# 再探一轮(SSC 未起,零代价);两轮仍不通=总线物理不通,维持降级; + * BYTE_TEST 通但 IDX_0 异常=放行+诊断。s_busReady 语义同步改写(0xE1 门不变)。 */ +constexpr uint32_t APP_FW_VERSION_PATCH = 40U; /* 1.6.33: OP 输入新鲜度不变量(EcatDbg_OpInputFreshness)——SM-Synchron 下输入 PDO 只靠 PDI_Isr,通路一停主站读到冻缓冲(WKC/AL 仍正常=假连接);现 OP 内输入停更>5ms 由主循环强制 PDO_InputMapping+重开门。根治"连接看起来正常但 CPU/CAN 不更新、复位 GD32 才好"。 */ /* 1.6.32: SPI CS EXTI 提到抢占级0(见 app_spi_command configureExti),根治 CS release 被 PDI/RT_CRITICAL 延迟导致的 RX 撕裂(0xE8) *//* /* 1.6.28:ESC 睡眠挂起门控(EscLan9252::pmSuspended)。pmDrive 进 D1-D3 置位后全固件 HBI 消费者静默——task_runner 停调 MainLoop/AL 守护/AL 心跳(MainLoop 每圈 HBI 读 0x220,睡眠中任何 HBI 访问含读都把芯片打回 D0,2026-09-12 实测休眠 ~20s 即被主站"复活":芯片毫秒级被 GD32 自己叫醒、SM 失效后主站 wedge 重建 OP);ethercat_sync 三 EXTI ISR 与 timer Sync0 路径清 pending 即返。解除:pmDrive 唤醒成功/op0 读到 D0 自愈/nrstDrive 释放复位/hardwareReset。配套主站侧 ecat_native /api/pm/hold 楔死看门狗豁免。 */ /* A/B 结束: 恢复 1.6.25 稀疏+差分写(用户裁定, 主站侧已有后台恢复兜底) */ /* A/B C组: SPARSE=0+DIFF=0, 回 1.6.21 恒全量 600B 写 ESC(查开机楔死) */ /* 1.6.25:P1 输入 PDO 差分写 ESC(三区:段1 桥状态/段2 电机密排/段3+5 快照诊断;变化后 3-buffer 冲刷协议,稳定段跳过;全跳过写末 2 字节完成 SM)。P2 csrRead 分级:verify 参数,SM/AL/邮箱默认双读,诊断/MII busy/init 轮询单读。开关 CONFIG_ECAT_DIFF_ESC_WRITE。 */ /* 1.6.24:pmDrive 进 D1/D2/D3 移除读回验证。板级实测:进入睡眠后任何 HBI 访问(含读,手册 6.3.4.2 只说写唤醒,芯片实际按主机 CS 活动判定)都会 <50ms 触发唤醒回 D0——读回验证自证失败,改为置信构造返回值(READY=0|SLEEP_EN=1|MODE=op);「读状态」语义改为"若在睡眠则主动打破睡眠"。 */ /* 1.6.23:LAN9252 芯片级电源管理诊断路径。EscLan9252::pmDrive()(0xA2 action 30~34:30 读状态/31 进 D1/32 进 D2/33 进 D3/34 手动唤醒),按 DS 6.3 写 PMT_CTRL(0x084) PM_MODE+PM_SLEEP_EN(分两次写);D3 前置两 PHY General Power Down(经 ESC 核心 MII 管理 0x0510-0x0514,PHY A 地址 0/2 运行期判定),唤醒=HBI 写 BYTE_TEST 后轮询 BYTE_TEST/READY(DWT CYCCNT 忙等,50µs 步进、上限 10ms——0xA2 在 CS EXTI ISR 上下文执行,禁用 SysTickTimer::delayMs 会死等;HBI 段 maskEnter 屏 EXTI0/1 防 SYNC0 突入)。唤醒后自动恢复 PHY 上电(自协商需数秒)。休眠期间 EtherCAT 通信中断属预期,主站 wkc 报错。 */ /* 1.6.22:P1 稀疏写 ESC。PDO_InputMapping 在密排布局下按 count 只写「段1+段2有效区(0..40+count*56)」与「段3+段5(544..599)」,跳过空槽区,省 HW_EscWriteIsr 的 PRAM 字节。开关 CONFIG_ECAT_SPARSE_ESC_WRITE=1;非密排/count 撑满自动回退整包。总长 600B/SII/主站解码不变。A/B:置 0 重编即 1.6.21 写侧。 */ /* 1.6.21:段 2 改「一帧=总线电机整轮状态」。用户裁定旧需求不对:不要 win_base 滑窗扫 27 槽,也不要段 4 历史帧覆盖窗反复重发。实测病理:接 3 个电机网页却看到 5 个状态帧——固定 5 槽里空槽 node_id=0 也占位,主站不滤就多报。本版:(1) 段 2 密排,count=本拍实际电机数(0..9),空节点不占位,槽序按 node_id 升序,同号多通道取 seq 最新;主站 `for i in 0..count` 按槽内 node_id 自动识别,无需滑窗。(2) 删段 4 覆盖窗口(fillRxWindow/RxWinSlot 整套),非 STATUS 仍走段 1/3 最近一帧。(3) 差分填充:节点集合与各 seq 未变时整段复用影子,跳过 MotorState 重读与逐槽 48B 拷贝。(4) 输入 PDO 总长仍 600B,SII 无需重刷;段 2 扩到 32..543、段 3 前移到 544、段 5 仍 584。**主站解码必须同步**(decode_input/transport-ecat.js:count 密排 + 段 3 偏移 320→544)。 */ /* 1.6.19:[DIFF-STATS] 仪表成本终于测对了,并推翻 1.6.18 自己的判断。1.6.18 认定"是 -Oz 把 memcpy(&a,p,4) 编成函数调用",改成手拼 4 字节移位,结果反汇编出**每字 8 条 ldrb.w**、比开销从 21.3µs/拍涨到 41µs/拍 —— 方向完全反了。真根因不在这里:alignas(4) 标在数组声明上只到调用点为止,而 observe(const uint8_t*) 的形参签名不带对齐信息,优化器无从证明指针 4 字节对齐,两版都只能逐字节走。修法是对两个入参各做一次 __builtin_assume_aligned(p,4) 再走 memcpy —— 反汇编确认循环体已缩成 ldr.w/ldr.w/cmp/bne 每字 4 条 (150 字 ≈ 750 cycle ≈ 4.2µs/拍,与最初估算同量级),且整个函数 bl 归零(600B 快照拷贝也被折成字搬运)。教训:**测量工具的开销会直接变成结论**,"差分判断很贵"这句话差一点就被我自己造的假象坐实。被测量(变化字节数 25%、run 峰值 12)不受此影响,两版读数一致,仍然有效。 */ /* 1.6.18:修 [DIFF-STATS] 仪表自己的假象 —— 比对循环里 `memcpy(&a,p,4)` 在 -Oz 下被编成**函数调用**而非单条 LDR,首轮实测 compare 开销虚报成 21.3µs/拍(≈300 次调用×995 拍),若拿它当"差分判断很贵"的证据就结论反了。改成小端 4 字节移位拼装:合法、不碰严格别名、且是 Clang 认得折 LDR 的模式。顺带把 alignas(4) 的理由改成"服务整块 memcpy",比对本身不再依赖它。Code 52716→52768(+52B),代价是体积不是速度。 */ /* 1.6.17:新增 [DIFF-STATS] 临时仪表,为"输入 PDO 改差分写"取证,**零行为变更**。动机:差分写的收益完全取决于每拍真正变了多少字节、变化点碎成几个 run,而 run 碎片数决定它会不会把一次 600B 整块写退化成几百次带 PRAM_AVAIL 轮询的小写——这两个数今天没有实测依据,所以先只上仪表再决定做不做。实现:comm/ethercat_diff_stats.{h,cpp},每拍在 PDI_Isr 里与上一份已发布镜像做 u32 粒度比对,按 1s 窗口(DWT 周期,同 cpu_load 手法)滚动,getter 只回报"上一个完整窗口"以免主站读到半累积值;量为 cycles / changedMax / runsTotal / runsMax / 段1..段5 各自变化字节 / 比对自身 DWT 开销。经对象字典 0x2000 sub13..23 上报(新增 11 个只读 UINT32)。**只加 SDO 子索引、PDO 长度与映射逐字节不动 ⇒ 无需重生成 SII、无需断电冷启动**,可与主站独立升级。整模块由 CONFIG_ECAT_DIFF_STATS 门控,置 0 后与 1.6.16 等价;挂接点统一 [DIFF-STATS] 标记,删除=删 2 文件+删标记行。已知副作用:开启时 sub9/sub10 写侧耗时含比对开销,不能直接与旧基线对拍,真实写侧基准须置 0 重测。 */ diff --git a/docs/compose/spec/warm-jump-ecat-probe.md b/docs/compose/spec/warm-jump-ecat-probe.md new file mode 100644 index 0000000..a6b9777 --- /dev/null +++ b/docs/compose/spec/warm-jump-ecat-probe.md @@ -0,0 +1,81 @@ +--- +feature: warm-jump-ecat-probe +status: delivered +updated: 2026-09-16 +branch: master +commits: 7d9d9c7..uncommitted +--- + +# Warm Jump 后 EtherCAT 起板探针修复 + +## Report + +**What was built** — APP 1.6.40:`EscLan9252::init` 起板探针放行门从 +`HBI_IDX_0==0x1234` 改为 `BYTE_TEST==0x87654321`。warm jump 残留的 +`HBI_IDX_0=0x0054` 只打 WARN 并放行;BYTE_TEST 不过才补一发 RST# 重探一轮。 +cold 路径行为不变(仍等芯片出复位到 0x1234 再测 BYTE_TEST)。 + +**Verification** — 无 ARM 交叉编译环境;静态审查通过(spec 合规 / 正确性 / +代码风格),reviewer 无 critical。上板验收:ECAT 刷完 warm jump 后串口应见 +`[ECAT][ESC] init: OK`(可带 `WARN idx0=0x0054`),`[APP] init` 不再报 +`skipped/failed: ethercat`,主站可建连;reset 路径回归不破坏。 + +**Journey log** +- BOOT 1.1.0.18 短路 `escResetForJump` 是刻意实验;APP 侧旧探针把「复位默认值」 + 当成通路判据,在 warm 残值上必然假阴性。 +- 主工作区曾有一份未完成的 1.6.40 草稿;本任务收口并跳过 warm 残值的 300ms 空等。 +- 用户选了隔离 worktree,但本环境拦截 `git worktree add`,改动落在主工作区未提交 diff。 + +## [S1] Problem + +刷完固件后 BOOT(1.1.0.18,跳转前 `escResetForJump()` 已短路)warm jump 进 APP: +APP banner 正常,但 EtherCAT 不可用(自检降级 / 主站无法建连)。按复位键走 cold +jump 后 EtherCAT 恢复。同一镜像、同一 APP 启动链,差异只在进入 APP 前 ESC 的 +HBI 索引寄存器状态。 + +## [S2] Design + +### 根因 + +1. BOOT `bootEcatDeinit()` 最后一条 `writeReg32(IRQ_CFG)` 把 LAN9252 的 + `HBI_IDX_0` 留在 **0x0054**(索引模式残值,DS 9.5.4.2 复位默认才是 0x1234)。 +2. 跳转前硬复位在 BOOT 1.1.0.18 被刻意短路(1.1.0.13/14 实测那发复位会打死 + warm jump),warm 路径芯片 **不会** 回到 POR 默认态。 +3. APP `EscLan9252::init()` 旧放行门是 `HBI_IDX_0==0x1234`。该值只证明 + 「芯片刚出过复位」,证明不了 HBI 通路。warm 路径上探针必然假阴性 → + `s_busReady=false` → SSC 跳过 → 降级 ethercat。 +4. cold 路径芯片自带 POR,`IDX_0` 天然 0x1234,与 PB10 那发复位无关 —— + 这正是「reset 能好、刷完不行」的分界。 + +### 修复契约 + +- **BYTE_TEST(0x064==0x87654321)是唯一物理通路门**。`readReg32` 每次访问前 + 都 `setHbiIndex(addr)`,完整「索引写 + 数据低/高字读」往返,与复位态无关。 +- `HBI_IDX_0` 降级为诊断读数:异常时打 WARN 并放行,不再导致 `return false`。 +- 任一判据不过 → 补一发 `hardwareReset()` 再探一轮(此刻 SSC 未 MainInit, + 二次复位零配置代价)。两轮仍不通 = 总线物理不通,维持降级。 +- `s_busReady` 语义同步:EXMC + BYTE_TEST 通过即 true(与 `initial.cpp` 的 + `isBusReady()&&isCoreReady()` 门控一致)。 + +### 不做的事 + +- 不改 BOOT(1.1.0.18 保持 RST# 短路);APP 侧已能兜住 warm 残值。 +- 不把 `HBI_IDX_0==0x1234` 恢复为硬门。 +- 不动 PDO/SII/主站解码。 + +## [S3] Out of Scope + +- BOOT `bootEcatDeinit` 的 PD/PE 浮空注释错误、`escResetForJump` 时序重设计。 +- PHY 链路重协商窗口与主站 probe 重试(另见 `ecat_boot_handoff_link_issue.md`)。 +- 1.6.35~38 启动链实验回退项(已在 1.6.39 完成)。 + +## Tasks + +- [x] T1: 改 `EscLan9252::init` 探针 — 放行门改为 BYTE_TEST,IDX 残值 WARN 放行,失败补一发 RST# 重探 — acceptance: 代码与 [S2] 契约一致 (covers: S2) +- [x] T2: 同步 `esc_lan9252.h` 的 `isBusReady` 文档语义 — acceptance: 注释与实现一致 (covers: S2; depends: T1) +- [x] T3: `config.h` 版本 bump 1.6.40 + 来由注释 — acceptance: PATCH=40 且注释写明 warm-jump 根因 (covers: S2) +- [x] T4: 逻辑核对探针与调用方 — acceptance: 无 ARM 交叉编译环境,完成静态审查;`initial.cpp` 仍用 `isBusReady()&&isCoreReady()` 门控,语义兼容 (covers: S2; depends: T1) + +### 工作区说明 + +用户选择隔离 worktree,但本环境拦截了 `git worktree add`(共享 ref store 保护)。改动落在主工作区未提交 diff,分支意图为 `fix/warm-jump-ecat-probe`。验收通过后需用户确认是否提交到 master。