STM32 BootLoader串口升级实战:Flash分区、固件接收与跳转机制详解
简介这份STM32 BootLoader固件串口UART升级工程源码面向嵌入式开发工程师与STM32学习者解决设备端通过串口接收上位机固件并自动完成程序升级与更新的问题。工程基于STM32单片机完整实现UART数据收发与命令解析、FLASH读写、CRC校验以及Bootloader固件加载与跳转复位后重新设置新APP向量地址形成闭环升级流程。压缩包共109个文件以72个h头文件与30个c源文件为主体另含ioc配置、ld链接脚本、makefile、s启动文件及jflash烧录配置等整体约833KB工程结构完整、便于直接编译与二次移植。内容覆盖HAL库使用、UART初始化、FLASH操作与CRC校验等关键环节适合作为IAP升级方案的学习模板与项目参考。目前已有1952人学习下载可帮助读者快速理解串口升级的完整链路与排错思路。1. 串口升级这件事比想象中更值得自己搭一遍去年帮一个做工业控制的朋友救急现场设备装在配电柜里固件有个小 bug 要修结果整机拆下来接仿真器重烧来回折腾了一整天。那次之后我就下定决心把 STM32 的 BootLoader 串口升级这套东西彻底吃透。这份工程源码干的就是这件事让 MCU 通过 UART 接收新固件自己擦写 Flash、跳转执行不用拆机、不用仿真器一根串口线就能完成升级。它适合正在做量产设备、需要远程维护固件的嵌入式开发者也适合想搞懂 BootLoader 跳转机制和 Flash 分区管理的同学。下面我按自己拆代码的顺序把这份工程从架构到跑通再到改造完整讲一遍。2. 工程架构拆解BootLoader 和 App 是怎么分工的2.1 整体分区思路这套工程的核心思路是把 STM32 的内部 Flash 切成两块一块给 BootLoader一块给用户 App。BootLoader 永远不动负责上电判断、接收新固件、擦写 App 区App 才是真正跑业务逻辑的代码。上电后先跑 BootLoader它检查有没有升级请求有就进升级流程没有就直接跳到 App 区执行。常见的分区方案是这样的以 128KB Flash 的型号为例区域起始地址大小用途BootLoader0x0800000016KB引导程序不参与升级App0x08004000100KB用户应用固件参数区0x0801D0004KB存放升级标志、固件长度等备份区0x0801E0008KB可选用于双备份升级这个划分不是死的具体要看芯片型号和 App 体积。我一般会先算清楚 App 编译出来多大再倒推 BootLoader 留多少空间。留太少以后加功能会后悔留太多又浪费。2.2 BootLoader 主流程BootLoader 的 main 函数逻辑其实很清晰我把它简化成下面这段int main(void) { /* 初始化时钟、串口、Flash 等外设 */ HAL_Init(); SystemClock_Config(); UART_Init(115200); /* 读取参数区判断是否有升级请求 */ uint32_t upgrade_flag Read_Upgrade_Flag(); if (upgrade_flag UPGRADE_REQUEST) { /* 有升级请求进入接收固件流程 */ if (Receive_Firmware() SUCCESS) { /* 接收完成清除标志跳转到 App */ Clear_Upgrade_Flag(); Jump_To_App(APP_START_ADDR); } else { /* 接收失败留在 BootLoader 等待重试 */ while (1) { UART_SendString(Upgrade failed, retry...\r\n); HAL_Delay(1000); } } } else { /* 没有升级请求直接跳转 App */ Jump_To_App(APP_START_ADDR); } }这里有几个关键点。Read_Upgrade_Flag是从参数区读一个特定值这个值由 App 在收到升级命令时写入。Receive_Firmware是核心接收函数后面单独讲。Jump_To_App负责关中断、设栈指针、跳转是最容易翻车的地方。2.3 App 端的配合逻辑App 不是被动等着被升级它也要主动配合。App 里需要做两件事一是收到上位机的升级命令时往参数区写升级标志然后触发软复位二是修改链接脚本把起始地址挪到 App 区。/* App 中收到升级命令的处理 */ void Handle_Upgrade_Command(void) { /* 往参数区写入升级标志 */ HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, UPGRADE_FLAG_ADDR, UPGRADE_REQUEST); HAL_FLASH_Lock(); /* 软复位让 BootLoader 重新跑 */ NVIC_SystemReset(); }链接脚本的修改在 Keil 或 IAR 里对应的是 IROM1 起始地址改成0x08004000大小改成0x19000。这一步如果忘了改App 编译出来还是从0x08000000开始跳转过去直接跑飞。注意App 的向量表偏移也要改在system_stm32f1xx.c里把VECT_TAB_OFFSET设成0x4000或者在 main 开头调用NVIC_SetVectorTable。这个坑我踩过现象是中断全部失效串口收不到数据。3. 核心模块实战固件接收、Flash 擦写与跳转3.1 串口接收固件的协议设计固件接收不能裸收得有协议。这份工程用的是一种简单的分包协议上位机把 bin 文件按固定长度分包每包带序号和校验BootLoader 收到后校验、写入、回 ACK。/* 接收一包固件的函数 */ typedef struct { uint8_t head; /* 帧头 0xAA */ uint16_t seq; /* 包序号 */ uint16_t len; /* 数据长度 */ uint8_t data[256]; /* 数据 */ uint16_t crc; /* CRC16 校验 */ } FirmwarePacket; int Receive_One_Packet(FirmwarePacket *pkt) { uint8_t buf[sizeof(FirmwarePacket)]; /* 等待帧头 */ if (UART_ReceiveBytes(buf, sizeof(FirmwarePacket), TIMEOUT) ! SUCCESS) return ERROR; /* 校验帧头 */ if (buf[0] ! 0xAA) return ERROR; /* 拷贝并校验 CRC */ memcpy(pkt, buf, sizeof(FirmwarePacket)); if (CRC16(pkt-data, pkt-len) ! pkt-crc) return ERROR; return SUCCESS; }seq用来检测丢包len限制单包最大 256 字节crc保证数据完整性。实际用的时候上位机每发一包等一个 ACK超时就重发。这个协议简单但够用工业现场跑 115200 波特率很稳。3.2 Flash 擦写的关键细节STM32 的 Flash 擦除是按页来的写入是按半字或字来的。擦之前要解锁擦完要上锁。下面这段是写入一包数据的核心int Write_Firmware_To_Flash(uint32_t addr, uint8_t *data, uint16_t len) { HAL_FLASH_Unlock(); /* 按页擦除注意 STM32F1 的页大小是 1KB 或 2KB */ FLASH_EraseInitTypeDef erase; uint32_t page_error 0; erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress addr; erase.NbPages 1; if (HAL_FLASHEx_Erase(erase, page_error) ! HAL_OK) { HAL_FLASH_Lock(); return ERROR; } /* 按半字写入 */ for (uint16_t i 0; i len; i 2) { uint16_t halfword data[i] | (data[i 1] 8); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr i, halfword) ! HAL_OK) { HAL_FLASH_Lock(); return ERROR; } } HAL_FLASH_Lock(); return SUCCESS; }PageAddress必须是页对齐的NbPages是擦几页。写入的时候地址要递增不能跨页写。如果 App 区跨了多页接收的时候要按页边界分包或者接收完整后再统一擦写。提示擦除操作会阻塞 CPU大页擦除可能几十毫秒期间串口如果还在收数据会丢包。常见做法是接收和擦写分开先收到 RAM 缓冲区收满一页再擦写。3.3 跳转到 App 的正确姿势跳转是整套工程里最玄学的一步写错了就是 HardFault。正确的跳转函数长这样typedef void (*pFunction)(void); void Jump_To_App(uint32_t app_addr) { pFunction jump_to_app; uint32_t stack_ptr; /* 检查 App 区是否有有效栈顶 */ stack_ptr *(volatile uint32_t *)app_addr; if ((stack_ptr 0x2FFE0000) ! 0x20000000) { /* 栈顶不在 RAM 范围说明 App 无效 */ return; } /* 关所有中断 */ __disable_irq(); /* 重设向量表 */ SCB-VTOR app_addr; /* 设栈指针 */ __set_MSP(stack_ptr); /* 取复位向量跳转 */ jump_to_app (pFunction)(*(volatile uint32_t *)(app_addr 4)); jump_to_app(); }app_addr就是 App 区的起始地址比如0x08004000。stack_ptr是 App 向量表的第一个字指向 RAM 顶部。app_addr 4是复位向量。跳转前必须关中断否则 BootLoader 的中断会干扰 App。SCB-VTOR重设向量表偏移这一步在 Cortex-M3/M4 上是必须的。4. 跑通全流程从编译到串口升级的完整步骤4.1 编译 BootLoader 和 App先编译 BootLoaderKeil 里 IROM1 起始地址保持0x08000000大小设成0x4000。编译出来一个 hex 或 bin用仿真器烧进去。然后编译 AppIROM1 起始地址改成0x08004000大小0x19000同时改VECT_TAB_OFFSET。编译出来的 bin 就是待升级的固件。# 用 fromelf 或 objcopy 生成 bin fromelf --bin --output app.bin ./Objects/app.axf # 或者用 arm-none-eabi arm-none-eabi-objcopy -O binary app.elf app.bin生成的app.bin就是上位机要发的文件。注意 bin 文件不带地址信息所以上位机必须知道从0x08004000开始写。4.2 上位机发送固件上位机可以用 Python 写逻辑就是读 bin、分包、发一包等 ACK、超时重发。核心代码import serial, struct, time def send_firmware(port, baud, bin_path): ser serial.Serial(port, baud, timeout1) with open(bin_path, rb) as f: data f.read() seq 0 for i in range(0, len(data), 256): chunk data[i:i256] # 构造包帧头 序号 长度 数据 CRC pkt struct.pack(BHH, 0xAA, seq, len(chunk)) chunk pkt struct.pack(H, crc16(chunk)) ser.write(pkt) # 等 ACK ack ser.read(1) if ack ! b\x06: print(f包 {seq} 失败重发) ser.write(pkt) ser.read(1) seq 1 print(发送完成)crc16要和 BootLoader 端一致多项式、初值、异或值都得对上。ser.read(1)等 ACK超时 1 秒。实际用的时候可以加重传次数上限避免死循环。4.3 验证升级结果升级完成后BootLoader 会跳转到 App。验证方法很简单App 里点个灯或者周期性发串口数据看现象对不对。如果没反应先检查跳转地址、向量表偏移、栈顶值这三个地方。我一般会在 BootLoader 里加一句串口打印跳转前把stack_ptr和复位向量打出来方便排查。5. 避坑与常见问题排查5.1 跳转后跑飞或 HardFault现象BootLoader 跳转后 App 不运行或者直接进 HardFault。原因通常是向量表没重设、栈指针不对、或者 App 编译地址没改。解决确认SCB-VTOR设成了 App 起始地址确认 App 的 IROM1 和VECT_TAB_OFFSET都改了确认跳转前关了中断。5.2 串口接收丢包现象升级到一半卡住或者 CRC 校验失败。原因可能是波特率太高、擦写阻塞导致丢包、或者上位机发太快。解决降低波特率到 115200 或 57600接收和擦写分开先收 RAM 再写 Flash上位机每包等 ACK。5.3 擦除后 App 区数据不对现象升级完成但 App 跑不起来读 Flash 发现数据错位。原因通常是擦除页没对齐或者写入地址算错。解决确认PageAddress是页大小整数倍写入地址从 App 起始地址开始递增不要跨页写。5.4 升级标志误触发现象每次上电都进升级模式不跳 App。原因可能是参数区没初始化读出来是随机值恰好等于升级标志。解决参数区加魔数校验只有魔数和标志都匹配才进升级否则清标志跳 App。5.5 App 中断失效现象App 跑起来了但串口、定时器中断不响应。原因就是向量表偏移没设。解决在 App 的system_stm32f1xx.c里改VECT_TAB_OFFSET或者在 main 开头调用NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000)。6. 二次开发把串口升级改成双备份和断点续传这套工程跑通之后我建议往两个方向改。第一个是双备份升级把 App 区分成 App_A 和 App_B新固件先写到备份区校验通过后再切换运行区。这样升级失败还能回滚不会变砖。实现上就是在参数区加一个active_region字段BootLoader 根据它决定跳哪个区。第二个是断点续传升级过程中断电重新上电后能从上次的包序号继续不用从头传。做法是每收若干包就把当前seq写到参数区BootLoader 启动时读出来告诉上位机从哪个包开始。/* 断点续传记录已接收的包序号 */ void Save_Progress(uint16_t seq) { HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, PROGRESS_ADDR, seq); HAL_FLASH_Lock(); }PROGRESS_ADDR放在参数区每次写之前不用擦因为 STM32 的 Flash 可以多次写 1 变 0但要注意写之前该地址得是擦除状态。如果频繁写建议单独开一页做日志式记录。还有一个实用技巧是加一个超时自动跳转BootLoader 启动后等 3 秒如果没收到升级命令就跳 App。这样正常上电不会卡在 BootLoader需要升级时上位机在这 3 秒内发命令就行。/* 等待升级命令超时跳 App */ uint32_t start HAL_GetTick(); while (HAL_GetTick() - start 3000) { if (UART_Check_Upgrade_Cmd()) { Receive_Firmware(); break; } } Jump_To_App(APP_START_ADDR);从那以后我每次改 BootLoader都强制走一遍「擦除-写入-跳转-中断验证」的完整流程不敢跳步。这套源码把最核心的机制都摆出来了剩下的就是按自己项目调分区和协议。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

个人品牌GEO实操:让AI认识你并主动推荐你的方法

个人品牌GEO实操:让AI认识你并主动推荐你的方法

做个人品牌这些年,我一直盯着搜索流量的变化。以前研究的是SEO关键词排位,后来算法改版,再后来不得不面对一个更前置的问题:当用户不再打开搜索框,而是直接问AI“帮我推荐一位擅长XXX的博主”时,你的名字凭…

2026/10/19 12:37:26 阅读更多 →
AI质检与MES深度集成:打造智能制造质量闭环实践指南

AI质检与MES深度集成:打造智能制造质量闭环实践指南

开头我先直接说结论:AI质检要和MES深度握手,才能真正把质量从“事后挑出坏件”变成“过程里拦住坏件”。这两年在智能制造项目里,我见过太多把AI质检单独架在产线上、拍完照判完级就完了,质量数据躺在一个孤岛系统里睡大觉的项目—…

2026/10/19 12:37:26 阅读更多 →
AI改写邮件三招:从通知式到邀请式,重塑专业人设

AI改写邮件三招:从通知式到邀请式,重塑专业人设

1. 别急着改措辞:通知式邮件损害人设的底层原因1.1 为什么"准时参加"比"方便的话记得来"更伤人我前几天翻自己的已发邮件,发现一个很扎心的事实:过去半年里,我发出的邮件大多数可以被归类为"通知"&…

2026/10/19 12:37:25 阅读更多 →

最新新闻

YOLO26轻量化优化:DualConv双卷积替换主干,mAP涨1.3且参数降27%

YOLO26轻量化优化:DualConv双卷积替换主干,mAP涨1.3且参数降27%

跑完新一轮YOLO26优化实验,我先把结论扔到前面:用轻量化双卷积DualConv替换掉主干网络里一部分标准卷积之后,模型mAP50-95涨了1.3个点,同时参数量从12.9M掉到9.4M,计算量从18.7G降到12.3G。这不是靠蒸馏,也…

2026/10/19 17:39:45 阅读更多 →
开源代码评审工作流:协议驱动的可审计人机协同范式

开源代码评审工作流:协议驱动的可审计人机协同范式

1. 项目概述:这不是一个工具,而是一套可落地的开源代码评审工作流“open-code-review”这个名称乍看像某个具体软件或CLI命令,但实际它代表的是一种正在快速演化的工程实践范式——把传统依赖人工、会议、PR评论的代码评审(Code R…

2026/10/19 17:39:45 阅读更多 →
开源CLI代码评审系统:基于LLM Agent的自动化Code Review实践

开源CLI代码评审系统:基于LLM Agent的自动化Code Review实践

1. 这不是又一个“AI写代码”工具,而是一套可嵌入开发流程的开源代码评审系统最近在几个技术团队内部做工具链调研时,发现一个有意思的现象:大家嘴上说着“用AI做code review”,但实际落地时,90%的人还在把diff粘贴进C…

2026/10/19 17:39:45 阅读更多 →
两相流THM热流固耦合建模:COMSOL收敛与求解器配置指南

两相流THM热流固耦合建模:COMSOL收敛与求解器配置指南

简介:基于COMSOL构建的两相流THM热流固耦合模型,面向石油工程、地热开发及二氧化碳地质封存等复杂多物理场耦合问题,适合从事渗流力学与岩土工程仿真的科研人员、工程师与研究生。模型以饱和水和油两相流动为核心,支持温度场—渗流…

2026/10/19 17:39:45 阅读更多 →
基于SpringBoot+Vue的校园管理系统毕业设计实战拆解

基于SpringBoot+Vue的校园管理系统毕业设计实战拆解

毕业设计想选校园管理系统、又不想纯手工从零搓代码的同学,我建议直接找一个 SpringBoot Vue 的前后端分离项目来二次开发,这是目前最稳妥、也最适合拿高分的路子。这套技术栈基本是 Java Web 方向毕设的标配,覆盖了后端接口开发、前端页面交…

2026/10/19 17:39:45 阅读更多 →
RGAT+GRU融合模型解决APT溯源图稀疏漏报问题

RGAT+GRU融合模型解决APT溯源图稀疏漏报问题

简介:本资源是华中科技大学2023届计算机相关专业(如计科、人工智能、网络安全等)优秀毕业设计成果,聚焦APT攻击检测这一前沿安全问题,提出并实现了一种基于溯源图建模与RGATGRU融合架构的检测方法优化方案。面向在校本…

2026/10/19 17:38:44 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →