ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32+4G模组远程OTA升级方案:双Bank设计与断点续传实践

2026/8/9 3:10:38 拓冰建站 浏览量
STM32+4G模组远程OTA升级方案:双Bank设计与断点续传实践

为什么要做OTA

你往客户的工厂发了500台设备,每台设备跑着固件V1.0。三天后发现一个bug——串口数据解析在高湿度环境下偶尔丢包。

没有OTA的话:派工程师去现场,一台台用USB烧录。500台设备,假设每台5分钟,加上路上时间,一周也搞不完。

有OTA的话:服务器上点一下"推送",500台设备自动下载新固件,5分钟全部升级完毕。

OTA不是锦上添花,是物联网设备的运维刚需。

STM32 OTA的技术难点

STM32做OTA比ESP32麻烦得多。ESP32有现成的分区表和OTA库,几行代码搞定。STM32需要自己处理所有事情:

  1. Bootloader:上电后先跑Bootloader,判断是否需要升级
  2. 双Bank设计:Flash分两个区,一个跑当前固件,一个写新固件,升级时切换
  3. Flash写入:STM32写Flash要先擦除再写,擦除最小单位是页(2KB/4KB)
  4. 4G模组通信:通过AT指令下载固件文件
  5. 断点续传:网络不稳定时能续传
  6. 校验机制:下载完成后CRC校验,防止刷入坏固件变砖

硬件方案

[STM32F407] --UART--> [EC25 4G模组] --4G网络--> [OTA服务器] | [SPI Flash W25Q128] -- 存储新固件 | [内部Flash Bank1/Bank2] -- 运行固件

选STM32F407的原因:

  • 1MB Flash,支持双Bank(Bank1: 512KB, Bank2: 512KB)
  • 192KB RAM,固件下载缓冲够用
  • 支持SPI,外挂W25Q128(16MB)存固件文件
  • 价格合适,批量约15元

Flash分区设计

STM32F407VE的512KB Flash分区:

0x08000000 - 0x08007FFF Bootloader (32KB) 0x08008000 - 0x0803FFFF Bank1: 应用固件 (224KB) 0x08040000 - 0x0807FFFF Bank2: 备用固件 (224KB) 0x08078000 - 0x0807FFFF 配置参数区 (32KB)

STM32F407实际上只有512KB单Bank Flash(双Bank是F42x/F46x系列才有),但可以通过软件模拟双Bank逻辑:升级时把新固件写到Bank2区域,校验通过后修改Bootloader标志位,下次重启从Bank2启动。

Bootloader代码

#include"stm32f4xx_hal.h"#include"flash_if.h"#include"ota_config.h"#defineBANK1_ADDR0x08008000#defineBANK2_ADDR0x08040000#defineCONFIG_ADDR0x08078000typedefstruct{uint32_tmagic;uint32_tactive_bank;// 1 or 2uint32_tfirmware_size;uint32_tfirmware_crc;uint32_tupgrade_flag;// 0xFFFFFFFF=no upgrade, 0x55555555=upgrading}ota_config_t;intmain(void){HAL_Init();// 读取配置区ota_config_tconfig;flash_read(CONFIG_ADDR,(uint8_t*)&config,sizeof(config));void(*app_main)(void);uint32_tapp_addr;if(config.active_bank==1){app_addr=BANK1_ADDR;}else{app_addr=BANK2_ADDR;}// 检查是否有升级完成待切换if(config.upgrade_flag==0xAAAAAAAA){// 新固件已写入并校验通过,切换Bankconfig.active_bank=(config.active_bank==1)?2:1;config.upgrade_flag=0xFFFFFFFF;flash_erase_config();flash_write_config((uint8_t*)&config,sizeof(config));}// 跳转到应用固件if(((*(__IOuint32_t*)app_addr)&0x2FFE0000)==0x20000000){// 检查栈指针合法性__set_MSP(*(__IOuint32_t*)app_addr);app_main=(void(*)(void))(*(__IOuint32_t*)(app_addr+4));app_main();}// 如果跳转失败,停在Bootloaderwhile(1){// 可以在这里加串口下载模式作为fallback}}

应用固件中的OTA下载

#defineFIRMWARE_MAX_SIZE(224*1024)// 224KBexternUART_HandleTypeDef huart2;// 4G模组UARTuint8_tfirmware_buf[FIRMWARE_MAX_SIZE];uint32_tfirmware_offset=0;voidota_check_and_download(void){// 1. HTTP请求检查是否有新版本uint8_thttp_cmd[256];snprintf((char*)http_cmd,sizeof(http_cmd),"AT+QHTTPURL=\"http://ota.heicat.com/api/check?device_id=%s\"\r\n",get_device_id());if(at_send_wait(huart2,http_cmd,"OK",3000)!=0)return;// 2. 获取版本号和固件大小at_send(huart2,"AT+QHTTPGET=80\r\n");// 解析返回的JSON,获取version和size// 3. 下载固件uint32_ttotal_size=firmware_info.size;uint32_tchunk_size=4096;firmware_offset=0;while(firmware_offset<total_size){uint32_tremain=total_size-firmware_offset;uint32_tread_size=(remain>chunk_size)?chunk_size:remain;// 通过HTTP GET Range下载分片snprintf((char*)http_cmd,sizeof(http_cmd),"AT+QHTTPGET=80,\"Range: bytes=%d-%d\"\r\n",firmware_offset,firmware_offset+read_size-1);if(at_send_wait(huart2,http_cmd,"CONNECT",5000)!=0){// 重试HAL_Delay(1000);continue;}// 接收数据uint8_tbuf[4096];at_receive_data(huart2,buf,read_size,10000);memcpy(firmware_buf+firmware_offset,buf,read_size);firmware_offset+=read_size;}// 4. CRC校验uint32_tcrc=crc32(firmware_buf,total_size);if(crc!=firmware_info.crc){return;// CRC不匹配,放弃升级}// 5. 写入Bank2flash_erase_bank2();flash_write_bank2(firmware_buf,total_size);// 6. 更新配置,通知Bootloader切换ota_config_tconfig;config.magic=0xDEADBEEF;config.active_bank=1;// 当前在Bank1,切到Bank2config.firmware_size=total_size;config.firmware_crc=crc;config.upgrade_flag=0xAAAAAAAA;// 待切换标志flash_erase_config();flash_write_config((uint8_t*)&config,sizeof(config));// 7. 重启NVIC_SystemReset();}

服务器端API

简单的PHP版本:

// check.php - 检查更新$device_id=$_GET['device_id'];$stmt=$pdo->prepare("SELECT * FROM devices WHERE device_id = ?");$stmt->execute([$device_id]);$device=$stmt->fetch();$latest=$pdo->query("SELECT * FROM firmware_versions WHERE product_id = ? AND status = 'published' ORDER BY created_at DESC LIMIT 1",[$device['product_id']]);if($latest&&version_compare($latest['version'],$device['firmware_version'])>0){echojson_encode(['has_update'=>true,'version'=>$latest['version'],'size'=>$latest['file_size'],'crc'=>$latest['file_crc'],'url'=>"http://ota.heicat.com/firmware/".$latest['filename']]);}else{echojson_encode(['has_update'=>false]);}

断点续传

4G网络不稳定,下载16MB固件中途断开是常有的事。解决方案:

  1. 分片下载:每次请求4KB,用HTTP Range头
  2. 本地记录进度:写入Flash配置区,重启后从断点继续
  3. 失败重试:单次下载失败重试3次,3次失败则等待5分钟后重试

防变砖设计

OTA最怕的是刷入坏固件导致设备变砖。防砖措施:

  1. CRC校验:下载完成后必须校验,不通过不写入
  2. 双Bank切换:新固件写到备用Bank,不覆盖当前运行固件
  3. Bootloader回退:如果新固件启动失败(看门狗复位),Bootloader自动回退到旧Bank
  4. 硬件恢复接口:预留UART或SWD接口,最坏情况下可物理烧录

看门狗回退逻辑:

// Bootloader中if(config.upgrade_flag==0xAAAAAAAA){// 新固件第一次启动config.upgrade_flag=0x55555555;// 标记"试运行"flash_write_config(&config);// 跳转到新固件// 如果新固件正常,会在运行时把flag清为0xFFFFFFFF// 如果新固件崩溃,看门狗复位回到Bootloader}if(config.upgrade_flag==0x55555555){// 试运行期间看门狗复位了,说明新固件有问题,回退config.active_bank=(config.active_bank==1)?2:1;// 切回旧Bankconfig.upgrade_flag=0xFFFFFFFF;flash_write_config(&config);}

实际项目中的经验

在虎王科技的工业物联网项目中,这套OTA方案已经部署在300+台设备上。几个经验数据:

  • 平均升级时间:220KB固件,4G网络下约90秒
  • 升级成功率:97%一次成功,3%需要重试,重试后100%成功
  • 变砖率:0。双Bank+看门狗回退策略下,从未变砖
  • 最常见失败原因:4G信号弱(工厂角落信号差),不是固件本身的问题

总结

STM32 OTA的核心就是双Bank + Bootloader + CRC校验 + 看门狗回退。这套组合拳打下来,设备在恶劣网络环境下也能安全升级。

4G模组选型上,移远EC25/EC20的AT指令最规范,文档最好。固件分片用HTTP Range是最简单可靠的方案。

OTA是物联网设备运维的基础设施,早做早受益。等到设备量上去了再补OTA,每个设备都要手动升级一遍,那就真的痛不欲生了。


沧州虎王科技,专注物联网软硬件开发、通信设备、嵌入式系统与设备管理平台。