1. 前言
简单说明为什么要学习 STM32 程序升级。
可以写:
平时开发 STM32 时,通常使用 ST-LINK 通过 SWD 接口下载程序,这种方式在开发调试阶段很方便。但是如果设备已经安装到现场,后期需要更新功能或者修复 Bug,每次都拆机接下载器就不现实。因此,需要一种可以通过通信接口升级程序的方法,例如串口升级、CAN 升级、USB 升级等。
本文主要记录 STM32 程序升级的基础知识,重点梳理 Bootloader、IAP、Flash 分区、串口接收程序、Flash 写入以及 Bootloader 跳转 App 的基本流程。
2. 学习 STM32 程序升级前需要先搞清楚的问题
在真正理解 STM32 程序升级之前,需要先搞明白下面几个问题:
1. Bootloader 是什么?
2. ISP 和 IAP 有什么区别?
3. STM32 Flash 地址为什么从 0x08000000 开始?
4. Bootloader 和 App 为什么要分区?
5. App 为什么要改起始地址?
6. Bootloader 怎么跳转到 App?
7. bin 文件怎么通过串口写进 Flash?
这几个问题是理解 STM32 串口升级的基础。后面的内容基本就是围绕这些问题展开。
3. Bootloader 是什么?
Bootloader 可以理解为 STM32 上电后最先运行的一段程序。
它一般放在 STM32 用户 Flash 的起始位置,也就是:
0x08000000
Bootloader 本身不是业务程序,它主要负责启动管理和程序升级。
Bootloader 的主要作用包括:
-
判断是否需要升级;
-
通过串口接收新的 App 程序;
-
擦除 App 所在的 Flash 区域;
-
将新程序写入 App 区;
-
升级完成后跳转到 App 运行。
可以简单理解为:
Bootloader = 程序更新器
App = 真正的业务程序
4. ICP,ISP,IAP 有什么区别?

STM32 程序升级里经常会看到 ISP 和 IAP,这两个概念很容易混。
4.1 ISP:使用芯片内部 Bootloader 升级
ISP 的意思是系统内编程。
STM32 芯片内部本身就带有一段系统 Bootloader。通过 BOOT0、BOOT1 启动配置,可以让芯片进入系统 Bootloader,然后通过串口、USB 等方式下载程序。
常见理解:
BOOT0 = 0:从用户 Flash 启动
BOOT0 = 1:进入系统 Bootloader
ISP 的特点:
-
不需要自己写 Bootloader;
-
可以通过串口等接口下载程序;
-
需要控制 BOOT0 引脚;
-
升级流程受 STM32 内部 Bootloader 限制。
4.2 IAP:自己写 Bootloader 升级
IAP 的意思是应用内编程。
它的核心是:
用户自己写 Bootloader
Bootloader 自己接收程序
Bootloader 自己擦写 Flash
Bootloader 自己跳转 App
IAP 的特点:
-
灵活性高;
-
可以自己定义升级协议;
-
可以通过串口、CAN、USB 等接口升级;
-
适合实际产品中的程序升级功能。
4.3 ICP:通过下载器烧录程序
ICP(In-Circuit Programming)可以理解为 在线电路编程 ,也就是通过外部下载器,直接把程序烧录到 STM32 芯片内部 Flash 中。
在 STM32 开发中,最常见的方式就是使用 ST-LINK 或 J-LINK ,通过 SWD 或 JTAG 接口下载程序。
这种方式一般用于开发调试阶段。
例如我们平时在 Keil、STM32CubeIDE 中点击下载,本质上就是通过下载器把编译好的程序写入到 STM32 的 Flash 中。
4.4 简单对比
ICP、ISP、IAP 都可以实现 STM32 程序下载或升级,但它们的使用场景不一样。
| 对比项 | ICP | ISP | IAP |
|---|---|---|---|
| 全称 | In-Circuit Programming | In-System Programming | In-Application Programming |
| 中文理解 | 在线电路编程 | 系统内编程 | 应用内编程 |
| 是否需要下载器 | 需要 ST-LINK / J-LINK | 不需要专用下载器 | 不需要专用下载器 |
| 是否需要自己写 Bootloader | 不需要 | 不需要,使用芯片内部 Bootloader | 需要,用户自己写 Bootloader |
| 常用接口 | SWD / JTAG | 串口 / USB 等 | 串口 / CAN / USB 等 |
| 是否需要控制 BOOT 引脚 | 不需要 | 通常需要控制 BOOT0 | 一般不需要 |
| 灵活性 | 一般 | 一般 | 高 |
| 适合场景 | 开发调试、量产烧录 | 简单程序下载 | 产品后期升级、自定义升级 |
简单理解:
ICP:用下载器直接烧程序
ISP:用芯片内部 Bootloader 烧程序
IAP:自己写 Bootloader,让程序自己升级程序
实际开发中可以这样区分:
- 开发调试阶段:一般用 ICP,也就是 ST-LINK 下载;
- 简单下载程序:可以用 ISP,进入 STM32 内部 Bootloader;
- 产品升级功能:更适合用 IAP,自己写 Bootloader,通过串口等接口升级 App。
5. STM32 Flash 地址为什么从 0x08000000 开始?
STM32 的用户程序一般存放在内部 Flash 中,而 STM32 用户 Flash 的映射地址通常从:
0x08000000
开始。
也就是说,普通情况下,我们写的程序会被下载到这个地址开始的位置。
如果没有 Bootloader,App 程序一般就是这样存放的:
0x08000000 → App 程序
STM32 上电后,会从启动地址读取中断向量表,然后执行复位中断入口,最终进入 main 函数。
所以 0x08000000 是理解 STM32 程序启动过程的关键地址。
6. Bootloader 和 App 为什么要分区?

加入 Bootloader 后,Flash 里面就不能只放一个 App 程序了。
因为 Bootloader 要先运行,App 也要存在,所以必须把 Flash 分成两个区域:
0x08000000 → Bootloader 区
0x08004000 → App 区
这里假设 Bootloader 占用 16KB 空间,所以 App 从 0x08004000 开始。
这样分区的原因是:
-
STM32 上电后先执行 Bootloader;
-
Bootloader 不能被 App 覆盖;
-
App 放在 Bootloader 后面的独立区域;
-
Bootloader 需要升级时,只擦除和写入 App 区;
-
不需要升级时,Bootloader 直接跳转 App。
简单来说:
Bootloader 管升级
App 管业务功能
两者分开放,程序结构才清楚。
7. App 为什么要改起始地址?
默认情况下,App 工程一般是按照:
0x08000000
作为起始地址编译的。
但是加入 Bootloader 后,0x08000000 已经被 Bootloader 占用了,App 实际要放到:
0x08004000
所以 App 工程也必须修改 Flash 起始地址。
例如 Keil 中需要修改:
IROM1 Start:0x08004000
IROM1 Size :根据实际剩余 Flash 设置
如果 App 工程不改起始地址,可能会出现这些问题:
-
App 写入位置和编译地址不一致;
-
Bootloader 跳转后程序跑飞;
-
中断向量表位置错误;
-
串口、定时器等中断不能正常工作。
所以做 Bootloader 时,App 起始地址修改是非常关键的一步。
8. bin 文件和 hex 文件有什么区别?
STM32 编译后常见的固件文件有 .hex 和 .bin。
8.1 hex 文件
hex 文件是文本格式,文件里面带有地址信息。
它常用于下载器烧录,比如 ST-LINK 下载程序。
8.2 bin 文件
bin 文件是纯二进制文件,不带地址信息。
它里面保存的就是要写入 Flash 的原始程序数据。
做串口 Bootloader 升级时,通常使用 bin 文件。
因为 Bootloader 已经知道 App 要写到哪里,比如:
0x08004000
所以串口只需要发送 bin 文件内容,Bootloader 按固定地址写入 Flash 即可。
9. bin 文件怎么通过串口写进 Flash?
这是串口升级的核心流程。
整体流程可以理解为:
上位机发送升级命令
↓
Bootloader 进入升级模式
↓
上位机发送 App 的 bin 文件
↓
Bootloader 通过串口接收数据
↓
Bootloader 擦除 App 区 Flash
↓
Bootloader 将接收到的数据写入 Flash
↓
接收完成后跳转到 App
9.1 发送升级命令
可以先通过串口发送一个升级命令,例如:
start:len
其中:
-
start表示开始升级; -
len表示后面要发送的 bin 文件长度。
9.2 分包接收 bin 文件
因为 bin 文件可能比较大,不能一次性全部放进 RAM,所以一般采用分包接收。
例如:
每次接收 256 字节
接收一包,写入一包
直到接收长度达到 len
9.3 写入 Flash
Bootloader 接收到数据后,将数据写入 App 区域:
0x08004000
写入时要注意:
-
写 Flash 前要先擦除;
-
只能擦除 App 区,不能擦除 Bootloader 区;
-
写入地址必须在 App 区范围内;
-
写入地址要注意对齐;
-
写完后可以校验接收长度。
10. STM32 Flash 擦除与写入流程
STM32 内部 Flash 不能像 RAM 一样直接随便覆盖写入。
写 Flash 前一般需要先擦除,擦除后的 Flash 内容通常为:
0xFF
基本操作流程是:
解锁 Flash
↓
擦除 App 区对应 Flash 页
↓
写入接收到的 bin 数据
↓
写入完成后锁定 Flash
这部分是 Bootloader 实现升级功能的核心之一。

11. Bootloader 怎么跳转到 App?

Bootloader 写完 App 后,不能直接跳到 0x08004000 就结束。
因为 App 起始地址处存放的是中断向量表,不是普通函数代码。
App 起始地址处有两个重要内容:
0x08004000 → App 初始栈顶地址
0x08004000 + 4 → App 复位中断入口地址
所以 Bootloader 跳转 App 的流程应该是:
1. 判断 App 起始地址是否合法;
2. 关闭中断;
3. 读取 App 初始栈顶地址;
4. 读取 App 复位中断入口地址;
5. 设置 MSP 主堆栈指针;
6. 跳转到 App 复位入口;
7. App 开始运行。
伪代码可以这样写:
#define APP_START_ADDR 0x08004000
typedef void (*pFunction)(void);
void Jump_To_App(void)
{
uint32_t app_stack;
uint32_t app_reset;
pFunction jump_to_app;
app_stack = *(__IO uint32_t*)APP_START_ADDR;
app_reset = *(__IO uint32_t*)(APP_START_ADDR + 4);
__disable_irq();
__set_MSP(app_stack);
jump_to_app = (pFunction)app_reset;
jump_to_app();
}
12. App 为什么要重定位中断向量表?
App 原来默认是从:
0x08000000
开始运行的。
但是加入 Bootloader 后,App 实际从:
0x08004000
开始运行。
所以 App 的中断向量表也要跟着偏移到新的地址。
在 App 初始化时可以设置:
SCB->VTOR = 0x08004000;
如果不设置中断向量表偏移,可能会出现:
-
main 函数可以运行;
-
串口中断进不去;
-
定时器中断异常;
-
外部中断异常;
-
程序运行不稳定。
所以,只要 App 不在默认地址 0x08000000 运行,就一定要注意中断向量表重定位问题。
13. 串口升级的完整流程总结
最后把整个串口升级流程串起来:
1. STM32 上电;
2. Bootloader 从 0x08000000 开始运行;
3. Bootloader 初始化串口;
4. Bootloader 等待升级命令;
5. 如果收到升级命令,开始接收 bin 文件;
6. Bootloader 擦除 App 区 Flash;
7. Bootloader 将 bin 数据写入 0x08004000;
8. 接收完成后检查数据长度;
9. Bootloader 设置 MSP;
10. Bootloader 跳转到 App 复位入口;
11. App 设置中断向量表偏移;
12. App 正常运行。
14. 总结
STM32 程序升级的核心可以总结为一句话:
Bootloader 先运行,通过串口接收新 App,写入 App 区 Flash,然后跳转到 App 执行。
理解这套流程,重点要搞清楚:
-
Bootloader 是什么;
-
ISP 和 IAP 有什么区别;
-
STM32 Flash 地址为什么从
0x08000000开始; -
Bootloader 和 App 为什么要分区;
-
App 为什么要改起始地址;
-
Bootloader 怎么跳转到 App;
-
bin 文件怎么通过串口写进 Flash。
这几个问题弄明白后,就基本理解了 STM32 Bootloader 串口升级的基础流程。