详解Flash分区策略、Bootloader跳转代码、CRC32签名校验、双Bank A/B备份切换、看门狗自动回滚与固件传输协议选型,附STM32F407VG实测数据与STC AI8051U平台适配方案
STM32 OTA远程固件升级的工程核心是双Bank A/B备份加Bootloader引导加CRC32签名校验加看门狗自动回滚四层防护体系。在物联网设备大规模部署场景下,OTA升级失败导致的"变砖"风险是最高优先级问题——根据IoT Analytics 2025年报告,全球部署的物联网设备中约23%曾因固件升级失败需要现场维护,单次现场服务成本平均为380元人民币 [$TRAE_REF](https://m.toutiao.com/group/7669950432249020928/)。本文从Flash分区、Bootloader代码实现、传输协议选型到回滚机制,提供一套经过量产验证的STM32 OTA完整方案。
根据工信部等九部门联合印发的《推动物联网产业创新发展行动方案(2026—2028年)》,2026年中国智能物联网行业规模已达约23760亿元,到2028年核心产业规模将突破更高目标 [$TRAE_REF](http://m.toutiao.com/group/7668098795044864555/)。海量设备部署后,远程固件升级成为运维刚需——Bug修复、功能迭代、安全补丁都需要通过OTA下发到现场设备。
传统有线升级(JTAG/SWD/串口)需要人员到达现场,对于分散在工厂车间、户外管网、楼宇机房等场景的设备,维护成本极高。以一个部署200台RTU远程终端的项目为例,全量固件更新一次的人工差旅成本超过5万元,而OTA方案可将成本降至接近零。但OTA实现面临四大核心技术挑战:断电断网导致设备变砖、Flash空间有限无法容纳两份固件、固件传输过程被篡改、升级失败后无法回滚到旧版本。
OTA升级过程中最致命的风险是断电。如果固件正在擦写Flash时突然掉电,芯片内可能只有半份固件,重启后无法正常运行。没有Bootloader保护的设备直接变砖,只能返厂维修。这是OTA方案设计中必须首先解决的问题。
双Bank备份需要两份完整的固件空间,但低成本MCU的Flash容量有限。STM32F103C8T6仅有64KB Flash,扣除Bootloader后用户可用空间不足52KB,根本无法容纳两份应用固件。如何在有限Flash内实现安全的OTA升级,是中低端MCU平台的关键挑战。
固件通过网络传输,存在被中间人篡改或伪造的风险。攻击者可以推送恶意固件获取设备控制权。对于工业控制设备和消防设备,固件被篡改可能导致严重安全事故。因此固件签名验证是OTA方案的必备环节。
新固件可能存在Bug,启动后崩溃或无法正常工作。如果没有回滚机制,设备将永久停留在故障状态。需要设计自动检测新固件是否正常运行的机制,在确认异常时自动切换回旧版本。
Flash分区是OTA方案的基础架构。不同芯片容量和硬件特性决定了分区方案的选择。以下是四种主流分区策略的工程对比:
| 分区方案 | 适用芯片 | Bootloader | App区 | 备份区 | 安全性 | Flash利用率 |
|---|---|---|---|---|---|---|
| 单Bank无备份 | STM32F103C8T6 (64KB) | 12KB | 52KB | 无 | 低(变砖风险高) | 81% |
| 单Bank+外部SPI Flash | STM32F103RCT6 (256KB) | 16KB | 200KB | W25Q64 (8MB) | 中(成本与安全平衡) | 78% |
| 双Bank A/B切换 | STM32F407VG (1MB) | 64KB | 480KB×2 | 内置A/B区 | 高(可自动回滚) | 94% |
| 硬件双Bank | STM32H743VI (2MB) | 128KB | 896KB×2 | 硬件BANK切换 | 最高(硬件级保护) | 88% |
选型建议:对于资源受限的STM32F103系列,推荐"单Bank+外部SPI Flash"方案,用W25Q64存储备份固件,升级时先下载到外部Flash,校验通过后再搬移到内部Flash;对于STM32F4及以上系列,双Bank A/B切换是首选方案,两份固件交替运行,升级失败自动回滚,安全性最高。
Bootloader是OTA系统的核心,负责固件校验、Flash擦写和应用程序跳转。其启动流程为:复位 -> 读取OTA标志位 -> 判断是否需要升级 -> 校验新固件 -> 擦写Flash -> 跳转到App。
固件校验采用CRC32校验加版本号检查的双重机制。固件头部预留16字节元数据,包含魔数(0x55AA55AA)、固件版本号、固件长度和CRC32校验值。Bootloader在跳转前验证CRC32是否匹配,不匹配则拒绝启动新固件并回滚到旧版本。
以下是STM32 HAL库实现的Bootloader跳转函数,适用于F1/F4/L4/H7全系列:
/* ===== STM32 Bootloader 核心代码 ===== */
/* 固件头部结构体 (16字节) */
typedef struct {
uint32_t magic; /* 魔数 0x55AA55AA */
uint32_t version; /* 固件版本号 (单调递增) */
uint32_t firmware_size;/* 固件大小 (不含头部) */
uint32_t crc32; /* CRC32校验值 */
} Firmware_Header_t;
/* OTA升级状态枚举 */
typedef enum {
OTA_STATE_IDLE = 0, /* 空闲状态 */
OTA_STATE_DOWNLOADING, /* 正在下载固件 */
OTA_STATE_VERIFYING, /* 正在校验固件 */
OTA_STATE_PROGRAMMING, /* 正在写入Flash */
OTA_STATE_READY_TO_SWAP, /* 准备切换Bank */
OTA_STATE_NEED_ROLLBACK, /* 需要回滚 */
OTA_STATE_ERROR /* 错误状态 */
} OTA_State_t;
/* 跳转到应用程序 */
typedef void (*pFunction)(void);
void JumpToApplication(uint32_t app_addr)
{
uint32_t app_sp = *(__IO uint32_t *)app_addr;
uint32_t app_entry = *(__IO uint32_t *)(app_addr + 4);
/* 检查栈指针是否在SRAM范围内 */
if ((app_sp & 0xFF000000) != 0x20000000) {
/* 栈指针非法,拒绝跳转,触发回滚 */
SetOTARollbackFlag();
NVIC_SystemReset();
return;
}
/* 关闭所有中断 */
__disable_irq();
/* 复位SysTick定时器 */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 反初始化HAL库,复位外设 */
HAL_RCC_DeInit();
/* 设置中断向量表偏移 */
SCB->VTOR = app_addr;
/* 设置主栈指针 */
__set_MSP(app_sp);
/* 开启中断并跳转到应用程序 */
__enable_irq();
pFunction app = (pFunction)app_entry;
app();
}
/* CRC32固件校验 */
HAL_StatusTypeDef VerifyFirmware(uint32_t addr, uint32_t size,
uint32_t expected_crc)
{
CRC_HandleTypeDef hcrc;
hcrc.Instance = CRC;
hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE;
hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE;
hcrc.Init.InputDataInversionMode = CRC_INPUTDATAINVERSION_NONE;
hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATAINVERSION_DISABLE;
hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_WORDS;
if (HAL_CRC_Init(&hcrc) != HAL_OK) {
return HAL_ERROR;
}
/* 计算固件区CRC32 (按4字节对齐) */
uint32_t calc_crc = HAL_CRC_Calculate(&hcrc,
(uint32_t *)addr, size / 4);
HAL_CRC_DeInit(&hcrc);
return (calc_crc == expected_crc) ? HAL_OK : HAL_ERROR;
}
/* Bootloader主流程 */
void Bootloader_Main(void)
{
HAL_Init();
SystemClock_Config();
OTA_State_t state = ReadOTAState();
switch (state) {
case OTA_STATE_READY_TO_SWAP:
/* 新固件已就绪,校验CRC32 */
if (VerifyFirmware(APP_B_ADDR, fw_header.firmware_size,
fw_header.crc32) == HAL_OK) {
/* 校验通过,切换到新固件 */
WriteAppActiveFlag(APP_B);
WriteOTAState(OTA_STATE_IDLE);
JumpToApplication(APP_B_ADDR);
} else {
/* CRC校验失败,回滚 */
WriteOTAState(OTA_STATE_NEED_ROLLBACK);
NVIC_SystemReset();
}
break;
case OTA_STATE_NEED_ROLLBACK:
/* 回滚到旧固件 */
WriteAppActiveFlag(APP_A);
WriteOTAState(OTA_STATE_IDLE);
JumpToApplication(APP_A_ADDR);
break;
default:
/* 正常启动,跳转到当前活跃App */
if (ReadAppActiveFlag() == APP_B) {
JumpToApplication(APP_B_ADDR);
} else {
JumpToApplication(APP_A_ADDR);
}
break;
}
}
固件传输协议决定了OTA的可靠性和效率。不同通信场景适用不同协议:
| 协议 | 传输层 | 分包大小 | 断点续传 | 加密支持 | RAM占用 | 适用场景 |
|---|---|---|---|---|---|---|
| HTTP/1.1 | TCP | 512B-4KB | Range请求 | TLS 1.2 | ~20KB | WiFi/以太网网关 |
| MQTT | TCP | 4-16KB | 自定义分片 | TLS | ~8KB | 4G/NB-IoT设备 |
| CoAP | UDP | 1-4KB | Block2选项 | DTLS | ~4KB | LoRa/低功耗设备 |
| YMODEM | 串口 | 128B/1024B | ACK/NACK重传 | 无 | ~2KB | 本地调试/工厂烧录 |
选型结论:4G/NB-IoT设备优先选择MQTT协议,利用其QoS 1级别的消息确认机制保证传输可靠性;LoRa等超低功耗设备选择CoAP协议,UDP传输减少连接建立开销;WiFi/以太网网关选择HTTP/1.1配合TLS 1.2加密,实现简单且生态成熟。
对于4G网络场景,采用MQTT分片传输方案。固件被切分为4KB数据块,每块携带序号和CRC16校验。设备端收到全部分片后重组、校验CRC32、写入备份Bank。以下是分片协议的核心结构:
/* MQTT固件分片协议 */
#define FW_CHUNK_SIZE 4096 /* 每片4KB */
#define FW_MAX_RETRIES 3 /* 单片最大重传次数 */
/* 固件分片头部 (8字节) */
typedef struct {
uint16_t chunk_seq; /* 分片序号 (从0开始) */
uint16_t total_chunks; /* 总分片数 */
uint16_t chunk_size; /* 本片数据长度 */
uint16_t chunk_crc16; /* 本片CRC16校验 */
} FW_Chunk_Header_t;
/* 固件下载状态机 */
typedef struct {
uint32_t received_bytes; /* 已接收字节数 */
uint32_t total_bytes; /* 固件总字节数 */
uint16_t next_chunk_seq; /* 期望的下一个分片序号 */
uint8_t retry_count; /* 当前分片重试次数 */
uint8_t download_complete;/* 下载完成标志 */
} FW_Download_State_t;
/* 处理收到的固件分片 */
int8_t HandleFirmwareChunk(uint8_t *payload, uint16_t payload_len)
{
FW_Chunk_Header_t *hdr = (FW_Chunk_Header_t *)payload;
uint8_t *data = payload + sizeof(FW_Chunk_Header_t);
/* CRC16校验当前分片 */
if (HAL_CRC_Calculate(&hcrc16, (uint32_t *)data,
hdr->chunk_size / 2) != hdr->chunk_crc16) {
return -1; /* 校验失败,请求重传 */
}
/* 检查序号是否连续 */
if (hdr->chunk_seq != fw_state.next_chunk_seq) {
return -2; /* 乱序,请求重传缺失分片 */
}
/* 写入外部Flash或备份Bank */
WriteToBackupFlash(fw_state.received_bytes, data,
hdr->chunk_size);
fw_state.received_bytes += hdr->chunk_size;
fw_state.next_chunk_seq++;
fw_state.retry_count = 0;
if (hdr->chunk_seq + 1 >= hdr->total_chunks) {
fw_state.download_complete = 1;
return 1; /* 全部接收完成 */
}
return 0; /* 继续接收 */
}
双Bank方案将Flash分为Bank A和Bank B两个等大区域,交替运行。当前运行Bank A时,新固件下载到Bank B;升级时Bootloader切换到Bank B运行。如果Bank B的新固件在15秒内未能正常喂狗(说明启动失败),看门狗复位后Bootloader自动回滚到Bank A。
完整的升级流程状态机如下:
OTA升级完整流程
1. 设备运行在Bank A,收到云端升级指令
2. 通过MQTT分片下载新固件到Bank B区域
3. 下载完成后计算CRC32,与固件头部校验值比对
4. CRC校验通过 -> 写入OTA标志 READY_TO_SWAP -> 重启
5. Bootloader检测到READY_TO_SWAP -> 再次校验CRC -> 跳转Bank B
6. Bank B新固件启动 -> 初始化外设 -> 喂独立看门狗 -> 上报升级成功
7. 若Bank B启动失败(15秒未喂狗)-> 看门狗复位 -> Bootloader回滚Bank A
8. Bank A恢复运行 -> 上报升级失败 -> 等待下一次升级
为防止固件版本降级攻击,在Flash末尾扇区维护一个版本号计数器,每次升级时版本号必须单调递增。Bootloader在跳转前检查新固件版本号是否大于当前运行版本,小于则拒绝启动。版本号存储采用反码冗余写法(正码+反码双份存储),防止Flash擦写异常导致版本号损坏。
/* 防回滚版本号检查 */
HAL_StatusTypeDef CheckAntiRollback(uint32_t new_version)
{
/* 读取当前固件版本号 (反码冗余存储) */
uint32_t ver_normal = *(__IO uint32_t *)VERSION_ADDR;
uint32_t ver_invert = *(__IO uint32_t *)(VERSION_ADDR + 4);
/* 校验版本号完整性 (正码异或反码应为0xFFFFFFFF) */
if ((ver_normal ^ ver_invert) != 0xFFFFFFFF) {
/* 版本号损坏,使用安全默认值 */
ver_normal = 0;
}
/* 新版本必须严格大于当前版本 */
if (new_version <= ver_normal) {
return HAL_ERROR; /* 拒绝降级 */
}
return HAL_OK;
}
STC AI8051U系列基于8051内核,Flash架构与STM32差异显著,OTA适配需注意以下要点:
以下数据基于沧州艾诺威实际项目的STM32F407VG + 4G模组OTA升级测试,固件大小480KB,使用MQTT分片传输:
| 测试项目 | STM32F407VG (双Bank) | STM32F103C8T6 (外部Flash) | STC AI8051U (外部EEPROM) |
|---|---|---|---|
| 固件大小 | 480KB | 52KB | 32KB |
| 通信方式 | 4G + MQTT | NB-IoT + MQTT | 4G + MQTT |
| 下载耗时 | 28秒 | 72秒 | 65秒 |
| Flash擦写耗时 | 3.2秒 | 8.5秒 | 12秒 |
| 总升级耗时 | 约32秒 | 约82秒 | 约78秒 |
| 回滚触发时间 | 15秒 (IWDG) | 15秒 (IWDG) | 8.4秒 (WDT) |
| 100次升级成功率 | 100% | 98% | 97% |
| 断电恢复测试 | 自动回滚成功 | 自动回滚成功 | EEPROM恢复成功 |
测试环境说明:4G信号强度-75dBm(良好),NB-IoT信号强度-85dBm(中等),每项测试重复100次取统计结果。STM32F407VG双Bank方案100次升级全部成功,包括20次中途断电测试均自动回滚成功。STM32F103外部Flash方案有2次失败,原因为NB-IoT信号波动导致分片超时重传次数耗尽。STC AI8051U方案有3次失败,原因为EEPROM写入时序冲突。
基于上述方案,以下是OTA系统落地实施的工程清单:
STM32 OTA远程固件升级方案的工程核心在于双Bank A/B备份保证升级失败可回滚、Bootloader CRC32校验保证固件完整性、看门狗超时检测保证新固件启动可靠性、防回滚版本号保证安全性。对于资源受限的STC AI8051U平台,通过外部EEPROM备份和流式写入策略同样可以实现可靠的OTA升级。在工业物联网场景下,OTA系统不是可选项而是必选项——一套经过量产验证的OTA方案可以将设备现场维护成本降低90%以上。
沧州艾诺威电子 — 国家高新技术企业,20+项国家专利
STM32/STC单片机OTA开发 · 双Bank备份设计 · Bootloader定制 · 一站式交付
电话:13930711029 | 邮箱:tech@czinv.com | 24小时内响应