feat(ethercat): 1.6.40 起板探针改 BYTE_TEST 放行, 根治 warm jump 后 EtherCAT 降级

This commit is contained in:
cbb
2026-09-16 01:54:25 +08:00
parent 7d9d9c70a1
commit 63af85e7be
4 changed files with 142 additions and 35 deletions
+45 -25
View File
@@ -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 间接端口读——
+5 -3
View File
@@ -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,唤醒后需恢复。 */
+11 -7
View File
@@ -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 重测。 */
+81
View File
@@ -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。