shuichan_old/STM32F103_BootLoader/m_app/ota_boot.c

567 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.

#include "ota_boot.h"
#include "main.h"
#include "boot_share.h"
#include "../HardWare/W25Q64/w25q64.h"
#include "../HardWare/LOG/log.h"
#include <stdint.h>
#include <string.h>
extern UART_HandleTypeDef huart3; /* main.c 中定义,跳转 APP 前需要关闭 */
extern DMA_HandleTypeDef hdma_usart3_rx;
extern IWDG_HandleTypeDef hiwdg; /* main.c 中定义的看门狗句柄 */
#define IWDG_FEED() HAL_IWDG_Refresh(&hiwdg)
/* 外部看门狗 TPL5010DONE 引脚接 PC13(LED2),电平翻转即喂狗,超时约 7 分钟。
* BootLoader 的擦除/拷贝全程远小于 7 分钟,这里做保险性喂狗 */
#define EXT_WDOG_FEED() HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13)
/*======================================================================*/
/*=========================Flash 驱动程序(start)=========================*/
/*======================================================================*/
static void FLASH_Unlock_Reg(void)
{
if ((FLASH->CR & FLASH_CR_LOCK) == 0) return;
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
static void FLASH_Lock_Reg(void)
{
FLASH->CR |= FLASH_CR_LOCK;
}
/* 等待内部 Flash 空闲,带超时;返回 1=就绪0=超时 */
static int FLASH_WaitBusy(uint32_t timeout_ms)
{
uint32_t start = HAL_GetTick();
while (FLASH->SR & FLASH_SR_BSY)
{
if ((HAL_GetTick() - start) > timeout_ms) return 0;
}
return 1;
}
static void FLASH_ClearFlags(void)
{
FLASH->SR = (FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR);
}
static int Erase_page(unsigned int pageaddr, unsigned int num)
{
log_info("> 擦除 %u 页,起始地址 0x%X...", num, pageaddr);
for (unsigned int i = 0; i < num; i++)
{
unsigned int pg_addr = pageaddr + i * PageSize;
IWDG_FEED();
EXT_WDOG_FEED();
FLASH_Unlock_Reg();
FLASH_ClearFlags();
if (!FLASH_WaitBusy(500))
{
log_error("> 擦除失败 @0x%X忙等待超时", pg_addr);
FLASH_Lock_Reg();
return 0;
}
FLASH->CR |= FLASH_CR_PER;
FLASH->AR = pg_addr;
FLASH->CR |= FLASH_CR_STRT;
if (!FLASH_WaitBusy(500))
{
log_error("> 擦除失败 @0x%X忙等待超时", pg_addr);
FLASH->CR &= ~FLASH_CR_PER;
FLASH_Lock_Reg();
return 0;
}
uint32_t sr = FLASH->SR;
FLASH->CR &= ~FLASH_CR_PER;
if (sr & FLASH_SR_WRPRTERR)
{
log_error("> 擦除失败 @0x%XSR=0x%X", pg_addr, (unsigned int)sr);
FLASH_ClearFlags();
FLASH_Lock_Reg();
return 0;
}
FLASH_Lock_Reg();
}
log_info("> 擦除完成(%u 页)", num);
return 1;
}
static int FLASH_WriteHalfWord(unsigned int addr, uint16_t data)
{
FLASH_Unlock_Reg();
FLASH_ClearFlags();
if (!FLASH_WaitBusy(100))
{
FLASH_Lock_Reg();
return 0;
}
FLASH->CR |= FLASH_CR_PG;
*(__IO uint16_t *)addr = data;
if (!FLASH_WaitBusy(100))
{
FLASH->CR &= ~FLASH_CR_PG;
FLASH_Lock_Reg();
return 0;
}
uint32_t sr = FLASH->SR;
FLASH->CR &= ~FLASH_CR_PG;
FLASH_ClearFlags();
if (sr & FLASH_SR_WRPRTERR)
{
FLASH_Lock_Reg();
return 0;
}
FLASH_Lock_Reg();
return 1;
}
static int WriteFlash(unsigned int addr, unsigned int * buff, int word_size)
{
for (int i = 0; i < word_size; i++)
{
unsigned int val = buff[i];
uint16_t low = (uint16_t)(val & 0xFFFF);
uint16_t high = (uint16_t)(val >> 16);
if (!FLASH_WriteHalfWord(addr + 4 * i, low))
{
log_error("> 写入失败 @0x%X (low=0x%04X)", addr + 4 * i, low);
return 0;
}
if (!FLASH_WriteHalfWord(addr + 4 * i + 2, high))
{
log_error("> 写入失败 @0x%X (high=0x%04X)", addr + 4 * i + 2, high);
return 0;
}
}
return 1;
}
static void ReadFlash(unsigned int addr, unsigned int * buff, uint16_t word_size)
{
for(int i =0; i < word_size; i++)
{
buff[i] = *(__IO unsigned int*)(addr + 4 * i);
}
}
/*====================================================================*/
/*=========================Flash 驱动程序(end)=========================*/
/*====================================================================*/
/* MODBUS CRC16 */
static uint16_t crc16_modbus_update(uint16_t crc, uint8_t data)
{
crc ^= data;
for (int i = 0; i < 8; i++) {
if (crc & 1) crc = (crc >> 1) ^ 0xA001;
else crc >>= 1;
}
return crc;
}
static uint16_t crc16_modbus(const uint8_t *data, uint32_t len)
{
uint16_t crc = 0xFFFF;
for (uint32_t i = 0; i < len; i++) crc = crc16_modbus_update(crc, data[i]);
return crc;
}
/*======================================================================*/
/* 外部 Flash 与内部 Flash 之间拷贝 */
/*======================================================================*/
#define COPY_CHUNK_WORDS 256 /* 1024 bytes */
#define COPY_CHUNK_BYTES (COPY_CHUNK_WORDS * 4)
static int Copy_External_To_App1(uint32_t ext_addr, uint32_t byte_size)
{
static uint32_t words[COPY_CHUNK_WORDS];
static uint8_t tmp[COPY_CHUNK_BYTES];
uint32_t offset = 0;
log_info("[BL] 拷贝外部 Flash 0x%06X -> APP1 0x%08X大小=%u",
(unsigned)ext_addr, Application_1_Addr, (unsigned)byte_size);
while (offset < byte_size)
{
uint32_t chunk = (byte_size - offset > COPY_CHUNK_BYTES) ? COPY_CHUNK_BYTES : (byte_size - offset);
IWDG_FEED();
EXT_WDOG_FEED();
W25Q64_Read(ext_addr + offset, tmp, chunk);
for (uint32_t i = 0; i < chunk / 4; i++) {
words[i] = ((uint32_t)tmp[i * 4]) |
((uint32_t)tmp[i * 4 + 1] << 8) |
((uint32_t)tmp[i * 4 + 2] << 16) |
((uint32_t)tmp[i * 4 + 3] << 24);
}
if (!WriteFlash(Application_1_Addr + offset, words, chunk / 4))
{
log_error("[FAIL] 写入 APP1 失败 @0x%X", Application_1_Addr + offset);
return 0;
}
for (uint32_t i = 0; i < chunk / 4; i++) {
uint32_t r = *(__IO uint32_t *)(Application_1_Addr + offset + i * 4);
if (r != words[i]) {
log_error("[FAIL] 校验 APP1 失败 @0x%Xwrote=0x%08X read=0x%08X",
Application_1_Addr + offset + i * 4, (unsigned)words[i], (unsigned)r);
return 0;
}
}
offset += chunk;
if ((offset & 0x3FFF) == 0) {
log_info("> 进度: %u/%u KB (%u%%)", offset / 1024, byte_size / 1024, offset * 100 / byte_size);
}
}
log_info("[ OK ] 拷贝完成");
return 1;
}
static int Copy_App1_To_External(uint32_t ext_addr, uint32_t byte_size)
{
static uint32_t words[COPY_CHUNK_WORDS];
static uint8_t tmp[COPY_CHUNK_BYTES];
uint32_t offset = 0;
log_info("[BL] 备份 APP1 -> 外部 Flash 0x%06X大小=%u", (unsigned)ext_addr, (unsigned)byte_size);
while (offset < byte_size)
{
uint32_t chunk = (byte_size - offset > COPY_CHUNK_BYTES) ? COPY_CHUNK_BYTES : (byte_size - offset);
IWDG_FEED();
EXT_WDOG_FEED();
ReadFlash(Application_1_Addr + offset, words, chunk / 4);
for (uint32_t i = 0; i < chunk / 4; i++) {
tmp[i * 4] = (uint8_t)(words[i]);
tmp[i * 4 + 1] = (uint8_t)(words[i] >> 8);
tmp[i * 4 + 2] = (uint8_t)(words[i] >> 16);
tmp[i * 4 + 3] = (uint8_t)(words[i] >> 24);
}
W25Q64_WriteBuffer(ext_addr + offset, tmp, chunk);
if (!W25Q64_Verify(ext_addr + offset, tmp, chunk)) {
log_error("[FAIL] W25Q64 备份校验失败 @0x%06X", (unsigned)(ext_addr + offset));
return 0;
}
offset += chunk;
}
return 1;
}
/* 校验固件完整性:检查栈顶地址和复位向量是否合法 */
static int Verify_Firmware(unsigned int addr, unsigned int size)
{
/* 检查栈顶地址是否在合法RAM范围 (0x20000000) */
unsigned int sp = *(__IO unsigned int *)addr;
if ((sp & 0x2FFE0000) != 0x20000000)
{
log_error("> 校验失败:栈顶地址非法 SP=0x%X", sp);
return 0;
}
/* 检查复位向量是否在APP1固件范围内固件链接地址是APP1 */
unsigned int reset_vector = *(__IO unsigned int *)(addr + 4);
if (reset_vector < Application_1_Addr || reset_vector > (Application_1_Addr + size))
{
log_error("> 校验失败:复位向量非法=0x%X", reset_vector);
return 0;
}
log_info("> 固件校验通过");
return 1;
}
/* 设置主堆栈的栈值 */
__asm void MSR_MSP (unsigned int ulAddr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
/* 程序跳转函数: 检查栈顶合法性后跳转到目标 APP */
typedef void (*Jump_Fun)(void);
void IAP_ExecuteApp (unsigned int App_Addr)
{
Jump_Fun JumpToApp;
if ( ( ( * ( __IO unsigned int * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )
{
unsigned int sp = *(__IO unsigned int *)App_Addr;
unsigned int pc = *(__IO unsigned int *)(App_Addr + 4);
log_info("[BL] 正在跳转到 APP1 @0x%08X...", App_Addr);
/* 跳转前关闭 BootLoader 使用的中断和外设,避免干扰 APP。
* 注意不能用 __disable_irq()跳转不是复位PRIMASK 会带进 APP。 */
HAL_UART_DMAStop(&huart3);
__HAL_UART_DISABLE_IT(&huart3, UART_IT_IDLE);
HAL_NVIC_DisableIRQ(USART3_IRQn);
HAL_NVIC_DisableIRQ(DMA1_Channel3_IRQn);
NVIC_ClearPendingIRQ(USART3_IRQn);
NVIC_ClearPendingIRQ(DMA1_Channel3_IRQn);
SysTick->CTRL = 0; /* 停止 SysTick */
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk; /* 清 SysTick 挂起位 */
JumpToApp = (Jump_Fun)pc;
MSR_MSP(sp);
JumpToApp();
}
else
{
log_error("[BL] 错误APP @0x%08X 栈顶地址非法!", App_Addr);
}
}
/*==== 主电源检测PA0/ADC1_CH0寄存器直读BL 无 ADC HAL 模块)====
* 外部 12V 断开时仅靠法拉电容维持,电压快速跌落;
* 阈值与 APP 电量公式一致:<10%(约 11.0VADC≈1171视为主电源未上电 */
#define BL_MAIN_POWER_ADC_MIN 1171u
static uint16_t BL_ReadPowerAdc(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
GPIOA->CRL &= ~0xFu; /* PA0 模拟输入 */
ADC1->SMPR2 |= 7u; /* 通道0 采样 239.5 周期 */
ADC1->SQR3 = 0;
ADC1->CR2 |= ADC_CR2_ADON; /* ADC 上电 */
for (volatile uint32_t i = 0; i < 8000; i++); /* 稳定时间 */
ADC1->CR2 |= ADC_CR2_RSTCAL;
while (ADC1->CR2 & ADC_CR2_RSTCAL);
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL);
ADC1->CR2 |= ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL; /* SWSTART 软件触发 */
ADC1->CR2 |= ADC_CR2_SWSTART;
for (volatile uint32_t i = 0; i < 100000; i++) {
if (ADC1->SR & ADC_SR_EOC) break;
}
return (uint16_t)ADC1->DR;
}
/* 主电源未上电时不跳 APP循环检测直到 12V 恢复;
* 法拉电容耗尽则整机断电12V 恢复后冷启动自然通过本检查 */
static void BL_WaitMainPower(void)
{
uint16_t adc = BL_ReadPowerAdc();
if (adc >= BL_MAIN_POWER_ADC_MIN) return;
log_warn("[BL] 主电源未上电 (ADC=%u),等待 12V 恢复...", (unsigned)adc);
while (1) {
IWDG_FEED();
HAL_Delay(500);
adc = BL_ReadPowerAdc();
if (adc >= BL_MAIN_POWER_ADC_MIN) {
log_info("[BL] 主电源已恢复 (ADC=%u)", (unsigned)adc);
return;
}
}
}
void Start_BootLoader(void)
{
log_info("");
log_info("* * * * * * * * * * * * * * * * * *");
log_info("* BootLoader *");
log_info("* Flash: %dKB *", *(volatile uint16_t *)0x1FFFF7E0);
log_info("* * * * * By Helei * * * * * *");
EXT_WDOG_FEED(); /* 进 BootLoader 先喂一次外部看门狗 */
/* 读取并清除复位原因:用于区分"固件卡死(看门狗复位)"和"现场断电/按键/正常流程"。
同时把 CSR 标志写入 BOOT_SHARE 共享 RAM 传给 APP复位原因打印与重启记录由 APP 完成) */
uint32_t reset_flags = RCC->CSR;
RCC->CSR |= RCC_CSR_RMVF;
if (boot_share_valid()) {
BOOT_SHARE->csr_flags = reset_flags; /* 保留 uptime/pending_reason热复位信息 */
} else {
BOOT_SHARE->uptime_sec = 0;
BOOT_SHARE->pending_reason = 0;
BOOT_SHARE->csr_flags = reset_flags;
BOOT_SHARE->magic = BOOT_SHARE_MAGIC;
}
boot_share_store();
log_info("[BL] 上次复位原因:%s%s%s%s%s",
(reset_flags & RCC_CSR_PORRSTF) ? " 上电" : "",
(reset_flags & RCC_CSR_PINRSTF) ? " 按键" : "",
(reset_flags & RCC_CSR_SFTRSTF) ? " 软件" : "",
(reset_flags & RCC_CSR_IWDGRSTF) ? " IWDG看门狗" : "",
(reset_flags & RCC_CSR_WWDGRSTF) ? " WWDG看门狗" : "");
/* 主电源未上电(仅法拉电容维持)时不跳 APP防止外设没上电程序乱跑 */
BL_WaitMainPower();
W25Q64_OtaInfo info;
int info_ok = (W25Q64_OTA_ReadInfo(&info) == 0);
/* 固件大小合法性检查:信息区 checksum 较弱,防止损坏的 size 导致越界写内部 Flash */
if (info_ok && info.new_valid
&& (info.update_state == W25Q64_STATE_DOWNLOADED || info.update_state == W25Q64_STATE_COPYING)
&& (info.new_size == 0 || info.new_size > Application_Size || (info.new_size & 0x3) != 0))
{
log_warn("[BL] 新固件大小非法 new_size=%u丢弃新固件", (unsigned)info.new_size);
W25Q64_OTA_ClearNew();
info.new_valid = 0; /* 阻止进入下面的更新分支 */
}
/* 1. 新固件已下载到外部 Flash备份当前 APP1然后写入 APP1。
* COPYING 也按下载完成处理:拷贝/擦除中途掉电或看门狗复位后,
* 外部新固件仍在,重新执行更新流程,否则会卡死在"无有效 APP"状态 */
if (info_ok && info.new_valid
&& (info.update_state == W25Q64_STATE_DOWNLOADED || info.update_state == W25Q64_STATE_COPYING))
{
log_info("[BL] 外部 Flash 中有新固件ver=%u size=%u crc=0x%04X",
(unsigned)info.new_version, (unsigned)info.new_size, (unsigned)info.new_crc16);
W25Q64_OTA_SetState(W25Q64_STATE_COPYING);
/* 备份策略:当前 APP1 有效时,必须先做出有效备份才允许更新,
* 否则新固件不健康时无版本可回退(变砖);备份失败则放弃本次更新,
* 外部新固件保留下次重启重试APP1 本身已损坏时无物可备,直接更新自救) */
int app1_valid = Verify_Firmware(Application_1_Addr, Application_Size);
if (!info.backup_valid && app1_valid)
{
/* 备份前必须先擦除备份槽W25Q64 只能把位从 1 写 0不擦除直接写会得到错误数据 */
log_info("[BL] 擦除备份槽...");
W25Q64_EraseRegion(W25Q64_SLOT_BACKUP_ADDR, Application_Size);
uint16_t backup_crc = crc16_modbus((const uint8_t *)Application_1_Addr, Application_Size);
if (Copy_App1_To_External(W25Q64_SLOT_BACKUP_ADDR, Application_Size)
&& W25Q64_OTA_SetBackupInfo(Application_Size, backup_crc) == 0)
{
info.backup_valid = 1; /* 同步本地副本,下面的更新分支要用 */
log_info("[BL] 备份完成crc=0x%04X", (unsigned)backup_crc);
}
else
{
log_error("[BL] 备份失败,放弃更新(保留旧 APP1下次启动重试");
W25Q64_OTA_SetState(W25Q64_STATE_DOWNLOADED);
}
}
else if (!app1_valid)
{
log_warn("[BL] APP1 无效,无可备份内容;更新是唯一自救手段");
}
if (info.backup_valid || !app1_valid)
{
log_info("[BL] 正在更新 APP1...");
if (Erase_page(Application_1_Addr, Application_Size / PageSize))
{
if (Copy_External_To_App1(W25Q64_SLOT_NEW_ADDR, info.new_size))
{
uint16_t app_crc = crc16_modbus((const uint8_t *)Application_1_Addr, info.new_size);
if (app_crc == info.new_crc16 && Verify_Firmware(Application_1_Addr, info.new_size))
{
log_info("[BL] APP1 更新成功crc=0x%04X跳转运行", (unsigned)app_crc);
W25Q64_OTA_SetState(W25Q64_STATE_INSTALLED);
W25Q64_OTA_ClearConfirmed();
IAP_ExecuteApp(Application_1_Addr);
return;
}
log_error("[BL] APP1 CRC 或头部校验不匹配app_crc=0x%04X", (unsigned)app_crc);
}
}
log_warn("[BL] APP1 更新失败,保留外部新固件以便重试");
W25Q64_OTA_SetState(W25Q64_STATE_DOWNLOADED);
}
}
/* 2. 已安装但未确认:启动次数超过阈值则回退 */
if (info_ok && info.update_state == W25Q64_STATE_INSTALLED)
{
if (info.app_confirmed)
{
log_info("[BL] 更新已确认,清理 OTA 标志");
W25Q64_OTA_ClearNew();
}
else
{
/* 只有看门狗复位才消耗试错次数(说明新固件真的卡死);
上电/按键/软件复位不计,避免现场断电或正常流程误回退。
APP 健康超时主动复位前会自行调用 W25Q64_OTA_IncTrial() 计一次。 */
if (reset_flags & (RCC_CSR_IWDGRSTF | RCC_CSR_WWDGRSTF))
{
W25Q64_OTA_IncTrial();
info.boot_trial_count++;
log_warn("[BL] APP 卡死(看门狗复位),试错次数=%u", (unsigned)info.boot_trial_count);
}
else
{
log_info("[BL] 试运行启动,复位标志=0x%08X不计数", (unsigned)reset_flags);
}
if (info.boot_trial_count >= W25Q64_MAX_TRIALS)
{
log_warn("[BL] 达到最大试错次数,回退到备份固件");
/* 备份大小合法性检查,防止越界写内部 Flash */
if (info.backup_valid && info.backup_size > 0
&& info.backup_size <= Application_Size && (info.backup_size & 0x3) == 0
&& Erase_page(Application_1_Addr, Application_Size / PageSize))
{
if (Copy_External_To_App1(W25Q64_SLOT_BACKUP_ADDR, info.backup_size))
{
uint16_t app_crc = crc16_modbus((const uint8_t *)Application_1_Addr, info.backup_size);
if (app_crc == info.backup_crc16 && Verify_Firmware(Application_1_Addr, Application_Size))
{
W25Q64_OTA_SetState(W25Q64_STATE_ROLLBACK);
IAP_ExecuteApp(Application_1_Addr);
return;
}
}
}
log_error("[BL] 回退失败!");
}
}
}
/* 3. 回退状态:跳转当前 APP1先校验防止 APP 被擦除后跳入空片) */
if (info_ok && info.update_state == W25Q64_STATE_ROLLBACK)
{
if (Verify_Firmware(Application_1_Addr, Application_Size))
{
IAP_ExecuteApp(Application_1_Addr);
return;
}
log_warn("[BL] 回退状态但 APP1 无效,清除 OTA 标志");
W25Q64_OTA_ClearNew();
}
/* 4. 没有 OTA 任务:直接启动 APP1 */
if (Verify_Firmware(Application_1_Addr, Application_Size))
{
IAP_ExecuteApp(Application_1_Addr);
}
else
{
/* 未找到有效 APP例如只刷了 BL延时 3s 后主动复位重试,
* 之后通过 JLink/OTA 刷入 APP 即可恢复 */
log_error("[BL] 未找到有效的 APP13s 后复位重试");
HAL_Delay(3000);
NVIC_SystemReset();
}
}