diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index 6805563..ae2c6de 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -54,6 +54,7 @@ volatile bool SpiOta::s_otaSessionActive = false; volatile bool SpiOta::s_ringPending = false; volatile u16 SpiOta::s_ringTxLen = 0U; volatile u32 SpiOta::s_ringDeadline = 0U; +SpiLinkState SpiOta::s_linkState; /* ============================================================ * CRC8 / 帧构建 / 帧解析 @@ -393,6 +394,11 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len) if (s_ringPending) { return false; } + if (!s_linkState.canRingNow()) { + /* ESP32 尚未完成上一帧读取(PB6 未出现上升沿),拒绝发起按铃, + * 避免下降沿被 ESP32 ISR 在非 IDLE 态丢弃。 */ + return false; + } u8 frame_buf[kTxBufSize]; u16 built_len = 0U; @@ -405,6 +411,7 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len) s_ringTxLen = built_len; s_ringPending = true; s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs; + s_linkState.onRingStart(); setSpiReady(false); exitCritical(primask); return true; @@ -421,6 +428,7 @@ void SpiOta::cancelRing() { s_txLen = 0U; s_txStreamPos = 0U; memset(s_txBuf, 0xFF, sizeof(s_txBuf)); + s_linkState.onRingTimeout(); setSpiReady(true); exitCritical(primask); } @@ -499,6 +507,23 @@ void SpiOta::configureExti() { nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U); } +void SpiOta::configureSpiReadyExti() { + /* EXTI6 上升沿:ESP32 releaseReady 通知 BOOT "读取完成"。 + * EXTI5_9_IRQn 已被 BootSleepManager 以 (0,0) 占用(PB8 深睡唤醒), + * 这里仅使能 EXTI_6 line,中断入口在 EXTI5_9_IRQHandler 中分发。 */ + gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_6); + exti_init(EXTI_6, EXTI_INTERRUPT, EXTI_TRIG_RISING); + exti_interrupt_enable(EXTI_6); + exti_interrupt_flag_clear(EXTI_6); +} + +void SpiOta::onSpiReadyExti() { + if (exti_interrupt_flag_get(EXTI_6) != RESET) { + exti_interrupt_flag_clear(EXTI_6); + s_linkState.onEsp32Complete(); + } +} + void SpiOta::init() { s_csActive = false; s_rxStreamLen = 0U; @@ -512,6 +537,8 @@ void SpiOta::init() { configureDma(); configureSpi0(); configureExti(); + configureSpiReadyExti(); + s_linkState.reset(); } /* ============================================================ diff --git a/GD32E50X_BOOT/device/SpiOta.h b/GD32E50X_BOOT/device/SpiOta.h index 5aee9b9..2830d2b 100644 --- a/GD32E50X_BOOT/device/SpiOta.h +++ b/GD32E50X_BOOT/device/SpiOta.h @@ -22,6 +22,24 @@ typedef enum { SPI_OTA_RSP_ERROR = 0x82 } spi_ota_frame_type_t; +/** + * @brief SPI 链路节奏控制状态机(与 DCU_APP AppSpiCommand::SpiLinkState 对齐)。 + * + * BOOT 主动按铃(requestEsp32Read)后,必须等待 ESP32 读取完成 + * (SPIready/PB6 上升沿,由 EXTI6 捕获)后才允许发起下一帧, + * 避免下降沿被 ESP32 ISR 在非 IDLE 态丢弃。 + */ +class SpiLinkState { +public: + bool canRingNow() const { return m_esp32Ready; } ///< 是否允许发起新按铃 + void onRingStart() { m_esp32Ready = false; } ///< 发起按铃时消费完成 flag + void onEsp32Complete() { m_esp32Ready = true; } ///< ESP32 releaseReady 上升沿触发(EXTI6 ISR 调用) + void onRingTimeout() { m_esp32Ready = true; } ///< 按铃超时取消时允许重试 + void reset() { m_esp32Ready = true; } ///< 初始化(首帧允许) +private: + volatile bool m_esp32Ready = true; +}; + class SpiOta { public: static constexpr u16 kRxBufSize = 256U; @@ -39,6 +57,13 @@ public: static void configureExti(); static void onSpiInterrupt(); static void onCsExtiInterrupt(); + + /** @brief EXTI6(PB6 SPIready) 上升沿 ISR 入口,通知 SpiLinkState ESP32 已完成读取。 + * @note 必须为 public,供 extern "C" EXTI5_9_IRQHandler 调用。 */ + static void onSpiReadyExti(); + + /** @brief 配置 EXTI6 上升沿中断(SPIready/PB6)。 */ + static void configureSpiReadyExti(); static void feedRxBytes(const u8 *data, u16 len); static bool hasTxFrame(); static bool hasActiveSession(); @@ -92,6 +117,8 @@ private: static void queueTxFrame(const u8 *frame, u16 len); static uint32_t enterCritical(); static void exitCritical(uint32_t primask); + + static SpiLinkState s_linkState; ///< 主动按铃节奏控制状态机 }; /* OTA.cpp 通过此变量更新 SPI 响应帧的 CREDIT 字段 */ diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index 22f1745..5c60f31 100644 --- a/GD32E50X_BOOT/sys/config.h +++ b/GD32E50X_BOOT/sys/config.h @@ -1,4 +1,4 @@ -#ifndef __CONFIG_H__ +#ifndef __CONFIG_H__ #define __CONFIG_H__ #include @@ -15,7 +15,7 @@ typedef uint32_t u32; /* BOOT 固件版本号 */ #define BOOT_FW_VERSION_MAJOR 1U #define BOOT_FW_VERSION_MINOR 0U -#define BOOT_FW_VERSION_PATCH 0U +#define BOOT_FW_VERSION_PATCH 1U #define BOOT_FW_VERSION_BUILD 1U /** diff --git a/GD32E50X_BOOT/sys/initial.cpp b/GD32E50X_BOOT/sys/initial.cpp index 34e1136..0c194c8 100644 --- a/GD32E50X_BOOT/sys/initial.cpp +++ b/GD32E50X_BOOT/sys/initial.cpp @@ -267,6 +267,9 @@ void BootSleepManager::onExtiInterrupt(void) system_soft_reset(); } + /** EXTI_6 (PB6 SPIready) 上升沿:通知 SpiOta 状态机 ESP32 已完成读取。 */ + SpiOta::onSpiReadyExti(); + /** 继续分发给 SPI OTA 的 CS 边沿处理逻辑。 */ spi_ota_cs_exti_handler(); }