feat(SpiOta): 添加SPI链路状态机避免帧丢失
- 引入SpiLinkState类管理SPI通信节奏控制状态机 - 实现链路状态控制,确保ESP32完成读取前不发起新的按铃请求 - 配置EXTI6中断监控PB6(SPIready)上升沿信号 - 在requestEsp32Read中检查canRingNow()状态防止帧冲突 - 添加onSpiReadyExti中断处理函数更新链路状态
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
|
|||||||
@@ -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 字段 */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user