feat(boot): 添加OTA状态检查避免心跳打印干扰

当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值时视为校验通过,
允许通过调试器直接烧录的应用程序正常启动。
This commit is contained in:
cbb
2026-06-02 22:13:30 +08:00
parent c801701783
commit 061bcabc37
5 changed files with 194 additions and 29 deletions
+19 -5
View File
@@ -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;
+3
View File
@@ -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,
+159 -23
View File
@@ -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();
+5 -1
View File
@@ -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();
+8
View File
@@ -11,6 +11,7 @@
#include "gd32e50x_rcu.h"
#include "gd32e50x_usart.h"
#include "systick.h"
#include <stdio.h>
/**
* @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);