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

资讯详情

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

STM32F10x官方IAP升级程序深度解析与量产改造指南

STM32F10x官方IAP升级程序深度解析与量产改造指南 简介这是意法半导体官方推出的STM32F10x系列IAP在应用编程升级程序包面向嵌入式开发者和固件维护人员解决MCU程序在线更新、串口烧录和远程维护问题。压缩包共350个文件大小约1.63MB涵盖C源码、头文件、汇编启动文件、链接脚本等构建文件以及Keil、IAR工程和说明文档结构完整便于直接导入编译。其中C源码与头文件负责实现串口驱动、Flash读写和Ymodem协议栈汇编文件完成启动初始化链接脚本则对闪存进行分区规划共同构成一套可直接参考的官方实现。程序支持通过UART串口下载新固件、读取现有固件内容并采用Ymodem协议保障文件传输可靠性同时包含中断向量表重定位、错误处理、安全校验等关键机制覆盖IAP实现的核心步骤。目前已有622人学习下载适合需要快速搭建BootLoader、理解固件升级原理或复用官方方案的工程师参考。 这些年陆陆续续帮人处理过不少STM32F10x的升级问题很多人第一版demo都是从ST官方那套IAP示例跑起来的但真正走到量产、走到OTA几乎都要在原版上大改一遍。这篇就把STM32F10x官方IAP升级程序的里里外外拆开讲一遍从官方代码的框架逻辑到App端工程怎么改再到那些“看起来能跑、量产必翻车”的坑一次性说透。适合刚接触IAP的开发者也适合已经在用官方示例但想完善升级方案的人。1. 为什么官方IAP程序到今天仍是F1升级绕不开的起点1.1 先从ISP和IAP的区别说起STM32F10x出厂时内置了一段固化在系统存储器里的Bootloader通过串口/USB等方式把程序烧进Flash这就是ISP。它不需要外部编程器但有一个硬伤这套Bootloader是出厂固化的你没法定制通信协议没法加校验和断点续传更没法做成远程OTA。IAP就不一样了。IAP是在应用程序运行过程中通过用户自己写的Bootloader去擦写Flash、接收新固件、最后跳转到新程序。整个升级流程完全由你控制所以不管是用串口、WiFi、GPRS还是CAN只要能把数据传进去就能实现升级。ST官方针对STM32F10x给出的IAP示例就是基于USART串口的一套完整参考实现配套文档是AN2557和UM0492。官方包的名字一般叫STM32F10x_AN2557_FW里面包含了两个独立工程IAP_Main也就是BootloaderIAP_APP一个跑在用户区的示例App。你不需要从零开始写Flash驱动也不用自己摸索中断向量表该往哪挪官方代码把这些关键环节都示范了一遍。1.2 官方这套程序解决的核心问题很多初学者第一次打开官方代码时会觉得它又长又绕实际上它只干了三件事接收固件数据、写Flash、跳转。接收固件数据这件事官方选的是串口加Intel HEX文件格式。上位机把编译生成的.hex文件像发文本一样逐行发给板子Bootloader解析每一行的地址、长度、数据类型然后把数据写到对应Flash区域。写完以后通过一个跳转函数把PC指针从Bootloader引导到用户App的复位向量。这个方案虽然不是效率最高的但胜在链路完整、每一环节都可验证。串口调试助手能看到HEX文本Flash写入后可以读回比对跳转失败也能用调试器一步步查。对刚接触IAP的人来说官方这套程序就是一个绝佳的“活教材”。我到现在给团队新人讲IAP仍然会先让他们把AN2557的代码从头到尾读一遍。2. 官方IAP包的代码是怎样组织的2.1 从main函数看一次完整升级的生命周期官方IAP_Main工程的main函数执行流程大致是int main(void) { // 配置系统时钟 // 初始化LED、串口等外设 // 打印提示信息 while (1) { // 检测是否需要进入升级模式 // 接收HEX文件解析地址和数据 // 调用FLASH_If_Write写入用户区 // 全部接收完成后跳转到用户程序 } }第一次看这段代码最容易忽略的是它如何决定“要不要升级”。官方示例的做法比较简单上电后直接等待串口数据如果在规定时间内没收到合法帧就执行跳转。实际工程里你可以改成检测按键、检测固件版本号、或者通过上位机主动发送升级指令原理都一样。跳转之前官方代码会做一件特别容易被忽略的事把用到的外设复位到默认状态。比如串口、定时器、中断控制器里的挂起中断都要清理干净。否则App一启动可能就因为Boot遗留的中断标志位直接HardFault。这个细节后面我会专门展开讲。2.2 Flash驱动部分擦除、写入和页大小陷阱官方代码里Flash操作的核心文件是FLASH_If.c它封装了解锁、擦除、编程、校验几个函数。F1的Flash有几个硬性规则官方代码遵守了但你自己改的时候很容易破坏。第一写Flash前必须先擦除。F1的Flash不允许原地改写要先把整页擦成0xFF然后再写入。官方代码里FLASH_If_Write会先根据接收到的地址范围计算要擦除的页数再逐页擦除。第二F1的Flash最小编程单位是半字也就是16位。写的时候必须调用FLASH_ProgramHalfWord不能像F4那样按字节甚至按字编程。第三页大小因型号而异。小容量和中容量的F1每页是1KB大容量的F10x每页是2KB。这个参数直接决定了擦除循环的边界很多人把F103C8T6的工程移植到F103ZET6时忘了改PAGE_SIZE结果擦除范围算错数据就写错位置了。/* 典型擦除循环 */ for (index 0; index NbrOfPage; index) { FLASH_ErasePage(Address (PAGE_SIZE * index)); }2.3 串口处理模块为什么官方选HEX而不是BIN官方串口接收逻辑放在common.c里用一个环形缓冲区接收串口数据然后逐行解析HEX文件。Intel HEX每一行都包含起始地址、数据长度、数据内容和校验和Bootloader拿到这些信息以后就能确定这一行数据应该写到Flash的哪个偏移。为什么不用更简单的BIN文件核心原因是BIN文件没有携带地址信息。你用BIN升级时必须提前约定“固件一律烧到0x08008000”一旦App起始地址调整上位机和Bootloader都得同步改。而HEX文件把加载地址写在数据里Bootloader只要照地址写就行灵活性高很多。但HEX也有问题文本传输效率低一行可能有几十个字符真正有用的数据只有16字节串口波特率不高的时候升级一个大固件会非常慢而且用串口助手手动发HEX时一旦开了自动换行、回车延迟之类的功能解析很容易出错。所以官方代码适合学习不适合直接做量产工具。3. 让官方App工程跑起来链接地址与向量表偏移必须一起改3.1 Keil工程里改IROM1的起点和大小官方代码里的IAP_APP示例工程已经帮你把App的链接地址改好了。实际做自己的项目时要改的是两项IROM1的起始地址和容量。假设Bootloader占32KBMCU是STM32F103C8T6Flash共64KB那么App的IROM1起始地址就是0x08008000大小是0x8000也就是32KB。打开Keil的Options for Target在Target页里把IROM1地址改掉区域起始地址大小Bootloader区0x080000000x8000App区0x080080000x8000用户参数区可选0x08010000剩余Flash改完地址后一个常见的疑问是中断向量表用不用改答案是要改但很多人改错了位置。3.2 SCB-VTORF1上没有内存重映射寄存器F4系列可以用SYSCFG_MEMRMP把向量表重映射到SRAMF1没有这个寄存器它靠的是SCB-VTOR。有些资料说F1不需要设置VTOR因为复位后向量表默认在0x08000000App的向量表如果放在0x08008000中断来了CPU会去0x08000000找向量结果找到的是Bootloader的向量表App的中断自然全乱套。正确做法是在main函数最开头、任何外设初始化之前设置SCB-VTOR 0x08008000;也可以用官方系统文件里的宏在system_stm32f10x.c中把VECT_TAB_OFFSET改成0x8000这样SystemInit会自动设置。但我个人更推荐在main第一行显式调用因为SystemInit的执行时机和应用启动逻辑耦合在一起不如自己控制来得直观。3.3 生成Bin文件和HEX文件的差别Keil默认生成的是.axf和.hex其中hex自带地址信息。如果你沿用官方方案直接发hex就行。但如果你打算按我后面说的自定义帧协议来做升级通常更推荐转成bin文件因为bin体积更小、解析更简单。Keil里加一条fromelf命令就能生成binfromelf --bin --output./Objects/App.bin ./Objects/App.axf注意生成bin以后Bootloader必须知道这个bin要烧到哪个地址所以bin文件一般配合固定App起始地址使用。HEX和bin本身没有优劣取决于你的升级协议设计。4. 官方代码直接量产必翻车五个高频坑的诊断过程4.1 跳转瞬间HardFault先查栈顶指针再看复位向量很多人第一次把官方IAP_Main改成自己的工程后遇到最诡异的现象是程序下载进去Boot能跑App单独下载也能跑但从Boot跳转过去就死机。先排查一个最简单的点跳转地址处的栈顶指针也就是App起始地址的4个字节是不是合法的RAM地址。F103的RAM在0x20000000附近如果读出来是0xFFFFFFFF说明App根本没烧进去如果读出0x0800xxxx这种值说明地址错了。再看复位向量也就是起始地址加4的位置必须落在App的Flash区域。用调试器在跳转前读这两个值基本能定位九成问题。还有一个隐蔽问题跳转前没有关闭全局中断Boot里的串口中断或者SysTick在跳转瞬间打断App还没来得及初始化就进了中断也会死机。官方代码跳转前会调用__disable_irq这个不要省。4.2 App正常跑但一进中断就死机向量表没跟上这类问题比直接死机更隐蔽。App上电后主循环能跑LED能闪可是一按按键、一收串口数据就死机十有八九是向量表偏移没设置成功。有一个很经典的原因编译器把SCB-VTOR赋值语句优化掉了或者你在外设中断使能之后才设置VTOR导致中断配置已经按旧向量表建立。解决方法是把VTOR设置放到main的第一行并且用volatile修饰相关寄存器访问。另外如果Boot和App都用同一个串口中断跳转前记得把串口完全DeInit否则App初始化串口时中断状态可能是乱的。4.3 写Flash写到一半卡死锁、页地址和对齐问题在Boot里写Flash最常碰到的就是FLASH_ProgramHalfWord执行后Flash状态寄存器一直不是EOP程序卡死在等待循环里。这时候先看有没有调FLASH_UnlockF1的Flash默认是锁定的必须解锁才能擦和写。再看是否动了写保护选项字节里如果开了WRP对应区域是写不进去的。还要注意地址对齐半字写入要求地址是偶数如果你从HEX解析出来的地址是奇数或者数据长度不是偶数写入就会出问题。官方FLASH_If_Write已经处理了偶数对齐但如果你改成了接收BIN就很容易忽略这个边界因为bin数据是按字节流的长度可能任意。处理办法是补一个缓冲把末尾单字节数据拼成半字再写。4.4 串口传输HEX总丢行超时机制和缓冲区设计用串口助手直接发HEX时如果固件比较大经常会出现“写到一半就失败”的情况。我排查过几个项目发现问题基本出在官方示例的串口接收方式上。官方示例用的是中断接收加环形缓冲区本身没毛病但缓冲区可能只有几十字节而HEX里长行可能有几十个字符。如果上位机一次性吐出一大段数据缓冲区就溢出丢字节了。正确做法是加大缓冲区同时做好“行超时”判断收到一行数据结束后启动一个定时器超时还没收到下一行就认为传输结束。如果你要自己写上位机建议一帧一帧发每帧之间留几十毫秒间隔可以显著降低丢包率。实测下来115200波特率下每次发256字节数据、间隔20ms最稳定。4.5 外设状态残留从Boot跳App前的“清理工作”跳转前的清理直接决定App能否稳定运行。官方代码在跳转前会把SysTick、串口等都恢复默认但实际工程里Boot可能还开了定时器、DMA、外部中断这些都必须在跳转前关掉。我习惯在跳转函数里做这几件事关闭所有外设时钟对应的中断、把SysTick清零并关闭、清空NVIC里所有挂起中断、关闭全局中断最后再设置VTOR和MSP。顺序很重要先关外设再清中断最后切栈指针跳转少一步都可能在某个版本上出问题。__disable_irq(); SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; for (i 0; i 8; i) { NVIC-ICER[i] 0xFFFFFFFF; NVIC-ICPR[i] 0xFFFFFFFF; } SCB-VTOR APP_ADDR; __set_MSP(*(volatile uint32_t *)APP_ADDR); ((pFunction)(*(volatile uint32_t *)(APP_ADDR 4)))();5. 从官方示例到量产工具我建议的IAP工程化改造清单5.1 通信协议用固定帧头加CRC替代直接传HEX官方直接传HEX文本的方式调试方便但用于量产设备并不合适。我一般会改成自定义二进制帧协议。一帧数据大致这样设计帧头2字节固定值比如0xAA55、命令字1字节、数据长度2字节、地址4字节、数据N字节、CRC32校验4字节、帧尾1字节。Boot收满一帧后先校验CRC通过再写Flash不通过就回NACK上位机重发。这个协议的优点是每帧都有地址理论上可以实现任意位置的增量写入。如果你只需要整包升级可以简化成“起始地址只传一次”后续帧只带偏移。5.2 版本管理与回滚降级量产升级最怕的不是升级失败而是升级成功后设备变砖。所以正规产品都会做版本管理。一个简单做法是在Flash末尾留一个参数区存放当前App版本号、升级时间、CRC值。Boot启动时检查App区的CRC如果CRC不正确或者版本号为0就认定App无效进入升级等待如果App有效直接跳转。这样即使升级中途断电下次上电Boot也能识别出App不完整不会跳到一个坏的App里。回滚机制一般依赖双备份App区。A区运行当前版本B区存放上一个稳定版本新固件先写入B区校验成功后切换启动地址。F1的Flash空间通常不大很多产品做不了双备份那就至少保证Boot不轻易跳转停留在升级模式等待重发。5.3 多分区与断点续传如果App比较大串口一次传不完就需要考虑断点续传。实现方案是在参数区里记录“当前正在下载的分区、已经写到的Flash地址、数据累加CRC”。Boot每次上电先看参数区有没有未完成的升级任务有就从上一次记录的地址继续接收。F103C8T6这类小容量芯片Flash本来就只有64KBApp可能占了50KB剩余空间很紧张。这时可以把参数区放在Flash末尾的最后几页用双页交替保存防止写参数时掉电把参数区写坏。虽然F1的Flash寿命标称1万次擦写但实际产品升级频率不高足够用。6. 我实测下来比较好用的一套IAP参数经验6.1 分区大小和起始地址怎么定分区规划建议从一开始就按Boot独立升级来设计。Boot本身通常只有8到32KB用不到太多空间但预留空间要比实际需要更大因为后期Boot也可能要升级。拿STM32F103C8T6举例我会这样分Boot区0x08000000大小0x4000也就是16KBApp区从0x08004000开始大小0x4000参数区放在最后几页0x0800F800起。这样做的好处是Boot和App各占一半以后App大了想扩容也能调整。如果用的芯片Flash更大比如512KB的ZET6Boot可以给32KB甚至64KBApp区相对宽裕这时VECT_TAB_OFFSET的值要跟着改。6.2 波特率、超时和重发的工程取舍官方示例默认波特率经常是9600或者115200实际工程里我习惯用115200再往上用460800确实更快但很多用户的串口线不稳定高速率下误码率明显上升。量产品控优先考虑稳定115200是个折中点。超时设置上Boot等待升级命令的超时建议设为2到5秒。太短了用户还没打开上位机就跳走了太长了生产线上测试效率低。数据传输过程中帧间超时给500ms足够我的经验是只要上位机发送节奏稳定500ms基本不会误判。重发机制要和CRC配合。上位机收到NACK后直接重发当前帧不要从第一帧重来。这样可以保证即使偶尔丢一帧升级也能顺利完成。6.3 打包与量产工具链调试阶段用串口助手发HEX很方便量产阶段一定要有专门的上位机工具。工具至少支持选择固件文件、设置目标板串口、显示升级进度和结果、记录日志。我常用的做法是写一个简单的Python脚本用pyserial实现Ymodem或者自定义帧协议打包成exe后发给产线。产线工人只需要点“开始升级”工具自动完成打开串口、发送固件、校验、重启设备整个流程。检验是否升级成功不能只看发送完必须等设备返回版本号确认版本一致才算通过。最后说一个我自己踩过的坑升级协议里一定要带上“硬件型号”或者“Boot版本号”字段。同一条产线上可能有多款硬件固件发错的话轻则重刷重则设备直接变砖。有了型号校验上位机发错文件时Boot直接拒绝接收能省下大量返工时间。本文还有配套的精品资源点击获取
返回列表