feat(ecat): 完善LAN9252硬件初始化与诊断功能

完善readReg32函数中高低字节读取顺序,确保按先低后高的固定顺序,
避免C++中运算符求值顺序未指定导致的不确定性问题。

在ESC硬件复位后增加50ms延时,等待内部PLL锁定与EEPROM启动,
对齐APP侧实现保持一致性。

增强EXMC初始化过程中的硬件连通性检测,添加互补图案回环测试
和短路排查机制,增加轮询机制确保BYTE_TEST寄存器读取成功。

新增诊断模式宏DIAG_HOLD_PE7_HIGH,用于上电时持续拉高PE7引脚
进行硬件诊断测试,便于万用表实测直流电平验证电路连接。
This commit is contained in:
cbb
2026-10-13 00:48:14 +08:00
parent 77354fd2a8
commit 644c8fb8a8
3 changed files with 77 additions and 208 deletions
+44 -5
View File
@@ -126,8 +126,12 @@ inline void setHbiIndex(uint16_t a) { BOOT_ESC_REG16(HBI_IDX0_OFFSET) = a; }
inline uint32_t readReg32(uint16_t a)
{
setHbiIndex(a);
return ((uint32_t)BOOT_ESC_REG16(HBI_DATA0_HIGH_OFF) << 16)
| BOOT_ESC_REG16(HBI_DATA0_LOW_OFF);
/* 必须显式先低后高。原来写成 `(uint32_t)REG(HI) << 16 | REG(LO)`,而 `|` 两侧求值
* 顺序在 C++ 里是未指定的——编译器可以先取高半字。同文件 writeReg32 本来就是先低后高,
* APP 侧实测跑通的那份(esc_lan9252.cpp:136-139)也是先低后高,这里对齐消除不确定性。 */
const uint16_t lo = BOOT_ESC_REG16(HBI_DATA0_LOW_OFF);
const uint16_t hi = BOOT_ESC_REG16(HBI_DATA0_HIGH_OFF);
return ((uint32_t)hi << 16) | lo;
}
inline void writeReg32(uint16_t a, uint32_t v)
{
@@ -305,6 +309,8 @@ void escHardwareReset()
gpio_bit_reset(ESC_NRST_PORT, ESC_NRST_PIN);
delay_1ms(ESC_NRST_ASSERT_MS);
gpio_bit_set(ESC_NRST_PORT, ESC_NRST_PIN);
/* 释放后等 50ms:对齐 APP esc_lan9252.cpp:249,等待内部 PLL 锁定与 EEPROM 启动 */
delay_1ms(50U);
/* 驱动高却读回低 ⇒ 该网络被短路到地或引脚损伤,后面所有 HBI 读都会是 0;
* 先在这里报出来,别让上层误判成「EEPROM 坏了」。 */
BOOT_ECAT_LOG("[BOOT][ESC] RST# %ums, 释放回读=%d\r\n", (unsigned)ESC_NRST_ASSERT_MS,
@@ -378,11 +384,44 @@ bool exmcInit()
(unsigned)idx0);
return false;
}
/* 第二步 BYTE_TEST:只有 DATA 偏移算对才读得到(0x40000/0x60000 错时这步才失败) */
if (readReg32(LAN9252_REG_BYTE_TEST) != LAN9252_BYTE_TEST_VALUE) {
BOOT_ECAT_LOG("[BOOT][ESC] FAIL BYTE_TEST → HBI_DATA_0 偏移错\r\n");
/* 互补图案 0x555A/0x5A55 做 IDX_0 写回环,逐位校验 D0-D15 硬件引脚通路 */
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x555AU;
const uint16_t echoA = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x5A55U;
const uint16_t echoB = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
/* 专项排查:Bit4 (D4 / Pin49) 与相邻位 Bit5 (D5 / Pin50) 短路排查 */
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x0010U; /* 仅 Bit4=1 */
const uint16_t echo_b4 = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x0020U; /* 仅 Bit5=1 */
const uint16_t echo_b5 = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x0030U; /* Bit4 与 Bit5 同时为 1 */
const uint16_t echo_both = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0xFFFFU; /* 全 1 */
const uint16_t echo_ffff = BOOT_ESC_REG16(HBI_IDX0_OFFSET);
BOOT_ESC_REG16(HBI_IDX0_OFFSET) = 0x0000U; /* 清残值 */
BOOT_ECAT_LOG("[BOOT][ESC] IDX回环 555A->0x%04X 5A55->0x%04X\r\n",
(unsigned)echoA, (unsigned)echoB);
BOOT_ECAT_LOG("[BOOT][ESC] 短路排查: b4(0010)->0x%04X b5(0020)->0x%04X 同高(0030)->0x%04X 全1(FFFF)->0x%04X\r\n",
(unsigned)echo_b4, (unsigned)echo_b5, (unsigned)echo_both, (unsigned)echo_ffff);
/* 第二步 BYTE_TEST:轮询直到读到 0x87654321 */
uint32_t byteTest = readReg32(LAN9252_REG_BYTE_TEST);
for (uint32_t w = 0U; byteTest != LAN9252_BYTE_TEST_VALUE && w < ESC_HBI_POLL_MS; w += ESC_HBI_POLL_STEP_MS) {
delay_1ms(ESC_HBI_POLL_STEP_MS);
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
}
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
const uint16_t loNow = BOOT_ESC_REG16(HBI_DATA0_LOW_OFF);
const uint16_t hiNow = BOOT_ESC_REG16(HBI_DATA0_HIGH_OFF);
BOOT_ECAT_LOG("[BOOT][ESC] FAIL BYTE_TEST=0x%08X(要 0x%08X) DATA直读 LO=0x%04X HI=0x%04X\r\n",
(unsigned)byteTest, (unsigned)LAN9252_BYTE_TEST_VALUE,
(unsigned)loNow, (unsigned)hiNow);
return false;
}
BOOT_ECAT_LOG("[BOOT][ESC] HBI探活成功: idx0=0x1234, BYTE_TEST=0x87654321\r\n");
return true;
}
+32
View File
@@ -467,6 +467,9 @@ void BootloaderSystem::run() {
}
}
/* 诊断测试开关:置 1 时上电死循环持续推挽拉高 PE7 (Pin 38 / ECAT_D4),方便万用表实测直流电平 */
#define DIAG_HOLD_PE7_HIGH 0
int main(void) {
/* v2 DATA_READY: 仅启用 GPIOB 时钟,不配 PB6 为推挽低。
* PB6 由 SpiOta::configureGpio() 初始化为开漏高(释放态)。
@@ -481,10 +484,39 @@ int main(void) {
gpio_bit_set(GPIOC, GPIO_PIN_9);
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
#if DIAG_HOLD_PE7_HIGH
SysTickTimer::init();
BootDebugUart::init();
printf("\r\n======================================================\r\n");
printf("[DIAG] PE7 (GD32 Pin 38 -> LAN9252 Pin 49 / D4) 拉高测试程序\r\n");
printf("======================================================\r\n");
/* 配置 PE7 为推挽输出 50MHz,并持续置 1 (3.3V) */
rcu_periph_clock_enable(RCU_GPIOE);
gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
gpio_bit_set(GPIOE, GPIO_PIN_7);
printf("[DIAG] PE7 已配置为推挽输出 HIGH (3.3V)!\r\n");
printf("[DIAG] 请现在使用万用表直流电压档测量:\r\n");
printf("[DIAG] 1. GD32 Pin 38 (PE7) 对地电压 -> 预期应为 ~3.3V\r\n");
printf("[DIAG] 2. LAN9252 Pin 49 (D4) 对地电压 -> 预期应为 ~3.3V\r\n");
printf("======================================================\r\n\r\n");
uint32_t cnt = 0;
while (1) {
gpio_bit_set(GPIOE, GPIO_PIN_7);
int readback = gpio_input_bit_get(GPIOE, GPIO_PIN_7);
printf("[DIAG] 持续输出 PE7=3.3V (第 %lu 秒), 内部回读电平=%d\r\n",
(unsigned long)++cnt, readback);
delay_1ms(1000);
}
#else
sys.initHardware();
sys.run();
/** 理论上 run() 不会返回,这里仅做保险。 */
while (1) {
}
#endif
}
+1 -203
View File
@@ -13,212 +13,10 @@ typedef uint32_t u32;
/** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */
#define DEBUG_PRINT 1
/**
* BOOT 固件版本变更说明(v1.1.0.6 / v1.1.0.7)
*
* v1.1.0.6: BOOT 原生 EtherCAT 收包。comm/ecat 那份与 APP 逐字节相同的 SSC 拷贝首次
* 入编译配置(邮箱-only:DC_SUPPORTED=0、MAX_PD_IN/OUT=2、无 SAFEOP/OP,
* APPL_Start{Input,Output}Handler 直接回 INVALIDALCONTROL 把从站钉在 PREOP);
* 新增 boot_ecat_hw.cpp(EXMC Region0 沿用 APP 1.5.57 的 BUSLAT=4/ADDSET=4/DSET=20 与
* SNCTL0 bit7 修补、HBI Indexed、CSR 间接端口双读校验 1.5.59、PRAM 逐 DWORD+AVAIL
* 1.6.2、PB10 RST# 30ms、LAN9252 IRQ_CFG/INT_EN 使能 1.5.43、PB1→EXTI1→PDI_Isr)与
* boot_ecat_app.cpp(0x2500 会话控制 + 0x2501 4KB 数据通道,SDO 分段由 SSC 攒完整块后
* 才回调一次,故从站无 begin/data/commit 拼包状态机)。**没写 foe.c、没改
* mailbox.c/ecatslv.c、没动 SII**——传输走这份拷贝本来就完整的 CoE SDO。
* flash 写仍只有 OTAHandler 一个:ecatBegin/Feed/Commit 直接复用
* eraseApplicationArea/writeApplicationData/validateApplication/CRC/OTA_SET,提交顺序与
* SPI 那条一致,掉电中断=AVAL 未置=留守可重灌的既有保证不变;并发上 ecatBegin 在
* SPI/CAN 会话活跃时拒绝。鉴权用配置页新 word8 arm 令牌(0xACA70001,避开擦除态与
* OTA_AVAL)。jumpToApplication 补 bootEcatDeinit(EXTI1/NVIC、LAN9252 IRQ 引脚、EXMC、
* DWT),否则 APP 会吃到 BOOT 留的 pending 中断并在被占用的总线上重跑 init。
*
* v1.1.0.7: 把上面那道 arm 门控改成能实际用的形态 —— 1.1.0.6 写完 ecatArmSet 却没有任何
* 调用者,令牌永远置不上,每次 ecatBegin 都被拒,等于整条路是死的。现在 ecatArmed() =
* 配置页令牌==KEY **或** A 区不可启动;判据与 main.cpp run() 决定 jump_latest_now 的
* CHECK/EN 两字逐字一致,所以空板首刷、下载中断电、A 区 CRC 失败、FWDGT 复位这四种 BOOT
* 留守情形天然放行,不需要 ESP32 在场(这正是「BOOT 原生刷」立项的理由);而 APP 正常在跑
* 时两字皆 OTA_AVAL,令牌仍是硬门,SDO 写不了令牌也就刷不动一台正常机器。ecatCommit 成功
* 路径在置三面标志之前 ecatArmSet(false) 收回令牌:用完即弃,不是一次 arm 永久敞开,同时
* 给 ecatArmSet 一个真实调用者。此刻 CHECK/EN 还是 eraseApplicationArea 标的 NOT_AVAL,
* 所以收回这一拍救砖分支照样为真、不影响本次提交;后面三个 OTA_SET 全走
* data==OTA_AVAL 的直接编程快路径,总共只多擦一页。
* 主站侧配套新增 ecat_native /api/ota/ecat(open/push/status/abort/probe/run)与
* ecat.html「BOOT 刷写」页签;SII 身份字未动,所以 BOOT 与 APP 在总线上唯一的区别是
* 0x1008 的字符串,而主站**只拿它当日志读数、不当门槛**(对着 APP 写 0x2500 只会 SDO
* abort,对着老 BOOT 只会超时,两种都写不进一个字节)。能不能刷这一台,唯一裁决点就是
* 本文件的 ecatArmed() —— 主站不复制第二份判据,否则两边一定会漂。
*
* 尺寸实测(v1.1.0.7 构建,map 里唯一的 ROM 区):ER_ROM0 Size 0xFC28=64,552B /
* Max 0x12000=73,728B ⇒ 72KB BOOT 区余 9,176B(用 87.6%);产物 bin 64,848B。
* 归因链:无 EtherCAT 基线 36,340B → 空移植层桩件 57,816B(净增 21,472B,对照组=
* 关掉唯一调用者后 --gc-sections 把 SSC 全剥回 36,340B,故增量可归因)→ 真移植层
* 64,552B。RAM 32,392B/131,072B:堆按 SSC 每笔分段下载 malloc 一整块而放大到
* 0x4000(见 regions_GD32E508VE.h),栈仍是 0x400 —— 所以 boot_ecat_app.cpp 的 SDO
* 回调里一律不许开块级大数组(s_dlStage 因此是文件级 static)。
*
* v1.1.0.8: 首次上板(空板、SWD 刷入、A 区全 0xFF)暴露两个问题,都是"静默"型的:
* ① boot_ecat_hw.cpp 没 include sys/config.h,而 DEBUG_PRINT 只在那儿定义 ——
* `#if DEBUG_PRINT` 按 0 处理,整套 [BOOT][ESC] 起板日志被编译掉,产物里连
* "RST#"/"PDI ok" 这些串都搜不到(实测 grep 计数 0)。于是"看串口 PDI ok"这条
* 判据从建立起就不可能成立。已补 include,并在注释里写明复核方法是 grep 产物而不是
* 看编译通过。
* ② ecatslv.c 的 Enable/DisableSyncManChannel 两个 do/while 等 SMx_Active(0x806) 的
* PDI_DISABLE 位翻转,**没有任何上界**;MainInit() 一进来就调它们,位不翻就永久自旋。
* BOOT 侧没开任何 MCU 看门狗,所以现场表现就是上电日志停在 path=stay_boot_wait_ota
* 之后一行都没有(连 2s 心跳都没了)—— 与本次实测完全一致。现在两处各加 3000 次
* 上界并把通道号/寄存器地址/最后读到的值 printf 出来,把死机换成一句能读的报错;
* 正常路径一位即满足、一次不多转,行为不变。真正的根因(哪一路、为什么位不翻转)
* 等这一版刷上去看报错再定,不在这里猜。
*
* v1.1.0.8 实测把上面 ② 的归因**证伪了**:刷上去后 `[BOOT][ESC] RST# 30ms, 释放回读=1`
* 打出来了(①的 include 修复生效),然后一行都没有 —— 既没有 exmcInit 四条有界报错里的
* 任何一条,也没有 2s 心跳。也就是说执行根本没走到 MainInit,SM 自旋不是这一拍的原因
* (上界本身是该加的,保留)。反汇编本版产物定位(exmcInit 已内联进
* bootEcatBringup@0x0800C3C8):printf 之后依次 4×rcu_periph_clock_enable、21×gpio_init、
* exmc_norsram_struct_para_init/init/enable、SNCTL0 读改写清 bit7(0x0800C5EC),
* 然后 **0x0800C5F8 是第一条 `LDRH r0,[r4,#0]`,r4=0x60000000**,其后才是 60 次
* delay_1ms 的有界轮询与那句 FAIL printf。没打出 FAIL ⇒ 执行停在或就在这一条 LDRH 上。
* 逐条排除(都有产物/实测依据,不是猜):MPU/SAU/GTZC 在 BOOT 树里一处都没有;本函数
* 反汇编 0 条浮点指令;栈不是原因(ARM_LIB_STACK 0x400@0x2001FC00,越界只落进
* 0x20007A88..0x2001FC00 这段没用 RAM,不会 fault);EXMC 参数与 APP 逐字段相同、两份
* gd32e50x_exmc.[ch] 与 APP diff 为 0;s_delayCountdown 是 volatile(不会被 -O2 提成
* b .);RST# 网络健康(驱动高读回 1)。总线侧独立旁证 MCU 彻底没在跑服务 PDI:
* /api/open 后 AL Control(0x120)=0x0011 而 AL Event Request(0x220)=0x0011 的 bit0 仍挂起
* —— PDI 只要读过 AL Control 就会清它;再请求 PREOP 超时且 AL Status Code=0x0000
* (活着但拒绝会回非零错误码,回 0 码=没人应答)。同一条判据对着 APP 每天都能过,有灵敏度。
*
* ③ 本版真修(从 v1.1.0.8 的 .bin 向量表直接读出来的,不依赖推理):EXTI1_IRQHandler
* 定义在本文件那个 extern "C" { 块**之外**(块从 471 行才开),按 C++ 修饰名编译,
* 覆盖不了启动文件里的弱符号 ⇒ 向量表 IRQn7(偏移 0x05C) 实测 = 0x080001D3,正是
* startup 的 `B .` 桩;连带整个 PDI_Isr 子树被 --gc-sections 当死代码剥掉,那一版镜像
* 里「ESC 中断」这条路根本不存在。加 extern "C" 后复测:0x05C=0x08001A11、map 里出现
* .text.EXTI1_IRQHandler、Code 61,388→62,684(+1,296B 就是原先被剥掉的那截);
* 另外四个 BOOT 用到的中断(CAN0_TX/RX0、EXTI5_9、EXTI10_15)实测向量都已是 REAL。
* 判据是读产物向量表,不是看编译通过 —— APP 侧 ethercat_sync.cpp:189 那句
* 「中断入口 C 包装」注释就是同一个坑的历史记录。
*
* 尺寸实测(v1.1.0.9):ER_ROM0 0x104B8=66,744B / Max 0x12000=73,728B ⇒ 72KB 区余
* 6,984B;bin 67,040B;RW/RAM 侧不变(Debug 构建实测 ER_ROM0 0x12450=74,832B 已经装不进
* 72KB,差 1,104B —— 排障要用调试器请直接拿 Release 的 .axf,它本来就带 -gdwarf-5 全符号)。
* 剩下的唯一未知是那条 LDRH 属于「AHB 不返回」还是「触发了 fault」,两者修法不同,判据
* 是接 SWD 读 PC:PC=0x0800C5F8 ⇒ 总线不返回;PC 落在 0x080001C2..0x080001D4 这批 `B .`
* 桩 ⇒ fault,接着读 CFSR/HFSR(0xE000ED28/0xE000ED2C) 与出栈 PC 定位。
*
* v1.1.0.10: 上面那句「唯一未知」也一并推翻 —— 逐行对 APP 起板路径时查出来根因,
* 根本走不到那条 LDRH。boot_ecat_hw.cpp 原来是
* exmc_norsram_struct_para_init(&ns); ns.read_write_timing=&rt; ns.write_timing=&wt;
* 而库函数 exmc_norsram_struct_para_init() **不是给这两个指针赋默认值,是拿着这两个
* 指针往里写 14 个默认字段**(gd32e50x_exmc.c:134-146)。先调后赋值 = 对栈上残留值做
* 14 次写。v1.1.0.9 反汇编实测:ns 在 sp+0x40,两个指针槽在 sp+0x74/sp+0x78,
* 0x0800C596 就 BL 了 para_init,直到 0x0800C59A/0x0800C59E 才把 &rt/&wt 写进去 ——
* 也就是说调用时那两个指针是上一帧留下的垃圾。命不中合法地址就是 HardFault,落进启动
* 文件的 B . 桩(本镜像里 HardFault=0x080001C2),BOOT 又没开 IWDG ⇒ 表现就是日志停在
* RST# 之后一行都没有、连 2s 心跳都没了,与两次实测完全一致。APP 侧
* esc_exmc_bus.cpp:181-183 一直是先赋指针再调,所以 APP 正常 —— 这就是两边的差别。
* 修法照抄 APP 的顺序(两行上移),并加注释钉住「para_init 会解引用这两个指针」。
* 同类调用全仓扫过:can/spi/dma 的 *_para_init 形参都是扁平结构体,无嵌套指针,只有
* exmc 这一个有。判据:改完反汇编看 0x…C596 之前必须已经出现两条 STR 到 ns 的指针槽。
*
* v1.1.0.11: EtherCAT 刷写的**头一步就被从站自己拒了** —— 0x2500 的条目描述符把
* sub1(cmd)/sub2(expected) 都写成了 ACCESS_READ,而 SSC 的 OBJ_Write 在 objdef.c:1325
* 就做通用写权限检查,过了它才在 1352 分发到 ObjFoeDlCtlWrite。于是主站"声明总长"直接
* 吃 abort,回调一次都没进过:SDO 写这半边从加进来那天起就没通过,之前上板证实的只有读。
* 实测读数(native 已会把从站的 SDO abort 码打进日志):写 0x2500:01/:02/:03 一律
* 0x06010002 "Attempt to write to a read only object";对照写 0x2501:00(唯一条目声明
* ACCESS_READWRITE)拿到的是 0x08000000 General error —— 那才是进了本对象回调、因无会话
* 而拒的正常形状。这条对照把病因精确钉在"逐条目权限位",排除了邮箱/SOEM/SSC 栈。
* 改法:sub1/sub2 → ACCESS_READWRITE,sub3(进度)/sub4(状态) 保持只读;只改常量表 2 字节。
* 判据(刷完第一件事):主站『探测』应先回 armed=1,再跑 run —— 日志里若还出现
* 0x06010002 就是没刷进去;变成"begin ok/已灌 xxxx B"才说明门过了。
*
* v1.1.0.12: 两件事,都是"给正在跑 APP 的机器做常驻刷写"逼出来的。
* ① 10s OTA 窗口不认 EtherCAT 会话 ⇒ **半截灌写会被送回 APP,形成活锁**。
* 实测 67,136 B 要 11.9s(17 块 / ~0.7s 每块),而 APP_DELAY_BOOT_TIME=10000 是固定的,
* 循环里只有 ota.isActive() 能让它转进无超时的 stay_in_ota —— 而 ecatBegin 只置
* ecat_session、**不碰 ota_state**。所以窗口一到点就走 ota_window_timeout_then_jump,
* 而 jumpToApplication 又无条件把 n_APP_LATEST_EN 写回 OTA_AVAL(OTA.cpp:545),
* 于是半截镜像被当合法 APP 跳进去 → HardFault → FWDGT 复位 → BOOT 又留守 10s → 循环。
* 改法:wait_ota_window 与 stay_in_ota 的活性判据都并上 ota.ecatSessionActive()
* (现成 getter,OTA.h:171)。commit 成功会清 ecat_session,退出评估照常;
* 实际回跳仍由 pending_app_jump 在 process() 里先做掉。
* ⚠ 仍存的约束:**交接之后到 begin 之前只有 10s**,所以页面必须"一键把整条链跑完",
* 不能靠人逐步点。
* ② 0x100A 从 SSC 占位 "1.0" 改成真版本号:DEVICE_SW_VERSION_LEN 0x3→0x10,
* coeappl.c 的 acSoftwareversion 显式给 LEN+1(原来靠初始化器推出 4 字节,
* 而条目位长按 LEN 声明 ⇒ 主站按 16 字节读会越界),initHardware 里 snprintf 成真版本。
* 主站已接:probe 回 sw、页面有「软件版本(0x100A)」格、刷写日志与完成态都带版本。
* v1.1.0.13: 跳 APP 前补一发 LAN9252 硬复位(bootEcatDeinit 里新增 escResetForJump)。
* 动机(板上实测):1.6.34 刷完首启必现 GD32 自检「ethercat 降级 / EXMC 总线未就绪」
* (fault_codes[10]=1、module_mask 缺 bit10/11),断电重上电即好。故障态现场读数:
* I210 链路 Up 100M、DL_STATUS=0x5613(bit0 PDI operational)、AL=PREOP、邮箱读得到
* name="GD32E508 DCU " sw="1.6.34" —— 芯片有电、PDI 通、不是 D3 睡死,是**它没被复位**。
* APP 起板探的第一项是 HBI_IDX_0==0x1234(DS 9.5.4.2 复位默认值),而 BOOT 侧 HBI 全是
* 索引两步事务,bootEcatDeinit 最后一条 writeReg32 把它留在 0x0054(IRQ_CFG);带着残值
* 跳进 APP 就报「LAN 没驱动总线」。此前复位只挂在 APP 自己 hardwareReset() 那一发 RST#
* 上,它落不上就整体失败——而 jumpToApplication 是改 VTOR 后跳转,不是 NVIC_SystemReset。
* 实现坑(别改回去):bootEcatDeinit 跑在 __disable_irq() 之后,**只能用 delay_1us()
* 这种 __NOP() 忙等**;复用 escHardwareReset()/delay_1ms() 会因 SysTick 中断不递减而
* 永久死等,BOOT 没开 IWDG ⇒ 刷完直接静默变砖,比原问题更糟。
* 判据:重刷 BOOT 后走「刷完不复位」这条链,自检应全 0;若仍报 fault 1 且 APP 串口打
* HBI_IDX_0=0x0054 ⇒ 复位仍没落到芯片上(BOOT/APP 两处都试过),转查 ECAT_NRST 网络与
* hardwareReset 的 pin reads 回读;若是 0xFFFF/0x0000 ⇒ 芯片真没驱动总线,另案。
* v1.1.0.14: 1.1.0.13 那发 RST# 补得不对,这里收口(板上实测:每次上电都卡在跳转后)。
* ① gate:改由 s_escBusUp(bootEcatBringup 里 exmcInit 成功才置位)决定发不发。
* 13 是无条件下发,于是 path=jump_latest_now(上电 3ms 直跳、这轮 ESC 从没起过)
* 也在电源刚稳的窗口里拉了 RST#,释放点距上电仅约 15ms —— 踩 LAN9252 的
* tpurstd(电源稳定→释放复位 ≥25ms),且那条路径上根本没有 HBI 残值要清。
* 实测后果:跳进 APP 后整机静默,串口停在 [J5],ESP32 侧 SPI 完全无应答
* (tx 706 / ready_timeout 2118 / role unknown),ESP32 uptime 一直涨 = 没有复位
* 循环,GD32 停在某处不动。APP 镜像本身完好(CRC 两次 OK)。
* ② 脉宽:delay_1us() 是 __NOP 环且按 4 cycle/迭代标定,实测 30000µs 只走出 8ms
* (J3→J4 的间隔),当复位脉宽不够格;换成 DWT CYCCNT 忙等真 30ms + 释放后 2ms。
* 这里不能用 delay_1ms():bootEcatDeinit 跑在 __disable_irq() 之后,SysTick 中断
* 不递减 = 永久死等,BOOT 无 IWDG,比原问题更糟。
* ③ 观测:跳转前打 [J6] boot v<ver> handoff 并 fflush(stdout)。BOOT 的 printf 是
* stdio 行缓冲,跳进 APP 后 __rt_lib_init 重铺 FILE 结构会把未冲出的尾巴一起丢掉
* —— 之前「串口停在 [J5-HEX] 半截」既可能是卡死也可能是跳转成功,读不出真相。
* 判据:上电直跳应看到 [J6] 且 APP 正常起(自检 ethercat 不再降级);走总线刷写路径
* 则 [BOOT][ESC] jump reset 出现、APP 起板 IDX_0 探到 0x1234。若直跳仍卡,说明与
* RST# 无关,转查 v1.1.0.12 首次上板的两条(10s 窗口并 ecatSessionActive、
* main.cpp:69 新增 snprintf —— BOOT 栈只有 1KB,拿 .map + fromelf 数栈余量)。
* v1.1.0.18 [WJ-TEST]: 单变量裁决 warm jump 失败(板上实测 2026-09-16)。
* 现象:刷完 warm jump 进不去 APP;按一下 reset 走 cold jump 同一镜像正常起
* ⇒ APP 启动链无罪,差异全在 s_escBusUp 为真时 bootEcatDeinit 真干了活。
* 本版只做一件事:把 bootEcatDeinit 里的 escResetForJump() 调用注释掉
* (boot_ecat_hw.cpp:507),其余逐指令与 14 相同。
* 版本号取 18 不复用 15/16/17 —— 那三版都真上过板,[J6] 读数必须唯一可辨。
* 判读:warm jump 能起(哪怕自检报 EXMC 未就绪/降级、IDX_0=0x0054)
* ⇒ 元凶=那发复位的 2ms 交 CPU 时序,改成轮询就绪后再跳或交给 APP;
* warm jump 仍起不来 ⇒ 与复位无关,转查 PD/PE 停在 AF、EXMC 时钟被关
* 的 MCU 侧残留(509 行那句"PD/PE 回浮空"是错的,关外设时钟不切 GPIO)。
* 注:本版与 1.1.0.15~17(关中断跳转实验、BKP 面包屑、EXTI15 清理)无关,
* 那批改动未提交且已整体回退到 46adcb7,补丁存于
* G:/Robot_v2/DCU/_revert_backup_20260916/。
* v1.1.0.19: 连续 FWDGT 崩溃计数 + 共享 OTA 布局头。
* ① 配置页新增 word9=crash_count:FWDGT 复位时 +1;APP 喂狗稳定满 30s 后清 0
* (task_runner::maybeClearCrashCount,**不在 init 里立刻清**,否则主循环崩溃
* 永远攒不到阈值)。达到 OTA_CRASH_STAY_MAX(3) 后 BOOT 停止一切自动跳 APP
* (jump_latest_now / 窗口超时 / stay_in_ota 结束 / 备份恢复均被门控),
* 只留在 BOOT 等外部灌写。
* 动机:旧逻辑一次 FWDGT 就 EN=NOT_AVAL,10s 后 CRC 过仍自动再跳——崩溃镜像会
* 以 10s 周期打转;同时“一次误触发就永远留守”过严。N 次阈值两边都收住。
* ② 地址/字布局抽到 G:/Robot_v2/DCU/shared/ota_layout.h,APP/BOOT 共用,禁止再抄。
* v1.1.0.20: CoE 0x2503「GD32 jump to app」——主站写 1 让 BOOT 跳回 APP。
* 与 APP 0x2502 对称;回调只置 pending_app_jump,process() 在 SDO 响应后跳。
* 主站 ecat op=jump_app / 前端「跳回应用」按钮配套。
* v1.1.0.21: 修 warm jump 卡死。现象:写 0x2503 成功(ok=true)后 SPI 仍 role=boot、
* EtherCAT 邮箱全死。根因:jumpToApplication → bootEcatDeinit 里两笔 writeReg32
* (INT_EN/IRQ_CFG) 走 EXMC HBI,LAN9252 不返回时锁死 AHB,主循环卡住;SPI 由 ISR
* 应答故看起来还活着。去掉这两笔(APP 1.6.40+ 用 BYTE_TEST,不依赖清残值)。
* v1.1.0.22: [J6] 后串口乱码。putChar 只等 TBE,末字节仍在移位寄存器就 BX,
* APP 重配 USART 后线上混成 "handoff"+乱码。跳转前有界等 USART TC(~2ms)。
* requestJumpToApp 改为与 ecatCommit 成功尾巴一致:先置 CHECK/AVAL/EN 再 pending,
* 仍走 process() 同一条 delay+jumpToApplication,不另起跳转。
*/
#define BOOT_FW_VERSION_MAJOR 1U
#define BOOT_FW_VERSION_MINOR 1U
#define BOOT_FW_VERSION_PATCH 0U
#define BOOT_FW_VERSION_BUILD 22U /* 变更来由见上方注释块 */
#define BOOT_FW_VERSION_BUILD 25U /* 变更来由见上方注释块 */
/**
* Flash 布局说明(GD32E508VE,512KB)——唯一真源 shared/ota_layout.h