☰
串口IAP-Bootloader设计实战:Flash分区、向量表重映射与跳转
2026/10/13 1:04:14 网站建设 项目流程

1. 串口IAP-Bootloader的核心设计思路拆解

1.1 为什么要在项目里引入Bootloader

做过嵌入式量产的朋友大概都有过这种体验:板子已经装进外壳、打上螺丝、甚至灌了胶,突然发现固件里有个小bug需要修复。如果没有Bootloader,唯一的办法就是把设备拆回来,用仿真器重新烧录,产线停工、物流来回、人工成本全都上来了。串口IAP-Bootloader要解决的就是这个问题——让设备自己具备"自我更新"的能力,通过一根串口线就能把新固件灌进去。

IAP全称是In-Application Programming,直译就是"在应用内编程"。它和ISP(In-System Programming)最大的区别在于:ISP需要芯片厂商预置的BootROM配合,通常要动BOOT引脚、用专用工具;而IAP完全由用户自己的代码控制,想怎么跳转就怎么跳转,想从哪个接口接收数据就从哪个接口接收。串口IAP则是把通信介质限定在UART上,因为串口几乎是每块板子都有的调试口,成本最低、通用性最强。

这个方案适合谁?我认为有三类人最需要:一是做工业控制、仪器仪表这类设备部署后难以物理接触的开发者;二是做消费电子、需要OTA升级但又不方便走无线的团队;三是刚接触STM32或类似MCU、想搞明白启动流程和Flash操作的学习者。哪怕你最后用的是CAN IAP或者无线升级,串口IAP这套底层逻辑都是相通的,把它吃透,其他介质只是换个收发驱动而已。

1.2 整体架构:Bootloader与App如何分工

串口IAP-Bootloader的本质是把一块MCU的Flash人为切成两半:前半部分放Bootloader,后半部分放应用程序(App)。上电后先跑Bootloader,它做几件事——初始化时钟和串口、检查是否有升级请求、如果有就接收新固件写入App区、校验通过后跳转到App;如果没有升级请求,直接跳转到App执行。

这里有个关键决策点:升级请求怎么判断?常见做法有三种。第一种是上电时检测某个GPIO按键,按住就进Bootloader等待升级,松开就跳App,适合有物理按键的设备。第二种是检测串口在固定时间内是否收到特定握手字符,收到就进升级模式,否则超时跳App,适合没有按键但串口常连的场景。第三种是在Flash里划一个标志区,App收到升级命令后写标志、软复位,Bootloader读标志决定是否升级,适合远程升级。我在实际项目里通常把第二种和第三种结合:上电先看标志区,有标志直接进升级;没标志再等300ms看串口有没有握手包,都没有就跳App。这样既支持远程触发,也支持本地强制升级。

分区规划是另一个必须提前想清楚的事。以常见的STM32F103C8T6为例,64KB Flash,我一般这样分:

区域起始地址大小用途
Bootloader0x0800000016KB引导程序
标志区0x080040002KB升级标志、固件信息
App区0x0800480044KB应用程序
备份区(可选)0x0800F8002KB参数备份

Bootloader给16KB是因为要包含串口驱动、Flash擦写、校验算法,实际编译出来通常8-12KB,留点余量。标志区单独划出来而不是塞在Bootloader末尾,是为了让App也能读写它——App收到升级命令后往标志区写个魔数,复位后Bootloader一读就知道该干活了。App区起始地址必须和Bootloader里定义的跳转地址、以及App工程里的链接地址三者完全一致,这是新手最容易翻车的地方,后面会细说。

1.3 方案选型:为什么是串口而不是其他介质

有人会问,现在无线这么方便,为什么还折腾串口?我的经验是:串口是保底方案,无线是锦上添花。无线升级受信号、干扰、功耗影响,一旦升级过程中断连,设备可能变砖;而串口线是物理连接,稳定性高得多。而且很多工业现场根本不允许无线信号存在,串口是唯一选择。

在串口协议设计上,我放弃了XMODEM、YMODEM这些现成协议,自己定了一套极简的帧格式。原因很简单:YMODEM虽然成熟,但它的128字节包、CRC校验、ACK/NAK握手对MCU来说代码量不小,而且调试时抓包不直观。我自己的协议长这样:

帧头(2B) + 命令(1B) + 长度(2B) + 数据(NB) + CRC16(2B) 帧头固定 0xAA 0x55 命令:0x01握手 0x02擦除 0x03写数据 0x04校验 0x05跳转

这套协议的好处是解析逻辑简单,一个状态机就能搞定,而且用串口助手手动发几个字节就能测试,不用专门写上位机。上位机我用Python写了个两百行的脚本,配合pyserial库,读bin文件、分包、算CRC、发命令,半小时就能跑通。

2. 核心细节解析与实操要点

2.1 Flash擦写:STM32的页与扇区陷阱

Flash操作是Bootloader里最容易出问题的地方,因为不同型号MCU的Flash结构差异很大。STM32F1系列是**页(Page)为单位,每页1KB或2KB;STM32F4系列是扇区(Sector)**为单位,扇区大小从16KB到128KB不等。这个差异直接决定了擦除策略。

F1的页小,擦除灵活,App区44KB就是22页,一页一页擦就行。F4的扇区大,比如Sector 5是128KB,你只想改1KB数据也得擦掉整个128KB,所以F4上做IAP通常要把App区放在大扇区里,或者用双区交替的方式。我在F4项目里就吃过亏:一开始按F1的思路规划,结果App区跨了两个扇区,擦除时把Bootloader所在扇区也擦了,板子直接变砖,只能拆下来用仿真器救。

擦除前必须做的一件事是解锁Flash。STM32的Flash默认是写保护的,要先往FLASH_KEYR寄存器写两个特定值(0x45670123和0xCDEF89AB)才能操作。写完数据后要上锁,防止程序跑飞误改Flash。这个解锁-操作-上锁的流程每次擦写都要走一遍,不能图省事只解锁一次。

写数据时要注意半字对齐。STM32的Flash编程是按半字(16位)进行的,如果你要写的数据长度是奇数,最后一个字节要补0xFF凑成半字。我见过有人直接按字节写,结果最后一个字节丢失,App跑起来莫名其妙死机,查了半天才发现是这里的问题。

注意:擦除操作会让Flash变成0xFF,而写入只能把1变0,不能把0变1。所以每次写之前必须先擦除,不能直接覆盖写。这是Flash的物理特性决定的,不是软件限制。

2.2 中断向量表重映射:跳转后跑飞的元凶

App能编译、能烧录、Bootloader也能跳过去,但一跑就进HardFault——这是串口IAP新手遇到最多的现象,九成原因是中断向量表没重映射。

Cortex-M内核的中断向量表默认从0x00000000开始,而STM32把Flash的0x08000000映射到了0x00000000。Bootloader在0x08000000,它的向量表就在那儿。当你跳转到App(比如0x08004800)后,如果App里发生了中断,内核还是去0x00000000找中断服务函数,结果找到的是Bootloader的向量表,跳到了错误的地址,自然就崩了。

解决办法是在App的main函数开头、初始化任何外设之前,调用SCB->VTOR = 0x08004800;把向量表偏移寄存器指向App自己的向量表。这一行代码必须放在最前面,晚一步都可能出问题。我习惯在App的启动文件里就改好,或者用NVIC_SetVectorTable函数,效果一样。

还有一个隐藏坑:Bootloader跳转前要关中断。如果Bootloader开着串口中断、滴答定时器中断就跳过去,App还没来得及重映射向量表,中断就来了,照样跑飞。所以跳转前必须__disable_irq(),跳转后再由App自己开中断。

2.3 校验算法:CRC16为什么比累加和靠谱

固件传输过程中可能因为串口干扰、上位机bug导致数据出错,所以必须校验。最简单的校验是累加和,把所有字节加起来取低8位或16位。但累加和有个致命弱点:如果两个字节同时出错,一个加多了、一个加少了,累加和可能不变,错误就漏过去了。

CRC16(循环冗余校验)对这种情况敏感得多。它的原理是把数据当成一个多项式,除以一个固定的生成多项式,余数就是CRC值。任何一位出错都会导致余数变化,检错能力远超累加和。我用的生成多项式是0xA001(对应CRC-16/MODBUS),查表法实现,256字节的表放在Flash里,计算速度很快。

实际使用时,上位机算好整个bin文件的CRC,通过0x04命令发给Bootloader;Bootloader把刚写入App区的数据读出来再算一遍CRC,两者比对。一致就跳转,不一致就报错、等待重传。这里有个细节:CRC要覆盖整个App区还是只覆盖实际固件长度?我建议只覆盖实际长度,因为App区后面可能有未擦除的旧数据,全算进去会不一致。所以上位机发完数据后要再发一个"固件长度"参数,Bootloader按这个长度算CRC。

3. 实操过程与核心环节实现

3.1 Bootloader工程配置:从零搭建的完整步骤

先建Bootloader工程。用Keil或STM32CubeIDE都行,我以CubeIDE为例。新建工程选好芯片型号,配置时钟树(外部晶振8MHz倍频到72MHz)、配置串口(115200波特率、8N1)、配置一个GPIO输出用于调试指示。生成代码后,第一件事是改链接脚本,把Bootloader的Flash范围限定在0x08000000到0x08004000。

在CubeIDE里改链接脚本是编辑.ld文件,找到FLASH那一行,改成FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K。改完编译,生成的bin文件大小不能超过16KB,超了就说明功能加多了,要精简。

然后写主循环逻辑。伪代码大概是这样:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 检查升级标志 if (check_update_flag() == UPDATE_REQUESTED) { clear_update_flag(); enter_bootloader_mode(); // 等待上位机命令,循环处理 } // 等待握手,超时300ms if (wait_handshake(300) == HANDshake_OK) { enter_bootloader_mode(); } // 跳转到App jump_to_app(APP_ADDRESS); }

enter_bootloader_mode里是一个while循环,不断读串口、解析帧、执行擦除/写入/校验命令,直到收到跳转命令或超时。jump_to_app里做三件事:关中断、设MSP为主栈指针(从App向量表第一个字读)、设PC为复位向量(第二个字),然后执行。

3.2 App工程配置:链接地址与向量表偏移

App工程要改两个地方。第一是链接脚本,Flash起始地址改成0x08004800,长度改成44K。第二是向量表偏移,在system_stm32f1xx.c里找到VECT_TAB_OFFSET,改成0x4800;或者在main开头手动设SCB->VTOR。

改完编译,生成的bin文件就是可以升级的固件。这里有个验证方法:用fromelf或objcopy把elf转成bin,然后用十六进制工具打开,看开头几个字节是不是App的栈顶地址和复位向量。如果是,说明链接地址改对了。

App里还要加一段"接收升级命令"的逻辑。比如串口收到特定字符串"UPDATE",就往标志区写魔数0x5A5A5A5A,然后调用NVIC_SystemReset()软复位。复位后Bootloader读到标志,就进升级模式。这段逻辑要放在App的主循环里,不能放在中断里,因为写Flash耗时较长,中断里做会阻塞其他中断。

3.3 上位机脚本:Python实现固件分包发送

上位机我用Python写,核心是pyserial。流程是:打开串口、发握手包、等回应、发擦除命令、按1KB分包发数据、每包等ACK、发校验命令、等结果、发跳转命令。

关键代码片段:

import serial, struct, time def crc16(data): crc = 0xFFFF for b in data: crc ^= b for _ in range(8): if crc & 1: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc def send_frame(ser, cmd, data=b''): frame = b'\xAA\x55' + bytes([cmd]) + struct.pack('<H', len(data)) + data frame += struct.pack('<H', crc16(frame)) ser.write(frame) # 等ACK,超时重传 ...

分包大小我选1KB,因为F1的一页就是1KB,正好一包对应一页,擦一页写一页,逻辑清晰。如果是F4,扇区大,就按扇区大小分包,或者干脆一次发完让Bootloader自己分。波特率115200下,1KB数据大约90ms传完,44KB固件大概4秒,加上擦除和校验,总共10秒左右,可以接受。想更快就上460800或921600,但要注意线材质量和干扰。

3.4 跳转函数:三行代码背后的门道

跳转函数看起来简单,但每行都有讲究:

void jump_to_app(uint32_t app_addr) { uint32_t sp = *(uint32_t*)app_addr; uint32_t pc = *(uint32_t*)(app_addr + 4); __disable_irq(); HAL_RCC_DeInit(); HAL_DeInit(); SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0; __set_MSP(sp); ((void(*)(void))pc)(); }

sp是App向量表的第一个字,也就是App的栈顶地址;pc是第二个字,也就是App的复位向量。__set_MSP把主栈指针切到App的栈,然后直接调用pc指向的函数,就跳过去了。

跳转前要HAL_RCC_DeInit和HAL_DeInit,把Bootloader初始化的外设全部复位,否则App再初始化时可能冲突。SysTick也要关掉,不然滴答中断会干扰。__disable_irq关总中断,跳过去后App自己会开。

有个细节:__set_MSP之后、调用pc之前,不能有任何函数调用或局部变量操作,因为栈已经切到App的了,Bootloader的栈帧失效了。所以这几行要写成内联或者确保编译器不插入额外指令。我一般用__asm volatile直接写汇编,最保险。

4. 常见问题与排查技巧实录

4.1 跳转后不运行:从现象反推原因

现象一:跳转后没反应,串口无输出。先查App的链接地址和Bootloader里定义的APP_ADDRESS是否一致。我遇到过App链接地址是0x08004800,但Bootloader里宏定义写的是0x08005000,差了2KB,跳过去正好是空白区,全是0xFF,当然不跑。用调试器看0x08004800处的数据,如果是栈顶地址(通常是0x2000xxxx),说明App烧对了;如果是0xFFFFFFFF,说明App没烧进去或者地址错了。

现象二:跳转后跑几秒进HardFault。九成是向量表没重映射。在App的HardFault_Handler里打个断点,看LR寄存器的值,如果指向Bootloader的地址范围,就证实了。解决就是加SCB->VTOR设置。还有一种可能是Bootloader跳转前没关中断,App还没重映射向量表,串口中断就来了,跳到Bootloader的中断服务函数里,而那个函数可能已经被擦除了,直接崩。

现象三:App能跑但串口收发不正常。检查App里串口初始化是否完整。Bootloader可能把串口配置成某种状态,App初始化时如果没重新配置,可能波特率不对。我习惯在App的串口初始化里把所有寄存器都重设一遍,不依赖复位值。

4.2 数据写入失败:Flash操作排查清单

现象可能原因排查方法
擦除后读出来不是0xFF擦除没成功检查FLASH_CR的PER位和STRT位是否置位,等BSY位清零
写入后数据错乱没按半字对齐检查写入地址是否2字节对齐,长度是否偶数
写入返回错误目标地址没擦除先擦后写,不能覆盖写
部分数据丢失写太快没等完成每次写后等BSY清零再写下一笔
整个App区被擦扇区规划错误核对芯片手册的扇区分布,F4尤其注意

我踩过最坑的一次是F4上擦除Sector 5,结果把Sector 4也擦了,因为Sector 4和5在同一个128KB块里,擦除命令一发就一起没了。后来查手册才发现F4的扇区是非均匀的,Sector 0-3各16KB,Sector 4是64KB,Sector 5-7各128KB。规划时一定要对着手册的表格来,不能想当然。

4.3 升级中断变砖:双区备份与恢复策略

串口升级最怕的是传到一半断电或拔线,App区被擦了一半,新固件没写完,旧固件也没了,设备变砖。解决办法有两个层次。

简单层次是升级前先擦标志区、写"升级中"标记,升级完成后写"升级完成"标记。Bootloader上电时如果读到"升级中"标记,说明上次升级没完成,就停留在Bootloader模式等待重新升级,不跳App。这样至少设备还能通过串口救回来。

进阶层次是双区备份:Flash里划两个App区,App A和App B。当前跑A,升级时写B,B校验通过后把标志切到B,下次启动跑B。如果B写坏了,标志还在A,照样跑A。这需要Flash容量足够大,F1C8T6只有64KB就不够,F4系列128KB以上可以考虑。实现上就是多一个标志位记录当前活动区,Bootloader根据标志决定跳哪个区。

提示:不管用哪种策略,升级过程中都不要断电。如果设备有电池,升级时确保电量充足;如果是市电供电,建议加个掉电检测,检测到掉电立即停止写Flash并保存状态。

4.4 波特率与超时:通信稳定性调优

115200是默认选择,但实际项目中我遇到过115200下丢包的情况,尤其是线材长、干扰大的工业现场。排查下来是上位机发送太快,Bootloader还没处理完上一包,下一包就来了,串口溢出。解决办法是每包等ACK再发下一包,而不是连续发。我在协议里加了序号和重传机制:上位机发一包等500ms,没收到ACK就重传,重传3次还失败就报错退出。

波特率可以往上提,但要注意MCU的主频和串口分频。72MHz主频下,115200的分频系数是39.0625,有误差但可接受;460800的分频系数是9.765,误差更大,可能丢包。想用高波特率就把主频提到更高,或者用支持分数波特率的型号。实测下来,F1系列在72MHz下115200最稳,F4系列在168MHz下921600也没问题。

超时时间设置也有讲究。握手超时我设300ms,因为上位机打开串口到发握手包通常不到100ms,300ms足够。擦除超时设5秒,因为擦44KB Flash在F1上大概2-3秒。写数据超时按包大小算,1KB包给500ms。校验超时给3秒,因为要读整个App区算CRC。这些值不是固定的,要根据实际芯片和固件大小调整。

4.5 版本管理与回滚:生产环境的必备考量

量产设备升级最怕的是新固件有bug,升上去之后设备集体罢工。所以Bootloader里要支持版本回滚。我的做法是在标志区存两个版本号:当前运行版本和备份版本。升级时先把当前版本备份到备份区(如果空间够),新固件校验通过后更新当前版本号。如果新固件跑起来后App自己检测到异常(比如看门狗复位次数超限),就往标志区写"请求回滚",复位后Bootloader把备份版本恢复回去。

版本号我建议用语义化版本,比如0x010203表示1.2.3,高字节主版本、中字节次版本、低字节修订号。上位机发固件时带上版本号,Bootloader比对,如果新版本比当前版本低就拒绝升级(除非强制)。这样防止误操作把旧固件刷进去。

还有一个生产细节:每台设备的固件要带唯一标识。比如在固件末尾附加设备序列号或MAC地址,Bootloader校验时比对,防止把A设备的固件刷到B设备上。这个在批量生产时特别重要,我见过产线工人拿错bin文件,一刷就是几百台,全部返工。

5. 工具链与调试手段的实战选择

5.1 调试工具:串口助手、逻辑分析仪与仿真器

调试串口IAP,串口助手是必备的。我常用的是开源的串口调试工具,支持十六进制收发、定时发送、脚本自动化。测试协议时,手动发几个字节看Bootloader回什么,比写上位机快得多。比如发AA 55 01 00 00 xx xx(握手命令),看是否回AA 55 81 ...(ACK)。

逻辑分析仪用来抓串口波形,排查波特率不对、数据错位的问题。我遇到过上位机发0xAA,Bootloader收到0x55的情况,用逻辑分析仪一看,波特率差了2%,调整后正常。逻辑分析仪还能测时序,比如擦除命令发出到ACK返回的耗时,判断是否超时。

仿真器(ST-Link或J-Link)用来单步调试Bootloader和App。跳转那几行汇编用仿真器看寄存器最直观,能清楚看到MSP和PC的变化。仿真器还能直接读Flash内容,验证写入是否正确。不过要注意,仿真器连接时可能会影响串口时序,调试通信问题时最好拔掉仿真器。

5.2 上位机选型:Python、C#还是Qt

上位机我推荐Python,理由是开发快、库全、跨平台。pyserial处理串口,struct处理二进制,tkinter或PyQt做界面,两百行就能出一个能用的工具。如果产线需要给工人用,可以打包成exe,用PyInstaller一行命令搞定。

C#在Windows上体验更好,串口库成熟,界面用WinForm拖拽就行,适合做正式的生产工具。Qt跨平台,但C++开发速度慢,适合大团队。我个人的选择是:自己调试用Python脚本,产线工具用C#,给客户演示用Qt做个好看的界面。

不管用什么语言,上位机都要有这几个功能:选择串口和波特率、加载bin文件、显示进度条、显示日志、支持断点续传(记录已发送的包序号,重连后从断点继续)。断点续传在升级大固件时特别有用,传了90%断线了,不用从头再来。

5.3 自动化测试:批量升级与压力测试

产线批量升级时,手动一台台操作不现实。我写过一个自动化脚本,用多串口卡同时连多台设备,并行升级。每台设备一个线程,独立走握手、擦除、写入、校验流程,互不干扰。脚本会记录每台设备的升级结果,失败的标红,方便返工。

压力测试是验证Bootloader稳定性的关键。我做过连续升级100次的测试,每次升级后跑一遍App的自检程序,确认功能正常。测试中发现过Flash擦写次数过多导致某页损坏的情况(F1的Flash擦写寿命约1万次),所以量产设备不要频繁升级,或者用双区交替延长寿命。

还有一个测试是异常注入:升级过程中随机拔线、随机断电、随机发错误数据,看Bootloader能否正确处理。我测下来,只要标志区逻辑写对了,拔线后重新上电都能回到Bootloader等待重传,不会变砖。但断电时机如果在写标志区的那一刻,可能标志写了一半,所以标志区要用两个备份地址,写的时候先写A再写B,读的时候A和B比对,不一致就用默认值。

6. 从串口IAP延伸出的工程经验

6.1 代码分区与模块化:让Bootloader可复用

Bootloader的代码应该和App完全解耦,不能有任何App的依赖。我把Bootloader拆成几个模块:串口驱动、Flash驱动、协议解析、跳转逻辑。每个模块一个.c和一个.h,接口清晰。这样换个芯片型号,只需要改Flash驱动和串口驱动,协议和跳转逻辑不用动。

协议解析用状态机实现,不阻塞主循环。每收到一个字节就喂给状态机,状态机根据当前状态决定下一步。这样即使串口数据来得慢,Bootloader也能同时做其他事(比如闪灯指示)。状态机的状态包括:等帧头1、等帧头2、等命令、等长度高、等长度低、等数据、等CRC高、等CRC低。每个状态处理完就切换,逻辑清晰,不容易出错。

6.2 安全加固:校验、加密与防篡改

串口IAP的安全需求取决于场景。民用产品可能只需要CRC校验防误码;工业产品可能需要防篡改,防止别人刷入恶意固件。防篡改的做法是在固件里加数字签名,Bootloader用公钥验签,验签通过才跳转。签名算法可以用ECDSA或RSA,但MCU上跑RSA比较吃力,ECDSA更合适。

简单一点的方案是固件加密:上位机发的是加密后的固件,Bootloader收到后先解密再写Flash。这样即使别人抓到了串口数据,也拿不到原始固件。加密算法用AES-128,密钥存在MCU的选项字节里,读不出来。不过加密会增加Bootloader的代码量和升级时间,要权衡。

还有一个容易被忽视的点:串口命令要加权限。不是谁发个握手包就能升级,应该有个简单的认证,比如上位机先发一个密码,Bootloader比对通过才进入升级模式。密码可以存在Flash的某个固定地址,或者用芯片唯一ID算出来。这样防止误触发升级。

6.3 从串口到其他介质:CAN、USB、无线的迁移思路

串口IAP跑通后,换其他介质其实只是换收发驱动。CAN IAP把串口收发换成CAN收发,协议帧可以复用,只是CAN一帧最多8字节,要分包。USB IAP用CDC虚拟串口,上位机代码几乎不用改。无线升级(比如蓝牙、LoRa)把串口换成无线模块的AT指令,协议层不变。

迁移时要注意的是传输速率和可靠性。CAN速率通常500Kbps,比串口快,但CAN有仲裁和重传,协议要适配。无线速率低、丢包率高,分包要更小,重传要更频繁。我做过LoRa IAP,1KB的包要传好几秒,44KB固件传了快10分钟,所以无线升级通常只用于小固件或差分升级。

差分升级是另一个方向:不传整个固件,只传新旧固件的差异部分,Bootloader在本地合并。这样传输量能减少90%以上,特别适合无线升级。但差分算法(如bsdiff)在MCU上跑需要不少内存,F1系列可能吃不消,F4以上可以考虑。

6.4 实际项目中的取舍:功能与资源的平衡

Bootloader不是功能越多越好,每加一个功能都要占Flash和RAM。F1C8T6只有64KB Flash、20KB RAM,Bootloader超过16KB就挤压App空间了。所以我在F1上只保留最核心的功能:串口收发、Flash擦写、CRC校验、跳转。加密、签名、双区备份这些统统砍掉,留给F4以上型号。

RAM也要省着用。接收缓冲区我设1KB,正好一页,收到一包就写一页,不缓存整个固件。CRC计算用查表法,表占512字节Flash,但速度快。状态机的变量用全局静态分配,不用malloc,避免堆碎片。

最后说个心态问题:Bootloader写完后一定要充分测试,不能只测成功路径。失败路径、异常路径、边界条件都要覆盖。我见过有人Bootloader写完只测了一次成功升级就量产,结果现场升级时因为串口干扰丢了一包,设备变砖,召回了几百台。测试的时间投入,远比返工的成本低。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询