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 "core_cm33.h"
#include "gd32e50x_gpio.h" #include "gd32e50x_gpio.h"
#include "gd32e50x_exti.h" #include "gd32e50x_exti.h"
@@ -26,11 +26,10 @@
#define SPI_TX_DMA_PERIPH DMA0 #define SPI_TX_DMA_PERIPH DMA0
#define SPI_TX_DMA_CH DMA_CH2 #define SPI_TX_DMA_CH DMA_CH2
/* 调试:多缓冲日志(8 条 ISR 日志缓冲区,避免关键事件丢失) */ /* 调试日志缓冲 */
static constexpr u16 kLogSlotCount = 8U; static constexpr u16 kLogSlotCount = 8U;
static constexpr u16 kLogSlotSize = 80U; static constexpr u16 kLogSlotSize = 80U;
static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {}; static u8 s_logSlots[kLogSlotCount][kLogSlotSize] = {};
static u16 s_logSlotLens[kLogSlotCount] = {};
static volatile u8 s_logWriteIdx = 0U; static volatile u8 s_logWriteIdx = 0U;
static volatile u8 s_logReadIdx = 0U; static volatile u8 s_logReadIdx = 0U;
static volatile u8 s_logCount = 0U; static volatile u8 s_logCount = 0U;
@@ -47,10 +46,7 @@ u8 SpiOta::s_txBuf[SpiOta::kTxBufSize] = {};
volatile u16 SpiOta::s_txLen = 0U; volatile u16 SpiOta::s_txLen = 0U;
volatile bool SpiOta::s_txPending = false; volatile bool SpiOta::s_txPending = false;
volatile bool SpiOta::s_isrResponded = false; volatile bool SpiOta::s_isrResponded = false;
volatile bool SpiOta::s_isrLogPending = false;
volatile bool SpiOta::s_otaSessionActive = false; volatile bool SpiOta::s_otaSessionActive = false;
u8 SpiOta::s_isrLogBuf[kIsrLogBufSize] = {};
u16 SpiOta::s_isrLogLen = 0U;
/* ============================================================ /* ============================================================
* CRC8 / 帧构建 / 帧解析 * CRC8 / 帧构建 / 帧解析
@@ -155,10 +151,8 @@ void SpiOta::tryProcessCompleteFrame() {
u16 rx_len = s_rxStreamLen; u16 rx_len = s_rxStreamLen;
if (rx_len == 0U) { return; } if (rx_len == 0U) { return; }
if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { /* 单字节 BOOT 命令留到事务结束后再判定。 */
isrProcessFrame(); if (s_rxStream[0] == SPI_OTA_CMD_BOOT) { return; }
return;
}
if (s_rxStream[0] != kSof) { return; } if (s_rxStream[0] != kSof) { return; }
if (rx_len < kFrameOverhead) { return; } if (rx_len < kFrameOverhead) { return; }
@@ -179,25 +173,19 @@ void SpiOta::isrProcessFrame() {
u16 rx_len = s_rxStreamLen; u16 rx_len = s_rxStreamLen;
if (rx_len == 0U) { return; } 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; 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}; u8 ack[4] = {0xB0U, 0xA5U, 0x4FU, 0x54U};
queueTxFrame(ack, sizeof(ack)); queueTxFrame(ack, sizeof(ack));
if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); } if (s_otaHandler != nullptr) { s_otaHandler->resetToIdle(); }
s_otaSessionActive = false; s_otaSessionActive = false;
s_rxStreamLen = 0U; s_rxStreamLen = 0U;
isrLogFrameEx("[BOOT-CMD]", s_txBuf, s_txLen);
s_isrResponded = true; s_isrResponded = true;
return; return;
} }
@@ -231,6 +219,11 @@ void SpiOta::isrProcessFrame() {
} }
if (frame_type == SPI_OTA_CMD_BOOT) { 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_ok = 0xA5U;
u8 ack_frame[kTxBufSize] = {}; u8 ack_frame[kTxBufSize] = {};
u16 ack_len = 0U; u16 ack_len = 0U;
@@ -248,22 +241,9 @@ void SpiOta::isrProcessFrame() {
s_otaSessionActive = true; s_otaSessionActive = true;
s_otaHandler->setTransportSender(sender); s_otaHandler->setTransportSender(sender);
ota_link_msg_type_t msg_type = mapMsgType(frame_type); 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); 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; 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++] = (u8)hx[data[i] & 0x0FU];
} }
buf[pos] = 0U; buf[pos] = 0U;
s_logSlotLens[s_logWriteIdx] = pos;
s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount; s_logWriteIdx = (s_logWriteIdx + 1U) % kLogSlotCount;
s_logCount++; s_logCount++;
} }
@@ -391,7 +370,6 @@ void SpiOta::init() {
s_txStreamPos = 0U; s_txStreamPos = 0U;
s_txLen = 0U; s_txLen = 0U;
s_txPending = false; s_txPending = false;
s_isrLogPending = false;
s_otaSessionActive = false; s_otaSessionActive = false;
memset(s_rxStream, 0, sizeof(s_rxStream)); memset(s_rxStream, 0, sizeof(s_rxStream));
memset(s_txBuf, 0xFF, sizeof(s_txBuf)); memset(s_txBuf, 0xFF, sizeof(s_txBuf));
@@ -412,68 +390,17 @@ void SpiOta::onCsAssert() {
s_txStreamPos = 0U; s_txStreamPos = 0U;
s_isrResponded = false; s_isrResponded = false;
/* 内部 NSS 已在初始化时常置 LOW,这里不再依赖 EXTI 时序切 NSS。 /* PA15 仅作为事务边界信号;这里重装 DMA 并清理旧状态。 */
* 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 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN; DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT); spi_dma_disable(SPI0, SPI_DMA_TRANSMIT);
/* 1.5 覆盖 SPI 发送缓冲区 (TBE) 中的旧字节 /* 覆盖 TBE 里的旧字节,避免下一次事务先吐出残留 ACK。 */
*
* 关键修复: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 推出的
* 正确帧开始匹配。
*/
spi_i2s_data_transmit(SPI0, 0xFFU); spi_i2s_data_transmit(SPI0, 0xFFU);
/* 如果 TBE FIFO 有两层,再写一个 0xFF 确保覆盖 */
if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) { if (spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) != RESET) {
spi_i2s_data_transmit(SPI0, 0xFFU); spi_i2s_data_transmit(SPI0, 0xFFU);
} }
/* 2. 清残留错误标志 /* 只清错误标志,不主动消费 RBNE。 */
*
* 关键:这里绝不能在 CS↓ 时“顺手读掉 RBNE”。
*
* 因为 internal NSS 现在常 LOW,ESP32 在 CS↓ 后很快就会发出首字节 A5。
* 如果 EXTI ISR 略晚于 SPI 接收,A5 可能已经先进了 RX 缓冲并置位 RBNE。
* 这时若在 onCsAssert() 里执行 spi_i2s_data_receive(),就会把真实首字节 A5
* 当成“残留垃圾”吞掉,导致后续看到的首字节变成 0x01。
*
* 所以这里仅清真正的错误标志,不主动消费 RBNE。
* 已到达的首字节留给 onSpiInterrupt() 正常读走。 */
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) { if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0); (void)spi_i2s_data_receive(SPI0);
} }
@@ -481,7 +408,7 @@ void SpiOta::onCsAssert() {
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR); spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
} }
/* 3. 配置并启动 DMA(RBNE 仍关闭,DMA 写 SPI_DATA 不会触发中断) */ /* 3. 配置并启动 DMA */
if (s_txPending && s_txLen > 0U) { if (s_txPending && s_txLen > 0U) {
spi_dma_enable(SPI0, SPI_DMA_TRANSMIT); spi_dma_enable(SPI0, SPI_DMA_TRANSMIT);
startSpiTxDma(s_txBuf, s_txLen); startSpiTxDma(s_txBuf, s_txLen);
@@ -500,12 +427,7 @@ void SpiOta::onCsRelease() {
if (!s_csActive) { return; } if (!s_csActive) { return; }
s_csActive = false; s_csActive = false;
/* 内部 NSS 保持常 LOW,不在 CS↑ 时切回 HIGH。 /* 内部 NSS 保持常 LOW;这里只停止 DMA。 */
* 这样 SPI 从机始终处于就绪态,下一次 CS↓ 不会因 EXTI 延迟丢首字节。
*
* 这里只停止 DMA,避免 CS 高期间继续空转消耗 s_txBuf 内容。
* s_txPending 保持不变——如果 ISR 已准备好 ACK 帧,
* 下次 CS↓ 时 onCsAssert 会重启 DMA 推出。 */
DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN; DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN;
spi_dma_disable(SPI0, SPI_DMA_TRANSMIT); 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) { if (s_rxStreamLen > 0U && !s_isrResponded) {
isrProcessFrame(); isrProcessFrame();
@@ -536,13 +450,7 @@ void SpiOta::onCsRelease() {
void SpiOta::onSpiInterrupt() { void SpiOta::onSpiInterrupt() {
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) { if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
/* 与 APP 侧稳定实现保持一致: /* EXTI 略晚时,在首个 RBNE 到来时补做 onCsAssert()。 */
* 如果 EXTI 下降沿因为中断时序略晚到达,但此时 PA15 已经为低,
* 则在首个 RBNE 到来时立即补做 onCsAssert()。
*
* 关键限制:只能在“本事务尚未收过任何字节”时补做。
* 否则若 EXTI / SPI 时序偶发抖动,重复 onCsAssert() 会把
* s_rxStreamLen 清零,导致已经收到的半帧被冲掉,最终永远拼不成完整帧。 */
if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) { if (!s_csActive && s_rxStreamLen == 0U && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert(); onCsAssert();
} }
@@ -551,11 +459,6 @@ void SpiOta::onSpiInterrupt() {
if (s_rxStreamLen < kRxBufSize) { if (s_rxStreamLen < kRxBufSize) {
s_rxStream[s_rxStreamLen++] = byte; s_rxStream[s_rxStreamLen++] = byte;
} }
/* 调试阶段先关闭 RBNE 首字节日志。
* 原因:ISR 日志缓冲是单槽的,RBNE 一旦先占位,
* onCsRelease() 里的 [CS-RX] 摘要日志就会被丢弃。
* 目前排查重点是“本次 CS 周期总共收到了哪些字节”,
* 所以优先保留 [CS-RX],暂时关闭 [RBNE]。 */
tryProcessCompleteFrame(); tryProcessCompleteFrame();
} }
/* TBE 已由 DMA 替代,不再处理 */ /* TBE 已由 DMA 替代,不再处理 */
@@ -584,28 +487,14 @@ void SpiOta::poll() {
if (s_isrResponded) { if (s_isrResponded) {
s_isrResponded = false; s_isrResponded = false;
} }
/* 打印 ISR 日志缓冲区 */
while (s_logCount > 0U) { while (s_logCount > 0U) {
__disable_irq(); __disable_irq();
if (s_logCount == 0U) { __enable_irq(); break; } if (s_logCount == 0U) { __enable_irq(); break; }
u8 *buf = s_logSlots[s_logReadIdx]; s_logReadIdx = (s_logReadIdx + 1U) % kLogSlotCount;
u8 nextRead = (s_logReadIdx + 1U) % kLogSlotCount;
s_logReadIdx = nextRead;
s_logCount--; s_logCount--;
__enable_irq(); __enable_irq();
printf("[SPI] %s\r\n", buf);
} }
if (s_otaHandler != nullptr) { s_otaHandler->process(); } 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; } void SpiOta::setSender(OTAHandler *handler) { s_otaHandler = handler; }
-3
View File
@@ -59,10 +59,7 @@ public:
static volatile u16 s_txLen; static volatile u16 s_txLen;
static volatile bool s_txPending; static volatile bool s_txPending;
static volatile bool s_isrResponded; static volatile bool s_isrResponded;
static volatile bool s_isrLogPending;
static volatile bool s_otaSessionActive; 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); 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/Can.h"
#include "device/SpiOta.h" #include "device/SpiOta.h"
#include "sys/config.h" #include "sys/config.h"
@@ -43,6 +43,7 @@ void BootloaderSystem::initHardware() {
(unsigned long)APP_MAX_SIZE); (unsigned long)APP_MAX_SIZE);
printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock); printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock);
#endif #endif
rcu_periph_clock_enable(RCU_CRC); /* OTA CRC 校验需要 CRC 外设时钟 */
CAN0_transmiter.init(); CAN0_transmiter.init();
ota.init(); ota.init();
SpiOta::init(); SpiOta::init();
@@ -50,6 +51,62 @@ void BootloaderSystem::initHardware() {
/** 让 SPI OTA 模块在收到完整帧后,转交给 ota 实例处理。 */ /** 让 SPI OTA 模块在收到完整帧后,转交给 ota 实例处理。 */
SpiOta::setSender(&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(); 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) { if (heartbeat_counter >= kHeartbeatPrintIntervalMs) {
heartbeat_counter = 0U; heartbeat_counter = 0U;
#if DEBUG_PRINT #if DEBUG_PRINT
if (ota.isActive()) {
return;
}
printf("[BOOT] \xe2\x99\xa5 %s ota_active=%d spi_tx_pending=%d\r\n", printf("[BOOT] \xe2\x99\xa5 %s ota_active=%d spi_tx_pending=%d\r\n",
path_label, path_label,
ota.isActive() ? 1 : 0, ota.isActive() ? 1 : 0,
@@ -125,16 +185,6 @@ void BootloaderSystem::run() {
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL); 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(); rcu_all_reset_flag_clear();
flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR; flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR;
@@ -150,19 +200,30 @@ void BootloaderSystem::run() {
/** /**
* 情况 1:应用已校验完成,且允许启动。 * 情况 1:应用已校验完成,且允许启动。
* 直接跳转到主应用,不在 Bootloader 多停留。 * 增加 CRC32 完整性校验;若校验失败则标记 A区无效,fall through 到恢复逻辑。
*/ */
if (app_check == OTA_AVAL && app_enable == OTA_AVAL) { if (app_check == OTA_AVAL && app_enable == OTA_AVAL) {
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] path=jump_latest_now\r\n"); printf("[BOOT] path=jump_latest_now\r\n");
#endif #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:应用是有效的,但当前被标记为"暂不启动"。 * 情况 2:应用是有效的,但当前被标记为"暂不启动"。
* 这通常表示系统希望给 OTA 留一个短暂窗口,若窗口内没有升级请求,再启动应用。 * 这通常表示系统希望给 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 delay_count = 0;
uint32_t heartbeat_counter = 0U; uint32_t heartbeat_counter = 0U;
int ota_requested = 0; int ota_requested = 0;
@@ -188,32 +249,37 @@ void BootloaderSystem::run() {
} }
if (ota_requested) { if (ota_requested) {
/**
* 已经进入 OTA 会话:
* 后续不再尝试跳应用,持续停留在 Bootloader 内处理升级。
*/
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] path=stay_in_ota\r\n"); printf("[BOOT] path=stay_in_ota\r\n");
#endif #endif
uint32_t hb_ota = 0U; uint32_t hb_ota = 0U;
while (1) { while (1) {
pollAllLinks(); pollAllLinks();
heartbeatPrint(hb_ota, 10U, "stay_in_ota"); heartbeatPrint(hb_ota, 1U, "stay_in_ota");
delay_1ms(10); delay_1ms(1);
} }
} else { } else {
/** 等待窗口内没有任何 OTA 请求,正常启动当前应用。 */
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] ota_window_timeout_then_jump\r\n"); printf("[BOOT] ota_window_timeout_then_jump\r\n");
#endif #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; uint32_t back_aval, back_check;
flash_ptr = (volatile uint32_t *)n_APP_BACKUP_AVAL_ADDR; flash_ptr = (volatile uint32_t *)n_APP_BACKUP_AVAL_ADDR;
back_aval = *flash_ptr; back_aval = *flash_ptr;
@@ -227,41 +293,45 @@ void BootloaderSystem::run() {
#endif #endif
if (back_check == OTA_AVAL && back_aval == OTA_AVAL) { if (back_check == OTA_AVAL && back_aval == OTA_AVAL) {
/**
* 备份区可用:
* 1. 先发送一帧提示,表示即将走备份恢复路径;
* 2. 把备份应用搬运到主应用区;
* 3. 跳转运行。
*/
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] path=restore_backup_then_jump\r\n"); printf("[BOOT] path=restore_backup_then_jump\r\n");
#endif #endif
CAN0_transmiter.sendBackup(); CAN0_transmiter.sendBackup();
move_app_data(); fmc_state_enum restore_result = move_app_data();
ota.jumpToApplication(); 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 { } else {
/**
* 当前区和备份区都不可用:
* 只能长期停留在 Bootloader,等待外部重新下发固件。
*/
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] path=stay_boot_wait_image\r\n"); printf("[BOOT] path=stay_boot_wait_image\r\n");
#endif #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) { int main(void) {
sys.initHardware(); sys.initHardware();
sys.run(); sys.run();
+280 -57
View File
@@ -1,4 +1,4 @@
#include "OTA.h" #include "OTA.h"
#include "Can.h" #include "Can.h"
#include "SpiOta.h" #include "SpiOta.h"
#include "aes128.h" #include "aes128.h"
@@ -26,8 +26,6 @@ __attribute__((naked, noreturn)) void branchToApplication(uint32_t msp_value, ui
"msr basepri, r2 \n" "msr basepri, r2 \n"
"msr faultmask, r2 \n" "msr faultmask, r2 \n"
"msr psp, r2 \n" "msr psp, r2 \n"
"msr psplim, r2 \n"
"msr msplim, r2 \n"
"msr msp, r0 \n" "msr msp, r0 \n"
"dsb \n" "dsb \n"
"isb \n" "isb \n"
@@ -113,6 +111,10 @@ static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) {
fmc_unlock(); fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(address, data); 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(); fmc_lock();
return status; 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; flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr; temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR; 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; temp_val[4] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR; flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr; 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_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_status = fmc_page_erase(OTA_CONFIG_PAGE_ADDR); 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) { if (fmc_status != FMC_READY) {
fmc_lock(); fmc_lock();
return FLASH_EEPROM_ERROR; 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; u32 current_addr = n_APP_LATEST_AVAL_ADDR + 4U * (u32)i;
if (current_addr != addr && temp_val[i] != OTA_NOT_AVAL) { if (current_addr != addr && temp_val[i] != OTA_NOT_AVAL) {
fmc_status = fmc_word_program(current_addr, temp_val[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 (fmc_status != FMC_READY) {
fmc_lock(); fmc_lock();
return FLASH_EEPROM_ERROR; return FLASH_EEPROM_ERROR;
@@ -186,6 +199,10 @@ u8 OTA_SET(u32 addr, u32 data) {
if (data != OTA_NOT_AVAL) { if (data != OTA_NOT_AVAL) {
fmc_status = fmc_word_program(addr, data); 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) { if (fmc_status != FMC_READY) {
fmc_lock(); fmc_lock();
return FLASH_EEPROM_ERROR; return FLASH_EEPROM_ERROR;
@@ -294,6 +311,11 @@ void OTAHandler::process() {
#endif #endif
send_structured_ack(0U); send_structured_ack(0U);
} }
drainFlashBuffer();
if (pending_finish_ack && flash_buf_cnt == 0U) {
pending_finish_ack = false;
}
if (pending_app_jump) { if (pending_app_jump) {
pending_app_jump = false; pending_app_jump = false;
@@ -313,7 +335,7 @@ void OTAHandler::process() {
* 非空闲态时,按固定节奏推进超时计数。 * 非空闲态时,按固定节奏推进超时计数。
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。 * 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
*/ */
delay_1us(100); delay_1us(20);
ota_time++; ota_time++;
} }
@@ -389,14 +411,6 @@ bool validateApplication() {
reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE)); reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE));
#if DEBUG_PRINT #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 #endif
return stack_valid && reset_valid; return stack_valid && reset_valid;
@@ -409,21 +423,23 @@ bool validateApplication() {
*/ */
void OTAHandler::jumpToApplication() { void OTAHandler::jumpToApplication() {
if (!validateApplication()) { if (!validateApplication()) {
return;
}
/** CRC32 完整性校验:若固件被破坏则不跳转。 */
if (!verifyAppCrc(APPLATEST_START_ADDR)) {
#if DEBUG_PRINT #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 #endif
return; return;
} }
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[BOOT] jumping to app @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR); printf("[J1]\r\n");
#endif #endif
/* /*
* [关键修复] 先关中断再做 Flash 操作。 * [关键修复] 先关中断再做 Flash 操作。
* 原来 OTA_SET 在 NVIC disable 之前执行。
* Flash 编程期间 CPU stall,若此时 SPI0/CAN0 中断触发,
* ISR 嵌套调用会撑爆栈 -> HardFault -> 死循环。
*/ */
__disable_irq(); __disable_irq();
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
@@ -434,7 +450,7 @@ void OTAHandler::jumpToApplication() {
__ISB(); __ISB();
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[DBG] NVIC disabled\r\n"); printf("[J2]\r\n");
#endif #endif
/* 把当前主应用重新标记为"有效且允许启动"。 */ /* 把当前主应用重新标记为"有效且允许启动"。 */
@@ -442,48 +458,24 @@ void OTAHandler::jumpToApplication() {
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL); OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[DBG] OTA_SET done\r\n"); printf("[J3]\r\n");
#endif #endif
/* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */ /* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */
DMA_CHCTL(DMA0, DMA_CH1) &= ~DMA_CHXCTL_CHEN; 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; DMA_CHCTL(DMA0, DMA_CH2) &= ~DMA_CHXCTL_CHEN;
#if DEBUG_PRINT
printf("[DBG] DMA_CH2 disabled\r\n");
#endif
can_deinit(CAN0); can_deinit(CAN0);
#if DEBUG_PRINT
printf("[DBG] CAN0 deinit\r\n");
#endif
spi_i2s_deinit(SPI0); spi_i2s_deinit(SPI0);
#if DEBUG_PRINT
printf("[DBG] SPI0 deinit\r\n");
#endif
exti_interrupt_disable(EXTI_15); exti_interrupt_disable(EXTI_15);
#if DEBUG_PRINT
printf("[DBG] EXTI15 disabled\r\n");
#endif
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[DBG] peripherals deinit done\r\n"); printf("[J4]\r\n");
#endif #endif
/*
* SysTick 不属于 NVIC 外设中断,跳转前单独关闭即可。
* APP 的 SystemInit() 会完整重置时钟树,因此这里不再切 IRC8M,
* 也不再关 GPIO/USART/RCU 时钟,避免 Boot 收尾逻辑引入额外变量。
*/
SysTick->CTRL = 0U; SysTick->CTRL = 0U;
SysTick->LOAD = 0U; SysTick->LOAD = 0U;
SysTick->VAL = 0U; SysTick->VAL = 0U;
#if DEBUG_PRINT
printf("[DBG] SysTick disabled\r\n");
#endif
/* 把中断向量表切到主应用起始地址。 */ /* 把中断向量表切到主应用起始地址。 */
SCB->VTOR = APPLATEST_START_ADDR; SCB->VTOR = APPLATEST_START_ADDR;
@@ -492,15 +484,50 @@ void OTAHandler::jumpToApplication() {
uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U); uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U);
#if DEBUG_PRINT #if DEBUG_PRINT
printf("[DBG] app MSP=0x%08lX Reset=0x%08lX\r\n", printf("[J5] msp=0x%08lX rst=0x%08lX\r\n",
(unsigned long)msp_value, (unsigned long)msp_value, (unsigned long)reset_handler);
(unsigned long)reset_handler); /* 打印 Reset_Handler 地址处的原始字节,验证是否有有效代码 */
printf("[DBG] calling app entry\r\n"); 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 #endif
/* 仍在 Boot 栈上做最后一次清理,真正切栈和跳转交给裸汇编 trampoline。 */ /*
rcu_all_reset_flag_clear(); * 直接用内联汇编跳转,消除 naked 函数调用的所有不确定性。
branchToApplication(msp_value, reset_handler); * 使用显式寄存器变量保证 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(); __disable_irq();
fmc_unlock(); 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; for (u32 addr = APPLATEST_START_ADDR;
addr < (APPLATEST_START_ADDR + APP_MAX_SIZE); addr += FMC_PAGE_SIZE) { 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); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
@@ -541,14 +574,52 @@ void OTAHandler::eraseApplicationArea() {
* @param size 数据长度。 * @param size 数据长度。
* @return 无。 * @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) { void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
/** /**
* 将一个分包写入主应用区: * 将一个分包写入主应用区:
* - 先按 4 字节对齐批量写 * - 先按 4 字节对齐批量写
* - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入 * - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入
*/ */
fmc_unlock();
u32 write_addr = APPLATEST_START_ADDR + offset; u32 write_addr = APPLATEST_START_ADDR + offset;
fmc_unlock();
u32 i = 0; u32 i = 0;
for (; i + 4U <= size; i += 4U) { for (; i + 4U <= size; i += 4U) {
(void)fmc_word_program(write_addr + i, *(u32 *)&data[i]); (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_encrypted_data = data + 8;
const u8 *receive_dataCRC32 = data + 60; 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 decrypted_data[OTA_PACKET_SIZE];
u8 calculated_dataCRC32[4]; u8 calculated_dataCRC32[4];
@@ -644,13 +722,24 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
return; return;
} else { } else {
u32 offset = receive_packet_num * OTA_PACKET_SIZE; u32 offset = receive_packet_num * OTA_PACKET_SIZE;
if (offset + OTA_PACKET_SIZE <= APP_MAX_SIZE) { if (offset >= expected_size) {
writeApplicationData(offset, decrypted_data, OTA_PACKET_SIZE);
} else {
ota_error = OTA_ERROR_INVALID_SIZE; ota_error = OTA_ERROR_INVALID_SIZE;
ota_state = OTA_STATE_IDLE; ota_state = OTA_STATE_IDLE;
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE); 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) { if (receive_IFEND == 0xFFFFFFFFU) {
ota_state = OTA_STATE_FINISHED; ota_state = OTA_STATE_FINISHED;
send_structured_ack(0xFFFFFFFFU); pending_finish_ack = true;
send_structured_ack(0xF0F0F0F0U);
return; return;
} }
@@ -678,6 +768,7 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
if (new_size <= APP_MAX_SIZE) { if (new_size <= APP_MAX_SIZE) {
expected_size = new_size; expected_size = new_size;
received_size = 0; received_size = 0;
next_expected_packet = 0U;
} else { } else {
ota_error = OTA_ERROR_INVALID_SIZE; ota_error = OTA_ERROR_INVALID_SIZE;
ota_state = OTA_STATE_IDLE; ota_state = OTA_STATE_IDLE;
@@ -704,10 +795,25 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
return; 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_CHECK_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL); OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_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}; u8 finish_ack[2] = {0x0A, 0xAC};
send_ack_frame(2, finish_ack); send_ack_frame(2, finish_ack);
pending_app_jump = true; 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); send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
return; 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 { else {
ota_error = OTA_ERROR_INVALID_PACKET; ota_error = OTA_ERROR_INVALID_PACKET;
#if DEBUG_PRINT #if DEBUG_PRINT
@@ -906,11 +1020,23 @@ fmc_state_enum move_app_data() {
* 3. 逐 word 拷贝非 0xFFFFFFFF 的数据 * 3. 逐 word 拷贝非 0xFFFFFFFF 的数据
* 4. 再计算目标区 CRC 比较,确认搬运成功 * 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; fmc_state_enum fmc_status = FMC_READY;
__disable_irq(); __disable_irq();
fmc_unlock(); fmc_unlock();
uint32_t num_pages = (APP_MAX_SIZE + FMC_PAGE_SIZE - 1U) / FMC_PAGE_SIZE; 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) { for (uint32_t i = 0; i < num_pages; ++i) {
uint32_t page_addr = APPLATEST_START_ADDR + i * FMC_PAGE_SIZE; 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); 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(); crc_data_register_reset();
uint32_t src_crc = crc_block_data_calculate((void *)APPBACK_START_ADDR, APP_MAX_SIZE / 4U, INPUT_FORMAT_WORD); 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) { for (uint32_t offset = 0; offset < APP_MAX_SIZE; offset += 4U) {
uint32_t src_data = *(uint32_t *)(APPBACK_START_ADDR + offset); uint32_t src_data = *(uint32_t *)(APPBACK_START_ADDR + offset);
if (src_data != 0xFFFFFFFFU) { if (src_data != 0xFFFFFFFFU) {
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); 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); fmc_status = fmc_word_program(APPLATEST_START_ADDR + offset, src_data);
#if DEBUG_PRINT
dbg_word_cnt++;
#endif
if (fmc_status != FMC_READY) { if (fmc_status != FMC_READY) {
fmc_lock(); fmc_lock();
__enable_irq(); __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(); fmc_lock();
__enable_irq(); __enable_irq();
@@ -947,6 +1085,12 @@ fmc_state_enum move_app_data() {
if (src_crc != dst_crc) { if (src_crc != dst_crc) {
return (fmc_state_enum)0xFFU; 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; 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[2] = (u8)((crc >> 16) & 0xFFU);
output[3] = (u8)((crc >> 24) & 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 #define __OTA_H
#include "device/Can.h" #include "device/Can.h"
@@ -98,9 +98,12 @@ class OTAHandler {
memset(&aes_ctx, 0, sizeof(aes_ctx)); memset(&aes_ctx, 0, sizeof(aes_ctx));
pending_flash_erase = false; pending_flash_erase = false;
pending_app_jump = false; pending_app_jump = false;
pending_flash_offset = 0; pending_finish_ack = false;
memset(pending_flash_data, 0, sizeof(pending_flash_data)); pending_finish_ota = false;
pending_flash_size = 0; 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_flash_erase = false; /**< 是否存在待处理的延迟擦除请求。 */
bool pending_app_jump = 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; /**< 当前底层回包发送函数。 */ 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 流程状态定义。 * @brief OTA 流程状态定义。
*/ */
@@ -300,4 +316,34 @@ fmc_state_enum move_app_data();
*/ */
u8 OTA_SET(u32 addr, u32 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 #endif
+19 -6
View File
@@ -1,4 +1,4 @@
#ifndef __CONFIG_H__ #ifndef __CONFIG_H__
#define __CONFIG_H__ #define __CONFIG_H__
#include <stdint.h> #include <stdint.h>
@@ -11,6 +11,12 @@ typedef uint32_t u32;
/** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */ /** 调试打印总开关。置 1 时所有 printf 调试输出编译进去;置 0 时完全剔除,零开销。 */
#define DEBUG_PRINT 1 #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) * Flash 布局说明(GD32E508VE,512KB)
* ----------------------------------- * -----------------------------------
@@ -24,9 +30,13 @@ inline constexpr u32 BOOTLOADER_START_ADDR = 0x08000000U;
inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */ inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */
inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */ inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */
inline constexpr u32 APPLATEST_START_ADDR = 0x08048000U; /**< 当前有效应用起始地址 */ 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 FMC_PAGE_SIZE = 0x00000800U; /**< 2KB,GD32E50x 单页大小 */
inline constexpr u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U; /**< OTA 元数据页 */ 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_START = 0x0807E800U; /**< 睡眠配置页起始 */
inline constexpr u32 SLEEP_CONFIG_END = 0x0807F000U; /**< 睡眠配置页结束(开区间) */ inline constexpr u32 SLEEP_CONFIG_END = 0x0807F000U; /**< 睡眠配置页结束(开区间) */
@@ -36,16 +46,19 @@ inline constexpr u32 SLEEP_ENABLE = 0x11111111U;
inline constexpr u32 SLEEP_DISABLE = 0x00000000U; inline constexpr u32 SLEEP_DISABLE = 0x00000000U;
/** /**
* OTA 配置页一共使用 6 个 word,分别保存: * OTA 配置页一共使用 8 个 word:
* latest_aval / backup_aval / latest_en / backup_en / latest_check / backup_check * 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_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_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_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_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_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_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 视为“有效/可用”, * 本项目把 0x00000000 视为“有效/可用”,
@@ -97,7 +110,7 @@ inline constexpr uint8_t K_master[16] = {
* - OTA_TIMEOUT:OTA 会话超时时间计数上限 * - OTA_TIMEOUT:OTA 会话超时时间计数上限
* - APP_DELAY_BOOT_TIME:发现应用存在但被禁用时,Bootloader 等待 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; inline constexpr u32 APP_DELAY_BOOT_TIME = 10000U;
/** 常用尺寸常量。 */ /** 常用尺寸常量。 */
+8
View File
@@ -11,6 +11,7 @@
#include "gd32e50x_rcu.h" #include "gd32e50x_rcu.h"
#include "gd32e50x_usart.h" #include "gd32e50x_usart.h"
#include "systick.h" #include "systick.h"
#include <stdio.h>
/** /**
* @brief 初始化 USART0 调试串口。 * @brief 初始化 USART0 调试串口。
@@ -163,6 +164,10 @@ fmc_state_enum BootSleepManager::writeAddress(u32 address, u32 data)
fmc_unlock(); fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(address, data); 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(); fmc_lock();
return status; return status;
} }
@@ -241,6 +246,9 @@ void BootSleepManager::onExtiInterrupt(void)
fmc_lock(); fmc_lock();
return; return;
} }
#if DEBUG_PRINT
printf("[FW] E page=0x%08lX\r\n", (unsigned long)SLEEP_CONFIG_START);
#endif
address = SLEEP_CONFIG_START - SIZE_U32; address = SLEEP_CONFIG_START - SIZE_U32;
} }
(void)writeAddress(address + SIZE_U32, SLEEP_DISABLE); (void)writeAddress(address + SIZE_U32, SLEEP_DISABLE);