feat(boot): 重构SPI OTA响应提交机制并修复CRC写入问题
- 将queueTxFrame重命名为submitResponse以更准确反映功能 - 移除已废弃的SPI ready EXTI配置和相关处理函数 - 删除未使用的feedRxBytes和copyTxFrame接口函数 - 修复CRC地址页擦除问题,确保OTA完成后能正确写入和验证CRC - 更新BOOT固件版本号从1.0.1.2到1.0.2.1
This commit is contained in:
@@ -186,7 +186,7 @@ void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
||||
u8 frame_buf[kTxBufSize] = {};
|
||||
u16 built_len = 0U;
|
||||
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
|
||||
queueTxFrame(frame_buf, built_len);
|
||||
submitResponse(frame_buf, built_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ static u16 stopSpiDmas(void) {
|
||||
return got;
|
||||
}
|
||||
|
||||
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
||||
void SpiOta::submitResponse(const u8 *frame, u16 len) {
|
||||
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
||||
uint32_t primask = enterCritical();
|
||||
memcpy(s_txBuf, frame, len);
|
||||
@@ -297,7 +297,7 @@ void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
||||
/* ★ 不在此处启动 DMA。
|
||||
*
|
||||
* 本函数可从 RBNE ISR 中调用(isrProcessFrame → handleLinkMessage
|
||||
* → send_structured_ack → sender → queueTxFrame),此时 CS 仍为 LOW,
|
||||
* → send_structured_ack → sender → submitResponse),此时 CS 仍为 LOW,
|
||||
* DMA 正在发送上一次 onCsAssert() 装载的旧响应。
|
||||
*
|
||||
* 若在此处 startSpiTxDma(),GD32 DMA 控制器会先 CHEN=0 再 CHEN=1,
|
||||
@@ -358,7 +358,7 @@ void SpiOta::isrProcessFrame() {
|
||||
(uint8_t)g_localCredit,
|
||||
(uint8_t)s_txSeq, hdr.tx_seq,
|
||||
hello_ack, &built)) {
|
||||
queueTxFrame(hello_ack, (u16)built);
|
||||
submitResponse(hello_ack, (u16)built);
|
||||
/* 缓存 HELLO_ACK 以应对 ESP32 重试同一 HELLO。 */
|
||||
memcpy(s_lastRespBuf, hello_ack, built);
|
||||
s_lastRespLen = (u16)built;
|
||||
@@ -380,7 +380,7 @@ void SpiOta::isrProcessFrame() {
|
||||
(uint8_t)g_localCredit, V2_ERR_SESSION,
|
||||
(uint8_t)s_expectedRxSeq,
|
||||
err_frame, &built)) {
|
||||
queueTxFrame(err_frame, (u16)built);
|
||||
submitResponse(err_frame, (u16)built);
|
||||
}
|
||||
isrLogFrameEx("[SES-ERR]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
s_rxStreamLen = 0U;
|
||||
@@ -399,7 +399,7 @@ void SpiOta::isrProcessFrame() {
|
||||
* 注意:缓存若仍是 ISR 即时 WAIT,而主循环已处理完并写过 ACK,
|
||||
* 应优先回放 ACK(type=0x80),避免 ESP32 长时间只见 WAIT → 断续。 */
|
||||
if (s_hasCachedResp && s_lastRespLen > 0U) {
|
||||
queueTxFrame(s_lastRespBuf, s_lastRespLen);
|
||||
submitResponse(s_lastRespBuf, s_lastRespLen);
|
||||
} else if (s_otaDataCnt > 0U) {
|
||||
u8 wait_hint = 0x03U;
|
||||
sender(OTA_LINK_TX_WAIT, 1U, &wait_hint);
|
||||
@@ -419,7 +419,7 @@ void SpiOta::isrProcessFrame() {
|
||||
(uint8_t)g_localCredit, V2_ERR_SEQ_JUMP,
|
||||
(uint8_t)s_expectedRxSeq,
|
||||
err_frame, &built)) {
|
||||
queueTxFrame(err_frame, (u16)built);
|
||||
submitResponse(err_frame, (u16)built);
|
||||
}
|
||||
isrLogFrameEx("[SEQ-JMP]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
s_rxStreamLen = 0U;
|
||||
@@ -432,7 +432,7 @@ void SpiOta::isrProcessFrame() {
|
||||
u8 ack_frame[kTxBufSize] = {};
|
||||
u16 built = 0U;
|
||||
if (buildFrame(SPI_OTA_RSP_ACK, nullptr, 0U, ack_frame, &built)) {
|
||||
queueTxFrame(ack_frame, built);
|
||||
submitResponse(ack_frame, built);
|
||||
/* 缓存 ACK 响应以应对重复的 0xB0。 */
|
||||
memcpy(s_lastRespBuf, ack_frame, built);
|
||||
s_lastRespLen = built;
|
||||
@@ -462,7 +462,7 @@ void SpiOta::isrProcessFrame() {
|
||||
u8 err_frame[kTxBufSize] = {};
|
||||
u16 built = 0U;
|
||||
if (buildFrame(SPI_OTA_RSP_ERROR, &err, 1U, err_frame, &built)) {
|
||||
queueTxFrame(err_frame, built);
|
||||
submitResponse(err_frame, built);
|
||||
}
|
||||
isrLogFrameEx("[UNK-TYPE]", s_rxStream, (rx_len < 8U) ? rx_len : 8U);
|
||||
s_rxStreamLen = 0U;
|
||||
@@ -616,7 +616,7 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len)
|
||||
}
|
||||
|
||||
uint32_t primask = enterCritical();
|
||||
queueTxFrame(frame_buf, built_len);
|
||||
submitResponse(frame_buf, built_len);
|
||||
s_ringTxLen = built_len;
|
||||
s_ringPending = true;
|
||||
s_ringDeadline = SysTickTimer::getMs() + kRingTimeoutMs;
|
||||
@@ -742,27 +742,6 @@ 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 深睡唤醒)。
|
||||
* 此处显式使能 EXTI5_9 NVIC 并设置抢占优先级 (3,1),低于 CS 中断 (1,1)
|
||||
* 与 SPI0 RBNE (2,0),确保 Ready 上升沿不会打断正在进行的帧处理。
|
||||
* 中断入口在 EXTI5_9_IRQHandler 中分发到 onSpiReadyExti()。 */
|
||||
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);
|
||||
nvic_irq_enable(EXTI5_9_IRQn, 3U, 1U);
|
||||
}
|
||||
|
||||
void SpiOta::onSpiReadyExti() {
|
||||
/* v2 DATA_READY: ESP32 不再操作 PB6,EXTI6 上升沿不再有意义。
|
||||
* 仅清理可能残留的挂起标志,避免 NVIC 重复进入。 */
|
||||
if (exti_interrupt_flag_get(EXTI_6) != RESET) {
|
||||
exti_interrupt_flag_clear(EXTI_6);
|
||||
}
|
||||
}
|
||||
|
||||
void SpiOta::init() {
|
||||
s_csActive = false;
|
||||
s_rxStreamLen = 0U;
|
||||
@@ -788,7 +767,6 @@ void SpiOta::init() {
|
||||
configureDma();
|
||||
configureSpi0();
|
||||
configureExti();
|
||||
configureSpiReadyExti();
|
||||
s_linkState.reset();
|
||||
/* 首笔 CS 到来前预装 DMA,避免 EXTI 晚于首 SCK */
|
||||
armSpiDmasForNextCs();
|
||||
@@ -837,7 +815,7 @@ void SpiOta::onCsRelease() {
|
||||
* pending 会永远为 true → updateDataReady() 把 PB6 永久拉低 →
|
||||
* ESP32 waitSpiReady(等 HIGH) 每包空等 SPI_READY_TIMEOUT_MS,
|
||||
* 1116 包 OTA 表现为"卡在 1/1116 十几分钟"。
|
||||
* 先清交付态;随后 isrProcessFrame/queueTxFrame 可再置 pending。
|
||||
* 先清交付态;随后 isrProcessFrame/submitResponse 可再置 pending。
|
||||
* 重复帧缓存在 s_lastRespBuf,不依赖 s_txPending 保活。
|
||||
*
|
||||
* ★ 修复 ACK 丢失死锁(2026-07-14):
|
||||
@@ -1022,30 +1000,8 @@ void SpiOta::poll() {
|
||||
|
||||
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
|
||||
|
||||
void SpiOta::feedRxBytes(const u8 *data, u16 len) {
|
||||
if (!data || len == 0U || !s_csActive) { return; }
|
||||
if (len > kRxBufSize) { data += (len - kRxBufSize); len = kRxBufSize; }
|
||||
memcpy(s_rxStream, data, len);
|
||||
s_rxStreamLen = len;
|
||||
}
|
||||
|
||||
bool SpiOta::hasTxFrame() { return s_txPending; }
|
||||
|
||||
bool SpiOta::hasActiveSession() { return s_otaSessionActive; }
|
||||
|
||||
u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
|
||||
if (!out || max_len == 0U) { return 0U; }
|
||||
uint32_t primask = enterCritical();
|
||||
if (!s_txPending || s_txLen == 0U) { exitCritical(primask); return 0U; }
|
||||
u16 copy_len = (s_txLen < max_len) ? s_txLen : max_len;
|
||||
memcpy(out, s_txBuf, copy_len);
|
||||
s_txPending = false;
|
||||
s_txLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
exitCritical(primask);
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
|
||||
u8 SpiOta::pendingDataCount() {
|
||||
return s_otaDataCnt;
|
||||
@@ -1065,9 +1021,7 @@ void spi_ota_spi0_init(void) { SpiOta::configureSpi0(); }
|
||||
void spi_ota_exti_init(void) { SpiOta::configureExti(); }
|
||||
void spi_ota_irq_handler(void) { SpiOta::onSpiInterrupt(); }
|
||||
void spi_ota_cs_exti_handler(void) { SpiOta::onCsExtiInterrupt(); }
|
||||
void spi_ota_feed_rx_bytes(const u8 *data, u16 len) { SpiOta::feedRxBytes(data, len); }
|
||||
bool spi_ota_has_tx_frame(void) { return SpiOta::hasTxFrame(); }
|
||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out, max_len); }
|
||||
|
||||
extern "C" void SPI0_IRQHandler(void) {
|
||||
SpiOta::onSpiInterrupt();
|
||||
|
||||
@@ -59,16 +59,7 @@ public:
|
||||
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();
|
||||
static u16 copyTxFrame(u8 *out, u16 max_len);
|
||||
|
||||
/** @brief BOOT 主动按铃:构建请求帧并拉低 SPIready,通知 ESP32 来读。
|
||||
* @param[in] frame_type 请求帧类型。
|
||||
@@ -124,7 +115,7 @@ private:
|
||||
const u8 **payload,
|
||||
u16 *payload_len);
|
||||
static ota_link_msg_type_t mapMsgType(spi_ota_frame_type_t type);
|
||||
static void queueTxFrame(const u8 *frame, u16 len);
|
||||
static void submitResponse(const u8 *frame, u16 len);
|
||||
static uint32_t enterCritical();
|
||||
static void exitCritical(uint32_t primask);
|
||||
|
||||
@@ -149,9 +140,7 @@ void spi_ota_spi0_init(void);
|
||||
void spi_ota_exti_init(void);
|
||||
void spi_ota_irq_handler(void);
|
||||
void spi_ota_cs_exti_handler(void);
|
||||
void spi_ota_feed_rx_bytes(const u8 *data, u16 len);
|
||||
bool spi_ota_has_tx_frame(void);
|
||||
u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ static void ota_spi_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
||||
* 关键修复:这里不能是空函数!
|
||||
*
|
||||
* 之前是占位实现,因为 ISR 内的回包由 SpiOta::isrProcessFrame() 直接调用
|
||||
* SpiOta::sender() → queueTxFrame() → DMA 推送。
|
||||
* SpiOta::sender() → submitResponse() → DMA 推送。
|
||||
*
|
||||
* 但 process() 主循环中也会发送 ACK(如擦除完成后的 ACK(seq=0)),
|
||||
* 这些调用走的是 transport_send = ota_spi_sender。
|
||||
@@ -635,6 +635,31 @@ void OTAHandler::eraseApplicationArea() {
|
||||
SpiOta::servicePendingTx();
|
||||
}
|
||||
}
|
||||
|
||||
/* 关键修复:CRC 地址 (n_APP_LATEST_CRC_ADDR = APP_FIRMWARE_SIZE 末尾) 所在的 page
|
||||
* 必须擦除,否则 writeAppCrc 调用 fmc_word_program 时会因目标 word 非擦除态
|
||||
* (上次 OTA 残留的旧 CRC) 触发 FMC_PGERR (status=2),导致 CRC 写入失败 →
|
||||
* verifyAppCrc FAIL → abort jump → GD32 卡在 BOOT,ESP32 探测 APP 超时。
|
||||
*
|
||||
* 当固件较小时(如 53KB),固件 pages 不覆盖 CRC page(0x0807D800~0x0807DFFF),
|
||||
* 需要额外擦除这一页。固件较大时(>0x35800)固件 pages 已覆盖 CRC page,跳过。
|
||||
* 仅多擦 1 page (~30ms),不破坏 eraseApplicationArea 的速度优化。 */
|
||||
const u32 crc_page_start = n_APP_LATEST_CRC_ADDR & ~(FMC_PAGE_SIZE - 1U);
|
||||
if (erase_end <= crc_page_start) {
|
||||
__disable_irq();
|
||||
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
|
||||
fmc_state_enum crc_status = fmc_page_erase(crc_page_start);
|
||||
__enable_irq();
|
||||
if (crc_status != FMC_READY) {
|
||||
ota_error = OTA_ERROR_FLASH_ERROR;
|
||||
ota_bind_spi_transport();
|
||||
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
|
||||
}
|
||||
#if DEBUG_PRINT
|
||||
printf("[FW] E crc_page=0x%08lX status=%u\r\n",
|
||||
(unsigned long)crc_page_start, (unsigned)crc_status);
|
||||
#endif
|
||||
}
|
||||
fmc_lock();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ typedef uint32_t u32;
|
||||
/* BOOT 固件版本号 */
|
||||
#define BOOT_FW_VERSION_MAJOR 1U
|
||||
#define BOOT_FW_VERSION_MINOR 0U
|
||||
#define BOOT_FW_VERSION_PATCH 1U
|
||||
#define BOOT_FW_VERSION_BUILD 2U
|
||||
#define BOOT_FW_VERSION_PATCH 2U
|
||||
#define BOOT_FW_VERSION_BUILD 1U
|
||||
|
||||
/**
|
||||
* Flash 布局说明(GD32E508VE,512KB)
|
||||
|
||||
@@ -267,9 +267,6 @@ void BootSleepManager::onExtiInterrupt(void)
|
||||
system_soft_reset();
|
||||
}
|
||||
|
||||
/** EXTI_6 (PB6 SPIready) 上升沿:通知 SpiOta 状态机 ESP32 已完成读取。 */
|
||||
SpiOta::onSpiReadyExti();
|
||||
|
||||
/* 注意:SPI OTA 的 CS 边沿信号 PA15 映射到 EXTI_15,属于 EXTI10_15_IRQn,
|
||||
* 由 EXTI10_15_IRQHandler 直接分发到 SpiOta::onCsExtiInterrupt(),
|
||||
* 不应在此处(EXTI5_9_Handler)调用 spi_ota_cs_exti_handler()。
|
||||
|
||||
Reference in New Issue
Block a user