Compare commits

...
4 Commits
Author SHA1 Message Date
cbb 0b086d4b86 refactor(spi): 优化SPI OTA模块代码结构
- 移除未使用的调试相关变量和函数,包括s_isrLogPending、s_isrLogBuf等
- 简化注释内容,保留核心功能说明
- 优化CS信号处理逻辑,提高代码可读性
- 修正帧处理中的竞态条件问题,提前设置响应标志避免重复处理

feat(boot): 添加BOOT固件版本号管理功能

- 在main.cpp中添加版本号写入逻辑,将BOOT版本号存储到OTA配置页
- 更新OTA配置页结构,从6个word扩展到8个word,增加版本号存储空间
- 实现版本变更检测机制,仅在版本更新时重写配置页
- 优化SpiOta模块的日志系统,移除多余的调试日志和缓冲区管理代码
2026-06-03 03:13:30 +08:00
cbb 061bcabc37 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值时视为校验通过,
允许通过调试器直接烧录的应用程序正常启动。
2026-06-02 22:13:30 +08:00
cbb c801701783 fix(OTA): 移除BOM标记并优化OTA处理逻辑
移除了OTA.cpp文件中的BOM标记,删除了调试打印代码,修复了
缓冲区溢出检查逻辑,并简化了结束确认帧的发送方式。

- 移除文件开头的BOM标记
- 删除DEBUG_PRINT相关的调试输出代码
- 修正缓冲区大小检查条件,使用expected_size替代硬编码值
- 将结束确认改为使用结构化ACK消息发送
2026-06-01 21:27:03 +08:00
cbb 4a39028f74 feat(bootloader): 添加CRC32完整性校验和SPI OTA优化
添加了硬件CRC32完整性校验机制,在固件更新后计算并验证CRC32,
确保应用区固件完整性;同时优化SPI OTA的数据处理流程,
引入环形缓冲机制解决帧重复处理问题,并增加传输日志记录。

BREAKING CHANGE: 固件布局调整,应用区末尾预留4字节存储CRC32值
2026-05-31 01:45:37 +08:00
7 changed files with 499 additions and 253 deletions
+22 -133
View File
@@ -1,4 +1,4 @@
#include "SpiOta.h"
#include "SpiOta.h"
#include "core_cm33.h"
#include "gd32e50x_gpio.h"
#include "gd32e50x_exti.h"
@@ -26,11 +26,10 @@
#define SPI_TX_DMA_PERIPH DMA0
#define SPI_TX_DMA_CH DMA_CH2
/* 调试:多缓冲日志(8 条 ISR 日志缓冲区,避免关键事件丢失) */
/* 调试日志缓冲 */
static constexpr u16 kLogSlotCount = 8U;
static constexpr u16 kLogSlotSize = 80U;
static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {};
static u16 s_logSlotLens[kLogSlotCount] = {};
static volatile u8 s_logWriteIdx = 0U;
static volatile u8 s_logReadIdx = 0U;
static volatile u8 s_logCount = 0U;
@@ -47,10 +46,7 @@ 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;
volatile bool SpiOta::s_otaSessionActive = false;
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
u16 SpiOta::s_isrLogLen = 0U;
/* ============================================================
* CRC8 / 帧构建 / 帧解析
@@ -155,10 +151,8 @@ void SpiOta::tryProcessCompleteFrame() {
u16 rx_len = s_rxStreamLen;
if (rx_len == 0U) { return; }
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) {
isrProcessFrame();
return;
}
/* 单字节 BOOT 命令留到事务结束后再判定。 */
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { return; }
if (s_rxStream[0] != kSof) { return; }
if (rx_len < kFrameOverhead) { return; }
@@ -179,25 +173,19 @@ void SpiOta::isrProcessFrame() {
u16 rx_len = s_rxStreamLen;
if (rx_len == 0U) { return; }
/* 关键修复:一旦开始处理本次 CS 周期中的完整帧,立刻标记“已响应/已处理”。
*
* 否则会出现这样的竞态:
* 1) SPI0 RBNE IRQ 在收到最后一个字节后调用 isrProcessFrame()
* 2) 还没执行到函数尾部的 s_isrResponded = true
* 3) CS 已经上升,优先级更高的 EXTI IRQ 抢占进来执行 onCsRelease()
* 4) onCsRelease() 看到 s_isrResponded 仍为 false,于是再次调用 isrProcessFrame()
* 5) 同一帧被重复处理一次,状态机被错误推进(如 challenge 又被当 confirm)
*
* 所以必须在函数入口就先置位,防止 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()) {
s_rxStreamLen = 0U;
return;
}
u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U};
queueTxFrame(ack, sizeof(ack));
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
s_otaSessionActive = false;
s_rxStreamLen = 0U;
isrLogFrameEx("[BOOT-CMD]", s_txBuf, s_txLen);
s_isrResponded = true;
return;
}
@@ -231,6 +219,11 @@ void SpiOta::isrProcessFrame() {
}
if (frame_type == SPI_OTA_CMD_BOOT) {
if (s_otaHandler != nullptr && s_otaHandler->isActive()) {
s_rxStreamLen = 0U;
s_isrResponded = true;
return;
}
u8 ack_ok = 0xA5U;
u8 ack_frame[kTxBufSize] = {};
u16 ack_len = 0U;
@@ -248,22 +241,9 @@ void SpiOta::isrProcessFrame() {
s_otaSessionActive = true;
s_otaHandler->setTransportSender(sender);
ota_link_msg_type_t msg_type = mapMsgType(frame_type);
/* 精简诊断: 只记录帧类型+状态, 减少ISR日志开销 */
{
u8 diag[3] = {(u8)frame_type, (u8)(s_otaHandler ? s_otaHandler->getState() : 0xFFU),
(u8)p_len};
isrLogFrameEx("[OT]", diag, 3);
}
s_otaHandler->handleLinkMessage(msg_type, const_cast<u8 *>(payload), (u8)p_len);
}
/* 精简: 只在非DATA包时记录响应帧内容, DATA包只记录ACK的seq */
if (frame_type != SPI_OTA_CMD_DATA) {
isrLogFrameEx("[RD]", s_txBuf, (s_txLen < 12U) ? s_txLen : 12U);
} else {
/* DATA包响应: 只记录payload前6字节(=BAC0+seq) */
isrLogFrameEx("[RD]", s_txBuf + 4, (s_txLen > 10U) ? 6U : (s_txLen > 4U ? s_txLen - 4U : 0U));
}
s_isrResponded = true;
}
@@ -290,7 +270,6 @@ static void isrLogFrameEx(const char *tag, const u8 *data, u16 len) {
buf[pos++] = (u8)hx[data[i] & 0x0FU];
}
buf[pos] = 0U;
s_logSlotLens[s_logWriteIdx] = pos;
s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount;
s_logCount++;
}
@@ -391,7 +370,6 @@ void SpiOta::init() {
s_txStreamPos = 0U;
s_txLen = 0U;
s_txPending = false;
s_isrLogPending = false;
s_otaSessionActive = false;
memset(s_rxStream, 0, sizeof(s_rxStream));
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
@@ -412,68 +390,17 @@ void SpiOta::onCsAssert() {
s_txStreamPos = 0U;
s_isrResponded = false;
/* 内部 NSS 已在初始化时常置 LOW,这里不再依赖 EXTI 时序切 NSS。
* PA15 仅作为“事务开始”的边界信号,用于清接收缓冲和重装 DMA。 */
/* 关键修复:先彻底停止 DMA,再重新配置并启动。
*
* 上一版预填 SPI_DATA 方案失败原因:
* spi_i2s_data_transmit() 写入的字节进入 TBE 发送缓冲区。
* 当 ESP32 第一个 SCK 边沿到来时,TBE 的预填字节被移入移位
* 寄存器推到 MISO,同时移位寄存器中的 MOSI 数据被推入 RBNE。
* 但 GD32E50x SPI 的 RBNE 标志在 SPI_DATA 写入后可能被误触发,
* 导致 isrLogFrame 报出预填值(如 0x80)而非 MOSI 真实数据。
*
* 正确方案:不预填 SPI_DATA,而是确保 DMA 在 CS↓ 后、ESP32
* 第一个 SCK 边沿之前就绪。DMA 第一拍自动把 s_txBuf[0] 写入
* SPI_DATA,不会产生 RBNE 误触发。
*
* 操作顺序:
* 1. 禁 DMA 通道 + 禁 SPI DMA 请求(彻底停止旧传输)
* 2. 清 RBNE / RXORERR / FERR 等残留标志
* 3. 配置并启动 DMA(此时 RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断)
* 4. 重新使能 RBNE 中断,开始接收 MOSI 数据
*/
/* 1. 停止旧 DMA */
/* PA15 仅作为事务边界信号;这里重装 DMA 并清理旧状态。 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
/* 1.5 覆盖 SPI 发送缓冲区 (TBE) 中的旧字节
*
* 关键修复:CS 高期间,queueTxFrame() 会把新 ACK 帧写入 s_txBuf
* 并启动 DMA,DMA 把 s_txBuf[0] 写入 SPI_DATA (TBE 缓冲区)。
* CS 高时没有外部 SCK,TBE 中的字节停留在发送缓冲区不会被移位输出。
*
* 当 ESP32 下次 CS↓ 时,onCsAssert() 重启 DMA,但 TBE 中已经有
* 旧字节。如果 ESP32 第一个 SCK 边沿在 DMA 写入新数据之前到达,
* MISO 上先输出 TBE 中的旧字节,然后 DMA 才开始推 s_txBuf[0]。
* 这导致 MISO 上第一个字节是旧 ACK 帧的残留,ESP32 的
* spi_ota_find_frame 先匹配到旧帧,拿到比期望少 1 的 seq 号
* ("差一拍" 现象)。
*
* 修复:在重启 DMA 前,写 0xFF 到 SPI_DATA 覆盖 TBE 中的旧字节。
* 这样即使 ESP32 第一个 SCK 先到,MISO 输出的是 0xFF 而非旧 ACK 残留。
* spi_ota_find_frame 会跳过 0xFF(不是 SOF=0xA5),从后面 DMA 推出的
* 正确帧开始匹配。
*/
/* 覆盖 TBE 里的旧字节,避免下一次事务先吐出残留 ACK。 */
spi_i2s_data_transmit(SPI0, 0xFFU);
/* 如果 TBE FIFO 有两层,再写一个 0xFF 确保覆盖 */
if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) {
spi_i2s_data_transmit(SPI0, 0xFFU);
}
/* 2. 清残留错误标志
*
* 关键:这里绝不能在 CS↓ 时“顺手读掉 RBNE”。
*
* 因为 internal NSS 现在常 LOW,ESP32 在 CS↓ 后很快就会发出首字节 A5。
* 如果 EXTI ISR 略晚于 SPI 接收,A5 可能已经先进了 RX 缓冲并置位 RBNE。
* 这时若在 onCsAssert() 里执行 spi_i2s_data_receive(),就会把真实首字节 A5
* 当成“残留垃圾”吞掉,导致后续看到的首字节变成 0x01。
*
* 所以这里仅清真正的错误标志,不主动消费 RBNE。
* 已到达的首字节留给 onSpiInterrupt() 正常读走。 */
/* 只清错误标志,不主动消费 RBNE。 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0);
}
@@ -481,7 +408,7 @@ void SpiOta::onCsAssert() {
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
}
/* 3. 配置并启动 DMA(RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断) */
/* 3. 配置并启动 DMA */
if (s_txPending && s_txLen > 0U) {
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(s_txBuf, s_txLen);
@@ -500,12 +427,7 @@ void SpiOta::onCsRelease() {
if (!s_csActive) { return; }
s_csActive = false;
/* 内部 NSS 保持常 LOW,不在 CS↑ 时切回 HIGH。
* 这样 SPI 从机始终处于就绪态,下一次 CS↓ 不会因 EXTI 延迟丢首字节。
*
* 这里只停止 DMA,避免 CS 高期间继续空转消耗 s_txBuf 内容。
* s_txPending 保持不变——如果 ISR 已准备好 ACK 帧,
* 下次 CS↓ 时 onCsAssert 会重启 DMA 推出。 */
/* 内部 NSS 保持常 LOW;这里只停止 DMA。 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
@@ -516,14 +438,6 @@ void SpiOta::onCsRelease() {
}
}
/* 关键调试日志:记录本次 CS 周期结束时到底收到了多少字节。
* 这样能直接判断:
* 1) 是不是完整收到了 ESP32 发来的整帧
* 2) 还是只收到了首字节 A5 / 少数字节,导致永远无法解析回包 */
/* 精简: CS-RX只记录前4字节, 减少ISR日志开销 */
if (s_rxStreamLen > 0U) {
isrLogFrame("[CR]", s_rxStream, (s_rxStreamLen < 4U) ? s_rxStreamLen : 4U);
}
if (s_rxStreamLen > 0U && !s_isrResponded) {
isrProcessFrame();
@@ -536,13 +450,7 @@ void SpiOta::onCsRelease() {
void SpiOta::onSpiInterrupt() {
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
/* 与 APP 侧稳定实现保持一致:
* 如果 EXTI 下降沿因为中断时序略晚到达,但此时 PA15 已经为低,
* 则在首个 RBNE 到来时立即补做 onCsAssert()。
*
* 关键限制:只能在“本事务尚未收过任何字节”时补做。
* 否则若 EXTI / SPI 时序偶发抖动,重复 onCsAssert() 会把
* s_rxStreamLen 清零,导致已经收到的半帧被冲掉,最终永远拼不成完整帧。 */
/* EXTI 略晚时,在首个 RBNE 到来时补做 onCsAssert()。 */
if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
}
@@ -551,11 +459,6 @@ void SpiOta::onSpiInterrupt() {
if (s_rxStreamLen < kRxBufSize) {
s_rxStream[s_rxStreamLen++] = byte;
}
/* 调试阶段先关闭 RBNE 首字节日志。
* 原因:ISR 日志缓冲是单槽的,RBNE 一旦先占位,
* onCsRelease() 里的 [CS-RX] 摘要日志就会被丢弃。
* 目前排查重点是“本次 CS 周期总共收到了哪些字节”,
* 所以优先保留 [CS-RX],暂时关闭 [RBNE]。 */
tryProcessCompleteFrame();
}
/* TBE 已由 DMA 替代,不再处理 */
@@ -584,28 +487,14 @@ void SpiOta::poll() {
if (s_isrResponded) {
s_isrResponded = false;
}
/* 打印 ISR 日志缓冲区 */
while (s_logCount > 0U) {
__disable_irq();
if (s_logCount == 0U) { __enable_irq(); break; }
u8 *buf = s_logSlots[s_logReadIdx];
u8 nextRead = (s_logReadIdx + 1U) % kLogSlotCount;
s_logReadIdx = nextRead;
s_logReadIdx = (s_logReadIdx + 1U) % kLogSlotCount;
s_logCount--;
__enable_irq();
printf("[SPI] %s\r\n", buf);
}
if (s_otaHandler != nullptr) { s_otaHandler->process(); }
/* 调试:定期打印 SPI 从机状态 — 降低频率, 每20000次才打印一次 */
static u32 dbg_tick = 0;
dbg_tick++;
if ((dbg_tick % 20000U) == 0U) {
printf("[SPI] st=%u rx=%u tx=%u ota=%u\r\n",
(unsigned)s_csActive, (unsigned)s_rxStreamLen,
(unsigned)s_txLen,
(unsigned)(s_otaHandler ? s_otaHandler->getState() : 0xFF));
}
}
void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
-3
View File
@@ -59,10 +59,7 @@ public:
static volatile u16 s_txLen;
static volatile bool s_txPending;
static volatile bool s_isrResponded;
static volatile bool s_isrLogPending;
static volatile bool s_otaSessionActive;
static u8 s_isrLogBuf[kIsrLogBufSize];
static u16 s_isrLogLen;
static void sender(ota_link_tx_type_t type, u8 len, const u8 *data);
+117 -47
View File
@@ -1,4 +1,4 @@
#include "main.h"
#include "main.h"
#include "device/Can.h"
#include "device/SpiOta.h"
#include "sys/config.h"
@@ -43,6 +43,7 @@ void BootloaderSystem::initHardware() {
(unsigned long)APP_MAX_SIZE);
printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock);
#endif
rcu_periph_clock_enable(RCU_CRC); /* OTA CRC 校验需要 CRC 外设时钟 */
CAN0_transmiter.init();
ota.init();
SpiOta::init();
@@ -50,6 +51,62 @@ void BootloaderSystem::initHardware() {
/** 让 SPI OTA 模块在收到完整帧后,转交给 ota 实例处理。 */
SpiOta::setSender(&ota);
/** 写入 BOOT 版本号到 OTA 配置页(仅当版本变更时重写)。
* 采用与 OTA_SET 一致的读-擦-写策略,但将 word6/word7 合并为单次页面擦除,
* 避免两次 OTA_SET 调用导致的双重擦除。 */
{
u32 ver_word0 = (u32)((BOOT_FW_VERSION_MAJOR & 0xFFFFU) | ((BOOT_FW_VERSION_MINOR & 0xFFFFU) << 16));
u32 ver_word1 = (u32)((BOOT_FW_VERSION_PATCH & 0xFFFFU) | ((BOOT_FW_VERSION_BUILD & 0xFFFFU) << 16));
volatile u32 *cur0 = (volatile u32 *)n_BOOT_VERSION_ADDR0;
volatile u32 *cur1 = (volatile u32 *)n_BOOT_VERSION_ADDR1;
if (*cur0 != ver_word0 || *cur1 != ver_word1) {
/** 版本变更:先读取整页 8 个配置字,再擦除整页后一次性回写。
* 读取逻辑与 app_spi_command.cpp 中 BOOT 版本读取方式一致。 */
u32 temp_val[OTA_CONFIG_NUM];
volatile u32 *flash_ptr;
for (int i = 0; i < OTA_CONFIG_NUM; ++i) {
flash_ptr = (volatile u32 *)(OTA_CONFIG_PAGE_ADDR + 4U * (u32)i);
temp_val[i] = *flash_ptr;
}
/** 用新版本号替换 word6/word7。 */
temp_val[6] = ver_word0;
temp_val[7] = ver_word1;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum 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) {
for (int i = 0; i < OTA_CONFIG_NUM; ++i) {
if (temp_val[i] != OTA_NOT_AVAL) {
u32 current_addr = OTA_CONFIG_PAGE_ADDR + 4U * (u32)i;
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) {
#if DEBUG_PRINT
printf("[BOOT] version write FAILED at word %d\r\n", i);
#endif
break;
}
}
}
}
fmc_lock();
#if DEBUG_PRINT
if (fmc_status == FMC_READY) {
printf("[BOOT] version written: v%u.%u.%u.%u\r\n",
BOOT_FW_VERSION_MAJOR, BOOT_FW_VERSION_MINOR,
BOOT_FW_VERSION_PATCH, BOOT_FW_VERSION_BUILD);
}
#endif
}
}
/** 检查是否需要进入深度睡眠模式。 */
{
const int sleep_state = BootSleepManager::check();
@@ -94,6 +151,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,
@@ -125,16 +185,6 @@ void BootloaderSystem::run() {
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
}
/** 检测看门狗复位:如果上次复位是由 APP 的 FWDGT 超时触发,
* 则将 app_enable 标记为 NOT_AVAL,阻止 BOOT 再次跳入卡死的 APP,
* 让 BOOT 停留在 OTA 稡式等待远程修复。 */
if (rcu_flag_get(RCU_FLAG_FWDGTRST) != RESET) {
#if DEBUG_PRINT
printf("[BOOT] FWDGT reset detected, blocking app jump\r\n");
#endif
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
}
rcu_all_reset_flag_clear();
flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR;
@@ -150,19 +200,30 @@ void BootloaderSystem::run() {
/**
* 情况 1:应用已校验完成,且允许启动。
* 直接跳转到主应用,不在 Bootloader 多停留。
* 增加 CRC32 完整性校验;若校验失败则标记 A区无效,fall through 到恢复逻辑。
*/
if (app_check == OTA_AVAL && app_enable == OTA_AVAL) {
#if DEBUG_PRINT
printf("[BOOT] path=jump_latest_now\r\n");
#endif
ota.jumpToApplication();
if (verifyAppCrc(APPLATEST_START_ADDR)) {
ota.jumpToApplication();
/** jumpToApplication() 正常不返回;若返回则继续 fall through。 */
} else {
/** CRC 校验失败:标记 A区无效,继续往下走恢复逻辑。 */
#if DEBUG_PRINT
printf("[BOOT] CRC fail, mark latest invalid\r\n");
#endif
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL);
}
}
/**
* 情况 2:应用是有效的,但当前被标记为"暂不启动"。
* 这通常表示系统希望给 OTA 留一个短暂窗口,若窗口内没有升级请求,再启动应用。
* 窗口超时后也做 CRC 校验,失败则 fall through。
*/
else if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) {
if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) {
uint32_t delay_count = 0;
uint32_t heartbeat_counter = 0U;
int ota_requested = 0;
@@ -188,32 +249,37 @@ void BootloaderSystem::run() {
}
if (ota_requested) {
/**
* 已经进入 OTA 会话:
* 后续不再尝试跳应用,持续停留在 Bootloader 内处理升级。
*/
#if DEBUG_PRINT
printf("[BOOT] path=stay_in_ota\r\n");
#endif
uint32_t hb_ota = 0U;
while (1) {
pollAllLinks();
heartbeatPrint(hb_ota, 10U, "stay_in_ota");
delay_1ms(10);
heartbeatPrint(hb_ota, 1U, "stay_in_ota");
delay_1ms(1);
}
} else {
/** 等待窗口内没有任何 OTA 请求,正常启动当前应用。 */
#if DEBUG_PRINT
printf("[BOOT] ota_window_timeout_then_jump\r\n");
#endif
ota.jumpToApplication();
if (verifyAppCrc(APPLATEST_START_ADDR)) {
ota.jumpToApplication();
/** jumpToApplication() 正常不返回;若返回则继续 fall through。 */
} else {
/** CRC 校验失败:标记 A区无效,继续往下走恢复逻辑。 */
#if DEBUG_PRINT
printf("[BOOT] CRC fail after OTA window, mark latest invalid\r\n");
#endif
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL);
}
}
}
/**
* 情况 3:当前主应用区不可用。
* 情况 3:当前主应用区不可用(标志无效或 CRC 校验失败 fall through)。
* 尝试检查备份区是否可恢复;如果可恢复,则复制到主应用区后启动。
*/
else {
{
uint32_t back_aval, back_check;
flash_ptr = (volatile uint32_t *)n_APP_BACKUP_AVAL_ADDR;
back_aval = *flash_ptr;
@@ -227,41 +293,45 @@ void BootloaderSystem::run() {
#endif
if (back_check == OTA_AVAL && back_aval == OTA_AVAL) {
/**
* 备份区可用:
* 1. 先发送一帧提示,表示即将走备份恢复路径;
* 2. 把备份应用搬运到主应用区;
* 3. 跳转运行。
*/
#if DEBUG_PRINT
printf("[BOOT] path=restore_backup_then_jump\r\n");
#endif
CAN0_transmiter.sendBackup();
move_app_data();
ota.jumpToApplication();
fmc_state_enum restore_result = move_app_data();
if (restore_result == FMC_READY) {
if (verifyAppCrc(APPLATEST_START_ADDR)) {
ota.jumpToApplication();
}
#if DEBUG_PRINT
printf("[BOOT] CRC fail after restore, stay in BOOT\r\n");
#endif
} else {
#if DEBUG_PRINT
printf("[BOOT] restore failed status=%u\r\n", (unsigned)restore_result);
#endif
}
} else {
/**
* 当前区和备份区都不可用:
* 只能长期停留在 Bootloader,等待外部重新下发固件。
*/
#if DEBUG_PRINT
printf("[BOOT] path=stay_boot_wait_image\r\n");
#endif
uint32_t hb_wait = 0U;
while (1) {
pollAllLinks();
heartbeatPrint(hb_wait, 10U, "stay_boot_wait_image");
delay_1ms(10);
}
}
/**
* 当前区和备份区都不可用,或恢复后校验失败:
* 只能长期停留在 Bootloader,等待外部重新下发固件。
*/
#if DEBUG_PRINT
printf("[BOOT] path=stay_boot_wait_ota\r\n");
#endif
uint32_t hb_wait = 0U;
while (1) {
pollAllLinks();
heartbeatPrint(hb_wait, 10U, "stay_boot_wait_ota");
delay_1ms(10);
}
}
}
/**
* @brief Bootloader 程序入口。
*
* @return 理论上不会返回;若异常返回则维持空转。
*/
int main(void) {
sys.initHardware();
sys.run();
+280 -57
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;
}
@@ -150,7 +152,7 @@ u8 OTA_SET(u32 addr, u32 data) {
}
}
/** 读取当前配置页中的全部状态,供擦除后重写。 */
/** 读取当前配置页中的全部状态(含 BOOT 版本 word 6/7),供擦除后重写。 */
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
@@ -163,11 +165,18 @@ u8 OTA_SET(u32 addr, u32 data) {
temp_val[4] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr;
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR0;
temp_val[6] = *flash_ptr;
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR1;
temp_val[7] = *flash_ptr;
/** 整页擦除后重建配置页。 */
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 +186,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 +199,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;
@@ -294,6 +311,11 @@ void OTAHandler::process() {
#endif
send_structured_ack(0U);
}
drainFlashBuffer();
if (pending_finish_ack && flash_buf_cnt == 0U) {
pending_finish_ack = false;
}
if (pending_app_jump) {
pending_app_jump = false;
@@ -313,7 +335,7 @@ void OTAHandler::process() {
* 非空闲态时,按固定节奏推进超时计数。
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
*/
delay_1us(100);
delay_1us(20);
ota_time++;
}
@@ -389,14 +411,6 @@ bool validateApplication() {
reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE));
#if DEBUG_PRINT
printf("[BOOT] validateApp: MSP=0x%08lX (%s) Reset=0x%08lX (%s) @0x%08lX\r\n",
(unsigned long)stack_ptr, stack_valid ? "OK" : "INVALID",
(unsigned long)reset_handler, reset_valid ? "OK" : "INVALID",
(unsigned long)APPLATEST_START_ADDR);
/* 额外打印前 4 个 word 帮助诊断固件内容是否正确 */
printf("[BOOT] vector[0..3]=%08lX %08lX %08lX %08lX\r\n",
(unsigned long)app_vector[0], (unsigned long)app_vector[1],
(unsigned long)app_vector[2], (unsigned long)app_vector[3]);
#endif
return stack_valid && reset_valid;
@@ -409,21 +423,23 @@ bool validateApplication() {
*/
void OTAHandler::jumpToApplication() {
if (!validateApplication()) {
return;
}
/** CRC32 完整性校验:若固件被破坏则不跳转。 */
if (!verifyAppCrc(APPLATEST_START_ADDR)) {
#if DEBUG_PRINT
printf("[BOOT] jump rejected: invalid app vector @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
printf("[BOOT] CRC verify FAIL, abort jump\r\n");
#endif
return;
}
#if DEBUG_PRINT
printf("[BOOT] jumping to app @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR);
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) {
@@ -434,7 +450,7 @@ void OTAHandler::jumpToApplication() {
__ISB();
#if DEBUG_PRINT
printf("[DBG] NVIC disabled\r\n");
printf("[J2]\r\n");
#endif
/* 把当前主应用重新标记为"有效且允许启动"。 */
@@ -442,48 +458,24 @@ void OTAHandler::jumpToApplication() {
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
#if DEBUG_PRINT
printf("[DBG] OTA_SET done\r\n");
printf("[J3]\r\n");
#endif
/* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */
DMA_CHCTL(DMA0, DMA_CH1) &= ~DMA_CHXCTL_CHEN;
#if DEBUG_PRINT
printf("[DBG] DMA_CH1 disabled\r\n");
#endif
DMA_CHCTL(DMA0, DMA_CH2) &= ~DMA_CHXCTL_CHEN;
#if DEBUG_PRINT
printf("[DBG] DMA_CH2 disabled\r\n");
#endif
can_deinit(CAN0);
#if DEBUG_PRINT
printf("[DBG] CAN0 deinit\r\n");
#endif
spi_i2s_deinit(SPI0);
#if DEBUG_PRINT
printf("[DBG] SPI0 deinit\r\n");
#endif
exti_interrupt_disable(EXTI_15);
#if DEBUG_PRINT
printf("[DBG] EXTI15 disabled\r\n");
#endif
#if DEBUG_PRINT
printf("[DBG] peripherals deinit done\r\n");
printf("[J4]\r\n");
#endif
/*
* SysTick 不属于 NVIC 外设中断,跳转前单独关闭即可。
* APP 的 SystemInit() 会完整重置时钟树,因此这里不再切 IRC8M,
* 也不再关 GPIO/USART/RCU 时钟,避免 Boot 收尾逻辑引入额外变量。
*/
SysTick->CTRL = 0U;
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
#if DEBUG_PRINT
printf("[DBG] SysTick disabled\r\n");
#endif
/* 把中断向量表切到主应用起始地址。 */
SCB->VTOR = APPLATEST_START_ADDR;
@@ -492,15 +484,50 @@ void OTAHandler::jumpToApplication() {
uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U);
#if DEBUG_PRINT
printf("[DBG] app MSP=0x%08lX Reset=0x%08lX\r\n",
(unsigned long)msp_value,
(unsigned long)reset_handler);
printf("[DBG] calling app entry\r\n");
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();
}
/**
@@ -519,6 +546,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);
@@ -541,14 +574,52 @@ void OTAHandler::eraseApplicationArea() {
* @param size 数据长度。
* @return 无。
*/
void OTAHandler::drainFlashBuffer() {
#if DEBUG_PRINT
u32 drain_cnt = 0;
#endif
while (1) {
FlashSlot slot = {};
bool has_slot = false;
/* 先原子出队,再慢速写 Flash。
* 这样即使 ISR 中途 resetToIdle() 清空了环形缓冲计数,
* 这里也不会在写完后再把 0 继续 -- 成 255。 */
__disable_irq();
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) {
/**
* 将一个分包写入主应用区:
* - 先按 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]);
@@ -629,6 +700,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];
@@ -644,13 +722,24 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
return;
} else {
u32 offset = receive_packet_num * OTA_PACKET_SIZE;
if (offset + OTA_PACKET_SIZE <= APP_MAX_SIZE) {
writeApplicationData(offset, decrypted_data, OTA_PACKET_SIZE);
} else {
if (offset >= expected_size) {
ota_error = OTA_ERROR_INVALID_SIZE;
ota_state = OTA_STATE_IDLE;
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE);
return;
}
if (flash_buf_cnt >= kFlashBufSlots) {
send_wait_frame(1, (const u8 *)"\x03");
return;
}
u8 wi = flash_buf_wi;
flash_buf[wi].offset = offset;
memcpy(flash_buf[wi].data, decrypted_data, OTA_PACKET_SIZE);
flash_buf_wi = (wi + 1U) % kFlashBufSlots;
__disable_irq();
flash_buf_cnt++;
__enable_irq();
next_expected_packet = receive_packet_num + 1U;
}
/**
@@ -660,7 +749,8 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
*/
if (receive_IFEND == 0xFFFFFFFFU) {
ota_state = OTA_STATE_FINISHED;
send_structured_ack(0xFFFFFFFFU);
pending_finish_ack = true;
send_structured_ack(0xF0F0F0F0U);
return;
}
@@ -678,6 +768,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;
@@ -704,10 +795,25 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
return;
}
/** 计算并写入 A区固件 CRC32,供后续启动时完整性校验。 */
u32 latest_crc = calculateAppCrc(APPLATEST_START_ADDR, APP_FIRMWARE_SIZE / 4U);
writeAppCrc(APPLATEST_START_ADDR, latest_crc);
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
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;
@@ -719,6 +825,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
@@ -906,11 +1020,23 @@ fmc_state_enum move_app_data() {
* 3. 逐 word 拷贝非 0xFFFFFFFF 的数据
* 4. 再计算目标区 CRC 比较,确认搬运成功
*/
/** 恢复前校验 B区固件 CRC 完整性,B区也坏了则不恢复。 */
if (!verifyAppCrc(APPBACK_START_ADDR)) {
#if DEBUG_PRINT
printf("[CRC] backup CRC verify FAIL, skip restore\r\n");
#endif
return (fmc_state_enum)0xFFU;
}
fmc_state_enum fmc_status = FMC_READY;
__disable_irq();
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 +1052,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 +1070,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();
@@ -947,6 +1085,12 @@ fmc_state_enum move_app_data() {
if (src_crc != dst_crc) {
return (fmc_state_enum)0xFFU;
}
/** 将 B区的固件 CRC 也写入 A区末尾,保持校验数据一致。 */
volatile u32 *backup_crc_ptr = (volatile u32 *)n_APP_BACKUP_CRC_ADDR;
u32 firmware_crc = *backup_crc_ptr;
writeAppCrc(APPLATEST_START_ADDR, firmware_crc);
return fmc_status;
}
@@ -987,3 +1131,82 @@ void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output) {
output[2] = (u8)((crc >> 16) & 0xFFU);
output[3] = (u8)((crc >> 24) & 0xFFU);
}
/**
* @brief 使用硬件 CRC 单元计算指定区域的 CRC32。
*
* @param start_addr 区域起始地址。
* @param word_count 区域长度(按 32 位字计数)。
* @return 计算得到的 CRC32 值。
*/
u32 calculateAppCrc(u32 start_addr, u32 word_count) {
crc_deinit();
crc_data_register_reset();
return crc_block_data_calculate((void *)start_addr, word_count, INPUT_FORMAT_WORD);
}
/**
* @brief 校验应用区固件 CRC 完整性。
*
* 计算 start_addr ~ start_addr + APP_FIRMWARE_SIZE 的硬件 CRC32,
* 并与应用区末尾 4 字节存储的 CRC 值比对。
*
* @param start_addr 应用区起始地址(A区或B区)。
* @retval true CRC 校验通过,固件完整。
* @retval false CRC 不匹配或未写入 CRC,固件不可信。
*/
bool verifyAppCrc(u32 start_addr) {
u32 crc_addr = start_addr + APP_FIRMWARE_SIZE;
volatile u32 *stored_crc_ptr = (volatile u32 *)crc_addr;
u32 stored_crc = *stored_crc_ptr;
/** Flash 擦除态 = 0xFFFFFFFF,如果 CRC 地址未写过说明无校验数据。
* APP 通过调试器直接烧录时 CRC 未写入,此时不应阻止跳转,
* 视为校验通过(未启用 CRC 保护),仅 OTA 升级后才真正校验。 */
if (stored_crc == FLASH_EMPTY) {
return true;
}
crc_deinit();
crc_data_register_reset();
u32 calculated = crc_block_data_calculate(
(void *)start_addr, APP_FIRMWARE_SIZE / 4U, INPUT_FORMAT_WORD);
#if DEBUG_PRINT
printf("[CRC] verify addr=0x%08lX stored=0x%08lX calc=0x%08lX %s\r\n",
(unsigned long)start_addr,
(unsigned long)stored_crc,
(unsigned long)calculated,
(calculated == stored_crc) ? "OK" : "FAIL");
#endif
return (calculated == stored_crc);
}
/**
* @brief 将 CRC 值写入应用区末尾。
*
* @param start_addr 应用区起始地址,用于计算 CRC 存储位置。
* @param crc_value 待写入的 CRC32 值。
* @return 无。
*/
void writeAppCrc(u32 start_addr, u32 crc_value) {
u32 crc_addr = start_addr + APP_FIRMWARE_SIZE;
#if DEBUG_PRINT
printf("[CRC] write addr=0x%08lX crc=0x%08lX\r\n",
(unsigned long)crc_addr,
(unsigned long)crc_value);
#endif
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(crc_addr, crc_value);
fmc_lock();
if (status != FMC_READY) {
#if DEBUG_PRINT
printf("[CRC] write FAILED status=%u\r\n", (unsigned)status);
#endif
}
}
+53 -7
View File
@@ -1,4 +1,4 @@
#ifndef __OTA_H
#ifndef __OTA_H
#define __OTA_H
#include "device/Can.h"
@@ -98,9 +98,12 @@ class OTAHandler {
memset(&aes_ctx, 0, sizeof(aes_ctx));
pending_flash_erase = false;
pending_app_jump = false;
pending_flash_offset = 0;
memset(pending_flash_data, 0, sizeof(pending_flash_data));
pending_flash_size = 0;
pending_finish_ack = false;
pending_finish_ota = false;
flash_buf_wi = 0U;
flash_buf_ri = 0U;
flash_buf_cnt = 0U;
next_expected_packet = 0U;
}
/**
@@ -178,11 +181,24 @@ class OTAHandler {
bool pending_flash_erase = false; /**< 是否存在待处理的延迟擦除请求。 */
bool pending_app_jump = false; /**< 是否存在待处理的应用跳转请求。 */
u32 pending_flash_offset = 0; /**< 预留:批量写入方案使用的偏移字段。 */
u8 pending_flash_data[64]; /**< 预留:批量写入方案使用的数据缓冲。 */
u8 pending_flash_size = 0; /**< 预留:批量写入方案使用的数据长度。 */
ota_send_fn_t transport_send = nullptr; /**< 当前底层回包发送函数。 */
/* Flash 写入环形缓冲(ISR 写入,主循环 drain 写 Flash) */
static constexpr u8 kFlashBufSlots = 8U;
struct FlashSlot {
u32 offset;
u8 data[OTA_PACKET_SIZE];
};
FlashSlot flash_buf[kFlashBufSlots];
volatile u8 flash_buf_wi = 0U; /**< ISR 写指针 */
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();
/**
* @brief OTA 流程状态定义。
*/
@@ -300,4 +316,34 @@ fmc_state_enum move_app_data();
*/
u8 OTA_SET(u32 addr, u32 data);
/**
* @brief 使用硬件 CRC 单元计算指定区域的 CRC32。
*
* @param start_addr 区域起始地址。
* @param word_count 区域长度(按 32 位字计数)。
* @return 计算得到的 CRC32 值。
*/
u32 calculateAppCrc(u32 start_addr, u32 word_count);
/**
* @brief 校验应用区固件 CRC 完整性。
*
* 计算 start_addr ~ start_addr + APP_FIRMWARE_SIZE 的硬件 CRC32,
* 并与应用区末尾 4 字节存储的 CRC 值比对。
*
* @param start_addr 应用区起始地址(A区或B区)。
* @retval true CRC 校验通过,固件完整。
* @retval false CRC 不匹配或未写入 CRC,固件不可信。
*/
bool verifyAppCrc(u32 start_addr);
/**
* @brief 将 CRC 值写入应用区末尾。
*
* @param start_addr 应用区起始地址,用于计算 CRC 存储位置。
* @param crc_value 待写入的 CRC32 值。
* @return 无。
*/
void writeAppCrc(u32 start_addr, u32 crc_value);
#endif
+19 -6
View File
@@ -1,4 +1,4 @@
#ifndef __CONFIG_H__
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include <stdint.h>
@@ -11,6 +11,12 @@ typedef uint32_t u32;
/** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */
#define DEBUG_PRINT 1
/* BOOT 固件版本号 */
#define BOOT_FW_VERSION_MAJOR 1U
#define BOOT_FW_VERSION_MINOR 0U
#define BOOT_FW_VERSION_PATCH 0U
#define BOOT_FW_VERSION_BUILD 1U
/**
* Flash 布局说明(GD32E508VE,512KB)
* -----------------------------------
@@ -24,9 +30,13 @@ inline constexpr u32 BOOTLOADER_START_ADDR = 0x08000000U;
inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */
inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */
inline constexpr u32 APPLATEST_START_ADDR = 0x08048000U; /**< 当前有效应用起始地址 */
inline constexpr u32 APP_MAX_SIZE = 0x00036000U; /**< 216KB */
inline constexpr u32 APP_MAX_SIZE = 0x00035FFCU; /**< 216KB - 4B,末尾预留 CRC32 */
inline constexpr u32 FMC_PAGE_SIZE = 0x00000800U; /**< 2KB,GD32E50x 单页大小 */
inline constexpr u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U; /**< OTA 元数据页 */
inline constexpr u32 APP_CRC_SIZE = 4U; /**< 固件区末尾 CRC32 占用字节数 */
inline constexpr u32 APP_FIRMWARE_SIZE = APP_MAX_SIZE - APP_CRC_SIZE; /**< 实际固件大小(不含 CRC) */
inline constexpr u32 n_APP_LATEST_CRC_ADDR = APPLATEST_START_ADDR + APP_FIRMWARE_SIZE; /**< A区 CRC 存储地址 */
inline constexpr u32 n_APP_BACKUP_CRC_ADDR = APPBACK_START_ADDR + APP_FIRMWARE_SIZE; /**< B区 CRC 存储地址 */
inline constexpr u32 SLEEP_CONFIG_START = 0x0807E800U; /**< 睡眠配置页起始 */
inline constexpr u32 SLEEP_CONFIG_END = 0x0807F000U; /**< 睡眠配置页结束(开区间) */
@@ -36,16 +46,19 @@ inline constexpr u32 SLEEP_ENABLE = 0x11111111U;
inline constexpr u32 SLEEP_DISABLE = 0x00000000U;
/**
* OTA 配置页一共使用 6 个 word,分别保存:
* latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
* OTA 配置页一共使用 8 个 word:
* word 0~5: latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check
* word 6~7: BOOT 版本号(major|minor / patch|build)
*/
inline constexpr u32 OTA_CONFIG_NUM = 6U;
inline constexpr u32 OTA_CONFIG_NUM = 8U;
inline constexpr u32 n_APP_LATEST_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x00U;
inline constexpr u32 n_APP_BACKUP_AVAL_ADDR = OTA_CONFIG_PAGE_ADDR + 0x04U;
inline constexpr u32 n_APP_LATEST_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x08U;
inline constexpr u32 n_APP_BACKUP_EN_ADDR = OTA_CONFIG_PAGE_ADDR + 0x0CU;
inline constexpr u32 n_APP_LATEST_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x10U;
inline constexpr u32 n_APP_BACKUP_CHECK_ADDR = OTA_CONFIG_PAGE_ADDR + 0x14U;
inline constexpr u32 n_BOOT_VERSION_ADDR0 = OTA_CONFIG_PAGE_ADDR + 0x18U; /**< BOOT版本 word0: major|minor */
inline constexpr u32 n_BOOT_VERSION_ADDR1 = OTA_CONFIG_PAGE_ADDR + 0x1CU; /**< BOOT版本 word1: patch|build */
/**
* 本项目把 0x00000000 视为“有效/可用”,
@@ -97,7 +110,7 @@ inline constexpr uint8_t K_master[16] = {
* - OTA_TIMEOUT:OTA 会话超时时间计数上限
* - APP_DELAY_BOOT_TIME:发现应用存在但被禁用时,Bootloader 等待 OTA 请求的窗口
*/
inline constexpr u32 OTA_TIMEOUT = 10000U;
inline constexpr u32 OTA_TIMEOUT = 50000U;
inline constexpr u32 APP_DELAY_BOOT_TIME = 10000U;
/** 常用尺寸常量。 */
+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);