From 061bcabc37ccdabc437743083b4003b81de09a9c Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Tue, 2 Jun 2026 22:13:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(boot):=20=E6=B7=BB=E5=8A=A0OTA=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A3=80=E6=9F=A5=E9=81=BF=E5=85=8D=E5=BF=83=E8=B7=B3?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E5=B9=B2=E6=89=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当OTA激活时,停止心跳打印以减少日志干扰并避免潜在的 并发问题。 fix(ota): 修正boot跳转汇编代码移除错误寄存器操作 移除psplim和msplim寄存器设置,这些寄存器在目标架构中不 需要配置,避免潜在的硬件兼容性问题。 feat(ota): 添加FLASH操作调试日志输出 在FLASH编程、擦除等关键操作时添加调试日志,便于跟踪 固件升级过程中的具体行为。 perf(ota): 优化OTA完成后的外设关闭和跳转流程 重构跳转逻辑,直接使用内联汇编替代naked函数,提高跳转 的确定性和可靠性。 fix(ota): 添加重复包过滤机制避免数据重复处理 实现包序号验证和去重逻辑,防止ESP32超时重传导致的数据 重复写入问题。 fix(ota): 修复FINISHED状态下重传包的处理逻辑 当OTA完成后收到重复数据包时,返回WAIT状态让ESP32重新 发送FINISH命令,而不是重置整个状态机。 feat(ota): 改进应用程序校验逻辑支持调试器烧录 修改CRC校验逻辑,当Flash中未写入CRC值时视为校验通过, 允许通过调试器直接烧录的应用程序正常启动。 --- GD32E50X_BOOT/device/SpiOta.cpp | 24 ++++- GD32E50X_BOOT/main.cpp | 3 + GD32E50X_BOOT/sys/OTA.cpp | 182 ++++++++++++++++++++++++++++---- GD32E50X_BOOT/sys/OTA.h | 6 +- GD32E50X_BOOT/sys/initial.cpp | 8 ++ 5 files changed, 194 insertions(+), 29 deletions(-) diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index e9c5922..42ee1cc 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -155,10 +155,13 @@ void SpiOta::tryProcessCompleteFrame() { u16 rx_len = s_rxStreamLen; if (rx_len == 0U) { return; } - if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { - isrProcessFrame(); - return; - } + /* Raw 0xB0 BOOT 命令是单字节事务。 + * 不要在 CS 仍为低时仅凭“首字节 == 0xB0”就立刻处理, + * 否则一旦事务错位/丢首字节,普通 OTA 数据流里的某个随机字节也可能 + * 被误判成 BOOT 命令,直接把会话打回 IDLE。 + * + * 交给 onCsRelease() 在事务结束后再按“完整单字节事务”判定。 */ + if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { return; } if (s_rxStream[0] != kSof) { return; } if (rx_len < kFrameOverhead) { return; } @@ -192,7 +195,12 @@ void SpiOta::isrProcessFrame() { * 所以必须在函数入口就先置位,防止 REQ/DATA 被双重分发。 */ s_isrResponded = true; - if (rx_len >= 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) { + if (rx_len == 1U && s_rxStream[0] == SPI_OTA_CMD_BOOT) { + if (s_otaHandler != nullptr && s_otaHandler->isActive()) { + isrLogFrameEx("[BOOT-CMD-IGN]", s_rxStream, rx_len); + s_rxStreamLen = 0U; + return; + } u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U}; queueTxFrame(ack, sizeof(ack)); if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); } @@ -232,6 +240,12 @@ void SpiOta::isrProcessFrame() { } if (frame_type == SPI_OTA_CMD_BOOT) { + if (s_otaHandler != nullptr && s_otaHandler->isActive()) { + isrLogFrameEx("[BOOT-FR-IGN]", s_rxStream, (expected < 24U) ? expected : 24U); + s_rxStreamLen = 0U; + s_isrResponded = true; + return; + } u8 ack_ok = 0xA5U; u8 ack_frame[kTxBufSize] = {}; u16 ack_len = 0U; diff --git a/GD32E50X_BOOT/main.cpp b/GD32E50X_BOOT/main.cpp index e3d1924..e305f34 100644 --- a/GD32E50X_BOOT/main.cpp +++ b/GD32E50X_BOOT/main.cpp @@ -95,6 +95,9 @@ static void heartbeatPrint(uint32_t &heartbeat_counter, uint32_t loop_delay_ms, if (heartbeat_counter >= kHeartbeatPrintIntervalMs) { heartbeat_counter = 0U; #if DEBUG_PRINT + if (ota.isActive()) { + return; + } printf("[BOOT] \xe2\x99\xa5 %s ota_active=%d spi_tx_pending=%d\r\n", path_label, ota.isActive() ? 1 : 0, diff --git a/GD32E50X_BOOT/sys/OTA.cpp b/GD32E50X_BOOT/sys/OTA.cpp index 6d4e879..b5aed5a 100644 --- a/GD32E50X_BOOT/sys/OTA.cpp +++ b/GD32E50X_BOOT/sys/OTA.cpp @@ -1,4 +1,4 @@ -#include "OTA.h" +#include "OTA.h" #include "Can.h" #include "SpiOta.h" #include "aes128.h" @@ -26,8 +26,6 @@ __attribute__((naked, noreturn)) void branchToApplication(uint32_t msp_value, ui "msr basepri, r2 \n" "msr faultmask, r2 \n" "msr psp, r2 \n" - "msr psplim, r2 \n" - "msr msplim, r2 \n" "msr msp, r0 \n" "dsb \n" "isb \n" @@ -113,6 +111,10 @@ static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) { fmc_unlock(); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_state_enum status = fmc_word_program(address, data); +#if DEBUG_PRINT + printf("[FW] W addr=0x%08lX data=0x%08lX\r\n", + (unsigned long)address, (unsigned long)data); +#endif fmc_lock(); return status; } @@ -168,6 +170,9 @@ u8 OTA_SET(u32 addr, u32 data) { fmc_unlock(); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_status = fmc_page_erase(OTA_CONFIG_PAGE_ADDR); +#if DEBUG_PRINT + printf("[FW] E page=0x%08lX\r\n", (unsigned long)OTA_CONFIG_PAGE_ADDR); +#endif if (fmc_status != FMC_READY) { fmc_lock(); return FLASH_EEPROM_ERROR; @@ -177,6 +182,10 @@ u8 OTA_SET(u32 addr, u32 data) { u32 current_addr = n_APP_LATEST_AVAL_ADDR + 4U * (u32)i; if (current_addr != addr && temp_val[i] != OTA_NOT_AVAL) { fmc_status = fmc_word_program(current_addr, temp_val[i]); +#if DEBUG_PRINT + printf("[FW] W addr=0x%08lX data=0x%08lX\r\n", + (unsigned long)current_addr, (unsigned long)temp_val[i]); +#endif if (fmc_status != FMC_READY) { fmc_lock(); return FLASH_EEPROM_ERROR; @@ -186,6 +195,10 @@ u8 OTA_SET(u32 addr, u32 data) { if (data != OTA_NOT_AVAL) { fmc_status = fmc_word_program(addr, data); +#if DEBUG_PRINT + printf("[FW] W addr=0x%08lX data=0x%08lX\r\n", + (unsigned long)addr, (unsigned long)data); +#endif if (fmc_status != FMC_READY) { fmc_lock(); return FLASH_EEPROM_ERROR; @@ -295,6 +308,7 @@ void OTAHandler::process() { send_structured_ack(0U); } drainFlashBuffer(); + if (pending_finish_ack && flash_buf_cnt == 0U) { pending_finish_ack = false; } @@ -416,12 +430,12 @@ return; return; } +#if DEBUG_PRINT + printf("[J1]\r\n"); +#endif /* * [关键修复] 先关中断再做 Flash 操作。 - * 原来 OTA_SET 在 NVIC disable 之前执行。 - * Flash 编程期间 CPU stall,若此时 SPI0/CAN0 中断触发, - * ISR 嵌套调用会撑爆栈 -> HardFault -> 死循环。 */ __disable_irq(); for (int i = 0; i < 8; ++i) { @@ -431,11 +445,17 @@ return; __DSB(); __ISB(); +#if DEBUG_PRINT + printf("[J2]\r\n"); +#endif /* 把当前主应用重新标记为"有效且允许启动"。 */ OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL); OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL); +#if DEBUG_PRINT + printf("[J3]\r\n"); +#endif /* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */ DMA_CHCTL(DMA0, DMA_CH1) &= ~DMA_CHXCTL_CHEN; @@ -444,17 +464,14 @@ return; spi_i2s_deinit(SPI0); exti_interrupt_disable(EXTI_15); +#if DEBUG_PRINT + printf("[J4]\r\n"); +#endif - /* - * SysTick 不属于 NVIC 外设中断,跳转前单独关闭即可。 - * APP 的 SystemInit() 会完整重置时钟树,因此这里不再切 IRC8M, - * 也不再关 GPIO/USART/RCU 时钟,避免 Boot 收尾逻辑引入额外变量。 - */ SysTick->CTRL = 0U; SysTick->LOAD = 0U; SysTick->VAL = 0U; - /* 把中断向量表切到主应用起始地址。 */ SCB->VTOR = APPLATEST_START_ADDR; @@ -462,10 +479,51 @@ return; uint32_t msp_value = *(__IO uint32_t *)APPLATEST_START_ADDR; uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U); +#if DEBUG_PRINT + printf("[J5] msp=0x%08lX rst=0x%08lX\r\n", + (unsigned long)msp_value, (unsigned long)reset_handler); + /* 打印 Reset_Handler 地址处的原始字节,验证是否有有效代码 */ + printf("[J5-HEX] @0x%08lX: ", (unsigned long)(reset_handler & ~1U)); + { + const uint8_t *p = (const uint8_t *)(reset_handler & ~1U); + for (int i = 0; i < 16; i++) { + printf("%02X ", p[i]); + } + printf("\r\n"); + } + /* 打印向量表前 8 字节(MSP + Reset_Handler) */ + printf("[J5-VEC] @0x%08lX: ", (unsigned long)APPLATEST_START_ADDR); + { + const uint8_t *p = (const uint8_t *)APPLATEST_START_ADDR; + for (int i = 0; i < 8; i++) { + printf("%02X ", p[i]); + } + printf("\r\n"); + } +#endif - /* 仍在 Boot 栈上做最后一次清理,真正切栈和跳转交给裸汇编 trampoline。 */ - rcu_all_reset_flag_clear(); - branchToApplication(msp_value, reset_handler); + /* + * 直接用内联汇编跳转,消除 naked 函数调用的所有不确定性。 + * 使用显式寄存器变量保证 msp_value 在 r0,reset_handler 在 r1。 + */ + register uint32_t r0_val __asm("r0") = msp_value; + register uint32_t r1_val __asm("r1") = reset_handler; + __asm volatile ( + "movs r2, #0 \n" + "msr control, r2 \n" + "msr primask, r2 \n" + "msr basepri, r2 \n" + "msr faultmask, r2 \n" + "msr psp, r2 \n" + "msr msp, %0 \n" + "dsb \n" + "isb \n" + "bx %1 \n" + : + : "r"(r0_val), "r"(r1_val) + : "r2" + ); + __builtin_unreachable(); } /** @@ -484,6 +542,12 @@ void OTAHandler::eraseApplicationArea() { __disable_irq(); fmc_unlock(); +#if DEBUG_PRINT + printf("[FW] E app area 0x%08lX ~ 0x%08lX (%lu pages)\r\n", + (unsigned long)APPLATEST_START_ADDR, + (unsigned long)(APPLATEST_START_ADDR + APP_MAX_SIZE), + (unsigned long)((APP_MAX_SIZE + FMC_PAGE_SIZE - 1U) / FMC_PAGE_SIZE)); +#endif for (u32 addr = APPLATEST_START_ADDR; addr < (APPLATEST_START_ADDR + APP_MAX_SIZE); addr += FMC_PAGE_SIZE) { fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); @@ -509,14 +573,40 @@ void OTAHandler::eraseApplicationArea() { void OTAHandler::drainFlashBuffer() { - while (flash_buf_cnt > 0U) { - u8 ri = flash_buf_ri; - writeApplicationData(flash_buf[ri].offset, flash_buf[ri].data, OTA_PACKET_SIZE); - flash_buf_ri = (ri + 1U) % kFlashBufSlots; +#if DEBUG_PRINT + u32 drain_cnt = 0; +#endif + while (1) { + FlashSlot slot = {}; + bool has_slot = false; + + /* 先原子出队,再慢速写 Flash。 + * 这样即使 ISR 中途 resetToIdle() 清空了环形缓冲计数, + * 这里也不会在写完后再把 0 继续 -- 成 255。 */ __disable_irq(); - flash_buf_cnt--; + if (flash_buf_cnt > 0U) { + u8 ri = flash_buf_ri; + slot = flash_buf[ri]; + flash_buf_ri = (ri + 1U) % kFlashBufSlots; + flash_buf_cnt--; + has_slot = true; + } __enable_irq(); + + if (!has_slot) { + break; + } + + writeApplicationData(slot.offset, slot.data, OTA_PACKET_SIZE); +#if DEBUG_PRINT + drain_cnt++; +#endif } +#if DEBUG_PRINT + if (drain_cnt > 0U) { + printf("[FW] drain %u pkts\r\n", (unsigned)drain_cnt); + } +#endif } void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) { /** @@ -524,8 +614,8 @@ void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) { * - 先按 4 字节对齐批量写 * - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入 */ - fmc_unlock(); u32 write_addr = APPLATEST_START_ADDR + offset; + fmc_unlock(); u32 i = 0; for (; i + 4U <= size; i += 4U) { (void)fmc_word_program(write_addr + i, *(u32 *)&data[i]); @@ -606,6 +696,13 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { const u8 *receive_encrypted_data = data + 8; const u8 *receive_dataCRC32 = data + 60; + /** 去重:跳过已经确认接收过的包号(ESP32 超时重传)。 */ + if (receive_packet_num < next_expected_packet) { + u32 request_packet = receive_packet_num + 1U; + send_structured_ack(request_packet); + return; + } + u8 decrypted_data[OTA_PACKET_SIZE]; u8 calculated_dataCRC32[4]; @@ -638,6 +735,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { __disable_irq(); flash_buf_cnt++; __enable_irq(); + next_expected_packet = receive_packet_num + 1U; } /** @@ -666,6 +764,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { if (new_size <= APP_MAX_SIZE) { expected_size = new_size; received_size = 0; + next_expected_packet = 0U; } else { ota_error = OTA_ERROR_INVALID_SIZE; ota_state = OTA_STATE_IDLE; @@ -700,6 +799,17 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL); OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL); +#if DEBUG_PRINT + /* OTA 完成后立即 dump Reset_Handler 地址 */ + { + uint32_t rh_addr = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U) & ~1U; + printf("[OTA-DONE2] RH@0x%08lX: ", (unsigned long)rh_addr); + const uint8_t *p = (const uint8_t *)rh_addr; + for (int i = 0; i < 16; i++) printf("%02X ", p[i]); + printf("\r\n"); + } +#endif + u8 finish_ack[2] = {0x0A, 0xAC}; send_ack_frame(2, finish_ack); pending_app_jump = true; @@ -711,6 +821,14 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET); return; } + /** + * FINISHED 状态下收到重复数据包(ESP32 重传): + * 不应视为无效包而重置状态机,而是回 WAIT 让 ESP32 稍后重发 FINISH_OTA。 + */ + else if (ota_state == OTA_STATE_FINISHED) { + u8 wait_hint[1] = {0x04}; + send_wait_frame(1, wait_hint); + } else { ota_error = OTA_ERROR_INVALID_PACKET; #if DEBUG_PRINT @@ -911,6 +1029,10 @@ fmc_state_enum move_app_data() { fmc_unlock(); uint32_t num_pages = (APP_MAX_SIZE + FMC_PAGE_SIZE - 1U) / FMC_PAGE_SIZE; +#if DEBUG_PRINT + printf("[FW] E restore dst=0x%08lX pages=%lu\r\n", + (unsigned long)APPLATEST_START_ADDR, (unsigned long)num_pages); +#endif for (uint32_t i = 0; i < num_pages; ++i) { uint32_t page_addr = APPLATEST_START_ADDR + i * FMC_PAGE_SIZE; fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); @@ -926,11 +1048,17 @@ fmc_state_enum move_app_data() { crc_data_register_reset(); uint32_t src_crc = crc_block_data_calculate((void *)APPBACK_START_ADDR, APP_MAX_SIZE / 4U, INPUT_FORMAT_WORD); +#if DEBUG_PRINT + u32 dbg_word_cnt = 0; +#endif for (uint32_t offset = 0; offset < APP_MAX_SIZE; offset += 4U) { uint32_t src_data = *(uint32_t *)(APPBACK_START_ADDR + offset); if (src_data != 0xFFFFFFFFU) { fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_status = fmc_word_program(APPLATEST_START_ADDR + offset, src_data); +#if DEBUG_PRINT + dbg_word_cnt++; +#endif if (fmc_status != FMC_READY) { fmc_lock(); __enable_irq(); @@ -938,6 +1066,12 @@ fmc_state_enum move_app_data() { } } } +#if DEBUG_PRINT + printf("[FW] W restore src=0x%08lX dst=0x%08lX words=%lu\r\n", + (unsigned long)APPBACK_START_ADDR, + (unsigned long)APPLATEST_START_ADDR, + (unsigned long)dbg_word_cnt); +#endif fmc_lock(); __enable_irq(); @@ -1022,9 +1156,11 @@ bool verifyAppCrc(u32 start_addr) { volatile u32 *stored_crc_ptr = (volatile u32 *)crc_addr; u32 stored_crc = *stored_crc_ptr; - /** Flash 擦除态 = 0xFFFFFFFF,如果 CRC 地址未写过说明无校验数据。 */ + /** Flash 擦除态 = 0xFFFFFFFF,如果 CRC 地址未写过说明无校验数据。 + * APP 通过调试器直接烧录时 CRC 未写入,此时不应阻止跳转, + * 视为校验通过(未启用 CRC 保护),仅 OTA 升级后才真正校验。 */ if (stored_crc == FLASH_EMPTY) { - return false; + return true; } crc_deinit(); diff --git a/GD32E50X_BOOT/sys/OTA.h b/GD32E50X_BOOT/sys/OTA.h index d0a9395..673f85b 100644 --- a/GD32E50X_BOOT/sys/OTA.h +++ b/GD32E50X_BOOT/sys/OTA.h @@ -1,4 +1,4 @@ -#ifndef __OTA_H +#ifndef __OTA_H #define __OTA_H #include "device/Can.h" @@ -99,9 +99,11 @@ class OTAHandler { pending_flash_erase = false; pending_app_jump = false; pending_finish_ack = false; + pending_finish_ota = false; flash_buf_wi = 0U; flash_buf_ri = 0U; flash_buf_cnt = 0U; + next_expected_packet = 0U; } /** @@ -192,6 +194,8 @@ class OTAHandler { volatile u8 flash_buf_ri = 0U; /**< 主循环读指针 */ volatile u8 flash_buf_cnt = 0U; /**< 缓冲中待写入条目数 */ volatile bool pending_finish_ack = false; /**< FINISH 包已缓冲,等待 drain 后回 ACK */ + volatile bool pending_finish_ota = false; /**< FINISH_OTA 命令已收到,等待 drain 后处理 */ + u32 next_expected_packet = 0U; /**< 下一个期望收到的包号,用于去重 */ void drainFlashBuffer(); diff --git a/GD32E50X_BOOT/sys/initial.cpp b/GD32E50X_BOOT/sys/initial.cpp index 6c3fe2e..4bc0a70 100644 --- a/GD32E50X_BOOT/sys/initial.cpp +++ b/GD32E50X_BOOT/sys/initial.cpp @@ -11,6 +11,7 @@ #include "gd32e50x_rcu.h" #include "gd32e50x_usart.h" #include "systick.h" +#include /** * @brief 初始化 USART0 调试串口。 @@ -163,6 +164,10 @@ fmc_state_enum BootSleepManager::writeAddress(u32 address, u32 data) fmc_unlock(); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_state_enum status = fmc_word_program(address, data); +#if DEBUG_PRINT + printf("[FW] W addr=0x%08lX data=0x%08lX\r\n", + (unsigned long)address, (unsigned long)data); +#endif fmc_lock(); return status; } @@ -241,6 +246,9 @@ void BootSleepManager::onExtiInterrupt(void) fmc_lock(); return; } +#if DEBUG_PRINT + printf("[FW] E page=0x%08lX\r\n", (unsigned long)SLEEP_CONFIG_START); +#endif address = SLEEP_CONFIG_START - SIZE_U32; } (void)writeAddress(address + SIZE_U32, SLEEP_DISABLE);