STM32F103 AB双分区OTA升级:基于FreeModbus与标准库的可靠实现
STM32F103做OTA已经不算新鲜事但要把OTA做成A/B双分区、带完整回滚机制、还能通过Modbus RTU串口稳定传输固件这组合确实少见。我这次是把FreeModbus v1.6移植到标准库v3.5环境下配合自定义Bootloader实现了基于RS232串口的A/B分区在线升级整个流程从零开始复现了一遍踩了不少坑也沉淀出一些能直接抄作业的经验。这篇就把完整思路、分区设计、关键代码和排障过程都摊开讲清楚给想在STM32F103上做可靠OTA的小伙伴一条能走通的路。1. 整体设计与思路拆解1.1 AB双分区方案为什么比传统IAP更可靠传统IAP方案基本都是单分区APP区就一块新固件直接覆盖写入写坏了就只能回Bootloader干瞪眼要是Bootloader里没留恢复逻辑设备直接变砖。我最初也图省事用过这种方案直到有一次升级过程中串口被误拔固件写到一半断掉整台设备彻底起不来只能用烧录器重新刷从那以后我就下定决心换A/B双分区。A/B方案的核心逻辑很简单Flash里同时存在两个APP分区一个active当前运行一个update待升级。新固件只写入update分区写入并校验通过后才切换启动标志让Bootloader下次启动时跳转到新分区如果新固件跑不起来或者健康检查失败Bootloader就自动回滚到旧的active分区。整个过程不破坏当前正在运行的程序任何异常情况下设备都能恢复到可用状态这在工业现场是刚需——设备一旦下线损失的可不止是时间。A/B双分区本质上是给固件升级买了一份“后悔药”用Flash容量换可靠性。对STM32F103来说Flash通常有256KB-512KB如果产品固件本身就小比如30KB以内完全有条件做双分区。我在F103ZET6上验证过512KB Flash划分256KB给APP区两个分区各128KBBootloader占8KB剩余空间做参数存储这套布局跑得很稳。1.2 标准库v3.5与FreeModbus v1.6的搭配原因固件工程师圈子里长期有“标准库还是HAL库”之争但在这个项目里我坚定选标准库v3.5原因就一个——资源占用更低、中断响应更可控。HAL库封装层次深尤其在高频中断里调试Modbus超时计时总是隔着一层有时出了诡异问题你都不知道是HAL层触发的还是应用层触发的。标准库直接操作寄存器代码量小逻辑透明特别适合Bootloader和通信协议栈这种对时序敏感的场景。FreeModbus v1.6是经典中的经典虽然官方已停止维护但它的协议处理框架非常清晰移植起来不费劲。它天然支持Modbus RTU和ASCII模式RTU模式下一帧报文必须有3.5字符时间的静默间隔这正好和串口升级的帧校验需求匹配——每帧升级数据用Modbus功能码封装天然具备CRC16校验不用自己再写一套传输协议。我甚至建议你把这两个组合想成“轻量级工业通信协议栈”它既能满足日常设备数据采集读寄存器、写线圈又能承担固件传输通道自定义功能码一套代码解决两个核心需求。1.3 空间布局与存储映射的取舍Flash划分是整个AB OTA方案的地基一步算错后面全乱。我的STM32F103ZET6是512KB Flash起始地址0x08000000扇区大小从主Flash后半段开始才是128KB大扇区前4个扇区是16KB其中第一个是16KB。实际分配见下表区域起始地址大小用途Bootloader0x0800000024KB引导程序、升级入口参数区0x080060008KB分区状态、回滚计数APP-A0x08008000128KB出厂固件/当前运行区APP-B0x08028000128KB待升级固件区备份区0x08048000剩余预留、字库等这里有个取舍要点BOOT区没卡在16KB边界上而是留了24KB因为Bootloader里除了跳转逻辑还要集成Modbus从站固件接收函数这部分代码量比想象中大留足余量能避免后期加功能没地方放的窘境。APP_A和APP_B各128KB对于大多数F103应用场景电机控制、传感器采集、简单HMI完全够用你编译出来的hex/bin文件如果超过128KB那基本上不是F103该干的活了建议直接换芯片。2. 环境准备与工具链选型2.1 标准库v3.5工程搭建的细节坑STM32F103标准库老项目很多但如果你是从零搭有几个文件必须手动改配置不然轻则编译告警重则链接失败stm32f10x.h里面需要根据芯片型号打开对应宏定义比如STM32F10X_HDsystem_stm32f10x.c里的SystemInit()函数会配置时钟F103最高72MHz外部晶振如果是8MHz这段代码能自动倍频启动文件startup_stm32f10x_hd.s必须对应选对HD表示高密度Flash芯片ZET6就是HD系列。工程目录我推荐这样组织USERmain、中断处理、CORE启动文件和内核头文件、SYSTEMdelay、uart、gpio封装、HARDWARE外设驱动比如Flash擦写、按键检测、FREEMODBUS协议栈源码和port层、APP业务逻辑和升级状态机。2.2 FreeModbus v1.6移植的核心动作FreeModbus的移植重点不是把源码加进工程而是把port.c、port.h和串口中断处理对接好。核心就三件事第一串口字节收发中断。RTU模式下每收到一个字节都要喂给eMBPortRxISR每发送完一个字节触发eMBPortTxISR。F103的USART2我用了接收和发送两个中断源接收中断里直接把字节塞给协议栈不经过任何缓冲队列——FreeModbus内部自己有缓冲区别画蛇添足。第二定时器。Modbus RTU的3.5字符时间间隔要用一个定时器来测量。我的做法是开TIM4作为协议栈时钟配置成1ms中断通过vMBPortSetTimer和prvvTIMERExpiredISR回调实现。注意这里的1ms不是固定的要按波特率推算9600波特率下3.5字符时间约4ms115200波特率下约0.3ms我用1ms粒度在115200下实测没问题但如果你追求极限性能可以改成0.1ms粒度的定时器。第三错误处理。vMBPortEventPost里的事件类型有EV_READY、EV_FRAME_RECEIVED、EV_FRAME_SENT、EV_ERROR排查通信问题时要重点看事件循环里拿到的到底是哪一类事件很多莫名奇妙的超时故障其实都是EV_ERROR被吞了。2.3 辅助工具OTA提取器与文件服务器选型热词里出现“ota提取器”和“nginx”在ARM Linux设备上很常见但F103这种纯MCU环境也可以借这个思路PC端生成的新固件bin文件最好用一个脚本工具做预处理——加头、算校验、拆分帧我管这个叫“OTA提取器”的MCU版本。我自己写的是个Python脚本读入app.bin在最前面加16字节自定义头魔数版本号固件长度CRC32校验分区目标然后按每帧256字节切片每帧再套一层帧序号和CRC16最后输出成可下载的二进制升级包。这个预处理动作相当于PC端做了“打包和加密”让MCU端解析逻辑尽量简化。nginx在MCU方案里不是必需品但如果你开发的是带WiFi/以太网的接入设备固件放nginx上、设备通过HTTP下载升级是常见玩法。F103本身不带以太网控制器但通过SPI接口挂ENC28J60可以实现这种情况下Bootloader里就得多集成一个轻量级TCP/IP协议栈比如lwIP复杂度上升一个量级。我这篇的重点是串口升级所以nginx场景只在思路层面参考实际验证用的是串口传输。3. 核心细节解析与实操要点3.1 分区状态设计与回滚机制分区状态是整个AB方案的大脑我设计了一个8字节的状态结构体存放在固定偏移的参数区Flashtypedef struct { uint32_t magic; // 0xA5A5A5A5 uint8_t active_slot; // 0 A区, 1 B区 uint8_t boot_count; // 启动计数 uint8_t max_boot_attempts; // 最大尝试次数建议3 uint8_t reserved01; uint32_t upgrade_status; // 升级进行中/完成/失败 } ota_state_t;这个结构体每次更新就写到参数区Flash的新地址利用Flash擦写寿命和磨损均衡核心逻辑是Bootloader每次启动时boot_count正常APP运行后上报“运行正常”标志将boot_count清零如果启动后超过max_boot_attempts次都没有上报“运行正常”则判定新固件异常触发回滚。回滚的流程我在第4部分详细讲这里先记一个原则A/B分区升级不是“切过去了就完事”而是“确认新固件能活着才切过去”。你可以在APP里放一个心跳标志区APP运行后5秒内写入健康标志Bootloader下次启动时看到这个标志才认为切换成功否则继续用旧分区。3.2 上位机下发AB包的会话流程升级过程不是一股脑把整个bin文件往串口灌而是按“会话”方式分组进行状态机如下空闲状态——设备正常执行业务逻辑Modbus寄存器区里有升级控制寄存器比如地址0x5000上位机写入特定值启动升级会话升级请求——上位机下发固件头16字节包含魔数和校验和数据设备校验通过后回复“OK”进入“接收数据”状态数据帧传输——固件按512字节一帧切割每帧包含帧序号2字节 数据长度2字节 数据N字节 CRC16校验。设备写一帧回一帧确认有确认才发下一帧这样即便中途串口异常也能及时重传结束确认——最后一帧传输完成后设备对整个缓冲区再做一次全量CRC32校验和固件头里记录的比对一致才把upgrade_status置为“升级完成”重启切换——设备软复位Bootloader检查upgrade_status后更新启动分区指针跳转到新固件。这套会话设计借鉴了Modbus RTU的主从确认思想跟Modbus协议天然亲和。在FreeModbus里你可以用自定义功能码0x66、0x67、0x68分别对应“升级请求”、“数据帧写入”、“升级结束确认”不需要额外定义协议结构直接用寄存器读写也能完成但自定义功能码接收大块数据效率更高。3.3 Flash擦写操作的关键禁区STM32F103的Flash擦写有几个绕不开的坑第一是“擦除期间不能跑Flash里的代码”。F103的Flash控制器在执行擦除操作时总线会阻塞如果你的擦除代码和中断向量表都在同一个Flash分区里一旦擦除开始中断响应会卡住串口数据直接丢。所以我的升级数据接收区放在RAM里攒够一页1KB或2KB再一次性写入Flash避免频繁的小块擦写。第二是“写Flash前必须先擦除且按扇区擦除”。F103的最小擦除单位是扇区16KB/64KB/128KB不是按字节擦的。这意味着你没法原地改一个字节。升级过程中我选按128字节写Flash编程一次最多可以写2字节但批量写更高效写入前必须先确保目标扇区已擦除。第三是“中断保护”。擦写Flash前建议先__disable_irq()擦写完再__enable_irq()。原因很简单擦写过程中CPU被Flash控制器占用中断一旦触发就可能会死等而且如果中断服务函数恰好调用了Flash操作会导致硬件错误HardFault。我的Bootloader代码里封装了一个flash_erase_region函数内部统一做了关中断保护和错误标志检查。3.4 加签验签避免非法固件烧进去热词里多次出现“加签验签”这个在汽车嵌入式OTA里是强制要求在工业设备上也越来越被重视。F103的资源做非对称加密RSA/ECC有点吃力但做对称加密AES-128-CBC或者是HMAC-SHA256的校验完全可行。我在上位机打包时用固定密钥对固件算了一个HMAC-SHA256摘要放到固件头里MCU接收完固件后用同样的密钥对收到的数据算摘要比对一致才认为固件合法。虽然对称加密的密钥在固件里逆向后会暴露但对防误升级和防篡改已经够用了。如果你有更高的安全需求可以把密钥放在MCU的Option Bytes区并开启Flash读保护RDP Level 1这样即使固件被读出来也拿不到密钥。注意加签验签不是“升级流程的必需项”但如果你做的是医疗、电力、安防类设备没有验签环节基本没法过认证。验签算法务必放Bootloader里不要放APP里否则APP被替换了验签就形同虚设。3.5 编译环境与map文件定位做OTA必须掌握从app.axf里提取bin文件的正确姿势以及阅读map文件定位问题的能力。我在工程里用的编译工具链是Keil MDK生成bin的命令是fromelf --bin --output ./output/app.bin ./build/app.axf在Keil的User选项卡里After Build/Rebuild处加上这行命令每次编译都会自动生成bin文件。map文件排查问题的思路升级后程序跑飞先看map文件里__initial_sp和Reset_Handler的地址是否指向了正确的加载区再做启动跳转前对比编译器的分散加载文件sct文件和实际烧写地址是否一致。F103的向量表偏移通过SCB-VTOR设置APP工程里的SystemInit会尝试重新映射向量表Bootloader跳转前还需要手动关闭所有外设中断。4. 实操过程与核心环节实现4.1 从零搭建Bootloader工程Bootloader部分我分四步搭建每一步都有对应的验证方法**第一步建立最小跳转框架。**工程只包含系统时钟初始化、LED闪烁、串口打印然后实现一个跳转函数typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t jump_addr *(volatile uint32_t *)(app_addr 4); // SP pFunction jump (pFunction)*(volatile uint32_t *)(app_addr); // Reset_Handler // 跳转前关闭所有中断复位外设状态 __disable_irq(); for (int i 0; i 8; i) { NVIC-ICER[i] 0xFFFFFFFF; } __set_MSP(jump_addr); // 设置主栈指针 jump(); // 跳到APP入口 }注意__disable_irq()之后进APP时要重新使能中断所以APP的SystemInit通常都是在main函数开头才调用这样能保证中断向量表重定向完成后才开中断。**第二步加入串口接收升级命令。**Bootloader里串口只做一件事——接收升级命令帧。我用的是USART2波特率1152008N1。中断配置好后检测到一帧合法升级请求魔数0xA5A5A5A5 目标分区标识立刻置一个标志位主循环进入升级模式。不加超时判断的话设备会卡死在等待升级状态所以升级模式下要加10秒无数据自动跳转到原APP的保护逻辑。**第三步实现Flash擦写函数。**封装擦除、编程、读取三个基本函数。擦除时注意F103的Flash扇区编号不是连续的主存储区起始地址0x08000000前四个扇区大小16KB之后是64KB扇区最后两个是128KB扇区。分区起始地址必须落在扇区边界上否则会影响相邻分区数据。**第四步集成FreeModbus作为升级通道。**Bootloader里只需要保留Modbus RTU从站模式地址固定为1或通过拨码开关设置注册一个“升级数据写入”功能码。整个Bootloader的思路就一句话“能启动APP启动APP。收到升级包写升级包。”4.2 APP工程的修改与中断向量重映射APP工程不只是在原工程里加几行代码至少需要改以下三处第一分散加载文件.sct。默认情况下Keil给APP分配的起始地址是0x08000000我们必须改成对应分区地址比如APP-A从0x08008000开始LR_IROM1 0x08008000 0x00020000 { ER_IROM1 0x08008000 0x00020000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00010000 { .ANY (RW ZI) } }如果你用的是MDK图形化界面在Options for Target - Target页签里把IROM1的Start和Size改成对应分区即可但要记得把STM32F10X_HD宏里的Flash大小改成256KB如果总共是512KB但APP区只分到128KB其实不用改宏宏对应的是芯片实际Flash。第二向量表重映射。在main函数一开始执行SCB-VTOR FLASH_BASE | 0x8000; // APP-A分区偏移如果不做这步任何中断包括SysTick、串口中断触发时都会跳到Bootloader的向量表去执行跑飞是必然的。F103支持VTOR寄存器偏移但前提是系统时钟已经在SystemInit里初始化完成。第三固件自身版本号和健康上报。APP里定义一个只读的固件信息结构体烧录地址固定放在分区首地址固定偏移处。APP运行后延时5秒给Bootloader留出判断时间再往参数区写入“运行正常”标志。这个上报动作可以复用一个普通Modbus寄存器地址也可以直接往Flash写。4.3 FreeModbus移植到标准库的关键代码展示FreeModbus的port.c里需要实现几个底层接口我抽核心片段// 串口发送完成回调 void vMBPortTxISR(void) { // 清除发送完成标志触发下一个字节发送 USART_ClearITPendingBit(USART2, USART_IT_TC); xMBPortEventPost(EV_FRAME_SENT); } // 串口接收中断 void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) ! RESET) { uint8_t byte USART_ReceiveData(USART2); eMBPortRxISR(byte); } if (USART_GetITStatus(USART2, USART_IT_TC) ! RESET) { vMBPortTxISR(); } } // 定时器超时中断判断3.5字符时间 void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); prvvTIMERExpiredISR(); } }FreeModbus协议栈初始化时要调用eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_EVEN)其中0x01是从机地址0是端口串口编号从0开始波特率115200校验方式偶校验。Modbus RTU默认用偶校验上位机配置时不能选错否则一个字节都通不了。4.4 上位机发送AB包与分帧传输实测有了上位机工具传输才有意义。我用Python写了一个简单的上位机脚本也推荐你用开源串口调试助手配合“OTA提取器”自动分包核心逻辑是先发送“升级请求帧”然后循环发送数据帧最后发送“结束帧”。实测数据是100KB固件在115200波特率下传输一次升级耗时约10秒左右。重点说下分帧传输的容错机制。每帧大小我选256字节包含2字节帧序号上位机发完一帧后等待设备回复一个字节的ACK/NAK等待时间设500ms超时重发。设备端收到帧后先查帧序号是否连续、CRC16是否正确不合法就直接丢弃等重发。这里有个细节设备端对“重复帧”的处理一定要健壮。比如上位机超时重发了但设备其实已经写进Flash了这时设备如果再次写入就会重复所以我用帧序号当前写入偏移做去重当收到偏移等于已写入位置时直接返回ACK不重复写Flash。4.5 回滚机制模拟从“升级失败”到“自动复原”回滚机制不是纸上谈兵实测验证一定要做。我模拟的场景是把新固件里人为制造一个死循环然后升级观察设备是否能自动回到旧固件。Bootloader判断回滚的完整代码逻辑void check_ota_state(void) { ota_state_t state read_ota_state(); if (state.upgrade_status UPGRADE_COMPLETED) { // 有一种情况升级完成但还未来得及上报运行正常 // 这里给一次尝试机会 if (state.boot_count state.max_boot_attempts) { state.boot_count; write_ota_state(state); jump_to_app(update_slot); } else { // 回滚到旧分区 state.active_slot old_slot; state.upgrade_status UPGRADE_ROLLBACK; state.boot_count 0; write_ota_state(state); jump_to_app(old_slot); } } else { jump_to_app(active_slot); } }这个状态机有几个细节容易踩坑一是boot_count清零的时机不能放在APP刚启动时必须放在业务逻辑确认无误后我放在定时任务跑通后二是回滚到旧分区后upgrade_status不能立即清空需要保留“发生过回滚”的痕迹方便上位机查询诊断三是回滚动作要记录次数连续多次回滚说明当前固件质量堪忧必须强制进入Bootloader等待人工处理。实测中我用一个LED颜色区分当前分区绿色常亮在A区蓝色常亮在B区回滚时LED红绿交替闪烁一眼就能看出系统处于什么状态。调试时这个可视反馈极大提升了排障效率。5. 常见问题与排查技巧实录5.1 跳转APP后跑飞这是AB_OTA最常遇到的问题。排查路径我固定按三步走第一步看栈指针。跳转前打印*(volatile uint32_t*)(app_addr)和*(volatile uint32_t*)(app_addr 4)确认不是0xFFFFFFFF。如果全是F表示APP区根本没烧进去或者烧录地址不对。第二步看VTOR。APP里没设置SCB-VTOR会导致中断向量表还在BOOT地址一旦串口中断触发就会跑飞。建议在跳转前打一个延时让APP先跑起来几秒判断是“直接跑飞”还是“进中断后跑飞”。第三步看优化级别。如果Code optimization开到了-O3跳转相关代码可能被编译器优化掉尤其是jump()前的__set_MSP建议关优化或设置__attribute__((optimize(O0)))。5.2 FreeModbus串口升级时丢包严重在115200波特率下出现丢包90%是因为Flash擦除期间串口数据没地方放。F103的USART接收寄存器只有1字节深Flash擦除一个128KB扇区需要上百毫秒这期间来的数据全被硬件丢弃。解决办法有两个一是升级数据先全部收进RAM攒够一整块比如4KB再统一擦写Flash二是用DMA接收在RAM里开环形缓冲区。我推荐DMA方式代码量增加不多但可靠性提升巨大。DMA接收时要注意缓冲区溢出判断FreeModbus的eMBPortRxISR调用时机是在DMA半满/全满中断和IDLE中断里IDLE中断用来判断一帧Modbus报文结束这是RTU模式最优雅的实现方式。5.3 升级后回滚失败系统反复重启设备反复重启是最让人崩溃的现象。我的实测经验是这类问题九成出在“健康上报”和“启动计数”的时序上。Bootloader判定“新固件启动失败”的依据是启动计数超限但APP如果延迟上报健康标志Bootloader在下一次重启时就会误判导致反复重启。排查方法是把max_boot_attempts调大从3调到10观察是否还重启把健康上报提前到APP初始化最前面但注意不能放在中断向量重映射之前在参数区加一个调试打印寄存器记录最近一次复位的原因和启动计数值。5.4 固件版本回退与版本校验回滚机制不是简单地“跳回去”它必须配合版本管理。我的做法是Bootloader在升级前把当前运行固件版本号存到参数区升级完成后APP上报新版本号如果Bootloader发现新版本号低于旧版本号或者异常的低则直接忽略这次升级。这能防止“误把老版本固件当新版本刷进去”。上位机打包工具里也要约束新固件版本号必须大于等于设备当前版本号且同一版本号不允许重复升级。因为AB切换是有代价的——每次切换整个分区都要重新擦写、搬运Flash寿命也是要钱的。5.5 常见问题速查表现象可能原因排查方法跳转后白屏/无响应向量表未重映射检查SCB-VTOR设置串口收不到ACKModbus应答超时排查3.5字符时间、波特率校验位升级到一半停止Flash擦除导致接收中断卡死改用DMA接收或环形缓冲新固件运行5秒后重启健康上报没写到Flash检查APP里上报标志写入位置升级成功后无法切回旧版回滚计数未置零检查boot_count清零逻辑升级包校验失败固件头信息解析错误检查魔数、CRC32比对字节序6. 方案扩展与更多应用场景6.1 从串口升级到HTTPOTA的演进思路如果你以后要做带WiFi/以太网的接入设备AB_OTA这套框架可以直接平移。区别主要在传输层从Modbus RTU换成HTTP从串口中断换成lwIP协议栈。分区布局、状态管理、回滚机制都不用重新设计只需把“数据帧接收”抽象成一个统一接口——数据从哪来不重要重要的是数据怎么存、怎么验、怎么切换。nginx作为固件服务器核心配置就是开一个目录存放升级包设备通过HTTP下载时能断点续传最好可以配合Content-Range头实现。MCU端lwIP本身占用RAM较多F103的内部SRAM是64KB跑lwIP mbedTLSTLS加密会非常紧张建议评估带外部SRAM的型号或者直接换F4系列。6.2 FreeModbus在车载、工业场景中的特殊要求车载嵌入式OTA、电力保护设备在线升级等场景对安全要求更高我建议在AB_OTA基础上至少增加三层防护第一层升级包加密。AES-128或AES-256密钥放进单独安全芯片或MCU Option Bytes区域防止固件被提取后直接逆向分析 第二层通信通道安全。如果走网络用TLS加密如果走串口至少增加白名单地址和升级时间窗口限制 第三层升级审计日志。每次升级的结果、发起方信息、失败原因都必须记录在Flash日志区方便后期追责和定位问题。6.3 不依赖Bootloader的AB实现可能性还有一个思路值得尝试不使用独立Bootloader而是在APP内实现“自举升级”。即APP运行状态下将新固件写入另一个分区写入完成后设置启动标志并软复位然后由APP开头的一段代码负责判断启动标志和跳转。这种方式省掉了Bootloader的维护但风险是APP本身崩溃后就无法升级只适合对可靠性要求不高的消费类产品。如果你的芯片Flash足够大比如512KB以上甚至可以做三个分区——增加一个出厂恢复分区。这个分区放一个最小可运行固件支持基础IO操作和串口升级入口一旦两个APP分区都损坏Bootloader还能拉起出厂固件救场。这就是汽车行业常说的“最小启动镜像”概念的简化版。写在最后这套STM32F103 AB_OTA方案我从立项到稳定跑通前后用了近两个星期大部分时间都花在调试“看似代码对但就是不工作”的问题上。回头总结最有价值的经验就几条一是分区规划和状态机设计一定要在写代码前定死后端改动牵一发动全身二是所有通信交互都要有超时和重试机制嵌入式升级99%的故障都是时序问题三是调试手段要可视化LED、串口打印、参数区状态寄存器三管齐下能帮你把定位时间从几小时缩短到几分钟。最后再分享一个小技巧把Bootloader和APP的串口打印信息加上不同前缀比如BOOT:和APP:这样抓串口日志时一眼就能看出当前执行到哪一部分。别看这个小改动不起眼它帮我省下的调试时间相当可观。如果你也在折腾F103或者其他Cortex-M3芯片的OTA方案这套思路可以照着搭遇到具体问题再来交流坑踩过了路就平了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →