diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index 4b7227b..0d45603 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -187,6 +187,18 @@ void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) { u16 built_len = 0U; if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) { submitResponse(frame_buf, built_len); + /* ★ 主循环发 ACK 时同步更新重发缓存(仅 ACK,不缓存 WAIT)。 + * 旧 bug:ISR 先写 WAIT 入缓存,主循环后续发 ACK 却不更新缓存, + * 一旦该 ACK 帧因时序丢失,ESP32 重发触发 DUP-SEQ 时只能回放过期 + * WAIT → ESP32 永远只见 WAIT → stall 中止(特别在 8MHz 下必现)。 + * 现在重发会回放最终 ACK,实现“重发快且不漏”。 */ + if (type == OTA_LINK_TX_ACK && built_len > 0U && built_len <= sizeof(s_lastRespBuf)) { + uint32_t primask = enterCritical(); + memcpy(s_lastRespBuf, frame_buf, built_len); + s_lastRespLen = built_len; + s_hasCachedResp = true; + exitCritical(primask); + } } } diff --git a/GD32E50X_BOOT/main.cpp b/GD32E50X_BOOT/main.cpp index 7419b8f..8d71ba5 100644 --- a/GD32E50X_BOOT/main.cpp +++ b/GD32E50X_BOOT/main.cpp @@ -265,11 +265,28 @@ void BootloaderSystem::run() { was_active = true; idle_since = 0U; } else if (was_active) { - /* 曾进入 OTA,现在回到 IDLE(CANCEL/超时/完成)→ 跳回 APP */ + /* 曾进入 OTA,现在回到 IDLE(CANCEL/超时/完成)→ 跳回 APP。 + * + * ★ 必须与 OTA.cpp 超时路径一样用应用区标志门控: + * eraseApplicationArea() 在 SIZE 阶段已把 CHECK/AVAL 置为 + * NOT_AVAL(0xFFFFFFFF),应用区处于“半擦/半写”状态;此时 + * verifyAppCrc() 的 FLASH_EMPTY 兜底(CRC 页已擦=0xFFFFFFFF + * → 判“无 CRC 保护=有效”)会误判通过,跳入半成品固件: + * 向量表已被前几包写入 → validateApplication 通过 → 直接 + * BX 到残缺代码 → HardFault → GD32 永久失联(SPI 全超时, + * 日志特征:CANCEL 后 queryFeedback 全部 ESP_ERR_TIMEOUT)。 + * 只有标志仍为 AVAL(OTA 从未擦过应用区,旧固件完好) + * 才允许跳回;中途中止则留在 BOOT 等重试/重启。 */ + flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR; + const u32 ended_check = *flash_ptr; + flash_ptr = (volatile uint32_t *)n_APP_LATEST_AVAL_ADDR; + const u32 ended_aval = *flash_ptr; #if DEBUG_PRINT - printf("[BOOT] stay_in_ota -> ota ended, jump app\r\n"); + printf("[BOOT] stay_in_ota -> ota ended, check=0x%08lX aval=0x%08lX\r\n", + (unsigned long)ended_check, (unsigned long)ended_aval); #endif - if (verifyAppCrc(APPLATEST_START_ADDR)) { + if (ended_check == OTA_AVAL && ended_aval == OTA_AVAL && + verifyAppCrc(APPLATEST_START_ADDR)) { ota.jumpToApplication(); } /* jumpToApplication 失败(CRC/向量不合法)则留在 BOOT */ diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index 90f3853..e3d0706 100644 --- a/GD32E50X_BOOT/sys/config.h +++ b/GD32E50X_BOOT/sys/config.h @@ -16,7 +16,7 @@ typedef uint32_t u32; #define BOOT_FW_VERSION_MAJOR 1U #define BOOT_FW_VERSION_MINOR 1U #define BOOT_FW_VERSION_PATCH 0U -#define BOOT_FW_VERSION_BUILD 1U +#define BOOT_FW_VERSION_BUILD 3U /* v1.1.0.3: stay_in_ota 跳回 APP 前加 CHECK/AVAL 标志门控,修复中断 OTA 后 verifyAppCrc FLASH_EMPTY 误判跳入半成品固件导致 GD32 永久失联 */ /** * Flash 布局说明(GD32E508VE,512KB)