STM32 开发板到手之后,真正让人卡住的往往不是写代码,而是从拆快递到点亮第一颗 LED 之间的那段空白。我见过太多人兴冲冲买回一块 STM32F103 板子,结果在装软件、找驱动、配环境这几步上耗掉一整个周末,最后板子吃灰。这篇内容就是写给刚拿到板子、或者正准备下单的朋友,把 STM32F103 入门这条路上该踩的、不该踩的坑一次讲清楚。不管你是电子专业的学生、想转嵌入式的程序员,还是纯粹想折腾硬件的爱好者,只要跟着走一遍,至少能少走两三个晚上的弯路。
1. 拿到 STM32F103 开发板先别急着上电
1.1 开箱之后第一件事是核对板子版本和核心芯片
很多人拆开包装就直接插 USB 线,这个习惯不太好。STM32F103 这个系列本身有很多细分型号,最常见的几种是 C8T6、RCT6、ZET6,区别主要在 Flash 容量、RAM 大小和引脚数量上。C8T6 是 64KB Flash、20KB RAM、48 引脚,RCT6 是 256KB Flash、48KB RAM、64 引脚,ZET6 则是 512KB Flash、64KB RAM、144 引脚。你买到的板子上焊的是哪一颗,直接决定了后面能跑多复杂的程序。
我一般拿到板子会先做三件事:第一,用放大镜或者手机微距拍一下芯片表面的丝印,确认型号;第二,翻一下板子背面,看看有没有标注版本号,比如 V3.0、V3.1 这种,不同版本的外设布局可能不一样;第三,对照卖家给的资料包,确认板载的晶振频率是 8MHz 还是 12MHz,这个直接影响后面时钟树的配置。
注意:有些低价板子会用 STM32F103C8T6 打磨后冒充 RCT6 卖,丝印模糊或者字体不对就要警惕。正规渠道买的板子一般不会有这个问题,但二手或者来路不明的要留个心眼。
1.2 上电前必须确认的供电和跳线状态
STM32F103 的供电范围是 2.0V 到 3.6V,大部分开发板通过 USB 的 5V 经过板载 LDO 降压到 3.3V 给芯片供电。上电之前要检查几个地方:BOOT0 和 BOOT1 跳线帽的位置,这决定了芯片从哪里启动。正常跑程序的时候 BOOT0 接 GND、BOOT1 随便,也就是从主 Flash 启动。如果要通过串口下载程序,需要把 BOOT0 接到 VCC,下载完再拨回去。
还有一个容易被忽略的点是 USB 接口的类型。有些板子用的是 Micro USB,有些是 Type-C,还有些老板子是 Mini USB。线材不对的话,插上去没反应,新手很容易以为是板子坏了。我建议拿到板子先用手电筒照一下接口,确认类型再找线。
另外,如果你买的板子带 ST-Link 或者 DAP-Link 调试器,要确认调试器的排线有没有插反。排线上一般有防呆凸起,但有些廉价排线没有,插反了轻则识别不到芯片,重则烧调试器。我自己的习惯是插之前先看板子上的丝印,SWDIO、SWCLK、GND、3V3 这四个脚的位置记清楚。
1.3 板载外设的初步检查清单
一块典型的 STM32F103 开发板通常会带这些外设:LED 指示灯(至少一个)、按键(复位键加用户键)、USB 转串口芯片(CH340 或者 CP2102)、SWD 调试接口、以及一些扩展排针。上电之后,如果板子出厂烧了测试程序,LED 应该会闪烁或者呼吸。如果没有反应,先别慌,可能只是出厂没烧程序。
我习惯用万用表蜂鸣档快速测一下几个关键点:3.3V 和 GND 之间有没有短路,USB 的 5V 有没有到 LDO 输入端,LDO 输出是不是 3.3V。这几个点正常,板子基本就没大问题。这一步花不了两分钟,但能避免很多"板子是不是坏了"的焦虑。
2. 开发环境搭建:Keil、CubeMX 和 VSCode 怎么选
2.1 Keil MDK 依然是 STM32F103 最稳的选择
网上关于 STM32 开发环境的讨论很多,有人推 Keil,有人推 IAR,还有人用 VSCode 加插件。我的建议是,如果你是零基础入门 STM32F103,先用 Keil MDK。原因很简单:资料最多、教程最全、遇到问题最容易搜到答案。Keil 的安装包不大,装完之后需要单独装 STM32F1 的器件支持包,也就是常说的 Pack。
安装顺序是这样的:先装 Keil MDK 主程序,然后去 Keil 官网下载 STM32F1 系列的 Device Family Pack,双击安装。装完之后打开 Keil,在 Project 菜单里新建工程,选择芯片型号的时候能看到 STM32F103 系列就说明装好了。这里有个细节,Keil 5 和 Keil 4 的 Pack 不通用,现在新装的都是 Keil 5,别下错版本。
提示:Keil 的编译速度在 STM32F103 这种小芯片上完全够用,没必要为了追求编译速度去折腾其他工具链。等你把 F103 玩熟了,再考虑换 VSCode 加 GCC 的方案。
2.2 STM32CubeMX 要不要学,什么时候学
CubeMX 是 ST 官方出的图形化配置工具,可以自动生成初始化代码。对于 STM32F103 来说,CubeMX 支持得非常好,基本上点几下鼠标就能把时钟、GPIO、串口、定时器都配好。但我不建议一上来就用 CubeMX,原因是你会对底层寄存器没有概念,后面遇到问题不知道怎么排查。
我的建议是分两步走:第一周先用标准库手写代码,把 GPIO 输出、GPIO 输入、串口收发这几个基础实验做一遍,理解一下寄存器是怎么操作的。第二周开始用 CubeMX 生成代码,对比一下它生成的初始化和你自己写的有什么区别。这样既懂了原理,又享受了工具的效率。
CubeMX 生成代码的时候要注意,它默认用的是 HAL 库,代码量比标准库大不少。STM32F103C8T6 只有 64KB Flash,用 HAL 库写复杂一点的项目可能会不够用。如果 Flash 吃紧,可以在 CubeMX 里选择用 LL 库,代码会精简很多。
2.3 VSCode 方案适合什么人
VSCode 加 STM32 的方案这两年越来越流行,核心是装 Cortex-Debug 插件,配合 OpenOCD 或者 ST-Link GDB Server 来调试。这套方案的好处是编辑器体验好、免费、跨平台,缺点是配置起来比 Keil 麻烦不少,尤其是 launch.json 和 tasks.json 这两个文件,新手很容易配错。
如果你已经有一定编程基础,平时就用 VSCode 写代码,那可以试试这套方案。配置的关键是这几个文件:c_cpp_properties.json 里要指定 ARM 的头文件路径,tasks.json 里要写编译命令,launch.json 里要配调试器路径和芯片型号。网上有现成的模板可以抄,但芯片型号和路径要改成你自己的。
我自己的主力环境是 Keil 加 VSCode 双开:Keil 用来编译和下载,VSCode 用来看代码和写代码。Keil 的编辑器确实不太好用,但它的编译器和调试器对 STM32F103 的支持是最成熟的,没必要为了编辑器体验去折腾整个工具链。
3. 第一个工程:从新建到点亮 LED 的完整链路
3.1 用标准库新建工程的目录结构
标准库新建工程是 STM32F103 入门最经典的方式,虽然现在 ST 主推 HAL 库,但标准库的代码结构清晰,适合理解底层。新建工程的步骤大致是:在 Keil 里新建 Project,选好芯片型号,然后把 ST 官方标准库里的启动文件、内核文件、外设驱动文件复制到工程目录里。
目录结构我一般这样组织:根目录下建四个文件夹,分别是 Start、Library、User、Output。Start 放启动文件和内核文件,Library 放标准库的外设驱动,User 放 main.c 和用户自己写的代码,Output 放编译生成的 hex 和 axf 文件。这样分的好处是结构清晰,后面加外设的时候不会乱。
启动文件的选择要注意,STM32F103C8T6 用的是 startup_stm32f10x_md.s,md 代表 Medium Density。如果是 RCT6 或者 ZET6,要用 hd 版本。选错了启动文件,编译能过但程序跑不起来,这个坑我踩过。
3.2 时钟配置:为什么是 72MHz
STM32F103 的最高主频是 72MHz,这个频率不是随便定的,而是通过外部 8MHz 晶振经过 PLL 倍频得到的。具体路径是:8MHz 晶振输入,经过 PLL 预分频器除以 1,然后 PLL 倍频 9 倍,得到 72MHz,再经过 AHB 预分频器除以 1,作为系统时钟。这个配置在标准库的 system_stm32f10x.c 文件里已经写好了,默认就是 72MHz。
但如果你用的是 12MHz 晶振的板子,这个默认配置就不对了,需要手动改 PLL 的倍频系数。12MHz 的话,倍频 6 倍得到 72MHz。改的地方在 SystemInit 函数里,找到 RCC_PLLConfig 那一行,把参数改成对应的值。这个细节很多教程不讲,但买板子的时候如果没注意晶振频率,后面串口波特率会全乱。
3.3 GPIO 输出实验:点亮第一颗 LED
点亮 LED 是嵌入式界的 Hello World,但这里面也有讲究。STM32F103 的 GPIO 有八种模式,输出模式下常用的是推挽输出和开漏输出。推挽输出可以输出高电平和低电平,驱动能力强;开漏输出只能输出低电平,高电平需要外部上拉。驱动 LED 一般用推挽输出。
配置 GPIO 的步骤是:先开 GPIO 时钟,然后设置引脚的模式、速度,最后用 GPIO_SetBits 或者 GPIO_ResetBits 控制电平。这里有个容易搞混的地方,STM32 的 GPIO 时钟是挂在 APB2 总线上的,开时钟要用 RCC_APB2PeriphClockCmd,别用成 APB1 的。
LED 的接法有两种:共阳极和共阴极。共阳极是 LED 正极接 3.3V,负极接 GPIO,GPIO 输出低电平点亮;共阴极是负极接 GND,正极接 GPIO,输出高电平点亮。大部分开发板用的是共阳极接法,所以 GPIO_ResetBits 是点亮,GPIO_SetBits 是熄灭。写代码之前先看一眼原理图,确认接法。
3.4 下载程序:SWD 和串口两种方式
下载程序有两种常用方式:SWD 和串口。SWD 需要 ST-Link 或者 DAP-Link 调试器,接线是 SWDIO、SWCLK、GND、3V3 四根线。串口下载需要 USB 转串口模块,接线是 TX、RX、GND 三根线,还要把 BOOT0 拨到 VCC。
SWD 的好处是可以在线调试,打断点、看变量都方便,推荐作为主要下载方式。串口下载的好处是不需要额外买调试器,板载的 USB 转串口芯片就能用,但只能下载不能调试。我一般两个都留着,SWD 用来日常开发,串口用来救急,比如不小心把 SWD 引脚复用了导致连不上芯片的时候。
注意:如果 SWD 连不上芯片,先检查 BOOT0 是不是被拨到了 VCC,再检查调试器驱动有没有装好。还不行的话,把板子断电,按住复位键,点下载,再松开复位键,这叫"复位下载法",能解决大部分连不上的问题。
4. 入门阶段最容易卡住的几个问题
4.1 串口乱码:波特率、时钟、线序三查
串口乱码是新手遇到最多的问题,没有之一。排查思路按这个顺序来:第一,确认波特率是不是两边一致,常用的有 9600、115200,板子和串口助手要设成一样的;第二,确认系统时钟是不是 72MHz,如果晶振频率不对导致时钟错了,波特率也会跟着错;第三,确认 TX 和 RX 有没有接反,板子的 TX 要接串口模块的 RX,板子的 RX 要接串口模块的 TX。
还有一个隐蔽的坑是串口助手的编码格式。有些串口助手默认用 GBK,有些用 UTF-8,如果程序里发的是中文,编码不对就会乱码。发英文和数字一般不会有这个问题。我习惯先用英文测试,确认通路正常再折腾中文。
4.2 程序下载后不运行:BOOT 引脚和复位电路
程序下载成功但板子没反应,先检查 BOOT0 是不是还在 VCC。下载完之后要把 BOOT0 拨回 GND,否则芯片会一直停在 Bootloader 里,不执行你烧的程序。这个坑几乎每个新手都会踩一次。
如果 BOOT0 没问题,检查复位电路。有些板子的复位按键质量不好,按下去弹不起来,芯片一直处于复位状态。用万用表测一下复位引脚的电平,正常应该是 3.3V,按下按键变成 0V。如果一直是 0V,那就是复位电路有问题。
还有一种情况是程序里把 SWD 引脚复用了,导致下载一次之后就连不上了。解决办法是用串口下载一个空程序,或者用复位下载法。预防措施是在程序开头加一段延时,给调试器留出连接时间。
4.3 Keil 编译报错:常见错误代码对照
Keil 编译报错信息有时候不太直观,我整理了几个常见的错误和解决办法:
| 错误提示 | 可能原因 | 解决办法 |
|---|---|---|
| cannot open source input file | 头文件路径没加 | 在 Options for Target 的 C/C++ 里添加 Include Paths |
| undefined symbol | 源文件没加到工程 | 把对应的 .c 文件添加到工程目录树 |
| flash download failed | 调试器配置不对 | 检查 Debug 选项卡里的调试器型号和 Flash 算法 |
| no target connected | 调试器没识别到芯片 | 检查接线、驱动、BOOT 引脚 |
| section .text overflow | Flash 空间不够 | 优化代码或者换 Flash 更大的芯片 |
这些错误看着吓人,其实原因都很简单。我建议每次报错先看第一行,Keil 的报错信息第一行通常是最关键的,后面的都是连锁反应。
4.4 延时函数 delay 卡死的原因
delay 函数卡死是 STM32F103 新手常见问题,尤其是用 SysTick 做延时时。卡死的原因通常是中断优先级配置不对,或者 SysTick 的时钟源没选对。STM32F103 的 SysTick 可以选择 HCLK 的 1/8 或者 HCLK,如果选错了,延时时间会差 8 倍,但一般不会卡死。
真正会卡死的情况是:在中断服务函数里调用了 delay,而 delay 又依赖中断,形成了死锁。解决办法是不要在中断里用延时,或者用不依赖中断的延时方式,比如简单的 for 循环空转。虽然 for 循环延时不准,但在中断里用足够了。
还有一种情况是 delay 函数里用了 while 等待标志位,但标志位一直不置位。这通常是初始化没做对,比如 SysTick 没使能,或者重装载值设成了 0。检查一下 SysTick_Config 的返回值,如果是 1 说明重装载值太大了,超出范围。
5. 从点亮 LED 到做出一个小项目
5.1 按键输入:消抖和中断两种方式
按键是继 LED 之后第二个要掌握的外设。STM32F103 的按键处理有两种方式:轮询和中断。轮询是在主循环里不断检测引脚电平,中断是配置外部中断,按键按下时触发中断服务函数。
轮询方式简单,但占用 CPU 时间,而且需要消抖。消抖有硬件消抖和软件消抖两种,硬件消抖是在按键两端并一个电容,软件消抖是检测到按下后延时 10ms 再检测一次。我一般用软件消抖,加个 10ms 的延时,简单有效。
中断方式响应快,但配置麻烦一点,要开 AFIO 时钟、配置 EXTI、配置 NVIC。这里有个坑,STM32F103 的 EXTI 线是复用的,PA0 和 PB0 共用 EXTI0,不能同时用。配置的时候要注意引脚编号,别冲突了。
5.2 串口通信:收发数据和 printf 重定向
串口是 STM32F103 最常用的通信接口,调试、通信、下载都靠它。配置串口的步骤是:开 GPIO 时钟和 USART 时钟,配置 GPIO 为复用推挽输出和浮空输入,配置 USART 的波特率、数据位、停止位、校验位,最后使能 USART。
printf 重定向是串口调试的利器,可以把 printf 的输出重定向到串口。具体做法是重写 fputc 函数,在里面调用 USART_SendData。这样就能用 printf 打印变量值,比用串口助手手动发数据方便多了。注意要在 Keil 的 Target 选项里勾选 Use MicroLIB,否则 printf 可能不工作。
串口接收数据一般用中断方式,每收到一个字节触发一次中断,把数据存到缓冲区里。如果数据量大,可以用 DMA 加空闲中断的方式,效率更高。STM32F103 的 USART1 支持 DMA,配置好之后 CPU 几乎不占用。
5.3 定时器:PWM 输出和定时中断
定时器是 STM32F103 最强大的外设之一,通用定时器 TIM2 到 TIM5 都是 16 位的,支持向上、向下、中央对齐三种计数模式。定时器最常用的两个功能是定时中断和 PWM 输出。
定时中断的配置步骤是:开定时器时钟,设置预分频器和自动重装载值,使能更新中断,配置 NVIC。定时时间的计算公式是:定时时间 = (预分频器 + 1) × (自动重装载值 + 1) / 时钟频率。比如 72MHz 时钟,预分频 7199,重装载 9999,定时时间就是 1 秒。
PWM 输出的配置稍微复杂一点,要设置 PWM 模式、输出比较极性、使能输出。PWM 频率的计算公式是:频率 = 时钟频率 / ((预分频器 + 1) × (自动重装载值 + 1))。占空比 = 比较值 / (自动重装载值 + 1)。用 PWM 控制 LED 亮度或者电机速度都很方便。
5.4 做一个超声波测距的小项目
把前面几个外设串起来,可以做一个超声波测距的小项目。超声波模块常用的是 HC-SR04,工作原理是发一个 10us 的高电平触发信号,模块发出 8 个 40kHz 的脉冲,然后等待回波。通过测量回波高电平的时间,乘以声速再除以 2,就是距离。
实现思路是用定时器输出一个 10us 的高电平触发信号,然后用定时器输入捕获功能测量回波的高电平时间。输入捕获的配置比 PWM 输出复杂,要设置捕获边沿、捕获通道、中断。测到时间后,用公式 距离 = 时间 × 340 / 2 / 10000 计算,单位是厘米。
这个项目涉及 GPIO 输出、定时器输出、定时器输入捕获、串口打印,是一个很好的综合练习。做完这个,STM32F103 的基础外设基本就摸清楚了。
6. 入门之后往哪个方向走
6.1 标准库转 HAL 库的过渡
标准库玩熟之后,建议花点时间了解一下 HAL 库。现在 ST 的新芯片基本只支持 HAL 库,标准库已经停止更新了。HAL 库的代码风格和标准库差别挺大,但核心概念是一样的,都是配置寄存器,只是封装方式不同。
过渡的时候重点看这几个文件:stm32f1xx_hal_gpio.c、stm32f1xx_hal_uart.c、stm32f1xx_hal_tim.c。HAL 库的函数名比较长,但命名有规律,比如 HAL_GPIO_WritePin、HAL_UART_Transmit。用 CubeMX 生成一个工程,对照着看,很快就能上手。
6.2 从 F103 到其他 STM32 系列的迁移
STM32F103 是入门的好选择,但实际项目中可能会用到其他系列,比如 F4 系列性能更强,G0 系列性价比更高,H7 系列适合高性能场景。从 F103 迁移到其他系列,核心概念是通的,主要差异在时钟树、外设数量和寄存器地址上。
迁移的时候建议先看芯片的参考手册和数据手册,重点看时钟树和外设框图。然后用 CubeMX 生成一个工程,对比一下和 F103 的差异。有 F103 的基础,迁移到其他系列一般一两周就能上手。
6.3 进阶方向:RTOS、通信协议、电机控制
STM32F103 的资源有限,跑 RTOS 有点吃力,但可以跑 FreeRTOS 的裁剪版。学 RTOS 的好处是理解任务调度、信号量、消息队列这些概念,对后面做复杂项目有帮助。
通信协议方面,CAN、485、Modbus 都是工业上常用的,STM32F103 自带 CAN 控制器,做 CAN 通信很方便。电机控制方面,F103 可以驱动步进电机和伺服电机,用 PWM 加定时器就能实现。
我自己的路径是:先做几个传感器的小项目,把 GPIO、串口、定时器、ADC 都摸一遍,然后学 FreeRTOS,再做 CAN 通信的项目。每一步都不用急,把当前的东西吃透再往下走。
6.4 一些实用的学习资源和习惯
最后分享几个我自己的学习习惯。第一,每学一个外设,先看参考手册对应的章节,再看例程,最后自己写一遍。第二,遇到问题先搜错误信息,大部分问题别人都遇到过。第三,准备一个笔记本,记录踩过的坑和解决办法,下次遇到直接翻。
资源方面,ST 官方的参考手册和数据手册是最权威的,虽然厚但值得翻。正点原子和野火的教程适合入门,讲得比较细。B 站上也有很多免费的 STM32 教程,选一个播放量高的跟着做就行。
提示:不要贪多,STM32F103 的外设很多,但常用的就那几个。把 GPIO、串口、定时器、中断、ADC 这五个玩熟,就能做大部分入门项目了。剩下的用到再学,不用一次全啃完。
板子买回来只是开始,真正有价值的是动手做。哪怕只是点亮一颗 LED,也比看十篇教程强。我见过太多人收藏了一堆资料,板子却一直在盒子里躺着。STM32F103 这块板子不贵,但能学到的东西很多,从寄存器操作到 RTOS,从裸机到通信协议,足够玩上大半年。别想着一口吃成胖子,先把第一个 LED 点亮,后面的路自然就清晰了。