@@ -7,28 +7,30 @@
# include "gd32e50x_spi.h"
# include "gd32e50x_dma.h"
# include "sys/systick.h"
# include "spi_link_v2.h"
# include <stdio.h>
# include <string.h>
/**
* @file SpiOta.cpp
* @brief Bootloader SPI OTA 从机模块实现(DMA 发送 版)。
* @brief Bootloader SPI OTA 从机模块实现(DMA 全双工 版)。
*
* CS 释放后,EXTI ISR 直接完成帧校验+处理+响应准备,
* 响应帧写入 s_txBuf,DMA0_CH2 自动推到 SPI0 TX 寄存器。
* 硬件 DMA 替代 TBE 中断逐字节发送,消除字节错位问题 。
* 接收走 DMA0_CH1( SPI0_RX),不再用 RBNE 逐字节 ISR 。
* ESP32 固定打 256B 时钟时,RBNE 路径必现 RX-OVR/SOF 丢失/PARSE-ERR。
*
* GD32E50x DMA 通道映射(固定):
* DMA0_CH0: ADC0, DMA0_CH1: SPI0_RX, DMA0_CH2: SPI0_TX
* DMA0_CH3: SPI1_RX, DMA0_CH4: SPI1_TX
*/
/* SPI0_TX = DMA0_CH2 (GD32E50x 固定映射) */
/* 主动按铃超时时间(毫秒) */
static constexpr u32 kRingTimeoutMs = 500U ;
# define SPI_TX_DMA_PERIPH DMA0
# define SPI_TX_DMA_CH DMA_CH2
# define SPI_RX_DMA_PERIPH DMA0
# define SPI_RX_DMA_CH DMA_CH1
/* 调试日志缓冲 */
static constexpr u16 kLogSlotCount = 8U ;
@@ -57,66 +59,93 @@ volatile u32 SpiOta::s_ringDeadline = 0U;
SpiLinkState SpiOta : : s_linkState ;
/* ============================================================
* CRC8 / 帧构建 / 帧解析
* SPI Link v2 SESSION/SEQ 状态 + 响应缓存(ISR 内读写)
* ============================================================ */
static constexpr u8 kSyncSof0 = 0xA5U ;
static constexpr u8 kSyncSof1 = 0x5AU ;
static constexpr u16 kSyncHeaderLen = 6U ;
static constexpr u16 kSyncCrcLen = 4U ;
static constexpr u16 kSyncOverhead = ( u16 ) ( kSyncHeaderLen + kSyncCrcLen ) ;
static volatile u32 s_sessionId = 0U ;
static volatile u8 s_txSeq = 0U ;
static volatile u8 s_expectedRxSeq = 0U ;
static volatile bool s_v2Active = false ;
/* 响应缓存:用于重复帧重发(dist 128~255 分支)。
* Boot 的 TX buffer 最多 128 字节,缓存长度对齐 kTxBufSize。 */
static u8 s_lastReqSeq = 0xFFU ;
static u8 s_lastRespBuf [ 128 ] = { } ; /* kTxBufSize=128 */
static u16 s_lastRespLen = 0U ;
static bool s_hasCachedResp = false ;
/* ============================================================
* 帧构建 / 帧解析(v2 委托 spi_link_v2.cpp)
* ============================================================ */
/* BOOT 侧同步状态机(ISR 内只读/原子写)。 */
volatile u8 g_localCredit = 8U ; /* 非 static:供 OTA.cpp extern 引用 */
static volatile u8 g_remoteCredit = 8U ;
static void putU16Le ( u8 * pp , u16 v ) {
pp [ 0 ] = ( u8 ) ( v & 0xFFU ) ;
pp [ 1 ] = ( u8 ) ( ( v > > 8 ) & 0xFFU ) ;
}
static u16 getU16Le ( const u8 * pp ) {
return ( u16 ) ( pp [ 0 ] | ( ( u16 ) pp [ 1 ] < < 8 ) ) ;
}
static void putU32Le ( u8 * pp , u32 v ) {
pp [ 0 ] = ( u8 ) ( v & 0xFFU ) ;
pp [ 1 ] = ( u8 ) ( ( v > > 8 ) & 0xFFU ) ;
pp [ 2 ] = ( u8 ) ( ( v > > 16 ) & 0xFFU ) ;
pp [ 3 ] = ( u8 ) ( ( v > > 24 ) & 0xFFU ) ;
}
static u32 getU32Le ( const u8 * pp ) {
return ( u32 ) pp [ 0 ] | ( ( u32 ) pp [ 1 ] < < 8 ) |
( ( u32 ) pp [ 2 ] < < 16 ) | ( ( u32 ) pp [ 3 ] < < 24 ) ;
/* Boot TX buffer 仅 128 字节,最大 payload = 128 - V2_OVERHEAD(20) = 108 字节。
* v2_build_frame 只检查 payload_len > V2_MAX_PAYLOAD(236), Boot 侧需额外检查
* total <= kTxBufSize。 */
static constexpr u16 kBootMaxPayload = ( u16 ) ( SpiOta : : kTxBufSize - V2_OVERHEAD ) ;
/* 当前请求的 TX_SEQ,写入响应 ACK_SEQ,供 ESP32 做响应归属匹配(延迟一拍)。 */
static volatile u8 s_pendingAckSeq = 0U ;
/*
* OTA 固件分包(64B DATA)延后到主循环处理。
* AES-128 + CRC + 入 flash 队列若在 CS EXTI ISR 内执行,会长时间关抢占:
* ESP32 下一笔 CS 到达时 DMA 尚未 re-arm → 中后期(约几十~上百包后)卡死。
* ISR 只入队;主循环 SpiOta::poll() 调 handleLinkMessage 再回 ACK。
*/
static constexpr u8 kOtaDataSlots = 4U ;
static u8 s_otaDataSlots [ kOtaDataSlots ] [ 64 ] ;
static volatile u8 s_otaDataWi = 0U ;
static volatile u8 s_otaDataRi = 0U ;
static volatile u8 s_otaDataCnt = 0U ;
static bool enqueueOtaDataIsr ( const u8 * payload )
{
if ( payload = = nullptr | | s_otaDataCnt > = kOtaDataSlots ) {
return false ;
}
memcpy ( s_otaDataSlots [ s_otaDataWi ] , payload , 64U ) ;
s_otaDataWi = ( u8 ) ( ( s_otaDataWi + 1U ) % kOtaDataSlots ) ;
s_otaDataCnt + + ;
return true ;
}
static u32 spiCrc32 ( const u8 * data , u16 len ) {
u32 crc = 0xFFFFFFFFU ;
for ( u16 i = 0U ; i < len ; + + i ) {
crc ^ = ( u32 ) data [ i ] < < 24 ;
for ( int b = 0 ; b < 8 ; + + b ) {
if ( crc & 0x80000000U ) {
crc = ( crc < < 1 ) ^ 0x04C11DB7U ;
} else {
crc < < = 1 ;
}
}
static bool dequeueOtaData ( u8 out [ 64 ] )
{
if ( out = = nullptr | | s_otaDataCnt = = 0U ) {
return false ;
}
return crc ;
uint32_t primask = __get_PRIMASK ( ) ;
__disable_irq ( ) ;
if ( s_otaDataCnt = = 0U ) {
__set_PRIMASK ( primask ) ;
return false ;
}
memcpy ( out , s_otaDataSlots [ s_otaDataRi ] , 64U ) ;
s_otaDataRi = ( u8 ) ( ( s_otaDataRi + 1U ) % kOtaDataSlots ) ;
s_otaDataCnt - - ;
__set_PRIMASK ( primask ) ;
return true ;
}
bool SpiOta : : buildFrame ( spi_ota_frame_type_t type , const u8 * payload , u16 payload_len ,
u8 * out , u16 * out_len ) {
if ( ! out | | ! out_len ) { return false ; }
u16 total_len = ( u16 ) ( kSyncOverhead + payload_len ) ;
if ( total_len > kTxBufSize ) { return false ; }
out [ 0 ] = kSyncSof0 ;
out [ 1 ] = kSyncSof1 ;
out [ 2 ] = g_localCredit ;
out [ 3 ] = ( u8 ) type ;
putU16Le ( & out [ 4 ] , payload_len ) ;
if ( payload & & payload_len > 0U ) { memcpy ( & out [ kSyncHeaderLen ] , payload , payload_len ) ; }
u32 crc = spiCrc32 ( out , ( u16 ) ( kSyncHeaderLen + payload_len ) ) ;
putU32Le ( & out [ kSyncHeaderLen + payload_len ] , crc ) ;
* out_len = total_len ;
if ( payload_len > kBootMaxPayload ) { return false ; }
/* FLAGS=RESPONSE: Boot 作为从机只发响应帧;填入当前 session/seq/credit。
* ACK_SEQ = 对端请求 TX_SEQ,不能写死 0,否则 ESP32 otaFindFrame 会丢弃合法响应。 */
size_t built = 0U ;
bool ok = v2_build_frame ( ( uint8_t ) type , V2_FLAG_RESPONSE ,
( uint32_t ) s_sessionId ,
( uint8_t ) s_txSeq , ( uint8_t ) s_pendingAckSeq , ( uint8_t ) g_localCredit ,
( const uint8_t * ) payload , ( size_t ) payload_len ,
( uint8_t * ) out , & built ) ;
if ( ! ok ) { return false ; }
if ( built > kTxBufSize ) { return false ; }
* out_len = ( u16 ) built ;
return true ;
}
@@ -124,28 +153,19 @@ bool SpiOta::tryParseFrame(const u8 *frame, u16 frame_len,
spi_ota_frame_type_t * type ,
const u8 * * payload ,
u16 * payload_len ) {
if ( ! frame | | frame_len < kSyncOverhead ) {
if ( ! frame | | frame_len < V2_OVERHEAD ) {
return false ;
}
/* 同步帧:允许首字节偏移,扫描 SOF0 SOF1。 */
for ( u16 s = 0U ; s + kSyncOverhead < = frame_len ; + + s ) {
if ( frame [ s ] ! = kSyncSof0 | | frame [ s + 1U ] ! = kSyncSof1 ) { continue ; }
u16 plen = getU16Le ( & frame [ s + 4U ] ) ;
u16 total = ( u16 ) ( kSyncOverhead + plen ) ;
if ( ( u16 ) ( frame_len - s ) < total ) { continue ; }
u32 expect = getU32Le ( & frame [ s + kSyncHeaderLen + plen ] ) ;
u32 calc = spiCrc32 ( & frame [ s ] , ( u16 ) ( kSyncHeaderLen + plen ) ) ;
if ( expect ! = calc ) { continue ; }
u8 credit = frame [ s + 2U ] ;
if ( type ) { * type = ( spi_ota_frame_type_t ) frame [ s + 3U ] ; }
if ( payload ) { * payload = ( plen > 0U ) ? & frame [ s + kSyncHeaderLen ] : nullptr ; }
if ( payload_len ) { * payload_len = plen ; }
g_remoteCredit = credit ;
return true ;
v2_header_t hdr ;
const uint8_t * pload = nullptr ;
if ( ! v2_parse_frame ( ( const uint8_t * ) frame , ( size_t ) frame_len , & hdr , & pload ) ) {
return false ;
}
return false ;
if ( type ) { * type = ( spi_ota_frame_type_t ) hdr . type ; }
if ( payload ) { * payload = ( const u8 * ) pload ; }
if ( payload_len ) { * payload_len = ( u16 ) hdr . payload_len ; }
g_remoteCredit = hdr . credit ;
return true ;
}
ota_link_msg_type_t SpiOta : : mapMsgType ( spi_ota_frame_type_t type ) {
@@ -189,6 +209,82 @@ static void startSpiTxDma(const u8 *buf, u16 len) {
DMA_CHCTL ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) | = DMA_CHXCTL_CHEN ;
}
/** @brief 启动 DMA 接收 SPI0_DATA → s_rxStream(容量 kRxBufSize)。 */
static void startSpiRxDma ( void ) {
DMA_CHCTL ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
DMA_INTC ( SPI_RX_DMA_PERIPH ) | = DMA_FLAG_ADD ( DMA_CHINTF_RESET_VALUE , SPI_RX_DMA_CH ) ;
DMA_CHMADDR ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) = ( uint32_t ) SpiOta : : s_rxStream ;
DMA_CHCNT ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) = SpiOta : : kRxBufSize ;
DMA_CHCTL ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) | = DMA_CHXCTL_CHEN ;
}
/** @brief 清空 SPI 接收通路残留(含 OVR)。 */
static void drainSpiRxHw ( void ) {
while ( spi_i2s_flag_get ( SPI0 , SPI_FLAG_RBNE ) ! = RESET ) {
( void ) spi_i2s_data_receive ( SPI0 ) ;
}
if ( spi_i2s_flag_get ( SPI0 , SPI_FLAG_RXORERR ) ! = RESET ) {
( void ) spi_i2s_data_receive ( SPI0 ) ;
( void ) SPI_STAT ( SPI0 ) ;
}
}
/**
* @brief 在 CS 空闲期预装下一笔全双工 DMA。
*
* 必须在首个 SCK 之前完成:EXTI CS 下降相对 ESP32 时钟经常偏晚,
* 若在 onCsAssert 里才 start DMA,整笔事务会收 0 字节 → 永不 HELLO_ACK → MISO 全 0xFF。
*
* TX 始终按 kTxBufSize 装载:响应帧在头部,其余 0xFF。
* 若只 DMA 实际帧长(~26B),主机打 96 时钟时 TX underrun,后续 MISO 不定。
*/
static void armSpiDmasForNextCs ( void ) {
DMA_CHCTL ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
DMA_CHCTL ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
spi_dma_disable ( SPI0 , SPI_DMA_TRANSMIT ) ;
spi_dma_disable ( SPI0 , SPI_DMA_RECEIVE ) ;
drainSpiRxHw ( ) ;
/* 预填 TX:有挂起响应用响应帧(头部)+ 0xFF 填充到满缓冲 */
if ( SpiOta : : s_txPending & & SpiOta : : s_txLen > 0U & & SpiOta : : s_txLen < = SpiOta : : kTxBufSize ) {
if ( SpiOta : : s_txLen < SpiOta : : kTxBufSize ) {
memset ( & SpiOta : : s_txBuf [ SpiOta : : s_txLen ] , 0xFF ,
( size_t ) ( SpiOta : : kTxBufSize - SpiOta : : s_txLen ) ) ;
}
} else {
SpiOta : : s_txPending = false ;
SpiOta : : s_txLen = 0U ;
memset ( SpiOta : : s_txBuf , 0xFF , SpiOta : : kTxBufSize ) ;
}
memset ( SpiOta : : s_rxStream , 0x00 , sizeof ( SpiOta : : s_rxStream ) ) ;
SpiOta : : s_rxStreamLen = 0U ;
/* 预装 SPI_DATA,避免 DMA 启动前首拍 TBE 吐旧值 */
spi_i2s_data_transmit ( SPI0 , SpiOta : : s_txBuf [ 0 ] ) ;
/* 先使能 SPI DMA 请求,再开 DMA 通道,保证首字节即可搬运 */
spi_dma_enable ( SPI0 , SPI_DMA_RECEIVE ) ;
spi_dma_enable ( SPI0 , SPI_DMA_TRANSMIT ) ;
startSpiRxDma ( ) ;
/* TX 永远满缓冲长度,与主机 96~128B 事务匹配 */
startSpiTxDma ( SpiOta : : s_txBuf , SpiOta : : kTxBufSize ) ;
}
/** @brief 停止双 DMA;返回本 CS 实际 RX 字节数(必须在 disable 前读 CHCNT)。 */
static u16 stopSpiDmas ( void ) {
const u16 remaining = ( u16 ) DMA_CHCNT ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) ;
DMA_CHCTL ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
DMA_CHCTL ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
spi_dma_disable ( SPI0 , SPI_DMA_TRANSMIT ) ;
spi_dma_disable ( SPI0 , SPI_DMA_RECEIVE ) ;
u16 got = 0U ;
if ( remaining < = SpiOta : : kRxBufSize ) {
got = ( u16 ) ( SpiOta : : kRxBufSize - remaining ) ;
}
return got ;
}
void SpiOta : : queueTxFrame ( const u8 * frame , u16 len ) {
if ( ! frame | | len = = 0U | | len > kTxBufSize ) { return ; }
uint32_t primask = enterCritical ( ) ;
@@ -223,76 +319,130 @@ void SpiOta::isrProcessFrame() {
/* 提前置位,避免同一事务被重复处理。 */
s_isrResponded = true ;
if ( rx_len = = 1U & & s_rxStream [ 0 ] = = SPI_OTA_CMD_BOOT ) {
/* 0xB0 始终回复 0xB5 并重置 OTA 状态机。
* 旧代码在 isActive() 时静默忽略,导致上次 OTA 失败后
* 所有后续 0xB0 都被吞掉,ESP32 永远收不到 0xB5。 */
u8 ack = 0xB5U ;
queueTxFrame ( & ack , 1U ) ;
if ( s_otaHandler ! = nullptr ) { s_otaHandler - > resetToIdle ( ) ; }
s_otaSessionActive = false ;
s_rxStreamLen = 0U ;
s_isrResponded = true ;
return ;
}
/* 裸字节 0xB0/0xB5 路径已删除:v2 协议下 ENTER_BOOT 走标准帧。
* 短于 v2 帧头的输入视为残包/空轮询,直接丢弃。 */
if ( rx_len < V2_OVERHEAD ) { return ; }
/* 新同步帧校验。 */
if ( rx_len < kSyncOverhead | |
s_rxStream [ 0 ] ! = kSyncSof0 | | s_rxStream [ 1 ] ! = kSyncSof1 ) {
return ;
}
u16 payload_len = getU16Le ( & s_rxStream [ 4U ] ) ;
u16 expected = ( u16 ) ( kSyncOverhead + payload_len ) ;
if ( expected > rx_len ) { return ; }
/* 帧收齐日志:确认 BOOT 收到了完整帧 */
isrLogFrameEx ( " [RX-FRM] " , s_rxStream , ( expected < 18U ) ? expected : 18U ) ;
u32 expect = getU32Le ( & s_rxStream [ kSyncHeaderLen + payload_len ] ) ;
u32 calc = spiCrc32 ( s_rxStream , ( u16 ) ( kSyncHeaderLen + payload_len ) ) ;
if ( expect ! = calc ) {
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 ( " [CRC-ERR] " , s_rxStream , ( rx_len < 18U ) ? rx_len : 18U ) ;
return ;
}
spi_ota_frame_type_t frame_type ;
/* ---- 1. v2 帧解析(内部扫描 SOF、校验 VERSION/CRC32) ---- */
v2_header_t hdr ;
const u8 * payload = nullptr ;
u16 p_len = 0U ;
if ( ! tryParseFrame ( s_rxStream , expected , & frame_type , & payload , & p_len ) ) {
# if DEBUG_PRINT
static volatile u8 s_parseFailCnt = 0U ;
if ( s_parseFailCnt < 10U ) {
s_parseFailCnt + + ;
printf ( " [SPI] parse fail rx=%u expected=%u sof=0x%02X type=0x%02X plen=%u \r \n " ,
rx_len , expected , s_rxStream [ 0 ] , s_rxStream [ 1 ] ,
( u16 ) ( s_rxStream [ 2 ] | ( ( u16 ) s_rxStream [ 3 ] < < 8 ) ) ) ;
if ( ! v2_parse_frame ( ( const uint8_t * ) s_rxStream , ( size_t ) rx_len , & hdr ,
( const uint8_t * * ) & payload ) ) {
/* ESP32 otaPoll 发全 0xFF dummy 属延迟一拍常态,不刷 PARSE-ERR。 */
bool all_ff = ( rx_len > 0U ) ;
const u16 chk = ( rx_len < 8U ) ? rx_len : 8U ;
for ( u16 i = 0U ; i < chk ; + + i ) {
if ( s_rxStream [ i ] ! = 0xFFU ) { all_ff = false ; break ; }
}
if ( ! all_ff ) {
isrLogFrameEx ( " [PARSE-ERR] " , s_rxStream , chk ) ;
}
# endif
isrLogFrameEx ( " [PARSE-ERR] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
return ;
}
if ( frame_type = = SPI_OTA_CMD_BOOT ) {
/* 帧式 0xB0 与裸字节 0xB0 行为一致:始终回裸字节 0xB5 + resetToIdle()。
* 旧代码在 isActive() 时静默吞帧且回 0xA5 帧式 ACK,违反
* "0xB0 始终回 0xB5" 约束,且与裸字节路径不一致(0xA5 vs 0xB5)。
* ESP32 appRequestBootloader() 只读首字节期望 0xB5,帧式 ACK 永不匹配。 */
u8 ack = 0xB5U ;
queueTxFrame ( & ack , 1U ) ;
u16 p_len = ( u16 ) hdr . payload_len ;
/* 正常收帧不打 [RX-FRM]:OTA 千包级会刷爆串口;异常路径仍有 PARSE-ERR/SEQ-JMP 等。 */
g_remoteCredit = hdr . credit ;
/* 响应归属:ACK_SEQ 回显请求 TX_SEQ(延迟一拍 + ESP32 otaFindFrame 匹配)。 */
s_pendingAckSeq = hdr . tx_seq ;
/* ---- 2. TYPE=0x00 HELLO:建立/重置 session ---- */
if ( hdr . type = = V2_TYPE_LINK_HELLO ) {
s_sessionId = ( u32 ) hdr . session_id ;
s_txSeq = 0U ;
s_expectedRxSeq = 0U ;
s_v2Active = true ;
u8 hello_ack [ kTxBufSize ] = { } ;
size_t built = 0U ;
if ( v2_build_hello_ack_frame ( ( uint32_t ) s_sessionId , V2_ROLE_BOOT ,
( uint8_t ) g_localCredit ,
( uint8_t ) s_txSeq , hdr . tx_seq ,
hello_ack , & built ) ) {
queueTxFrame ( hello_ack , ( u16 ) built ) ;
/* 缓存 HELLO_ACK 以应对 ESP32 重试同一 HELLO。 */
memcpy ( s_lastRespBuf , hello_ack , built ) ;
s_lastRespLen = ( u16 ) built ;
s_lastReqSeq = hdr . tx_seq ;
s_hasCachedResp = true ;
}
isrLogFrameEx ( " [HELLO] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
s_rxStreamLen = 0U ;
return ;
}
/* ---- 3. v2 已激活:SESSION/SEQ 校验 ---- */
if ( s_v2Active ) {
/* 3a. session 校验:不匹配回 LINK_ERROR(0x02)。 */
if ( ( u32 ) hdr . session_id ! = s_sessionId ) {
u8 err_frame [ kTxBufSize ] = { } ;
size_t built = 0U ;
if ( v2_build_error_frame ( ( uint32_t ) s_sessionId , ( uint8_t ) s_txSeq , hdr . tx_seq ,
( uint8_t ) g_localCredit , V2_ERR_SESSION ,
( uint8_t ) s_expectedRxSeq ,
err_frame , & built ) ) {
queueTxFrame ( err_frame , ( u16 ) built ) ;
}
isrLogFrameEx ( " [SES-ERR] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
s_rxStreamLen = 0U ;
return ;
}
/* 3b. SEQ 距离判定。 */
u8 dist = v2_seq_distance ( hdr . tx_seq , ( uint8_t ) s_expectedRxSeq ) ;
if ( dist = = 0U ) {
/* 正常:序号匹配,推进期望序号(volatile 原子写)。 */
s_expectedRxSeq = ( u8 ) ( s_expectedRxSeq + 1U ) ;
} else if ( dist > = 128U ) {
/* 重复请求(rx_seq 落后于 expected):重发上一条缓存响应,
* 不推进序号。若 DATA 仍在主循环排队,回 WAIT 让 ESP32 继续 poll。 */
if ( s_hasCachedResp & & s_lastRespLen > 0U ) {
queueTxFrame ( s_lastRespBuf , s_lastRespLen ) ;
} else if ( s_otaDataCnt > 0U ) {
u8 wait_hint = 0x03U ;
sender ( OTA_LINK_TX_WAIT , 1U , & wait_hint ) ;
}
isrLogFrameEx ( " [DUP-SEQ] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
s_rxStreamLen = 0U ;
return ;
} else {
/* 1..127:跳序(丢失了 dist 帧),回 LINK_ERROR(0x04)。 */
u8 err_frame [ kTxBufSize ] = { } ;
size_t built = 0U ;
if ( v2_build_error_frame ( ( uint32_t ) s_sessionId , ( uint8_t ) s_txSeq , hdr . tx_seq ,
( uint8_t ) g_localCredit , V2_ERR_SEQ_JUMP ,
( uint8_t ) s_expectedRxSeq ,
err_frame , & built ) ) {
queueTxFrame ( err_frame , ( u16 ) built ) ;
}
isrLogFrameEx ( " [SEQ-JMP] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
s_rxStreamLen = 0U ;
return ;
}
}
/* ---- 4. TYPE=0xB0 ENTER_BOOT:标准帧 ACK + resetToIdle ---- */
if ( hdr . type = = SPI_OTA_CMD_BOOT ) {
u8 ack_frame [ kTxBufSize ] = { } ;
u16 built = 0U ;
if ( buildFrame ( SPI_OTA_RSP_ACK , nullptr , 0U , ack_frame , & built ) ) {
queueTxFrame ( ack_frame , built ) ;
/* 缓存 ACK 响应以应对重复的 0xB0。 */
memcpy ( s_lastRespBuf , ack_frame , built ) ;
s_lastRespLen = built ;
s_lastReqSeq = hdr . tx_seq ;
s_hasCachedResp = true ;
}
if ( s_otaHandler ! = nullptr ) { s_otaHandler - > resetToIdle ( ) ; }
s_otaSessionActive = false ;
s_otaDataWi = 0U ;
s_otaDataRi = 0U ;
s_otaDataCnt = 0U ;
s_rxStreamLen = 0U ;
s_isrResponded = true ;
return ;
}
/* ---- 5. 走原有 OTA 状态机分发 ---- */
spi_ota_frame_type_t frame_type = ( spi_ota_frame_type_t ) hdr . type ;
/* 仅转发 BOOT 实际支持的 OTA 协议帧(REQ/DATA/CANCEL)给 OTA 状态机。
* 其它帧类型(如 0xC0~0xC3 CAN bridge 命令,由 DCU_APP 注册处理器处理,
* BOOT 无此功能)直接返回 ERROR,避免 mapMsgType 的 default 分支把
@@ -309,7 +459,6 @@ void SpiOta::isrProcessFrame() {
}
isrLogFrameEx ( " [UNK-TYPE] " , s_rxStream , ( rx_len < 8U ) ? rx_len : 8U ) ;
s_rxStreamLen = 0U ;
s_isrResponded = true ;
return ;
}
@@ -317,7 +466,31 @@ void SpiOta::isrProcessFrame() {
s_otaSessionActive = true ;
s_otaHandler - > setTransportSender ( sender ) ;
ota_link_msg_type_t msg_type = mapMsgType ( frame_type ) ;
s_otaHandler - > handleLinkMessage ( msg_type , const_cast < u8 * > ( payload ) , ( u8 ) p_len ) ;
/* 64B 固件分包:只入队,AES/Flash 放到主循环,避免 ISR 过长。 */
if ( frame_type = = SPI_OTA_CMD_DATA & & p_len = = 64U & & payload ! = nullptr ) {
if ( ! enqueueOtaDataIsr ( payload ) ) {
g_localCredit = 0U ;
u8 wait_hint = 0x03U ;
sender ( OTA_LINK_TX_WAIT , 1U , & wait_hint ) ;
isrLogFrameEx ( " [DATA-QFULL] " , s_rxStream , 8U ) ;
} else {
/* 立即 WAIT:告诉 ESP32“已收包、处理中”,主循环完成后再 ACK。
* 若主循环在 ESP32 首 poll 前已写好 ACK,会覆盖本 WAIT。 */
u8 wait_hint = 0x03U ;
sender ( OTA_LINK_TX_WAIT , 1U , & wait_hint ) ;
}
} else {
s_otaHandler - > handleLinkMessage ( msg_type , const_cast < u8 * > ( payload ) , ( u8 ) p_len ) ;
}
}
/* ---- 6. 分发后缓存响应到 s_lastRespBuf ---- */
if ( s_txPending & & s_txLen > 0U & & s_txLen < = sizeof ( s_lastRespBuf ) ) {
memcpy ( s_lastRespBuf , s_txBuf , s_txLen ) ;
s_lastRespLen = s_txLen ;
s_lastReqSeq = hdr . tx_seq ;
s_hasCachedResp = true ;
}
s_isrResponded = true ;
@@ -386,6 +559,14 @@ void SpiOta::setSpiReady(bool ready) {
}
}
/* v2 DATA_READY: PB6 低 = 有待发数据,高 = 队列空。
* 替代旧 BUSY/READY 语义中所有 setSpiReady 调用。 */
static void updateDataReady ( )
{
const bool has_data = ( SpiOta : : s_txPending & & SpiOta : : s_txLen > 0U ) | | SpiOta : : s_ringPending ;
SpiOta : : setSpiReady ( ! has_data ) ;
}
/* ============================================================
* BOOT 主动按铃:下拉 SPIready 请求 ESP32 来读
* ============================================================ */
@@ -412,7 +593,7 @@ bool SpiOta::requestEsp32Read(u8 frame_type, const u8 *payload, u16 payload_len)
s_ringPending = true ;
s_ringDeadline = SysTickTimer : : getMs ( ) + kRingTimeoutMs ;
s_linkState . onRingStart ( ) ;
setSpiReady ( false ) ;
updateDataReady ( ) ;
exitCritical ( primask ) ;
return true ;
}
@@ -429,7 +610,7 @@ void SpiOta::cancelRing() {
s_txStreamPos = 0U ;
memset ( s_txBuf , 0xFF , sizeof ( s_txBuf ) ) ;
s_linkState . onRingTimeout ( ) ;
setSpiReady ( true ) ;
updateDataReady ( ) ;
exitCritical ( primask ) ;
}
@@ -470,18 +651,17 @@ void SpiOta::configureSpi0() {
spi_nss_internal_low ( SPI0 ) ;
/* 初始化时清空 SPI_DATA 寄存器,防止残留值影响首次传输 */
( void ) spi_i2s_data_receive ( SPI0 ) ;
spi_i2s_interrupt_enable ( SPI0 , SPI_I2S_INT_RBNE ) ;
spi_i2s_interrupt_enable ( SPI0 , SPI_I2S_INT_ERR ) ;
/* 接收改走 DMA0_CH1:禁用 RBNE 逐字节中断,避免 256B 事务 RX-OVR。 */
spi_i2s_interrupt_disable ( SPI0 , SPI_I2S_INT_RBNE ) ;
spi_i2s_interrupt_disable ( SPI0 , SPI_I2S_INT_ERR ) ;
spi_i2s_interrupt_disable ( SPI0 , SPI_I2S_INT_TBE ) ;
/* SPI0 RBNE 中断优先级设为 (2, 0),低于 EXTI CS 中断 (1, 1)。
* 这确保 CS 下降沿的 onCsAssert() 在任何 RBNE 之前执行,
* 否则 s_csActive 仍为 false, RBNE 中断丢弃接收字节。 */
nvic_irq_enable ( SPI0_IRQn , 2U , 0U ) ;
spi_enable ( SPI0 ) ;
}
void SpiOta : : configureDma ( ) {
rcu_periph_clock_enable ( RCU_DMA0 ) ;
/* TX: SPI0 → memory? no, memory → SPI0 */
dma_deinit ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) ;
dma_parameter_struct dma_init_struct ;
dma_struct_para_init ( & dma_init_struct ) ;
@@ -497,6 +677,22 @@ void SpiOta::configureDma() {
dma_init ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH , & dma_init_struct ) ;
dma_circulation_disable ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) ;
dma_channel_disable ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) ;
/* RX: SPI0 → s_rxStream */
dma_deinit ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) ;
dma_struct_para_init ( & dma_init_struct ) ;
dma_init_struct . periph_addr = ( uint32_t ) ( SPI0 + 0x0CU ) ;
dma_init_struct . periph_width = DMA_PERIPHERAL_WIDTH_8BIT ;
dma_init_struct . memory_addr = ( uint32_t ) s_rxStream ;
dma_init_struct . memory_width = DMA_MEMORY_WIDTH_8BIT ;
dma_init_struct . number = kRxBufSize ;
dma_init_struct . priority = DMA_PRIORITY_HIGH ;
dma_init_struct . periph_inc = DMA_PERIPH_INCREASE_DISABLE ;
dma_init_struct . memory_inc = DMA_MEMORY_INCREASE_ENABLE ;
dma_init_struct . direction = DMA_PERIPHERAL_TO_MEMORY ;
dma_init ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH , & dma_init_struct ) ;
dma_circulation_disable ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) ;
dma_channel_disable ( SPI_RX_DMA_PERIPH , SPI_RX_DMA_CH ) ;
}
void SpiOta : : configureExti ( ) {
@@ -509,18 +705,22 @@ void SpiOta::configureExti() {
void SpiOta : : configureSpiReadyExti ( ) {
/* EXTI6 上升沿:ESP32 releaseReady 通知 BOOT "读取完成"。
* EXTI5_9_IRQn 已被 BootSleepManager 以 (0,0) 占用(PB8 深睡唤醒),
* 这里仅 使能 EXTI_6 line,中断入口在 EXTI5_9_IRQHandler 中分发。 */
* 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 ) ;
s_linkState . onEsp32Complete ( ) ;
}
}
@@ -533,12 +733,26 @@ void SpiOta::init() {
s_otaSessionActive = false ;
memset ( s_rxStream , 0 , sizeof ( s_rxStream ) ) ;
memset ( s_txBuf , 0xFF , sizeof ( s_txBuf ) ) ;
/* v2 链路状态复位。 */
s_sessionId = 0U ;
s_txSeq = 0U ;
s_expectedRxSeq = 0U ;
s_v2Active = false ;
s_lastReqSeq = 0xFFU ;
s_lastRespLen = 0U ;
s_hasCachedResp = false ;
memset ( s_lastRespBuf , 0 , sizeof ( s_lastRespBuf ) ) ;
s_otaDataWi = 0U ;
s_otaDataRi = 0U ;
s_otaDataCnt = 0U ;
configureGpio ( ) ;
configureDma ( ) ;
configureSpi0 ( ) ;
configureExti ( ) ;
configureSpiReadyExti ( ) ;
s_linkState . reset ( ) ;
/* 首笔 CS 到来前预装 DMA,避免 EXTI 晚于首 SCK */
armSpiDmasForNextCs ( ) ;
}
/* ============================================================
@@ -548,117 +762,113 @@ 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 ;
/* CS 下降: DMA 已在空闲期预装,此处只标记 忙碌。
* v2 DATA_READY: 不在此处操作 PB6, CS 本身即互斥,
* PB6 电平完全由队列状态(updateDataReady)决定。
* 切勿在这里 stop/restart DMA,否则赶不上 ESP32 已发出的 SCK 。 */
s_txStreamPos = 0U ;
s_isrResponded = false ;
/* PA15 仅作为事务边界信号;这里重装 DMA 并清理旧状态。 */
DMA_CHCTL ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
spi_dma_disable ( SPI0 , SPI_DMA_TRANSMIT ) ;
/* 覆盖 TBE 里的旧字节,避免下一次事务先吐出残留 ACK。 */
spi_i2s_data_transmit ( SPI0 , 0xFFU ) ;
if ( spi_i2s_flag_get ( SPI0 , SPI_FLAG_TBE ) ! = RESET ) {
spi_i2s_data_transmit ( SPI0 , 0xFFU ) ;
}
/* 只清错误标志,不主动消费 RBNE。 */
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_RXORERR ) ! = RESET ) {
( void ) spi_i2s_data_receive ( SPI0 ) ;
}
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_FERR ) ! = RESET ) {
spi_i2s_format_error_clear ( SPI0 , SPI_FLAG_FERR ) ;
}
/* 3. 配置并启动 DMA */
if ( s_txPending & & s_txLen > 0U ) {
spi_dma_enable ( SPI0 , SPI_DMA_TRANSMIT ) ;
startSpiTxDma ( s_txBuf , s_txLen ) ;
} else {
s_txPending = false ;
s_txLen = kTxBufSize ;
memset ( s_txBuf , 0xFF , kTxBufSize ) ;
spi_dma_enable ( SPI0 , SPI_DMA_TRANSMIT ) ;
startSpiTxDma ( s_txBuf , s_txLen ) ;
}
/* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据 */
spi_i2s_interrupt_enable ( SPI0 , SPI_I2S_INT_RBNE ) ;
}
void SpiOta : : onCsRelease ( ) {
if ( ! s_csActive ) { return ; }
if ( ! s_csActive ) {
/* EXTI 丢失下降沿时:仍尝试收尾并 re-arm,避免永久失步 */
s_rxStreamLen = stopSpiDmas ( ) ;
drainSpiRxHw ( ) ;
/* 本 CS 已输出预装 TX:先标记交付,避免 pending 永久为 true。 */
if ( s_txPending & & s_txLen > 0U ) {
s_txPending = false ;
s_txLen = 0U ;
s_txStreamPos = 0U ;
}
if ( s_rxStreamLen > = V2_OVERHEAD & & ! s_isrResponded ) {
isrProcessFrame ( ) ;
}
armSpiDmasForNextCs ( ) ;
updateDataReady ( ) ;
return ;
}
s_csActive = false ;
/* 内部 NSS 保持常 LOW;这里只停止 DMA。 */
DMA_CHCTL ( SPI_TX_DMA_PERIPH , SPI_TX_DMA_CH ) & = ~ DMA_CHXCTL_CHEN ;
spi_dma_disable ( SPI0 , SPI_DMA_TRANSMIT ) ;
/* 停 DMA 并取本笔 RX 长度(先读 CHCNT 再 disable) */
s_rxStreamLen = stopSpiDmas ( ) ;
drainSpiRxHw ( ) ;
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_RBNE ) ! = RESET ) {
u8 last_byte = ( u8 ) ( spi_i2s_data_receive ( SPI0 ) & 0xFFU ) ;
if ( s_rxStreamLen < kRxBufSize ) {
s_rxStream [ s_rxStreamLen + + ] = last_byte ;
}
}
/* 如果这是 ESP32 响应 BOOT 主动按铃来读请求帧,直接清空按铃状态并释放 ready。
* 按铃帧已在 CS 期间通过 DMA 输出到 MISO,不需要再解析本次接收到的 dummy 字节。 */
if ( s_ringPending ) {
s_ringPending = false ;
s_ringTxLen = 0U ;
/* ★ 关键:本 CS 已时钟输出预装的 TX 帧(延迟一拍响应)。
* armSpiDmasForNextCs() 装载后并不清除 s_txPending,若此处不标记交付,
* pending 会永远为 true → updateDataReady() 把 PB6 永久拉低 →
* ESP32 waitSpiReady(等 HIGH) 每包空等 SPI_READY_TIMEOUT_MS,
* 1116 包 OTA 表现为“卡在 1/1116 十几分钟”。
* 先清交付态;随后 isrProcessFrame/queueTxFrame 可再置 pending。
* 重复帧缓存在 s_lastRespBuf,不依赖 s_txPending 保活。 */
if ( s_txPending & & s_txLen > 0U ) {
s_txPending = false ;
s_txLen = 0U ;
s_txStreamPos = 0U ;
s_rxStreamLen = 0U ;
s_isrResponded = true ;
memset ( s_txBuf , 0xFF , sizeof ( s_txBuf ) ) ;
setSpiReady ( true ) ;
return ;
}
/* 按铃中若 ESP32 发的是 v2 协议帧(HELLO/OTA 等),不能当 dummy 读吞掉 */
if ( s_ringPending ) {
bool is_v2_frame = false ;
if ( s_rxStreamLen > = 2U ) {
const u16 last = ( s_rxStreamLen < 16U ) ? ( u16 ) ( s_rxStreamLen - 1U ) : 16U ;
for ( u16 i = 0U ; i < last ; + + i ) {
if ( s_rxStream [ i ] = = V2_SOF0 & & s_rxStream [ i + 1U ] = = V2_SOF1 ) {
is_v2_frame = true ;
break ;
}
}
}
if ( is_v2_frame ) {
s_ringPending = false ;
s_ringTxLen = 0U ;
/* 保留 s_txBuf 由 process 覆盖;pending 已在上方清掉 */
s_txStreamPos = 0U ;
/* v2: ESP32 CS 完成即确认读走,补充 onEsp32Complete 信号
* ( ESP32 不再操作 PB6,EXTI6 上升沿信号源消失)。 */
s_linkState . onEsp32Complete ( ) ;
/* 继续 isrProcessFrame */
} else {
s_ringPending = false ;
s_ringTxLen = 0U ;
s_txPending = false ;
s_txLen = 0U ;
s_txStreamPos = 0U ;
s_rxStreamLen = 0U ;
s_isrResponded = true ;
memset ( s_txBuf , 0xFF , sizeof ( s_txBuf ) ) ;
armSpiDmasForNextCs ( ) ;
updateDataReady ( ) ;
return ;
}
}
if ( s_rxStreamLen > 0U & & ! s_isrResponded ) {
isrProcessFrame ( ) ;
} else if ( s_rxStreamLen = = 0U ) {
/* 诊断:预装 DMA 仍收 0 字节时打点(日志槽满则静默) */
isrLogFrameEx ( " [RX0] " , NULL , 0U ) ;
}
/* BOOT 在 ISR 内同步完成帧处理,响应帧已就绪(或为空轮询/残包无需响应),
* 拉高 PB6 通知主机可发起下一笔事务。
* flash_buf 满(g_localCredit==0)时不拉高,等 drainFlashBuffer 写完 Flash
* 再拉高,形成硬件级背压,避免 ESP32 疯狂重发导致 overrun。 */
if ( g_localCredit > 0U ) {
setSpiReady ( true ) ;
}
/* 处理完立即预装下一笔,保证下一 CS 首字节即可收发 */
armSpiDmasForNextCs ( ) ;
/* v2 DATA_READY: PB6 电平由队列状态决定。
* flash_buf 满时 CREDIT=0 已通知 ESP32 停止发送,不再用 PB6 背压。 */
updateDataReady ( ) ;
}
/* ============================================================
* SPI0 中断处理 — 只处理 RBNE(接收)和错误,TBE 已由 DMA 替代
* SPI0 中断处理 — RX 已改 DMA,保留空壳兼容旧 IRQ 向量
* ============================================================ */
void SpiOta : : onSpiInterrupt ( ) {
/* 遗留 ERR 标志清理(RBNE 已禁用) */
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_RBNE ) ! = RESET ) {
/* EXTI 略晚时,在首个 RBNE 到来时补做 onCsAssert()。 */
if ( ! s_csActive & & s_rxStreamLen = = 0U & & gpio_input_bit_get ( GPIOA , GPIO_PIN_15 ) = = RESET ) {
onCsAssert ( ) ;
}
u8 byte = ( u8 ) ( spi_i2s_data_receive ( SPI0 ) & 0xFFU ) ;
if ( s_rxStreamLen < kRxBufSize ) {
s_rxStream [ s_rxStreamLen + + ] = byte ;
}
/* 帧处理统一在 onCsRelease() (EXTI CS 释放中断) 里执行,
* 不在 RBNE 中断内调 isrProcessFrame(),避免 CRC32+AES+buildFrame
* 重操作(10-20us)期间 SPI 外设继续收字节导致 RXORERR。 */
( void ) spi_i2s_data_receive ( SPI0 ) ;
}
/* TBE 已由 DMA 替代,不再处理 */
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_RXORERR ) ! = RESET ) {
( void ) spi_i2s_data_receive ( SPI0 ) ;
s_rxStreamLen = 0U ;
isrLogFrameEx ( " [RX-OVR] " , NULL , 0U ) ;
( void ) SPI_STAT ( SPI0 ) ;
}
if ( spi_i2s_interrupt_flag_get ( SPI0 , SPI_I2S_INT_FLAG_FERR ) ! = RESET ) {
spi_i2s_format_error_clear ( SPI0 , SPI_FLAG_FERR ) ;
@@ -677,6 +887,13 @@ void SpiOta::onCsExtiInterrupt() {
* 主循环接口
* ============================================================ */
void SpiOta : : servicePendingTx ( ) {
if ( ! s_csActive & & s_txPending & & s_txLen > 0U ) {
armSpiDmasForNextCs ( ) ;
updateDataReady ( ) ;
}
}
void SpiOta : : poll ( ) {
if ( s_isrResponded ) {
s_isrResponded = false ;
@@ -699,7 +916,28 @@ void SpiOta::poll() {
}
}
if ( s_otaHandler ! = nullptr ) { s_otaHandler - > process ( ) ; }
if ( s_otaHandler ! = nullptr ) {
/* 先消化 ISR 入队的 64B DATA( AES+入 flash 槽),再 process 擦除/drain。 */
u8 data_pkt [ 64 ] ;
u8 drained = 0U ;
while ( drained < kOtaDataSlots & & dequeueOtaData ( data_pkt ) ) {
s_otaHandler - > setTransportSender ( sender ) ;
s_otaHandler - > handleLinkMessage ( OTA_LINK_MSG_DATA , data_pkt , 64U ) ;
if ( s_txPending & & s_txLen > 0U & & s_txLen < = sizeof ( s_lastRespBuf ) ) {
memcpy ( s_lastRespBuf , s_txBuf , s_txLen ) ;
s_lastRespLen = s_txLen ;
s_hasCachedResp = true ;
}
drained + + ;
}
s_otaHandler - > process ( ) ;
}
/* process()/DATA 处理可能在主循环排队 ACK; CS 空闲时 re-arm + 拉低 PB6。 */
if ( ! s_csActive & & s_txPending & & s_txLen > 0U ) {
armSpiDmasForNextCs ( ) ;
updateDataReady ( ) ;
}
}
void SpiOta : : setSender ( OTAHandler * handler ) { s_otaHandler = handler ; }
@@ -734,6 +972,7 @@ u16 SpiOta::copyTxFrame(u8 *out, u16 max_len) {
void spi_ota_init ( void ) { SpiOta : : init ( ) ; }
void spi_ota_poll ( void ) { SpiOta : : poll ( ) ; }
void spi_ota_service_pending_tx ( void ) { SpiOta : : servicePendingTx ( ) ; }
void spi_ota_set_sender ( OTAHandler * handler ) { SpiOta : : setSender ( handler ) ; }
void spi_ota_on_cs_assert ( void ) { SpiOta : : onCsAssert ( ) ; }
void spi_ota_on_cs_release ( void ) { SpiOta : : onCsRelease ( ) ; }