diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index d8c8049..66c5520 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -5,18 +5,27 @@ #include "gd32e50x_rcu.h" #include "gd32e50x_misc.h" #include "gd32e50x_spi.h" +#include "gd32e50x_dma.h" #include #include /** * @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(payload), (u8)p_len); + } - s_otaHandler->setTransportSender(sender); - - ota_link_msg_type_t msg_type = mapMsgType(frame_type); - s_otaHandler->handleLinkMessage(msg_type, const_cast(payload), (u8)payload_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(); -} \ No newline at end of file diff --git a/GD32E50X_BOOT/device/SpiOta.h b/GD32E50X_BOOT/device/SpiOta.h index 33147e7..aa22ea8 100644 --- a/GD32E50X_BOOT/device/SpiOta.h +++ b/GD32E50X_BOOT/device/SpiOta.h @@ -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); @@ -105,4 +100,4 @@ u16 spi_ota_copy_tx_frame(u8 *out, u16 max_len); } #endif -#endif +#endif \ No newline at end of file diff --git a/GD32E50X_BOOT/main.cpp b/GD32E50X_BOOT/main.cpp index a6f7ce3..23c6577 100644 --- a/GD32E50X_BOOT/main.cpp +++ b/GD32E50X_BOOT/main.cpp @@ -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(); @@ -213,4 +237,4 @@ int main(void) { /** 理论上 run() 不会返回,这里仅做保险。 */ while (1) { } -} \ No newline at end of file +} diff --git a/GD32E50X_BOOT/sys/OTA.cpp b/GD32E50X_BOOT/sys/OTA.cpp index ea79af2..d18beb7 100644 --- a/GD32E50X_BOOT/sys/OTA.cpp +++ b/GD32E50X_BOOT/sys/OTA.cpp @@ -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 字节结果 diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index 0dad1b3..7e613c8 100644 --- a/GD32E50X_BOOT/sys/config.h +++ b/GD32E50X_BOOT/sys/config.h @@ -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)