feat(device): SPI OTA模块改用DMA发送替代TBE中断
SPI OTA模块从PendSV延迟响应模式改为DMA发送模式,主要改进包括: - 添加DMA支持,使用DMA0_CH2自动发送SPI0数据,消除字节错位问题 - EXTI ISR直接完成帧校验+处理+响应准备,不再依赖PendSV - 移除PendSV相关代码和中断处理函数 - CS释放后直接处理帧数据,响应帧写入s_txBuf由DMA自动发送 - 更新注释文档说明新的工作流程和DMA通道映射关系
This commit is contained in:
@@ -5,18 +5,27 @@
|
||||
#include "gd32e50x_rcu.h"
|
||||
#include "gd32e50x_misc.h"
|
||||
#include "gd32e50x_spi.h"
|
||||
#include "gd32e50x_dma.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* @file SpiOta.cpp
|
||||
* @brief Bootloader SPI OTA 从机模块实现(PendSV 延迟响应版)。
|
||||
* @brief Bootloader SPI OTA 从机模块实现(DMA 发送版)。
|
||||
*
|
||||
* CS 释放后,EXTI ISR 只做轻量校验(SOF + LEN + CRC),
|
||||
* 校验通过则触发 PendSV(最低优先级 ISR),由 PendSV ISR 调用
|
||||
* handleLinkMessage 完成帧处理和响应准备。
|
||||
* CS 释放后,EXTI ISR 直接完成帧校验+处理+响应准备,
|
||||
* 响应帧写入 s_txBuf,DMA0_CH2 自动推到 SPI0 TX 寄存器。
|
||||
* 硬件 DMA 替代 TBE 中断逐字节发送,消除字节错位问题。
|
||||
*
|
||||
* 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 固定映射) */
|
||||
#define SPI_TX_DMA_PERIPH DMA0
|
||||
#define SPI_TX_DMA_CH DMA_CH2
|
||||
|
||||
OTAHandler *SpiOta::s_otaHandler = nullptr;
|
||||
volatile bool SpiOta::s_csActive = false;
|
||||
volatile u16 SpiOta::s_rxStreamLen = 0U;
|
||||
@@ -25,10 +34,10 @@ u8 SpiOta::s_rxStream[SpiOta::kRxBufSize] = {};
|
||||
u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
|
||||
volatile u16 SpiOta::s_txLen = 0U;
|
||||
volatile bool SpiOta::s_txPending = false;
|
||||
volatile bool SpiOta::s_isrResponded = false;
|
||||
volatile bool SpiOta::s_isrLogPending = false;
|
||||
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
|
||||
u16 SpiOta::s_isrLogLen = 0U;
|
||||
volatile bool SpiOta::s_pendSVPending = false;
|
||||
|
||||
/* ============================================================
|
||||
* CRC8 / 帧构建 / 帧解析
|
||||
@@ -103,24 +112,38 @@ void SpiOta::exitCritical(uint32_t primask) {
|
||||
__set_PRIMASK(primask);
|
||||
}
|
||||
|
||||
/** @brief 启动 DMA 传输 s_txBuf → SPI0_DATA */
|
||||
static void startSpiTxDma(const u8 *buf, u16 len) {
|
||||
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
/* 等待通道完全停止 */
|
||||
while (DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) & DMA_CHXCTL_CHEN) {}
|
||||
DMA_CHMADDR(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = (uint32_t)buf;
|
||||
DMA_CHCNT(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) = len;
|
||||
/* 清除传输完成标志 */
|
||||
DMA_INTC(SPI_TX_DMA_PERIPH) |= DMA_FLAG_ADD(DMA_CHINTF_RESET_VALUE, SPI_TX_DMA_CH);
|
||||
dma_channel_enable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
}
|
||||
|
||||
void SpiOta::queueTxFrame(const u8 *frame, u16 len) {
|
||||
if (!frame || len == 0U || len > kTxBufSize) { return; }
|
||||
bool enable_tbe = false;
|
||||
uint32_t primask = enterCritical();
|
||||
memcpy(s_txBuf, frame, len);
|
||||
s_txLen = len;
|
||||
s_txPending = true;
|
||||
s_txStreamPos = 0U;
|
||||
enable_tbe = s_csActive;
|
||||
/* 填充剩余空间为 0xFF */
|
||||
if (len < kTxBufSize) { memset(&s_txBuf[len], 0xFF, kTxBufSize - len); }
|
||||
exitCritical(primask);
|
||||
if (enable_tbe) { spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE); }
|
||||
/* 启用 SPI DMA TX + 启动 DMA 通道 */
|
||||
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
|
||||
startSpiTxDma(s_txBuf, s_txLen);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* EXTI ISR:轻量校验 + 触发 PendSV
|
||||
* CS 释放后 ISR 直接帧处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::isrValidateAndPend() {
|
||||
void SpiOta::isrProcessFrame() {
|
||||
u16 rx_len = s_rxStreamLen;
|
||||
if (rx_len < kFrameOverhead) { return; }
|
||||
|
||||
@@ -140,39 +163,21 @@ void SpiOta::isrValidateAndPend() {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ★ 校验通过,触发 PendSV ★ */
|
||||
s_pendSVPending = true;
|
||||
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
|
||||
|
||||
isrLogFrame("[EXTI-OK]", s_rxStream, expected);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* PendSV ISR:在低优先级 ISR 中完成帧处理
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onPendSvProcess() {
|
||||
if (!s_pendSVPending) { return; }
|
||||
s_pendSVPending = false;
|
||||
|
||||
u16 frame_len = s_rxStreamLen;
|
||||
|
||||
spi_ota_frame_type_t frame_type;
|
||||
const u8 *payload = nullptr;
|
||||
u16 payload_len = 0U;
|
||||
|
||||
if (!tryParseFrame(s_rxStream, frame_len, &frame_type, &payload, &payload_len)) {
|
||||
u16 p_len = 0U;
|
||||
if (!tryParseFrame(s_rxStream, expected, &frame_type, &payload, &p_len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_otaHandler == nullptr) { return; }
|
||||
|
||||
if (s_otaHandler != nullptr) {
|
||||
s_otaHandler->setTransportSender(sender);
|
||||
|
||||
ota_link_msg_type_t msg_type = mapMsgType(frame_type);
|
||||
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)payload_len);
|
||||
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)p_len);
|
||||
}
|
||||
|
||||
isrLogFrame("[PendSV-DONE]", s_txBuf, s_txLen);
|
||||
isrLogFrame("[ISR-DONE]", s_txBuf, s_txLen);
|
||||
s_isrResponded = true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
@@ -227,6 +232,7 @@ void SpiOta::configureSpi0() {
|
||||
spi_init_struct.prescale = SPI_PSC_8;
|
||||
spi_init(SPI0, &spi_init_struct);
|
||||
spi_nss_internal_low(SPI0);
|
||||
/* RBNE 中断接收,TBE 由 DMA 替代 */
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
@@ -234,14 +240,32 @@ void SpiOta::configureSpi0() {
|
||||
spi_enable(SPI0);
|
||||
}
|
||||
|
||||
void SpiOta::configureDma() {
|
||||
/* DMA0_CH2 = SPI0_TX (GD32E50x 固定映射) */
|
||||
rcu_periph_clock_enable(RCU_DMA0);
|
||||
dma_deinit(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
dma_parameter_struct dma_init_struct;
|
||||
dma_struct_para_init(&dma_init_struct);
|
||||
dma_init_struct.periph_addr = (uint32_t)(SPI0 + 0x0CU); /* SPI0_DATA 寄存器 */
|
||||
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
|
||||
dma_init_struct.memory_addr = (uint32_t)s_txBuf;
|
||||
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
|
||||
dma_init_struct.number = 0U; /* 运行时由 queueTxFrame 设置 */
|
||||
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_MEMORY_TO_PERIPHERAL;
|
||||
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);
|
||||
}
|
||||
|
||||
void SpiOta::configureExti() {
|
||||
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
|
||||
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
|
||||
exti_interrupt_enable(EXTI_15);
|
||||
exti_interrupt_flag_clear(EXTI_15);
|
||||
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
|
||||
/* PendSV 优先级设最低(不阻塞 SPI/EXTI),通过 SCB->ICSR 触发,无需 NVIC_EnableIRQ */
|
||||
NVIC_SetPriority(PendSV_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
|
||||
}
|
||||
|
||||
void SpiOta::init() {
|
||||
@@ -251,10 +275,10 @@ void SpiOta::init() {
|
||||
s_txLen = 0U;
|
||||
s_txPending = false;
|
||||
s_isrLogPending = false;
|
||||
s_pendSVPending = false;
|
||||
memset(s_rxStream, 0, sizeof(s_rxStream));
|
||||
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
|
||||
configureGpio();
|
||||
configureDma();
|
||||
configureSpi0();
|
||||
configureExti();
|
||||
}
|
||||
@@ -268,46 +292,30 @@ void SpiOta::onCsAssert() {
|
||||
s_csActive = true;
|
||||
s_rxStreamLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
if (s_txPending && s_txLen > 0U) {
|
||||
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE);
|
||||
}
|
||||
/* DMA 已在 queueTxFrame 中启动,无需再操作 */
|
||||
}
|
||||
|
||||
void SpiOta::onCsRelease() {
|
||||
if (!s_csActive) { return; }
|
||||
s_csActive = false;
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
/* 停止 DMA 传输 */
|
||||
dma_channel_disable(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH);
|
||||
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
|
||||
if (s_rxStreamLen > 0U && s_rxStreamLen <= kRxBufSize) {
|
||||
isrValidateAndPend();
|
||||
isrProcessFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* SPI0 中断处理
|
||||
* SPI0 中断处理 — 只处理 RBNE(接收)和错误,TBE 已由 DMA 替代
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::onSpiInterrupt() {
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
|
||||
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
|
||||
u8 byte = (u8)(spi_i2s_data_receive(SPI0) & 0xFFU);
|
||||
if (s_csActive && s_rxStreamLen < kRxBufSize) { s_rxStream[s_rxStreamLen++] = byte; }
|
||||
}
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_TBE) != RESET) {
|
||||
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { onCsAssert(); }
|
||||
u8 tx_byte = 0xFFU;
|
||||
if (s_csActive && s_txPending && s_txStreamPos < s_txLen) {
|
||||
tx_byte = s_txBuf[s_txStreamPos++];
|
||||
if (s_txStreamPos >= s_txLen) {
|
||||
s_txPending = false;
|
||||
s_txLen = 0U;
|
||||
s_txStreamPos = 0U;
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
}
|
||||
} else {
|
||||
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
|
||||
}
|
||||
spi_i2s_data_transmit(SPI0, tx_byte);
|
||||
}
|
||||
/* TBE 已由 DMA 替代,不再处理 */
|
||||
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
|
||||
(void)spi_i2s_data_receive(SPI0);
|
||||
s_rxStreamLen = 0U;
|
||||
@@ -330,8 +338,13 @@ void SpiOta::onCsExtiInterrupt() {
|
||||
* ============================================================ */
|
||||
|
||||
void SpiOta::poll() {
|
||||
if (s_isrResponded) {
|
||||
s_isrResponded = false;
|
||||
}
|
||||
if (s_isrLogPending) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[SPI-BOOT] %s\r\n", s_isrLogBuf);
|
||||
#endif
|
||||
s_isrLogPending = false;
|
||||
}
|
||||
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
|
||||
@@ -385,11 +398,3 @@ u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len) { return SpiOta::copyTxFrame(out
|
||||
extern "C" void SPI0_IRQHandler(void) {
|
||||
SpiOta::onSpiInterrupt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PendSV 中断向量入口。
|
||||
* @note 调用 SpiOta::onPendSvProcess() 在低优先级 ISR 中完成帧处理。
|
||||
*/
|
||||
extern "C" void PendSV_Handler(void) {
|
||||
SpiOta::onPendSvProcess();
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
/**
|
||||
* @file SpiOta.h
|
||||
* @brief SPI OTA 从机协议头文件(PendSV 延迟响应版)。
|
||||
* @brief SPI OTA 从机协议头文件(ISR 直接响应版)。
|
||||
*
|
||||
* CS 释放后,EXTI ISR 只做轻量校验(SOF + LEN + CRC),
|
||||
* 校验通过则触发 PendSV(最低优先级 ISR),由 PendSV 调用
|
||||
* handleLinkMessage 完成帧处理和响应准备。
|
||||
* CS 释放后,EXTI ISR 直接完成帧校验+处理+响应准备,
|
||||
* 响应帧在 ISR 中写入 s_txBuf,下次 CS 拉低时 DMA 自动发出。
|
||||
* 无需 TBE 中断,DMA 硬件逐字节推到 MISO,消除字节错位问题。
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
static void onCsRelease();
|
||||
static void configureGpio();
|
||||
static void configureSpi0();
|
||||
static void configureDma();
|
||||
static void configureExti();
|
||||
static void onSpiInterrupt();
|
||||
static void onCsExtiInterrupt();
|
||||
@@ -43,14 +44,12 @@ public:
|
||||
static bool hasTxFrame();
|
||||
static u16 copyTxFrame(u8 *out, u16 max_len);
|
||||
|
||||
/** @brief EXTI ISR 内轻量校验,通过则触发 PendSV。 */
|
||||
static void isrValidateAndPend();
|
||||
/** @brief PendSV ISR 内执行帧处理和响应准备。 */
|
||||
static void onPendSvProcess();
|
||||
/** @brief CS 释放后 ISR 中直接完成帧校验+处理+响应准备。 */
|
||||
static void isrProcessFrame();
|
||||
/** @brief ISR 内轻量日志写入延迟打印缓冲。 */
|
||||
static void isrLogFrame(const char *tag, const u8 *data, u16 len);
|
||||
|
||||
/* ---- PendSV 需要访问的成员放 public ---- */
|
||||
/* ---- 公开成员 ---- */
|
||||
static OTAHandler *s_otaHandler;
|
||||
static volatile bool s_csActive;
|
||||
static volatile u16 s_rxStreamLen;
|
||||
@@ -59,10 +58,10 @@ public:
|
||||
static u8 s_txBuf[kTxBufSize];
|
||||
static volatile u16 s_txLen;
|
||||
static volatile bool s_txPending;
|
||||
static volatile bool s_isrResponded; // ISR 已发送响应帧,主循环本轮不再发 WAIT
|
||||
static volatile bool s_isrLogPending;
|
||||
static u8 s_isrLogBuf[kIsrLogBufSize];
|
||||
static u16 s_isrLogLen;
|
||||
static volatile bool s_pendSVPending;
|
||||
|
||||
private:
|
||||
static u8 crc8(const u8 *data, u16 len);
|
||||
@@ -83,10 +82,6 @@ private:
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 引脚约定:CS=PA15, SCK=PB3, MISO=PB4, MOSI=PB5
|
||||
*/
|
||||
|
||||
void spi_ota_init(void);
|
||||
void spi_ota_poll(void);
|
||||
void spi_ota_set_sender(OTAHandler *handler);
|
||||
|
||||
@@ -33,6 +33,7 @@ void BootloaderSystem::initHardware() {
|
||||
*/
|
||||
SysTickTimer::init();
|
||||
BootDebugUart::init();
|
||||
#if DEBUG_PRINT
|
||||
printf("\r\n[BOOT] power on\r\n");
|
||||
printf("[BOOT] BOOT=0x%08lX size=0x%08lX APPBACK=0x%08lX APPLATEST=0x%08lX APP_MAX=0x%08lX\r\n",
|
||||
(unsigned long)BOOTLOADER_START_ADDR,
|
||||
@@ -41,6 +42,7 @@ void BootloaderSystem::initHardware() {
|
||||
(unsigned long)APPLATEST_START_ADDR,
|
||||
(unsigned long)APP_MAX_SIZE);
|
||||
printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock);
|
||||
#endif
|
||||
CAN0_transmiter.init();
|
||||
ota.init();
|
||||
SpiOta::init();
|
||||
@@ -51,7 +53,9 @@ void BootloaderSystem::initHardware() {
|
||||
/** 检查是否需要进入深度睡眠模式。 */
|
||||
{
|
||||
const int sleep_state = BootSleepManager::check();
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] sleep_check=%d\r\n", sleep_state);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +84,12 @@ static void heartbeatPrint(uint32_t &heartbeat_counter, uint32_t loop_delay_ms,
|
||||
heartbeat_counter += loop_delay_ms;
|
||||
if (heartbeat_counter >= kHeartbeatPrintIntervalMs) {
|
||||
heartbeat_counter = 0U;
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] \xe2\x99\xa5 %s ota_active=%d spi_tx_pending=%d\r\n",
|
||||
path_label,
|
||||
ota.isActive() ? 1 : 0,
|
||||
SpiOta::hasTxFrame() ? 1 : 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,16 +110,20 @@ void BootloaderSystem::run() {
|
||||
flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR;
|
||||
app_check = *flash_ptr;
|
||||
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] app_enable=0x%08lX app_check=0x%08lX\r\n",
|
||||
(unsigned long)app_enable,
|
||||
(unsigned long)app_check);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 情况 1:应用已校验完成,且允许启动。
|
||||
* 直接跳转到主应用,不在 Bootloader 多停留。
|
||||
*/
|
||||
if (app_check == OTA_AVAL && app_enable == OTA_AVAL) {
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=jump_latest_now\r\n");
|
||||
#endif
|
||||
ota.jumpToApplication();
|
||||
}
|
||||
/**
|
||||
@@ -125,7 +135,9 @@ void BootloaderSystem::run() {
|
||||
uint32_t heartbeat_counter = 0U;
|
||||
int ota_requested = 0;
|
||||
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=wait_ota_window timeout_ms=%lu\r\n", (unsigned long)APP_DELAY_BOOT_TIME);
|
||||
#endif
|
||||
|
||||
while (delay_count < APP_DELAY_BOOT_TIME) {
|
||||
pollAllLinks();
|
||||
@@ -133,7 +145,9 @@ void BootloaderSystem::run() {
|
||||
|
||||
if (ota.isActive()) {
|
||||
ota_requested = 1;
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] ota_requested at %lu ms\r\n", (unsigned long)delay_count);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -146,7 +160,9 @@ void BootloaderSystem::run() {
|
||||
* 已经进入 OTA 会话:
|
||||
* 后续不再尝试跳应用,持续停留在 Bootloader 内处理升级。
|
||||
*/
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=stay_in_ota\r\n");
|
||||
#endif
|
||||
uint32_t hb_ota = 0U;
|
||||
while (1) {
|
||||
pollAllLinks();
|
||||
@@ -155,7 +171,9 @@ void BootloaderSystem::run() {
|
||||
}
|
||||
} else {
|
||||
/** 等待窗口内没有任何 OTA 请求,正常启动当前应用。 */
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] ota_window_timeout_then_jump\r\n");
|
||||
#endif
|
||||
ota.jumpToApplication();
|
||||
}
|
||||
}
|
||||
@@ -170,9 +188,11 @@ void BootloaderSystem::run() {
|
||||
flash_ptr = (volatile uint32_t *)n_APP_BACKUP_CHECK_ADDR;
|
||||
back_check = *flash_ptr;
|
||||
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] back_aval=0x%08lX back_check=0x%08lX\r\n",
|
||||
(unsigned long)back_aval,
|
||||
(unsigned long)back_check);
|
||||
#endif
|
||||
|
||||
if (back_check == OTA_AVAL && back_aval == OTA_AVAL) {
|
||||
/**
|
||||
@@ -181,7 +201,9 @@ void BootloaderSystem::run() {
|
||||
* 2. 把备份应用搬运到主应用区;
|
||||
* 3. 跳转运行。
|
||||
*/
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=restore_backup_then_jump\r\n");
|
||||
#endif
|
||||
CAN0_transmiter.sendBackup();
|
||||
move_app_data();
|
||||
ota.jumpToApplication();
|
||||
@@ -190,7 +212,9 @@ void BootloaderSystem::run() {
|
||||
* 当前区和备份区都不可用:
|
||||
* 只能长期停留在 Bootloader,等待外部重新下发固件。
|
||||
*/
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] path=stay_boot_wait_image\r\n");
|
||||
#endif
|
||||
uint32_t hb_wait = 0U;
|
||||
while (1) {
|
||||
pollAllLinks();
|
||||
|
||||
+72
-54
@@ -19,14 +19,14 @@ OTAHandler ota;
|
||||
/**
|
||||
* OTA.cpp 模块说明
|
||||
* ----------------
|
||||
* 这里实现 Bootloader 的核心 OTA 逻辑:
|
||||
* 这里实现 Bootloader 的核心 OTA 逻辑:
|
||||
* 1. 握手与会话密钥派生
|
||||
* 2. 固件大小确认
|
||||
* 3. 擦除应用区
|
||||
* 4. 固件包校验、解密、写 Flash
|
||||
* 5. 超时处理、错误上报、应用跳转
|
||||
*
|
||||
* 设计上,OTA 状态机与底层传输层解耦:
|
||||
* 设计上,OTA 状态机与底层传输层解耦:
|
||||
* - CAN 与 SPI 只负责收发原始消息
|
||||
* - 进入本文件后统一走 handleLinkMessage()
|
||||
*/
|
||||
@@ -70,7 +70,7 @@ static void ota_spi_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
|
||||
|
||||
/**
|
||||
* 这里仍保留占位实现。
|
||||
* 当前 SPI OTA 的真正回包发送,是由 device/SpiOta.cpp 内的 spi_ota_sender() 接管的。
|
||||
* 当前 SPI OTA 的真正回包发送,是由 device/SpiOta.cpp 内的 spi_ota_sender() 接管的。
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -98,14 +98,14 @@ static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) {
|
||||
*
|
||||
* @param addr 目标配置字地址。
|
||||
* @param data 目标写入值。
|
||||
* @return FLASH_EEPROM_OK 表示成功,FLASH_EEPROM_ERROR 表示失败。
|
||||
* @return FLASH_EEPROM_OK 表示成功,FLASH_EEPROM_ERROR 表示失败。
|
||||
*/
|
||||
u8 OTA_SET(u32 addr, u32 data) {
|
||||
/**
|
||||
* OTA 配置页更新策略:
|
||||
* - 如果目标值本来就是 data,直接返回成功
|
||||
* - 如果只是把某个字从擦除态写成 OTA_AVAL(0),可以直接写
|
||||
* - 其它情况(尤其涉及从 0 改回 1)则必须整页擦除后重写全部 6 个配置字
|
||||
* OTA 配置页更新策略:
|
||||
* - 如果目标值本来就是 data,直接返回成功
|
||||
* - 如果只是把某个字从擦除态写成 OTA_AVAL(0),可以直接写
|
||||
* - 其它情况(尤其涉及从 0 改回 1)则必须整页擦除后重写全部 6 个配置字
|
||||
*/
|
||||
fmc_state_enum fmc_status;
|
||||
u32 temp_val[OTA_CONFIG_NUM];
|
||||
@@ -117,7 +117,7 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flash 只能把 bit 从 1 写成 0,所以写 OTA_AVAL(0x00000000) 时可以尝试直接编程。
|
||||
* Flash 只能把 bit 从 1 写成 0,所以写 OTA_AVAL(0x00000000) 时可以尝试直接编程。
|
||||
*/
|
||||
if (data == OTA_AVAL) {
|
||||
fmc_status = write_ADDRESS(addr, data);
|
||||
@@ -126,7 +126,7 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取当前配置页中的全部状态,供擦除后重写。 */
|
||||
/** 读取当前配置页中的全部状态,供擦除后重写。 */
|
||||
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
|
||||
temp_val[0] = *flash_ptr;
|
||||
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
|
||||
@@ -179,8 +179,8 @@ u8 OTA_SET(u32 addr, u32 data) {
|
||||
*/
|
||||
static inline uint32_t extract_u32_from_4bytes(const uint8_t *array) {
|
||||
/**
|
||||
* 协议里数值字段按小端排列存放:
|
||||
* byte0 -> 最低字节,byte3 -> 最高字节
|
||||
* 协议里数值字段按小端排列存放:
|
||||
* byte0 -> 最低字节,byte3 -> 最高字节
|
||||
*/
|
||||
return ((uint32_t)array[3] << 24) | ((uint32_t)array[2] << 16) |
|
||||
((uint32_t)array[1] << 8) | (uint32_t)array[0];
|
||||
@@ -240,7 +240,7 @@ void ota_bind_spi_transport(void) {
|
||||
*/
|
||||
void OTAHandler::process() {
|
||||
/**
|
||||
* 把真正耗时的 Flash 擦除动作推迟到主循环做,
|
||||
* 把真正耗时的 Flash 擦除动作推迟到主循环做,
|
||||
* 避免在接收消息的处理路径里执行过重操作。
|
||||
*/
|
||||
if (pending_flash_erase) {
|
||||
@@ -257,19 +257,15 @@ void OTAHandler::process() {
|
||||
}
|
||||
|
||||
if (ota_state == OTA_STATE_IDLE) {
|
||||
/**
|
||||
* 空闲态持续清零超时计数,并定期发 WAIT 帧,
|
||||
* 相当于告诉上位机“Bootloader 在线,尚未进入升级事务”。
|
||||
*/
|
||||
ota_time = 0;
|
||||
static u8 zero[1] = {0};
|
||||
send_wait_frame(0, zero);
|
||||
/* 不再主动发 WAIT 帧——ISR 处理完请求后会发 ACK/WAIT/ERROR。
|
||||
* 主动发 WAIT 会覆盖 ISR 写入的响应帧,导致 ESP32 读到 WAIT 而非 ACK。 */
|
||||
delay_1ms(10);
|
||||
return;
|
||||
} else {
|
||||
/**
|
||||
* 非空闲态时,按固定节奏推进超时计数。
|
||||
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
|
||||
* 非空闲态时,按固定节奏推进超时计数。
|
||||
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
|
||||
*/
|
||||
delay_1us(100);
|
||||
ota_time++;
|
||||
@@ -277,10 +273,10 @@ void OTAHandler::process() {
|
||||
|
||||
if (ota_time > OTA_TIMEOUT) {
|
||||
/**
|
||||
* OTA 会话超时:
|
||||
* OTA 会话超时:
|
||||
* - 复位状态机到空闲态
|
||||
* - 上报超时错误
|
||||
* - 如果当前主应用仍有效,则尝试回到主应用运行
|
||||
* - 如果当前主应用仍有效,则尝试回到主应用运行
|
||||
*/
|
||||
ota_time = 0;
|
||||
ota_state = OTA_STATE_IDLE;
|
||||
@@ -334,7 +330,7 @@ void OTAHandler::sendWait(u8 len, const u8 *data) {
|
||||
*/
|
||||
bool validateApplication() {
|
||||
/**
|
||||
* 通过应用向量表做一个最基本的合法性检查:
|
||||
* 通过应用向量表做一个最基本的合法性检查:
|
||||
* 1. 初始 MSP 必须落在 SRAM 范围内
|
||||
* 2. Reset_Handler 必须落在主应用区范围内
|
||||
*/
|
||||
@@ -355,14 +351,18 @@ bool validateApplication() {
|
||||
*/
|
||||
void OTAHandler::jumpToApplication() {
|
||||
if (!validateApplication()) {
|
||||
/** 向量表不合法时,拒绝跳转,避免飞到未知地址。 */
|
||||
/** 向量表不合法时,拒绝跳转,避免飞到未知地址。 */
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] jump rejected: invalid app vector @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG_PRINT
|
||||
printf("[BOOT] jumping to app @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
|
||||
#endif
|
||||
|
||||
/** 把当前主应用重新标记为“有效且允许启动”。 */
|
||||
/** 把当前主应用重新标记为"有效且允许启动"。 */
|
||||
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
|
||||
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
|
||||
|
||||
@@ -407,7 +407,7 @@ void OTAHandler::jumpToApplication() {
|
||||
*/
|
||||
void OTAHandler::eraseApplicationArea() {
|
||||
/**
|
||||
* 开始真正接收新固件前,先把当前主应用状态全部标记为无效,
|
||||
* 开始真正接收新固件前,先把当前主应用状态全部标记为无效,
|
||||
* 防止中途掉电后误跳到一个被擦了一半的应用。
|
||||
*/
|
||||
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL);
|
||||
@@ -440,9 +440,9 @@ void OTAHandler::eraseApplicationArea() {
|
||||
*/
|
||||
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
|
||||
/**
|
||||
* 将一个分包写入主应用区:
|
||||
* 将一个分包写入主应用区:
|
||||
* - 先按 4 字节对齐批量写
|
||||
* - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入
|
||||
* - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入
|
||||
*/
|
||||
fmc_unlock();
|
||||
u32 write_addr = APPLATEST_START_ADDR + offset;
|
||||
@@ -468,7 +468,7 @@ void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
|
||||
*/
|
||||
void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
/**
|
||||
* CANCEL:无条件终止当前 OTA 会话,回到空闲态。
|
||||
* CANCEL:无条件终止当前 OTA 会话,回到空闲态。
|
||||
*/
|
||||
if (type == OTA_LINK_MSG_CANCEL) {
|
||||
ota_state = OTA_STATE_IDLE;
|
||||
@@ -477,13 +477,31 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* DATA:根据当前状态解释为“固件分包”或“固件大小信息”等。
|
||||
* REQ: 握手/开始请求。
|
||||
* 空闲态回复 ACK(进入握手流程),非空闲态回复 WAIT。
|
||||
*/
|
||||
else if (type == OTA_LINK_MSG_REQ) {
|
||||
ota_time = 0;
|
||||
if (ota_state == OTA_STATE_IDLE) {
|
||||
/* 空闲态收到 REQ,回复 ACK + echo 0x00 0xAC 表示 BOOT 就绪 */
|
||||
u8 ack_echo[2] = {0x00, 0xAC};
|
||||
send_ack_frame(2, ack_echo);
|
||||
ota_state = OTA_STATE_WAITING_RANDOM;
|
||||
} else {
|
||||
/* 非空闲态收到 REQ,回复 WAIT */
|
||||
u8 wait_hint[1] = {0x01};
|
||||
send_wait_frame(1, wait_hint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* DATA:根据当前状态解释为"固件分包"或"固件大小信息"等。
|
||||
*/
|
||||
else if (type == OTA_LINK_MSG_DATA) {
|
||||
ota_time = 0;
|
||||
|
||||
/**
|
||||
* 正常固件数据包:
|
||||
* 正常固件数据包:
|
||||
* - 固定 64 字节
|
||||
* - 含会话号、包号、48 字节密文、结束标志、CRC32
|
||||
*/
|
||||
@@ -502,7 +520,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
aes128_set_key(&aes_ctx, current_K_session);
|
||||
aes128_ecb_decrypt_48bytes(&aes_ctx, receive_encrypted_data, decrypted_data);
|
||||
|
||||
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
|
||||
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
|
||||
calculate_crc32_with_key(current_K_session, decrypted_data, calculated_dataCRC32);
|
||||
if (memcmp(receive_dataCRC32, calculated_dataCRC32, SIZE_U32) != 0) {
|
||||
ota_error = OTA_ERROR_INVALID_CRC;
|
||||
@@ -520,8 +538,8 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
}
|
||||
|
||||
/**
|
||||
* IFEND == 0xFFFFFFFF 表示最后一包:
|
||||
* 先反馈“全部写入完成,等待 FINISH_OTA”,
|
||||
* IFEND == 0xFFFFFFFF 表示最后一包:
|
||||
* 先反馈"全部写入完成,等待 FINISH_OTA",
|
||||
* 真正跳转放到收到 0x0A AC 后再执行。
|
||||
*/
|
||||
if (receive_IFEND == 0xFFFFFFFFU) {
|
||||
@@ -535,9 +553,9 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
send_structured_ack(request_packet);
|
||||
}
|
||||
/**
|
||||
* 固件大小阶段:
|
||||
* 在握手成功后,由上位机先发 4 字节固件大小,
|
||||
* Bootloader 校验是否超过 APP_MAX_SIZE,并准备擦除 Flash。
|
||||
* 固件大小阶段:
|
||||
* 在握手成功后,由上位机先发 4 字节固件大小,
|
||||
* Bootloader 校验是否超过 APP_MAX_SIZE,并准备擦除 Flash。
|
||||
*/
|
||||
else if (len == 4U && ota_state == OTA_STATE_CHECKING_FIRMWARE) {
|
||||
u32 new_size = *(u32 *)data;
|
||||
@@ -554,10 +572,10 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
/** 把擦除动作延后到 process() 中执行。 */
|
||||
pending_flash_erase = true;
|
||||
|
||||
/** 固件大小合法,返回严格 seq 语义 ACK:阶段起始包号 0。 */
|
||||
/** 固件大小合法,返回严格 seq 语义 ACK:阶段起始包号 0。 */
|
||||
send_structured_ack(0U);
|
||||
}
|
||||
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
|
||||
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
|
||||
else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
|
||||
if (data[0] == 0x0AU && data[1] == 0xACU) {
|
||||
if (!validateApplication()) {
|
||||
@@ -590,13 +608,13 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* REQ:用于握手阶段。
|
||||
* REQ:用于握手阶段。
|
||||
*/
|
||||
else if (type == OTA_LINK_MSG_REQ) {
|
||||
ota_time = 0;
|
||||
|
||||
/**
|
||||
* 第一步:上位机发固定魔术字 0x00 0xAC,申请开始握手。
|
||||
* 第一步:上位机发固定魔术字 0x00 0xAC,申请开始握手。
|
||||
*/
|
||||
if (data[0] == 0x00U && data[1] == 0xACU && len == 2U && ota_state == OTA_STATE_IDLE) {
|
||||
u8 send_data[2] = {0x00, 0xAC};
|
||||
@@ -604,8 +622,8 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
ota_state = OTA_STATE_WAITING_RANDOM;
|
||||
}
|
||||
/**
|
||||
* 第二步:上位机发送 16 字节随机数 nonce_server。
|
||||
* Bootloader:
|
||||
* 第二步:上位机发送 16 字节随机数 nonce_server。
|
||||
* Bootloader:
|
||||
* - 读取设备 UID
|
||||
* - 用主密钥加密 nonce 派生会话密钥 K_session
|
||||
* - 回传 UID + nonce 片段 + 加密后的 K_session
|
||||
@@ -636,8 +654,8 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
ota_state = OTA_STATE_WAITING_CONFIRM;
|
||||
}
|
||||
/**
|
||||
* 第三步:上位机使用会话密钥加密 (nonce_server XOR 0x55),回传 16 字节确认块。
|
||||
* Bootloader 解密后比对,成功则握手完成。
|
||||
* 第三步:上位机使用会话密钥加密 (nonce_server XOR 0x55),回传 16 字节确认块。
|
||||
* Bootloader 解密后比对,成功则握手完成。
|
||||
*/
|
||||
else if (len == 16U && ota_state == OTA_STATE_WAITING_CONFIRM) {
|
||||
u8 plaintext[16];
|
||||
@@ -673,7 +691,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 兼容旧接口:按 CAN ID 分发 OTA 消息。
|
||||
* @brief 兼容旧接口:按 CAN ID 分发 OTA 消息。
|
||||
*
|
||||
* @param id CAN 扩展帧 ID。
|
||||
* @param data 指向消息数据缓冲区的指针。
|
||||
@@ -681,7 +699,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
|
||||
* @return 无。
|
||||
*/
|
||||
void OTAHandler::handleOTAMessage(u32 id, u8 *data, u8 len) {
|
||||
/** 兼容旧的“按 CAN ID 分发”调用方式。 */
|
||||
/** 兼容旧的"按 CAN ID 分发"调用方式。 */
|
||||
if (id == CANFD_OTA_CANCEL_ID) {
|
||||
handleLinkMessage(OTA_LINK_MSG_CANCEL, data, len);
|
||||
} else if (id == CANFD_OTA_DATA_ID) {
|
||||
@@ -721,9 +739,9 @@ void send_wait_frame(u8 len, const u8 *data) {
|
||||
*/
|
||||
void send_error_frame(u32 packet_num, u8 error) {
|
||||
/**
|
||||
* 结构化错误帧:
|
||||
* 结构化错误帧:
|
||||
* [0..1] = 0xBA 0xC0
|
||||
* [2..5] = 相关包号(小端)
|
||||
* [2..5] = 相关包号(小端)
|
||||
* [6] = 错误码
|
||||
*/
|
||||
u8 error_frame[7] = {0xBA, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, error};
|
||||
@@ -756,15 +774,15 @@ void getDeviceSerialNumber(u8 *buffer) {
|
||||
/**
|
||||
* @brief 将备份应用区整份固件搬运到当前主应用区。
|
||||
*
|
||||
* @return FMC 状态码;CRC 校验失败时返回 0xFF。
|
||||
* @return FMC 状态码;CRC 校验失败时返回 0xFF。
|
||||
*/
|
||||
fmc_state_enum move_app_data() {
|
||||
/**
|
||||
* 把备份应用区整体复制到当前主应用区:
|
||||
* 把备份应用区整体复制到当前主应用区:
|
||||
* 1. 先擦当前主应用区
|
||||
* 2. 计算备份区 CRC
|
||||
* 3. 逐 word 拷贝非 0xFFFFFFFF 的数据
|
||||
* 4. 再计算目标区 CRC 比较,确认搬运成功
|
||||
* 4. 再计算目标区 CRC 比较,确认搬运成功
|
||||
*/
|
||||
fmc_state_enum fmc_status = FMC_READY;
|
||||
__disable_irq();
|
||||
@@ -820,7 +838,7 @@ fmc_state_enum move_app_data() {
|
||||
*/
|
||||
void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output) {
|
||||
/**
|
||||
* 计算方式:
|
||||
* 计算方式:
|
||||
* 1. 先把 16 字节会话密钥喂入 CRC32
|
||||
* 2. 再把 48 字节有效数据喂入 CRC32
|
||||
* 3. 最终按小端输出 4 字节结果
|
||||
|
||||
@@ -8,11 +8,8 @@ typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
|
||||
/** 调试开关:需要时可按模块打开。 */
|
||||
#define EEP_DEBUG 0
|
||||
#define DEBUG_OTA 0
|
||||
#define CAN_DEBUG 0
|
||||
#define SLEEP_DEBUG 0
|
||||
/** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */
|
||||
#define DEBUG_PRINT 0
|
||||
|
||||
/**
|
||||
* Flash 布局说明(GD32E508VE,512KB)
|
||||
|
||||
Reference in New Issue
Block a user