From 45690f61340851b55c6da85aba364c6176093eaa Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Tue, 1 Sep 2026 02:48:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(SpiOta):=20=E4=BF=AE=E5=A4=8DISR=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E7=BC=93=E5=AD=98=E9=80=BB=E8=BE=91=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E7=BC=93=E5=AD=98ACK/ERROR=E6=8B=92=E7=BB=9DWAIT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此前isrProcessFrame()无条件将当前s_txBuf缓存到s_lastRespBuf, 导致OTA数据包在ISR入队时立即返回的WAIT被写入重发缓存,覆盖 了sender()内只缓存ACK的语义。当ESP32超时重发时,DUP-SEQ分支 回放的仍是过期WAIT,造成50ms等待→重发链被拉长的问题。 现在按v2头TYPE字节过滤,只将ACK/ERROR放入重发缓存,WAIT由 sender()途径主循环写ACK时再统一更新缓存。 feat(main): 添加ESP32_EN引脚初始化和调试信息输出 添加PC9引脚初始化代码,确保BOOT阶段ESP32能正常启动。 同时增加调试打印信息,便于检查ESP32_EN状态和D3V3电源域 就绪情况。 perf(boot): 优化主循环节拍控制策略 回滚之前"有工作时不休眠"的尝试,恢复为固定1ms节拍。 因为条件化延迟会让ESP32在armSpiDmasForNextCs()的 spi_dma_disable/enable窗口中抓取CS,读到错帧导致通信异常。 fix(OTA): 恢复20us超时步进以保证SPI通信稳定性 回滚之前"本轮干活就不delay"的尝试,恢复为统一20us步进。 因为实测发现条件化延迟会让ESP32在DMA开关窗口中抓取CS, 导致READ错帧和saw_wait=0问题。 chore(config): 更新固件版本号至v1.1.0.5 feat(UART): 将调试串口波特率提升至3Mbps 提升Bootloader系统的调试串口通信速率,从11520提升至 3000000,加快日志输出和调试效率。 --- GD32E50X_BOOT/device/SpiOta.cpp | 22 +++++++++++++++++----- GD32E50X_BOOT/main.cpp | 21 ++++++++++++++++++++- GD32E50X_BOOT/sys/OTA.cpp | 6 +++++- GD32E50X_BOOT/sys/config.h | 2 +- GD32E50X_BOOT/sys/initial.cpp | 4 ++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index 0d45603..4c574f0 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -503,12 +503,24 @@ void SpiOta::isrProcessFrame() { } } - /* ---- 6. 分发后缓存响应到 s_lastRespBuf ---- */ + /* ---- 6. 分发后缓存响应到 s_lastRespBuf:只缓存 ACK/ERROR,拒收 WAIT ---- + * + * 此前这里无条件 memcpy 当前 s_txBuf 进 s_lastRespBuf。OTA 数据包 + * 在 ISR 入队时会立刻回 WAIT(见上),WAIT 会被写进重发缓存, + * 覆盖 sender() 内“只缓存 ACK”的语义:之后 ESP32 一旦 50ms 超时重发, + * DUP-SEQ 分支回放的还是过期 WAIT,又是 50ms 等待 → 重发链被拉长, + * 只剩主循环终于把 ACK 写进 s_txBuf 并“凑巧”再经过这里时才解脱。 + * + * 按 v2 头 TYPE 字节(V2_OFF_TYPE=11)过滤:只把 ACK/ERROR 放进重发缓存, + * WAIT 由 sender() 途经主循环写 ACK 时再统一更新缓存。 */ if (s_txPending && s_txLen > 0U && s_txLen <= sizeof(s_lastRespBuf)) { - memcpy(s_lastRespBuf, s_txBuf, s_txLen); - s_lastRespLen = s_txLen; - s_lastReqSeq = hdr.tx_seq; - s_hasCachedResp = true; + const u8 cached_type = (s_txLen > V2_OFF_TYPE) ? s_txBuf[V2_OFF_TYPE] : 0xFFU; + if (cached_type == SPI_OTA_RSP_ACK || cached_type == SPI_OTA_RSP_ERROR) { + memcpy(s_lastRespBuf, s_txBuf, s_txLen); + s_lastRespLen = s_txLen; + s_lastReqSeq = hdr.tx_seq; + s_hasCachedResp = true; + } } s_isrResponded = true; diff --git a/GD32E50X_BOOT/main.cpp b/GD32E50X_BOOT/main.cpp index 8d71ba5..5ff023c 100644 --- a/GD32E50X_BOOT/main.cpp +++ b/GD32E50X_BOOT/main.cpp @@ -1,4 +1,4 @@ -#include "main.h" +#include "main.h" #include "device/Can.h" #include "device/SpiOta.h" #include "sys/config.h" @@ -42,6 +42,11 @@ void BootloaderSystem::initHardware() { (unsigned long)APPLATEST_START_ADDR, (unsigned long)APP_MAX_SIZE); printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock); + printf("[BOOT] ESP32_EN PC9=%d (1=released, 0=HOLDING ESP32 IN RESET)\r\n", + (int)gpio_input_bit_get(GPIOC, GPIO_PIN_9)); + /* D3V3 正常时 HIGH;若此处读回 0 说明 ESP32 上拉电源域未就绪 */ + printf("[BOOT] D3V3 check: PC9 readback=%d\r\n", + (int)gpio_input_bit_get(GPIOC, GPIO_PIN_9)); #endif rcu_periph_clock_enable(RCU_CRC); /* OTA CRC 校验需要 CRC 外设时钟 */ CAN0_transmiter.init(); @@ -292,6 +297,12 @@ void BootloaderSystem::run() { /* jumpToApplication 失败(CRC/向量不合法)则留在 BOOT */ was_active = false; } + /* 呼吸节拍:固定 1ms 节流,避免满速空转反复 re-arm SPI DMA。 + * 之前做过"有工作时不睡"的尝试(配合 P1b),实测会让 ESP32 在 + * 主循环 armSpiDmasForNextCs() 的 spi_dma_disable/enable 窗口里 + * 抓 CS,读到错帧 → saw_wait=0 → 连 WAIT 都看不到。 + * 回滚为固定 1ms:DATA→ACK 延迟由 SpiOta::poll 内的 + * 消化队列+回 ACK 承担,不在这条 1ms 上。 */ delay_1ms(1); } } else { @@ -374,6 +385,14 @@ int main(void) { * 复位后 GPIO 高阻,靠外部上拉保持 HIGH,不再制造 LOW→HIGH 上升沿。 */ rcu_periph_clock_enable(RCU_GPIOB); + /* ESP32 EN 释放:BOOT 阶段也必须把 PC9 拉高。 + * 原理图 p3:ESP_CHIP_EN = R39 上拉 D3V3,U11(WLESS_EN=PC9)/U10(PROGRAM_EN) 肖特基钳低。 + * 复位后 PC9 为浮空输入,WLESS_EN 被 C41 拉低 → ESP32 一直被按住复位。 + * APP 在 initial.cpp 中会再次置高;BOOT 等 OTA 时若不主动拉高,ESP32 永远起不来。 */ + rcu_periph_clock_enable(RCU_GPIOC); + gpio_bit_set(GPIOC, GPIO_PIN_9); + gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); + sys.initHardware(); sys.run(); diff --git a/GD32E50X_BOOT/sys/OTA.cpp b/GD32E50X_BOOT/sys/OTA.cpp index 4f0ee20..d56d00b 100644 --- a/GD32E50X_BOOT/sys/OTA.cpp +++ b/GD32E50X_BOOT/sys/OTA.cpp @@ -410,7 +410,11 @@ void OTAHandler::process() { } else { /** * 非空闲态时,按固定节奏推进超时计数。 - * 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。 + * 这里用 20us 粗略步进,不追求绝对精确,只做会话保活超时保护。 + * ★ 回滚说明:曾尝试过"本轮干活就不 delay",但实测配合主循环取掉 + * 1ms 节拍后会导致 SpiOta 被反复满速调用,armSpiDmasForNextCs() + * 的 spi_dma_disable/enable 窗口被 ESP32 抓到,READ 到错帧、 + * saw_wait=0。恢复为统一 20us 步进,让回 ACK 节奏稳定。 */ delay_1us(20); ota_time++; diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index e3d0706..c963004 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 3U /* v1.1.0.3: stay_in_ota 跳回 APP 前加 CHECK/AVAL 标志门控,修复中断 OTA 后 verifyAppCrc FLASH_EMPTY 误判跳入半成品固件导致 GD32 永久失联 */ +#define BOOT_FW_VERSION_BUILD 5U /* v1.1.0.5: isrProcessFrame 分发尾部缓存只收 ACK/ERROR,滤掉 WAIT——之前 WAIT 会污染 s_lastRespBuf,ESP32 超时重发时 DUP-SEQ 回放的仍是过期 WAIT 而非最终 ACK,重发链被拉长。曾同时试验过 stay_in_ota 去掉固定 1ms delay + process() 20us 步进条件化(v1.1.0.5 中间态),实测 ESP32 会在 armSpiDmasForNextCs() 的 dma disable/enable 窗口里抓 CS 读到错帧、saw_wait=0,已回滚为固定节拍。 */ /** * Flash 布局说明(GD32E508VE,512KB) diff --git a/GD32E50X_BOOT/sys/initial.cpp b/GD32E50X_BOOT/sys/initial.cpp index 2f7c66e..a08af58 100644 --- a/GD32E50X_BOOT/sys/initial.cpp +++ b/GD32E50X_BOOT/sys/initial.cpp @@ -28,9 +28,9 @@ u8 BootDebugUart::init() gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10); - /** 配置串口为 115200 8N1(其余参数使用默认值)。 */ + /** 配置串口为 3000000 8N1(其余参数使用默认值)。 */ usart_deinit(USART0); - usart_baudrate_set(USART0, 115200U); + usart_baudrate_set(USART0, 3000000U); usart_receive_config(USART0, USART_RECEIVE_ENABLE); usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); usart_enable(USART0);