feat(bootloader): 添加CRC32完整性校验和SPI OTA优化

添加了硬件CRC32完整性校验机制,在固件更新后计算并验证CRC32,
确保应用区固件完整性;同时优化SPI OTA的数据处理流程,
引入环形缓冲机制解决帧重复处理问题,并增加传输日志记录。

BREAKING CHANGE: 固件布局调整,应用区末尾预留4字节存储CRC32值
This commit is contained in:
cbb
2026-05-31 01:45:37 +08:00
parent b39a78e3fc
commit 4a39028f74
5 changed files with 254 additions and 129 deletions
+2 -22
View File
@@ -1,4 +1,4 @@
#include "SpiOta.h"
#include "SpiOta.h"
#include "core_cm33.h"
#include "gd32e50x_gpio.h"
#include "gd32e50x_exti.h"
@@ -188,6 +188,7 @@ void SpiOta::isrProcessFrame() {
* 4) onCsRelease() 看到 s_isrResponded 仍为 false,于是再次调用 isrProcessFrame()
* 5) 同一帧被重复处理一次,状态机被错误推进(如 challenge 又被当 confirm)
*
isrLogFrameEx("[RX-RAW]", s_rxStream, (rx_len < 16U) ? rx_len : 16U);
* 所以必须在函数入口就先置位,防止 REQ/DATA 被双重分发。 */
s_isrResponded = true;
@@ -248,22 +249,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;
}
@@ -516,14 +504,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();
+49 -38
View File
@@ -1,4 +1,4 @@
#include "main.h"
#include "main.h"
#include "device/Can.h"
#include "device/SpiOta.h"
#include "sys/config.h"
@@ -43,6 +43,7 @@ void BootloaderSystem::initHardware() {
(unsigned long)APP_MAX_SIZE);
printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock);
#endif
rcu_periph_clock_enable(RCU_CRC); /* OTA CRC 校验需要 CRC 外设时钟 */
CAN0_transmiter.init();
ota.init();
SpiOta::init();
@@ -125,16 +126,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 +141,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
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 +190,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
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 +234,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();
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 {
#if DEBUG_PRINT
printf("[BOOT] path=stay_boot_wait_image\r\n");
#endif
}
/**
* 当前区和备份区都不可用:
* 当前区和备份区都不可用,或恢复后校验失败:
* 只能长期停留在 Bootloader,等待外部重新下发固件。
*/
#if DEBUG_PRINT
printf("[BOOT] path=stay_boot_wait_image\r\n");
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_image");
heartbeatPrint(hb_wait, 10U, "stay_boot_wait_ota");
delay_1ms(10);
}
}
}
}
/**
* @brief Bootloader 程序入口。
*
* @return 理论上不会返回;若异常返回则维持空转。
*/
int main(void) {
sys.initHardware();
sys.run();
+139 -51
View File
@@ -1,4 +1,4 @@
#include "OTA.h"
#include "OTA.h"
#include "Can.h"
#include "SpiOta.h"
#include "aes128.h"
@@ -294,6 +294,14 @@ void OTAHandler::process() {
#endif
send_structured_ack(0U);
}
drainFlashBuffer();
if (pending_finish_ack && flash_buf_cnt == 0U) {
pending_finish_ack = false;
#if DEBUG_PRINT
printf("[OTA] flash drain done, send FINISH ACK\r\n");
#endif
send_structured_ack(0xFFFFFFFFU);
}
if (pending_app_jump) {
pending_app_jump = false;
@@ -313,7 +321,7 @@ void OTAHandler::process() {
* 非空闲态时,按固定节奏推进超时计数。
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
*/
delay_1us(100);
delay_1us(20);
ota_time++;
}
@@ -389,14 +397,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,15 +409,17 @@ 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);
#endif
/*
* [关键修复] 先关中断再做 Flash 操作。
@@ -433,43 +435,19 @@ void OTAHandler::jumpToApplication() {
__DSB();
__ISB();
#if DEBUG_PRINT
printf("[DBG] NVIC disabled\r\n");
#endif
/* 把当前主应用重新标记为"有效且允许启动"。 */
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
#if DEBUG_PRINT
printf("[DBG] OTA_SET done\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");
#endif
/*
* SysTick 不属于 NVIC 外设中断,跳转前单独关闭即可。
@@ -480,9 +458,6 @@ void OTAHandler::jumpToApplication() {
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
#if DEBUG_PRINT
printf("[DBG] SysTick disabled\r\n");
#endif
/* 把中断向量表切到主应用起始地址。 */
SCB->VTOR = APPLATEST_START_ADDR;
@@ -491,12 +466,6 @@ void OTAHandler::jumpToApplication() {
uint32_t msp_value = *(__IO uint32_t *)APPLATEST_START_ADDR;
uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U);
#if DEBUG_PRINT
printf("[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");
#endif
/* 仍在 Boot 栈上做最后一次清理,真正切栈和跳转交给裸汇编 trampoline。 */
rcu_all_reset_flag_clear();
@@ -541,6 +510,18 @@ void OTAHandler::eraseApplicationArea() {
* @param size 数据长度。
* @return 无。
*/
void OTAHandler::drainFlashBuffer() {
while (flash_buf_cnt > 0U) {
u8 ri = flash_buf_ri;
writeApplicationData(flash_buf[ri].offset, flash_buf[ri].data, OTA_PACKET_SIZE);
flash_buf_ri = (ri + 1U) % kFlashBufSlots;
__disable_irq();
flash_buf_cnt--;
__enable_irq();
}
}
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
/**
* 将一个分包写入主应用区:
@@ -644,13 +625,23 @@ 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 + OTA_PACKET_SIZE > APP_MAX_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();
}
/**
@@ -660,7 +651,9 @@ 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;
u8 wait_hint[1] = {0x04};
send_wait_frame(1, wait_hint);
return;
}
@@ -704,6 +697,10 @@ 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);
@@ -906,6 +903,14 @@ 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();
@@ -947,6 +952,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 +998,80 @@ 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 地址未写过说明无校验数据。 */
if (stored_crc == FLASH_EMPTY) {
return false;
}
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
}
}
+48 -6
View File
@@ -98,9 +98,10 @@ 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;
flash_buf_wi = 0U;
flash_buf_ri = 0U;
flash_buf_cnt = 0U;
}
/**
@@ -178,11 +179,22 @@ 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 */
void drainFlashBuffer();
/**
* @brief OTA 流程状态定义。
*/
@@ -300,4 +312,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
+7 -3
View File
@@ -1,4 +1,4 @@
#ifndef __CONFIG_H__
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include <stdint.h>
@@ -24,9 +24,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; /**< 睡眠配置页结束(开区间) */
@@ -97,7 +101,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;
/** 常用尺寸常量。 */