/** ****************************************************************************** * @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_BASE,BAK/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******************/