Files
mcu-codegen-template/Bootloader/Src/boot_main.c
T

505 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
******************************************************************************
* @file boot_main.c
* @brief 可配置 OTA Bootloader — PY32F040
*
* 三种模式(由 ota_config.h 的 OTA_BACKUP_MODE / OTA_SWAP_STRATEGY 决定):
*
* ① 单备份(SINGLE):
* PENDING → 校验 BAK → 拷贝覆盖主区 → 跳转主区(无回滚)
* 掉电:PENDING 不清除,下次启动重新校验+拷贝(BAK 完好,天然安全)
*
* ② 双备份 + RAM 缓冲交换(AB + RAM):
* PENDING → 校验 B 区 → 逐页交换(RAM 双页缓冲)→ BOOT_NEW → 跳转 A 区
* SWAPPING → 交换掉电中断,续做(phase/progress 恢复)
* BOOT_NEW 未确认(崩溃/IWDG 复位)→ 反向交换回滚 → 跳转 A 区
*
* ③ 双备份 + Flash 暂存区交换(AB + SCRATCH):
* 同上,但交换用 scratch 暂存一页(Flash 持久),掉电恢复可完整保留两页,
* 回滚永远可用;升级耗时更短。
*
* 跳转新固件前使能 IWDG:新固件崩溃则看门狗复位,Bootloader 再次判定并回滚。
* 交换/拷贝期间使能 IWDG 并逐页喂狗(单页耗时 < 100ms,远小于 1s 超时)。
******************************************************************************
*/
#include "boot_main.h"
#include "flash.h"
#include "crc32.h"
#include "iwdg_config.h"
#define DEBUG
/* Private variables ---------------------------------------------------------*/
static ota_param_t g_ota;
/* Private function prototypes -----------------------------------------------*/
static int fw_vector_ok(uint32_t base);
static int fw_validate(uint32_t base, uint32_t size, uint32_t crc);
static void boot_iwdg_enable(void);
static void boot_iwdg_feed(void);
static void boot_jump(uint32_t base);
#if (OTA_BACKUP_MODE == OTA_MODE_SINGLE)
static void copy_bak_to_run(void);
#else
static void swap_execute(int reverse);
#endif
/**
* @brief 系统复位
*/
void mcuRestart(void)
{
Log("[OTA] Restarting MCU...\r\n");
HAL_NVIC_SystemReset();
}
/* ── OTA 状态区读写 ─────────────────────────────────────────────────────────
* 由 flash.h 的 ota_param_load / ota_param_store 提供(4 页轮换磨损均衡)。
* 状态区无效时 load 自动回退默认 IDLE。 */
/* ── 固件校验 ─────────────────────────────────────────────────────────────── */
/* 向量表合法性:栈顶在 RAM,复位向量落在运行区 Flash 范围内。
* 注意:固件始终链接运行区 OTA_RUN_ADDR_BASEBAK/B 区只是下载/备份缓冲,
* 存储位置 ≠ 链接地址。无论 base 传 RUN 还是 BAK,都应校验复位向量是否指向
* 运行区(固件真正执行处),否则 BAK 校验会因复位向量指向 RUN 而被误判非法。 */
static int fw_vector_ok(uint32_t base)
{
uint32_t sp = *(volatile uint32_t *)base;
uint32_t rv = *(volatile uint32_t *)(base + 4);
(void)base;
if ((sp < RAM_BASE_ADDR) || (sp >= RAM_END_ADDR))
{
return 0;
}
if ((rv < OTA_RUN_ADDR_BASE) || (rv >= (OTA_RUN_ADDR_BASE + OTA_SLOT_MAX_SIZE)))
{
return 0;
}
return 1;
}
/* 完整校验:向量表合法 + CRC32 匹配 */
static int fw_validate(uint32_t base, uint32_t size, uint32_t crc)
{
if ((size == 0) || (size > OTA_SLOT_MAX_SIZE))
{
Log("[BL] fw size invalid: %lu\r\n", (unsigned long)size);
return 0;
}
if (!fw_vector_ok(base))
{
Log("[BL] fw vector invalid @ %08X\r\n", (unsigned int)base);
return 0;
}
if (crc32_compute((uint8_t *)base, size) != crc)
{
Log("[BL] fw CRC32 mismatch @ %08X\r\n", (unsigned int)base);
return 0;
}
Log("[BL] fw validate OK @ %08X (size %lu)\r\n", (unsigned int)base, (unsigned long)size);
return 1;
}
/* ── IWDG:跳转前使能,新固件崩溃则由看门狗复位回滚;交换/拷贝期间逐页喂狗 ── */
static void boot_iwdg_enable(void)
{
/* 使能 LSI 并等待就绪 */
SET_BIT(RCC->CSR, RCC_CSR_LSION);
for (uint32_t i = 0; i < 10000; i++)
{
if (READ_BIT(RCC->CSR, RCC_CSR_LSIRDY))
{
break;
}
}
/* IWDG 配置:参数见 Shared/iwdg_config.h */
IWDG->KR = 0x5555; /* 解锁 */
IWDG->PR = IWDG_PR_REG; /* /16 */
IWDG->RLR = IWDG_RELOAD_VALUE;
IWDG->KR = 0xAAAA; /* 重装 */
IWDG->KR = 0xCCCC; /* 启动 */
Log("[BL] IWDG enabled (~1s)\r\n");
}
static void boot_iwdg_feed(void)
{
IWDG->KR = 0xAAAA; /* 重装计数,防交换/拷贝期间复位 */
}
/* ── 跳转到 APP ───────────────────────────────────────────────────────────── */
typedef void (*iapfun)(void);
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0
BX r14
}
static void boot_jump(uint32_t base)
{
uint32_t sp, rv;
if (!fw_vector_ok(base))
{
Log("[BL] Jump target vector invalid! Hang.\r\n");
while (1)
{
}
}
sp = *(volatile uint32_t *)base;
rv = *(volatile uint32_t *)(base + 4);
Log("[BL] Jump to APP @ %08X\r\n", (unsigned int)base);
/* 关闭全局中断,设置向量表与栈,跳转 */
__disable_irq();
SCB->VTOR = base; /* PY32F040 支持 VTOR */
MSR_MSP(sp);
((iapfun)rv)();
while (1)
{
}
}
/* ── 单备份:BAK 拷贝覆盖主区 ─────────────────────────────────────────────── */
#if (OTA_BACKUP_MODE == OTA_MODE_SINGLE)
static void copy_bak_to_run(void)
{
uint8_t buf[OTA_PAGE_SIZE];
uint32_t i, pages = OTA_SLOT_PAGE_NB;
Log("[BL] copy BAK(%08X) -> RUN(%08X), %lu pages\r\n",
(unsigned int)OTA_BAK_ADDR_BASE, (unsigned int)OTA_RUN_ADDR_BASE,
(unsigned long)pages);
for (i = 0; i < pages; i++)
{
uint32_t src = OTA_BAK_ADDR_BASE + i * OTA_PAGE_SIZE;
uint32_t dst = OTA_RUN_ADDR_BASE + i * OTA_PAGE_SIZE;
APP_FlashRead(src, buf, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(dst, OTA_PAGE_SIZE);
APP_FlashWrite(dst, buf, OTA_PAGE_SIZE);
boot_iwdg_feed();
}
/* 拷贝完成:置 BOOT_NEW(等待 App 确认并回复 OTA 完成;单备份无回滚目标,
* 若 App 未确认崩溃,下次启动见 BOOT_NEW 直接继续运行)。
* 中途掉电则下次重新拷贝(PENDING 未清,BAK 完好) */
g_ota.state = OTA_STATE_BOOT_NEW;
ota_param_store(&g_ota);
Log("[BL] copy done\r\n");
}
#endif /* SINGLE */
/* ── 双备份:A/B 区交换 ───────────────────────────────────────────────────── */
#if (OTA_BACKUP_MODE == OTA_MODE_AB)
#if (OTA_SWAP_STRATEGY == OTA_SWAP_SCRATCH)
/**
* @brief Flash 暂存区交换(scratch 持久保存一页,掉电可完整恢复)
* @param reverse 0=正向(A=旧,B=新 → A=新,B=旧,部署新固件)
* 1=反向(A=新,B=旧 → A=旧,B=新,回滚旧固件)
*/
static void swap_execute(int reverse)
{
uint32_t a = OTA_RUN_ADDR_BASE;
uint32_t b = OTA_BAK_ADDR_BASE;
uint32_t i, total_pages = OTA_SLOT_PAGE_NB;
uint8_t buf[OTA_PAGE_SIZE];
if (g_ota.state != OTA_STATE_SWAPPING)
{
g_ota.state = OTA_STATE_SWAPPING;
g_ota.progress = 0;
g_ota.phase = 0;
ota_param_store(&g_ota);
}
Log("[BL] swap(%s) %lu pages\r\n", reverse ? "rollback" : "forward",
(unsigned long)total_pages);
for (i = g_ota.progress; i < total_pages; i++)
{
uint32_t addr_a = a + i * OTA_PAGE_SIZE;
uint32_t addr_b = b + i * OTA_PAGE_SIZE;
/* 掉电恢复:phase==1 时 scratch 保存着 addr_a 的原内容,
* 直接补写 addr_b 完成本页,再继续(scratch 持久,不丢) */
if ((g_ota.phase == 1) && (i == g_ota.progress))
{
APP_FlashRead(OTA_SCRATCH_ADDR, buf, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(addr_b, OTA_PAGE_SIZE);
APP_FlashWrite(addr_b, buf, OTA_PAGE_SIZE);
g_ota.phase = 0;
g_ota.progress = (uint16_t)(i + 1);
ota_param_store(&g_ota);
boot_iwdg_feed();
continue;
}
/* 1) scratch ← A[i](持久保存被覆盖方) */
APP_FlashRead(addr_a, buf, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(OTA_SCRATCH_ADDR, OTA_SCRATCH_SIZE);
APP_FlashWrite(OTA_SCRATCH_ADDR, buf, OTA_PAGE_SIZE);
g_ota.phase = 1;
ota_param_store(&g_ota);
/* 2) A[i] ← B[i] */
APP_FlashRead(addr_b, buf, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(addr_a, OTA_PAGE_SIZE);
APP_FlashWrite(addr_a, buf, OTA_PAGE_SIZE);
/* 3) B[i] ← scratch(原 A[i] */
APP_FlashRead(OTA_SCRATCH_ADDR, buf, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(addr_b, OTA_PAGE_SIZE);
APP_FlashWrite(addr_b, buf, OTA_PAGE_SIZE);
g_ota.phase = 0;
g_ota.progress = (uint16_t)(i + 1);
ota_param_store(&g_ota);
boot_iwdg_feed();
}
g_ota.state = reverse ? OTA_STATE_IDLE : OTA_STATE_BOOT_NEW;
ota_param_store(&g_ota);
Log("[BL] swap done, state=%u\r\n", g_ota.state);
}
#else /* OTA_SWAP_STRATEGY == OTA_SWAP_RAM */
/**
* @brief RAM 缓冲交换(不预留暂存区,A/B 各 55K;掉电恢复见 phase 说明)
* @param reverse 0=正向(部署新) 1=反向(回滚)
*
* 正向每页顺序(关键:先部署新到 A,再备份旧到 B):
* phase=1 写入后 → 擦/写 A[i](若中断,B[i] 仍是新固件页,可重建 A[i])
* → 擦/写 B[i](若中断,旧固件页丢失,但新固件完整 → 升级成功、回滚失效)
* 掉电恢复(phase==1 且 i==progress):从 B[i] 重建 A[i]
* 放弃本页旧备份(B[i] 保持新固件页),继续后续页 —— 保证新固件永远可收敛。
*/
static void swap_execute(int reverse)
{
uint32_t a = OTA_RUN_ADDR_BASE;
uint32_t b = OTA_BAK_ADDR_BASE;
uint32_t i, total_pages = OTA_SLOT_PAGE_NB;
uint8_t buf_new[OTA_PAGE_SIZE];
uint8_t buf_old[OTA_PAGE_SIZE];
if (g_ota.state != OTA_STATE_SWAPPING)
{
g_ota.state = OTA_STATE_SWAPPING;
g_ota.progress = 0;
g_ota.phase = 0;
ota_param_store(&g_ota);
}
Log("[BL] swap(%s) %lu pages (RAM buf)\r\n", reverse ? "rollback" : "forward",
(unsigned long)total_pages);
for (i = g_ota.progress; i < total_pages; i++)
{
uint32_t addr_a = a + i * OTA_PAGE_SIZE;
uint32_t addr_b = b + i * OTA_PAGE_SIZE;
/* 掉电恢复:phase==1 时 A[i] 可能半写,B[i] 必为新固件页(未动)→ 重建 A[i] */
if ((g_ota.phase == 1) && (i == g_ota.progress))
{
APP_FlashRead(addr_b, buf_new, OTA_PAGE_SIZE);
APP_FlashEraseWithCheck(addr_a, OTA_PAGE_SIZE);
APP_FlashWrite(addr_a, buf_new, OTA_PAGE_SIZE);
/* 旧固件页已在掉电时丢失 → 本页放弃旧备份(B[i] 保持新固件页) */
g_ota.phase = 0;
g_ota.progress = (uint16_t)(i + 1);
ota_param_store(&g_ota);
boot_iwdg_feed();
continue;
}
APP_FlashRead(addr_a, buf_old, OTA_PAGE_SIZE);
APP_FlashRead(addr_b, buf_new, OTA_PAGE_SIZE);
/* 先写 A(部署新):中断时 B[i] 完好可重建 */
g_ota.phase = 1;
ota_param_store(&g_ota);
APP_FlashEraseWithCheck(addr_a, OTA_PAGE_SIZE);
APP_FlashWrite(addr_a, buf_new, OTA_PAGE_SIZE);
/* 再写 B(备份旧) */
APP_FlashEraseWithCheck(addr_b, OTA_PAGE_SIZE);
APP_FlashWrite(addr_b, buf_old, OTA_PAGE_SIZE);
g_ota.phase = 0;
g_ota.progress = (uint16_t)(i + 1);
ota_param_store(&g_ota);
boot_iwdg_feed();
}
g_ota.state = reverse ? OTA_STATE_IDLE : OTA_STATE_BOOT_NEW;
ota_param_store(&g_ota);
Log("[BL] swap done, state=%u\r\n", g_ota.state);
}
#endif /* OTA_SWAP_STRATEGY */
#endif /* OTA_BACKUP_MODE == AB */
/**
* @brief Main program.
* @retval int
*/
int main(void)
{
HAL_Init();
#ifdef DEBUG
DEBUG_USART_Config(); /* 将串口配置成日志口 */
#endif
Log("\r\n==== PY32F040 OTA Bootloader V3.0 ====\r\n");
#if (OTA_BACKUP_MODE == OTA_MODE_SINGLE)
Log("[BL] mode: SINGLE backup\r\n");
#else
/* 双备份:OTA_SWAP_STRATEGY / OTA_SCRATCH_* 由 ota_config.h 的
* #if (OTA_BACKUP_MODE == OTA_MODE_AB) 保护,此处一定已定义 */
#if (OTA_SWAP_STRATEGY == OTA_SWAP_SCRATCH)
Log("[BL] mode: AB backup, SCRATCH swap\r\n");
#else
Log("[BL] mode: AB backup, RAM swap\r\n");
#endif
#endif
ota_param_load(&g_ota);
Log("[BL] state=%u fw_size=%lu\r\n", g_ota.state, (unsigned long)g_ota.fw_size);
#if (OTA_BACKUP_MODE == OTA_MODE_SINGLE)
/* ── ① 单备份 ── */
switch (g_ota.state)
{
case OTA_STATE_PENDING:
Log("[BL] Pending: validate BAK ...\r\n");
if (fw_validate(OTA_BAK_ADDR_BASE, g_ota.fw_size, g_ota.fw_crc32))
{
boot_iwdg_enable();
copy_bak_to_run();
Log("[BL] Switch to NEW fw in RUN area\r\n");
boot_jump(OTA_RUN_ADDR_BASE);
}
else
{
/* 新固件无效 → 继续跑主区旧固件(BAK 被丢弃) */
Log("[BL] New fw invalid, keep old RUN fw\r\n");
g_ota.state = OTA_STATE_IDLE;
ota_param_store(&g_ota);
boot_iwdg_enable();
boot_jump(OTA_RUN_ADDR_BASE);
}
break;
case OTA_STATE_BOOT_NEW:
/* 单备份无回滚目标:App 未确认(崩溃/掉电)也只能继续运行主区 */
Log("[BL] SINGLE: BOOT_NEW not confirmed, keep RUN fw\r\n");
g_ota.state = OTA_STATE_IDLE;
ota_param_store(&g_ota);
boot_iwdg_enable();
boot_jump(OTA_RUN_ADDR_BASE);
break;
case OTA_STATE_IDLE:
default:
Log("[BL] Normal boot, RUN area\r\n");
boot_iwdg_enable();
boot_jump(OTA_RUN_ADDR_BASE);
break;
}
#else /* OTA_BACKUP_MODE == AB */
/* ── ②/③ 双备份 ── */
switch (g_ota.state)
{
case OTA_STATE_PENDING:
Log("[BL] Pending: validate B area ...\r\n");
if (fw_validate(OTA_BAK_ADDR_BASE, g_ota.fw_size, g_ota.fw_crc32))
{
boot_iwdg_enable(); /* 交换期间逐页喂狗,跳转后由新固件喂狗 */
swap_execute(0); /* 正向交换:A=新, B=旧 */
Log("[BL] Switch to NEW fw\r\n");
boot_jump(OTA_RUN_ADDR_BASE);
}
else
{
/* 新固件无效 → 继续跑 A 区旧固件 */
Log("[BL] New fw invalid, keep old RUN fw\r\n");
g_ota.state = OTA_STATE_IDLE;
ota_param_store(&g_ota);
boot_iwdg_enable();
boot_jump(OTA_RUN_ADDR_BASE);
}
break;
case OTA_STATE_SWAPPING:
/* 上次交换掉电中断 → 续做(部署新固件) */
Log("[BL] Resuming interrupted swap ...\r\n");
boot_iwdg_enable();
swap_execute(0);
Log("[BL] Switch to NEW fw (resumed)\r\n");
boot_jump(OTA_RUN_ADDR_BASE);
break;
case OTA_STATE_BOOT_NEW:
/* 上次启动的新固件未确认(崩溃/掉电)→ 回滚旧固件 */
Log("[BL] New fw not confirmed, ROLLBACK ...\r\n");
boot_iwdg_enable();
swap_execute(1); /* 反向交换:A=旧, B=新 */
Log("[BL] Rollback done, boot old fw\r\n");
boot_jump(OTA_RUN_ADDR_BASE);
break;
case OTA_STATE_IDLE:
default:
Log("[BL] Normal boot, RUN area\r\n");
boot_iwdg_enable();
boot_jump(OTA_RUN_ADDR_BASE);
break;
}
#endif /* OTA_BACKUP_MODE */
while (1)
{
}
}
/***********************************************************************************************************************
* Function Name: Error_Handler
* @brief 错误处理函数
* @param *file:文件名,line:行号
* @return None
***********************************************************************************************************************/
void Error_Handler(uint8_t *file, uint32_t line)
{
while (1)
{
appPrintf(LOG_ERROR, "%s %d\r\n", file, line);
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
while (1)
{
}
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT Puya *****END OF FILE******************/