1.6 构建、烧录与调试
将宿主机上交叉编译生成的目标机器码固化进物理微控制器的存储介质中,是固件开发闭环的核心动作。
在 Cortex-M 微控制器上,芯片上电复位后从何处读取第一条指令,由板上的硬件引脚电平状态决定。STM32F103 提供了两根专用的自举引脚(BOOT0 与 BOOT1),开发板通常通过跳线帽或拔码开关将其上拉至 3.3 V 或下拉至 GND。其硬件自举映射关系如下:
| BOOT0 | BOOT1 | 映射物理存储区 | 运行行为与工程应用场景 |
|---|---|---|---|
0 | x | 片内用户闪存 (Main Flash Memory) | 正常运行模式。基地址 0x08000000 被硬件映射至 0x00000000,芯片复位后直接运行开发者烧录的固件程序 |
1 | 0 | 系统存储器 (System Memory) | 串口 ISP 烧录模式。芯片复位后执行 ST 出厂在 ROM 中固化的内置自举程序,用于通过串口下载新固件 |
1 | 1 | 内置 SRAM 存储器 (Embedded SRAM) | SRAM 调试模式。代码直接在片内运行内存中就地执行,适合免擦除 Flash 的快速算法验证或极端救援场景 |
1. 串口在系统编程(ISP)
在微控制器工程领域,将代码灌入芯片物理介质主要有两条技术路线:一种是借助专用的片上调试探针(如 ST-LINK、J-Link)通过 SWD 双线接口直接操控总线写入 Flash(我们将在 1.7 SWD、JTAG 与片上调试 中深入展开);另一种则是利用芯片出厂内置的自举逻辑,通过通用异步串口进行在系统编程 (In-System Programming, ISP)。
与需要额外购置硬件仿真器的 SWD 路线相比,串口 ISP 的突出优势在于成本极低:往往只需一根几元钱的 USB 转串口线即可完成烧录,适合大批量产线烧录或极简实验场景;其局限在于下载速率受限于串口波特率、无法进行在线断点单步调试,且每次烧录都需要配合手动拔动 BOOT 跳线。
1.1 USB 转串口驱动
在桌面电脑与微控制器的物理交互中,个人电脑普遍已淘汰了传统的 DB9 RS-232 物理串口,取而代之的是高速 USB 接口;而微控制器片上原生集成的 USART 接口吐出的是 3.3 V 的 TTL 逻辑电平。因此,两者之间必须依托一颗 USB 转串口电平桥接芯片充当物理翻译官。
在市售的最小系统板或外挂模块中,南京沁恒的 CH340 系列芯片凭借其高性价比与成熟稳定性得到了极其广泛的普及;此外 Silicon Labs 的 CP2102、FTDI 的 FT232 亦属同类桥接方案。在使用该通信链路之前,需在宿主机操作系统中安装对应的虚拟串口(VCP)驱动程序。驱动就绪后,将模块插入电脑,设备管理器中将识别出形如 COM3(Windows)或 /dev/ttyUSB0(Linux)的通信端口号。
1.2 物理接线与电气连接
使用 CH340 模块与 STM32F103C8T6 最小系统板互联时,两者的接线遵循串行通信的交叉连接法则:
- 模块的接收引脚 RXD 连接微控制器的串口发送引脚 PA9 (USART1_TX);
- 模块的发送引脚 TXD 连接微控制器的串口接收引脚 PA10 (USART1_RX);
- 模块的 GND 必须与开发板的 GND 牢固相连,确立统一的基准逻辑电平;
- 模块的 3.3 V 引脚可根据供电需求选择性连入开发板供电轨(若开发板已独立插上 USB 供电,则切勿并联 3.3 V 供电线)。
其物理接线形态如下图所示:

之所以必须交叉连接,是因为串口的通信属于单工或全双工的点对点流转:本端的发送器必须正对对端的接收器缓冲队列,任何“同名引脚直连(TX 接 TX)”的粗心接法,都将导致两个驱动器在线路上互相推挽对抗,通信全然无法成立。
1.3 烧录软件操作与固件下载
在 Windows 宿主机上,FlyMCU(mcuisp)是一款经典的串口 ISP 烧录工具;在 Linux 或 macOS 终端下,则常使用开源跨平台的命令行工具 stm32flash。
以 FlyMCU 为例,执行一次完整串口烧录的标准操作步骤如下:
- 调整自举跳线:在断电或复位状态下,将开发板上的 BOOT0 跳线帽拔至 1 (3.3 V),BOOT1 跳线帽保持为 0 (GND);
- 硬件复位生效:按下开发板上的 Reset 轻触按键,迫使芯片退出执行当前 Flash 中的用户代码,转而将控制权交由内置自举 ROM;
- 配置上位机软件:打开 FlyMCU,在端口下拉框中选定对应的 COM 端口,波特率通常设为
115200 bps,校验位选择偶校验(Even Parity); - 加载固件镜像:载入构建生成的
debug_demo.hex或纯二进制镜像文件; - 触发烧录写入:点击“开始编程”按钮,软件自动与芯片内部的 Bootloader 建立握手帧,依次执行整页擦除、按块写入与数据校验;
- 恢复运行自举:烧录成功后,必须将 BOOT0 跳线帽重新插回 0 (GND),随后再次按下 Reset 按键,芯片硬件才会顺理成章地跳入 Flash 用户代码区,启动全新烧录的固件。
1.4 ISP 原理深度解析
微控制器出厂时空空如也,究竟是谁在背后听从串口指令并把数据精确写入 Flash 的?
这一奥秘深植于芯片内部的系统存储器 (System Memory)。在 STM32F103 的硅片制造过程中,原厂在从物理地址 0x1FFFF000 开始的一片专用 ROM 区域中,永久固化了一段只读的工厂引导程序(Bootloader)。这段代码在硅片出厂后不可篡改,且不占用用户任何 Flash 空间。
当外部硬件引脚处于 BOOT0 = 1, BOOT1 = 0 并发生复位时,内核内部的重映射开关动作,将系统存储器映射为 CPU 的启动空间。固化自举程序随即开始执行以下自动化流程:
- 波特率自动自适应:自举程序将 USART1 初始化在接收监听模式下,并不盲目假定外部波特率。上位机首先向微控制器发送一个基准字节
0x7F;自举程序利用内部高精度定时器捕获该字节起始位与第一个数据位跳变的真实时间跨度,动态算出上位机的实际通信波特率,从而实现从 9600 bps 到 115200 bps 的全速率兼容。 - 严密的帧安全握手:依据 ST 官方 AN3155 应用协议规范,每一次写入交互均以命令码与其反码成对出现(例如写内存命令为
0x31,紧跟其按位取反0xCE)。自举程序在硬件接收到该帧后,立即执行互补校验,只有校验无误时才向主机回送单字节应答码0x79(ACK);若线路存在干扰导致校验失配,则回送0x1F(NACK) 并丢弃指令。 - 片内闪存控制器解锁与烧写:在收到写入地址和数据负载后,自举程序在特权级下向 Flash 访问控制寄存器(FLASH_KEYR)依次注入合法的两道解锁密钥(
0x45670123与0xCDEF89AB),解除片内闪存的硬件写保护;随后按照数据手册规定的半字/字时序,将数据逐笔烧写进目标物理扇区,并在全部搬运结束后重新锁死 Flash。
理解了这套物理机制,便能清醒认识到:串口 ISP 本质上是芯片出厂预埋的一个轻量级固件服务。它虽然无法提供 SWD 那样窥探内核寄存器、设置硬件断点的交互式调试能力,却在没有任何专用编程器的极端环境下,为微控制器固件注入了最初始的生命力。
2. 习题
对照本页开头的 BOOT 引脚表回答:
(1) 只想正常运行 Flash 里的程序,BOOT0 与 BOOT1 各接什么电平?
(2) 用 FlyMCU 串口下载时,板上的跳线帽要拨到哪个组合?下载完成、按下复位之前要不要拨回来?不拨回来直接复位,芯片会执行哪段程序?
(3) 「从 SRAM 启动」一行的 BOOT0、BOOT1 是什么组合?这个模式对调试有什么用处?
(4) CH340 的两个串口引脚按 RXD -> PA9、TXD -> PA10 连接。为什么这两根线是交叉接的?把两根都接成同名相连,现象是什么?
串口下载走的是 ST 固化在系统存储器里的自举程序,它的帧格式在 AN3155 里规定。本题按下面这份摘要实现拼帧,不需要真串口:
- 串口参数:115200 波特率、8 位数据、偶校验、1 位停止位。
- 每个命令帧两个字节:命令字节,紧接它的按位取反。自举程序发现两者不互补就回 NACK。
- 带参数的命令先发命令帧,再发参数帧;参数帧最后一个字节是这一帧前面所有字节的按位异或。
- 应答一个字节:
0x79为 ACK,0x1F为 NACK。 - 常用命令:
0x00Get(无参数),0x31Write Memory(4 字节地址),0x43Erase(1 字节页掩码,0xFF为全片),0x11Read Memory(4 字节地址)。
写一个主机程序 isp_frame.c,按命令行参数打印一整条 Write Memory 命令的全部帧:
$ ./isp_frame 0x08000000 0x10
cmd 31 CE
addr 08 00 00 00 08
len 0F
data 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
csum 0F2
3
4
5
6
要求:
- 地址与字节数都从命令行解析,用
strtoul加base 0,0x前缀与十进制都能用。 len行打印 N−1,N 为字节数;data行是从 0 开始的递增序列,长度等于 N。csum行是数据帧的校验字节:N−1 与全部数据字节异或。- 字节数为 0、超过 256,或不是 4 的倍数时打印一行错误,以 1 退出。
验收:上面那条命令的输出与给出的完全一致;再试 ./isp_frame 0x0800FC00 8,addr 行应为 08 00 FC 00 F4,csum 行应为 07。用 gcc -std=c17 -Wall -Wextra isp_frame.c -o isp_frame 编译。
把「构建产物交给串口下载」这条链上的格式问题查清楚。
(1) 用 1.7 的 debug_demo 工程(或 1.8 的 minimal)构建一次,得到 .elf、.bin、.hex 三个文件,用 ls -l 记下三者的大小。三者各是什么格式?串口下载工具收的是哪一种?
(2) od -A x -t x1 -N 8 debug_demo.bin 的头 8 个字节是什么?按 1.8 的约定,前 4 个字节与后 4 个字节分别会被芯片当成什么?
(3) 写一个主机程序 hexcheck.c,读入一个 Intel HEX 文件,逐行校验每行末尾的校验字节(该行除校验字节外所有字节之和的低 8 位取反加 1),统计:总行数、类型 00 的数据行数、数据字节总数、最低与最高地址、校验失败的行号。
(4) 对同一个工程的 .hex 跑一次,把五项统计与 .bin 的文件大小对照,说明为什么 .hex 的文件大小会比 .bin 大。
验收:hexcheck 在 debug_demo.hex 上零校验错误;人为把某一行的最后一个字符改掉,程序能报出那一行的行号并非零退出。
串口 ISP 与 SWD 都能把固件写进 Flash,但两者能做的事差别很大。本题先把对照表写出来,再动手做一个能给出可信结论的实验。
(1) 列一张对照表,比较串口 ISP 与 SWD 探针:接线根数、是否需要改 BOOT 电平、能否在线调试、能否读回 Flash、烧录速度量级、能否在程序运行时介入。
(2) 串口 ISP 的速度可以算出来。按 115200 波特率、8 位数据、偶校验、1 位停止位,一个字节在线路上占几位?另有起始位 1 位,写出每秒能传多少字节。把 debug_demo.bin(520 字节)与 1.8 节 minimal.bin 的大小换算成理论最短耗时,再乘以协议开销系数(每帧有应答、命令与校验,取 1.3~1.6 倍),给出两档估计值。与 SWD 的 openocd -c "program ..." 实测耗时对照。
(3) 现场设备装在外壳里,BOOT0 与 NRST 都引不出来,只有一根串口线。按 6.4 Bootloader 与固件升级 的思路,说明为什么要在应用里写一个 Bootloader,而不是指望用户拨 BOOT0。这个 Bootloader 至少要做哪四件事?
(4) 另一种做法是应用收到升级命令后直接跳回系统存储器里的自举程序。查 AN2606,列出跳转前必须做的三件事(关掉已使能的中断、把用到的引脚恢复成复位状态、把栈指针交还给自举程序),写出跳转函数的骨架。
(5) 这条「跳回自举程序」的路径少了一道保护:它不校验新固件是否完整。给出两条你会补上的检查,并说明它们分别挡住什么情况。
验收:第 (2) 问给出算式与两个耗时数,其中 debug_demo.bin 在 115200 下的理论最短时间约为 0.05 s;跳转函数里能看到关中断、恢复引脚、设置 MSP 与跳转到系统存储器地址;第 (5) 问的两条检查要一条与长度或校验和有关,一条与「写失败后能否回退」有关。