十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32 Bootloader自升级:IAP方案完整设计

STM32 Bootloader自升级:IAP方案完整设计 STM32 Bootloader 自升级IAP 方案完整设计先说结论STM32 IAP 方案适合有串口/USB/CAN 通信通道的产品。Bootloader 约 8-16KB占 Flash 前 2-4 页App 从第 5 页开始。升级流程 10 秒内完成。产品出厂后固件需要更新但 MCU 没有 OTA 云端推送。IAPIn-Application Programming是最经典的方案MCU 内部跑两段代码——Bootloader 和 App。Bootloader负责接收新固件写入 FlashApp负责业务逻辑升级时重启进 Bootloader写完再跳到 App。一、Flash 分区设计以 STM32F407VE512KB Flash为例0x08000000 - 0x08003FFF Bootloader 16KB (4页×4KB) 0x08004000 - 0x08007FFF 参数区 16KB (NVS/标志位) 0x08008000 - 0x0807FFFF App固件 480KB (业务代码)分区表#defineBOOTLOADER_ADDR0x08000000#defineBOOTLOADER_SIZE0x4000// 16KB#definePARAM_ADDR0x08004000#definePARAM_SIZE0x4000// 16KB#defineAPP_ADDR0x08008000#defineAPP_SIZE0x78000// 480KB// 升级标志#defineFLAG_ADDR(PARAM_ADDR0x00)#defineFLAG_UPGRADE0xDEADBEEF// 需要升级#defineFLAG_NORMAL0x12345678// 正常启动#defineFLAG_RECOVERY0xCAFEBABE// 恢复模式// 固件信息#defineFW_INFO_ADDR(PARAM_ADDR0x10)#defineFW_SIZE_ADDR(PARAM_ADDR0x14)#defineFW_CRC_ADDR(PARAM_ADDR0x18)二、Bootloader 主流程#includestm32f4xx_hal.h#includeflash.htypedefvoid(*pFunction)(void);intmain(void){HAL_Init();SystemClock_Config();// 初始化通信接口UARTUART_Init();// 检查升级标志uint32_tflag*(__IOuint32_t*)FLAG_ADDR;if(flagFLAG_UPGRADE){printf(Upgrade mode\n);firmware_upgrade_mode();}elseif(flagFLAG_RECOVERY){printf(Recovery mode\n);recovery_mode();}else{// 正常启动 → 跳转到Appif(check_app_valid()){jump_to_app();}else{printf(App invalid, entering upgrade mode\n);firmware_upgrade_mode();}}while(1);}// 检查App是否有效intcheck_app_valid(void){// 检查栈顶地址是否合法uint32_tstack_ptr*(__IOuint32_t*)APP_ADDR;if((stack_ptr0xFFFE0000)!0x20000000){return0;// 栈指针不在SRAM范围}// 检查CRCuint32_texpected_crc*(__IOuint32_t*)FW_CRC_ADDR;uint32_tactual_crccalc_crc32((uint8_t*)APP_ADDR,*(__IOuint32_t*)FW_SIZE_ADDR);returnexpected_crcactual_crc;}// 跳转到Appvoidjump_to_app(void){// 关闭所有中断__disable_irq();// 关闭外设HAL_DeInit();// 清除所有中断挂起位for(inti0;i8;i){NVIC-ICER[i]0xFFFFFFFF;NVIC-ICPR[i]0xFFFFFFFF;}// 设置向量表SCB-VTORAPP_ADDR;// 获取App的栈指针和复位地址uint32_tapp_sp*(__IOuint32_t*)APP_ADDR;uint32_tapp_pc*(__IOuint32_t*)(APP_ADDR4);// 设置MSP__set_MSP(app_sp);// 跳转((pFunction)app_pc)();}三、固件升级流程Bootloader 接收固件数据并写入 Flash#definePACKET_SIZE1024// 每包1KB#defineMAX_RETRY3// 升级协议帧格式// [0x55] [0xAA] [cmd] [data_len] [data...] [crc_h] [crc_l]typedefenum{CMD_HANDSHAKE0x01,CMD_ERASE0x02,CMD_WRITE0x03,CMD_READ0x04,CMD_VERIFY0x05,CMD_FINISH0x06,CMD_JUMP0x07,CMD_NACK0xFF,}cmd_t;voidfirmware_upgrade_mode(void){printf(Waiting for firmware...\n);uint32_ttotal_size0;uint32_twrite_offset0;while(1){uint8_trx_buf[PACKET_SIZE8];intlenuart_receive(rx_buf,sizeof(rx_buf),5000);if(len0){// 超时send_nack();continue;}// 校验帧头if(rx_buf[0]!0x55||rx_buf[1]!0xAA){send_nack();continue;}// CRC校验uint16_tcrccrc16(rx_buf,len-2);uint16_trx_crc(rx_buf[len-2]8)|rx_buf[len-1];if(crc!rx_crc){send_nack();continue;}uint8_tcmdrx_buf[2];uint16_tdata_len(rx_buf[3]8)|rx_buf[4];uint8_t*datarx_buf[5];switch(cmd){caseCMD_HANDSHAKE:// 握手返回Bootloader版本和Flash大小send_handshake_response();break;caseCMD_ERASE:// 擦除App区域flash_erase(APP_ADDR,APP_SIZE);write_offset0;send_ack();break;caseCMD_WRITE:{// 写入固件数据if(write_offsetdata_lenAPP_SIZE){send_nack();break;}flash_write(APP_ADDRwrite_offset,data,data_len);write_offsetdata_len;send_ack();break;}caseCMD_VERIFY:{// CRC校验uint32_texpected(data[0]24)|(data[1]16)|(data[2]8)|data[3];uint32_tactualcalc_crc32((uint8_t*)APP_ADDR,write_offset);if(expectedactual){// 保存固件信息flash_write_word(FW_SIZE_ADDR,write_offset);flash_write_word(FW_CRC_ADDR,expected);send_ack();}else{send_nack();}break;}caseCMD_FINISH:// 升级完成清除标志flash_write_word(FLAG_ADDR,FLAG_NORMAL);printf(Upgrade complete, restarting...\n);HAL_Delay(100);NVIC_SystemReset();break;caseCMD_JUMP:// 直接跳转到App不升级flash_write_word(FLAG_ADDR,FLAG_NORMAL);jump_to_app();break;}}}四、App 端触发升级App 运行中收到升级命令后设置标志并重启// App端代码voidtrigger_upgrade(void){// 写入升级标志HAL_FLASH_Unlock();FLASH_EraseInitTypeDef erase{0};erase.TypeEraseFLASH_TYPEERASE_SECTORS;erase.SectorFLASH_SECTOR_4;// 参数区所在扇区erase.NbSectors1;uint32_tpage_error;HAL_FLASHEx_Erase(erase,page_error);HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,FLAG_ADDR,FLAG_UPGRADE);HAL_FLASH_Lock();// 等数据写入完成HAL_Delay(50);// 重启进BootloaderNVIC_SystemReset();}// App启动时检查标志voidapp_init(void){uint32_tflag*(__IOuint32_t*)FLAG_ADDR;if(flagFLAG_UPGRADE||flagFLAG_RECOVERY){// 不应该到这里但万一跳转失败NVIC_SystemReset();}}五、CRC32 校验固件完整性校验用 CRC32#includestm32f4xx_hal_crc.huint32_tcalc_crc32(uint8_t*data,uint32_tlen){// 用硬件CRC外设STM32自带hcrc.InstanceCRC;HAL_CRC_Init(hcrc);// 4字节对齐处理uint32_t*data32(uint32_t*)data;uint32_tlen32len/4;uint32_tcrcHAL_CRC_Calculate(hcrc,data32,len32);// 处理剩余字节if(len%4!0){uint8_t*remainderdatalen32*4;uint32_tlast0;memcpy(last,remainder,len%4);// 手动计算剩余字节crccrc^last;}returncrc;}六、Python 升级工具PC 端升级脚本importserialimportstruct,zlibclassStm32Bootloader:def__init__(self,port):self.serserial.Serial(port,115200,timeout5)defsend_frame(self,cmd,datab):framebytes([0x55,0xAA,cmd,len(data)8,len(data)0xFF])framedata crcself.crc16(frame)framebytes([crc8,crc0xFF])self.ser.write(frame)defwait_ack(self):respself.ser.read(2)returnrespb\x55\xAAdefupgrade(self,firmware_path):withopen(firmware_path,rb)asf:fwf.read()print(fFirmware size:{len(fw)}bytes)# 握手self.send_frame(0x01)assertself.wait_ack(),Handshake failed# 擦除self.send_frame(0x02)assertself.wait_ack(),Erase failed# 分包写入offset0packet_size1024whileoffsetlen(fw):chunkfw[offset:offsetpacket_size]self.send_frame(0x03,chunk)assertself.wait_ack(),fWrite failed at offset{offset}offsetlen(chunk)progressoffset/len(fw)*100print(f\rProgress:{progress:.1f}%,end)# CRC校验crczlib.crc32(fw)0xFFFFFFFFself.send_frame(0x05,struct.pack(I,crc))assertself.wait_ack(),Verify failed# 完成self.send_frame(0x06)print(\nUpgrade complete!)七、实测数据STM32F407VE 168MHzUART 115200bps步骤耗时擦除480KB Flash1.2秒写入480KB固件45秒CRC32校验480KB30ms跳转到App1ms总升级时间~47秒如果 UART 提到 921600bps写入时间降到6 秒。八、踩坑清单中断向量表App 的向量表在 App Flash 地址。Bootloader 跳转前必须设SCB-VTOR APP_ADDR否则 App 的中断跑飞栈指针App 固件的第一字节是初始 MSP 值。跳转前__set_MSP()设置不然 HardFaultFlash 解锁每次写 Flash 前HAL_FLASH_Unlock()写完HAL_FLASH_Lock()。中间有中断可能出问题擦除粒度STM32F4 以 Sector 为单位擦除最小 16KB。Bootloader 和参数区各占一个 Sector不能跨 Sector双 BankSTM32F42x/F43x 有双 Bank Flash可以在运行一个 Bank 时写另一个。普通 STM32F407 单 Bank写 Flash 时 CPU 停顿看门狗升级过程中喂狗。Flash 擦写耗时几秒不看门狗会复位Bootloader 升级 BootloaderBootloader 自己不能升级自己。如果 Bootloader 要升级只能用 ST-Link/JTAG编译链接脚本App 的链接脚本起始地址要改成APP_ADDR。标准 STM32 项目默认从 0x08000000 开始MSP/PSP跳转前设 MSP 不是 PSP。Bootloader 在 Handler 模式用 MSPApp 初始也在 Handler 模式电源升级过程中断电会导致 Flash 写了一半。加标志位 恢复机制Bootloader 检测到不完整固件时进入恢复模式
返回列表