Compare commits
4
Commits
b39a78e3fc
...
0b086d4b86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b086d4b86 | ||
|
|
061bcabc37 | ||
|
|
c801701783 | ||
|
|
4a39028f74 |
+22
-133
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/** 常用尺寸常量。 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user