feat(SpiOta): 添加SPI链路状态机避免帧丢失

- 引入SpiLinkState类管理SPI通信节奏控制状态机
- 实现链路状态控制,确保ESP32完成读取前不发起新的按铃请求
- 配置EXTI6中断监控PB6(SPIready)上升沿信号
- 在requestEsp32Read中检查canRingNow()状态防止帧冲突
- 添加onSpiReadyExti中断处理函数更新链路状态
This commit is contained in:
cbb
2026-07-09 02:46:56 +08:00
parent 59c2bf5527
commit dfc12611ed
4 changed files with 59 additions and 2 deletions
+27
View File
@@ -54,6 +54,7 @@ volatile bool SpiOta::s_otaSessionActive = false;
volatile bool SpiOta::s_ringPending = false; volatile bool SpiOta::s_ringPending = false;
volatile u16 SpiOta::s_ringTxLen = 0U; volatile u16 SpiOta::s_ringTxLen = 0U;
volatile u32 SpiOta::s_ringDeadline = 0U; volatile u32 SpiOta::s_ringDeadline = 0U;
SpiLinkState SpiOta::s_linkState;
/* ============================================================ /* ============================================================
* CRC8 / 帧构建 / 帧解析 * CRC8 / 帧构建 / 帧解析
@@ -393,6 +394,11 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len)
if (s_ringPending) { if (s_ringPending) {
return false; return false;
} }
if (!s_linkState.canRingNow()) {
/* ESP32 尚未完成上一帧读取(PB6 未出现上升沿),拒绝发起按铃,
* 避免下降沿被 ESP32 ISR 在非 IDLE 态丢弃。 */
return false;
}
u8 frame_buf[kTxBufSize]; u8 frame_buf[kTxBufSize];
u16 built_len = 0U; 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_ringTxLen = built_len;
s_ringPending = true; s_ringPending = true;
s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs; s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
s_linkState.onRingStart();
setSpiReady(false); setSpiReady(false);
exitCritical(primask); exitCritical(primask);
return true; return true;
@@ -421,6 +428,7 @@ void SpiOta::cancelRing() {
s_txLen = 0U; s_txLen = 0U;
s_txStreamPos = 0U; s_txStreamPos = 0U;
memset(s_txBuf, 0xFF, sizeof(s_txBuf)); memset(s_txBuf, 0xFF, sizeof(s_txBuf));
s_linkState.onRingTimeout();
setSpiReady(true); setSpiReady(true);
exitCritical(primask); exitCritical(primask);
} }
@@ -499,6 +507,23 @@ void SpiOta::configureExti() {
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U); 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() { void SpiOta::init() {
s_csActive = false; s_csActive = false;
s_rxStreamLen = 0U; s_rxStreamLen = 0U;
@@ -512,6 +537,8 @@ void SpiOta::init() {
configureDma(); configureDma();
configureSpi0(); configureSpi0();
configureExti(); configureExti();
configureSpiReadyExti();
s_linkState.reset();
} }
/* ============================================================ /* ============================================================
+27
View File
@@ -22,6 +22,24 @@ typedef enum {
SPI_OTA_RSP_ERROR = 0x82 SPI_OTA_RSP_ERROR = 0x82
} spi_ota_frame_type_t; } 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 { class SpiOta {
public: public:
static constexpr u16 kRxBufSize = 256U; static constexpr u16 kRxBufSize = 256U;
@@ -39,6 +57,13 @@ public:
static void configureExti(); static void configureExti();
static void onSpiInterrupt(); static void onSpiInterrupt();
static void onCsExtiInterrupt(); 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 void feedRxBytes(const u8 *data, u16 len);
static bool hasTxFrame(); static bool hasTxFrame();
static bool hasActiveSession(); static bool hasActiveSession();
@@ -92,6 +117,8 @@ private:
static void queueTxFrame(const u8 *frame, u16 len); static void queueTxFrame(const u8 *frame, u16 len);
static uint32_t enterCritical(); static uint32_t enterCritical();
static void exitCritical(uint32_t primask); static void exitCritical(uint32_t primask);
static SpiLinkState s_linkState; ///< 主动按铃节奏控制状态机
}; };
/* OTA.cpp 通过此变量更新 SPI 响应帧的 CREDIT 字段 */ /* OTA.cpp 通过此变量更新 SPI 响应帧的 CREDIT 字段 */
+2 -2
View File
@@ -1,4 +1,4 @@
#ifndef __CONFIG_H__ #ifndef __CONFIG_H__
#define __CONFIG_H__ #define __CONFIG_H__
#include <stdint.h> #include <stdint.h>
@@ -15,7 +15,7 @@ typedef uint32_t u32;
/* BOOT 固件版本号 */ /* BOOT 固件版本号 */
#define BOOT_FW_VERSION_MAJOR 1U #define BOOT_FW_VERSION_MAJOR 1U
#define BOOT_FW_VERSION_MINOR 0U #define BOOT_FW_VERSION_MINOR 0U
#define BOOT_FW_VERSION_PATCH 0U #define BOOT_FW_VERSION_PATCH 1U
#define BOOT_FW_VERSION_BUILD 1U #define BOOT_FW_VERSION_BUILD 1U
/** /**
+3
View File
@@ -267,6 +267,9 @@ void BootSleepManager::onExtiInterrupt(void)
system_soft_reset(); system_soft_reset();
} }
/** EXTI_6 (PB6 SPIready) 上升沿:通知 SpiOta 状态机 ESP32 已完成读取。 */
SpiOta::onSpiReadyExti();
/** 继续分发给 SPI OTA 的 CS 边沿处理逻辑。 */ /** 继续分发给 SPI OTA 的 CS 边沿处理逻辑。 */
spi_ota_cs_exti_handler(); spi_ota_cs_exti_handler();
} }