From 52e59876cf0fd8241b75049f097e2a62e1f64023 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Mon, 6 Jul 2026 19:13:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(SpiOta):=20=E6=B7=BB=E5=8A=A0SPI=20OTA?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=A1=AC=E4=BB=B6=E6=8F=A1=E6=89=8B=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对BOOT实际支持的OTA协议帧(REQ/DATA/CANCEL)的过滤, 对不支持的帧类型(如CAN bridge命令)返回ERROR响应,避免误处理 - 实现SPI Ready硬件握手功能,复用PB6引脚作为SPI Ready信号输出, CS下降沿拉低表示从机忙碌,响应完成后拉高表示就绪 - 新增setSpiReady接口用于控制SPI Ready引脚状态, 确保与DCU_APP行为一致,适配ESP32的硬件握手时序 - 修复因未知帧类型被误认为CANCEL而导致OTA状态机被频繁重置的问题 ``` --- GD32E50X_BOOT/device/SpiOta.cpp | 48 +++++++++++++++++++++++++++++++-- GD32E50X_BOOT/device/SpiOta.h | 6 +++++ GD32E50X_BOOT/sys/config.h | 12 ++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index 94ddb26..839b553 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -1,4 +1,4 @@ -#include "SpiOta.h" +#include "SpiOta.h" #include "core_cm33.h" #include "gd32e50x_gpio.h" #include "gd32e50x_exti.h" @@ -237,6 +237,26 @@ void SpiOta::isrProcessFrame() { return; } + /* 仅转发 BOOT 实际支持的 OTA 协议帧(REQ/DATA/CANCEL)给 OTA 状态机。 + * 其它帧类型(如 0xC0~0xC3 CAN bridge 命令,由 DCU_APP 注册处理器处理, + * BOOT 无此功能)直接返回 ERROR,避免 mapMsgType 的 default 分支把 + * 未知类型误当成 CANCEL,导致 ESP32 待机时 1Hz CAN 反馈轮询(0xC1) + * 不断重置 OTA 状态机,干扰正常 OTA 握手。 */ + if (frame_type != SPI_OTA_CMD_REQ && + frame_type != SPI_OTA_CMD_DATA && + frame_type != SPI_OTA_CMD_CANCEL) { + u8 err = 0xEEU; + u8 err_frame[kTxBufSize] = {}; + u16 built = 0U; + if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) { + queueTxFrame(err_frame, built); + } + isrLogFrameEx("[UNK-TYPE]", s_rxStream, (rx_len < 8U) ? rx_len : 8U); + s_rxStreamLen = 0U; + s_isrResponded = true; + return; + } + if (s_otaHandler != nullptr) { s_otaSessionActive = true; s_otaHandler->setTransportSender(sender); @@ -286,8 +306,21 @@ void SpiOta::configureGpio() { gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE); gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5); gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4); - gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7); + gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_7); gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15); + /* PB6 复用为 SPI Ready 握手输出,默认 HIGH(就绪),允许主机立刻发起首帧。 + * 与 DCU_APP app_spi_command.cpp configureGpio 行为一致, + * 适配 ESP32 GPIO10 输入监测 PB6 电平的硬件握手时序。 */ + gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN); + gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); +} + +void SpiOta::setSpiReady(bool ready) { + if (ready) { + gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); + } else { + gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); + } } void SpiOta::configureSpi0() { @@ -386,6 +419,11 @@ void SpiOta::init() { void SpiOta::onCsAssert() { if (s_csActive) { return; } s_csActive = true; + /* CS 下降沿立即拉低 PB6:表示从机即将忙碌, + * 防止主机在 onCsRelease 准备好响应前抢跑发起下一笔 CS。 + * (若此帧为空轮询/残包,onCsRelease 会恢复 HIGH) + * 与 DCU_APP AppSpiCommand::onCsAssert 行为一致。 */ + setSpiReady(false); s_rxStreamLen = 0U; s_txStreamPos = 0U; s_isrResponded = false; @@ -442,6 +480,12 @@ void SpiOta::onCsRelease() { if (s_rxStreamLen > 0U && !s_isrResponded) { isrProcessFrame(); } + + /* BOOT 在 ISR 内同步完成帧处理,响应帧已就绪(或为空轮询/残包无需响应), + * 拉高 PB6 通知主机可发起下一笔事务。 + * 与 DCU_APP processCommand() 末尾 setSpiReady(true) 行为一致; + * 空轮询事务同样恢复 HIGH,避免 PB6 卡 LOW 导致 ESP32 waitSpiReady 超时。 */ + setSpiReady(true); } /* ============================================================ diff --git a/GD32E50X_BOOT/device/SpiOta.h b/GD32E50X_BOOT/device/SpiOta.h index f65e905..fff793f 100644 --- a/GD32E50X_BOOT/device/SpiOta.h +++ b/GD32E50X_BOOT/device/SpiOta.h @@ -46,6 +46,12 @@ public: static bool hasActiveSession(); static u16 copyTxFrame(u8 *out, u16 max_len); + /** @brief 翻转 SPI Ready 握手引脚(PB6)电平。 + * @param[in] ready true=就绪(HIGH), false=忙(LOW)。 + * @note GPIO BSRR/BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。 + * 与 DCU_APP AppSpiCommand::setSpiReady 行为一致,适配 ESP32 硬件握手。 */ + static void setSpiReady(bool ready); + static void isrProcessFrame(); static void tryProcessCompleteFrame(); static void isrLogFrame(const char *tag, const u8 *data, u16 len); diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index f89d4a1..22f1745 100644 --- a/GD32E50X_BOOT/sys/config.h +++ b/GD32E50X_BOOT/sys/config.h @@ -1,7 +1,8 @@ -#ifndef __CONFIG_H__ +#ifndef __CONFIG_H__ #define __CONFIG_H__ #include +#include "gd32e50x.h" /** 项目内部统一使用的简写类型。 */ typedef uint8_t u8; @@ -77,6 +78,15 @@ inline constexpr u32 CANFD_OTA_ACK_ID = 0x00000102U; inline constexpr u32 CANFD_OTA_WAIT_ID = 0x00000103U; inline constexpr u32 CANFD_OTA_CANCEL_ID = 0x00000000U; +/** + * SPI Ready 握手输出引脚(PB6 复用为 SPI Ready)。 + * 与 DCU_APP sys/config.h 保持一致,适配 ESP32 waitSpiReady 硬件握手: + * - CS 下降沿拉低 PB6,表示从机忙碌 + * - 响应帧就绪后拉高 PB6,通知主机可发起下一笔事务 + */ +inline constexpr u32 CONFIG_SPI_READY_PORT = GPIOB; +inline constexpr u32 CONFIG_SPI_READY_PIN = GPIO_PIN_6; + /** GD32E50x 芯片 UID 三段地址。 */ inline constexpr u32 DEVICE_SERIAL_ADDR_LSB = 0x1FFFF7E8U; inline constexpr u32 DEVICE_SERIAL_ADDR_MID = 0x1FFFF7ECU;