STM32F103开发板到手,先把这条学习路线理顺
板子刚拿到手,别急着插线、别急着点灯,先把思路理顺。STM32F103这款芯片,在嵌入式圈子里几乎是“入门标配”,不是因为它的性能有多爆炸,而是因为它太典型了——Cortex-M3内核、丰富的外设、海量的教程、便宜的板子,几乎你能搜到的所有嵌入式问题,最后都会落到这颗芯片上。这篇文章就围绕STM32F103开发板,从环境搭建、工程创建、核心外设到实战项目,再到调试点位,把新手最常踩的坑和最值得花时间的地方,一次性说清楚。
内容比较多,适合刚把板子买回来、还没有明确学习计划的朋友,也适合已经点过灯、但是对定时器、串口、CAN这些外设还停留在“能跑就行”的开发者。哪怕是纯粹做毕业设计,这套流程也一样用得上。
1. 为什么F103开发板是入门的“标准答案”
1.1 一颗芯片撑起整个MCU学习路径
STM32F103C8T6这颗芯片,几十块钱就能买到,板子更是几十块到几百块不等。它内部有什么?72MHz主频、20KB RAM、64KB Flash,这个配置放在今天不算高,但学习完全够用——GPIO、定时器、USART、I2C、SPI、ADC、DAC、CAN、USB,几乎所有MCU该有的外设它都有。也就是说,你在这块板子上学会的东西,换到F4、H7、甚至国产GD32、AT32上,都是同样的思路。
我经常跟新手说一句话:玩STM32,不是在学某一种芯片,而是在学一类芯片。你把F103的寄存器手册看懂了,换任何一款Cortex-M3/M4的芯片,翻手册的路径都是通的。F103的资源少,反而逼迫你去做底层机制的理解,比如时钟树是怎么走的、外设总线是怎么分的、中断优先级是怎么嵌套的。这些东西在高端芯片上反而容易被封装隐藏掉。
1.2 Cortex-M3与标准外设库的历史地位
F103用的是ARM Cortex-M3内核,虽然已经推出了十几年,但它的中断系统、存储映射、指令集,依然是嵌入式开发的“通用语言”。很多国产芯片(像GD32、APT32)在设计上兼容STM32F103,你在这块板上写的代码,改改引脚定义就能跑到国产芯片上,这在工程上是巨大的便利。
学习F103还有一条隐性红利:资料密度。市面上关于F103的教程、源码、答疑,加起来可以用“海量”形容。无论你是想看寄存器版的硬核讲解,还是标准库版的手把手教学,或者HAL库的图形化配置,都能找到对应材料。所以第一步选型,F103就是“标准答案”。
2. 从零搭建开发环境:Keil5、芯片包与调试器
2.1 Keil5兼容C51和STM32的安装方式
很多人在Keil5上栽过跟头:装完发现建不了STM32工程,一查才知道芯片包没装。Keil5和旧版Keil4不一样,它把设备支持包(Device Family Pack)拆出去了。默认安装的MDK只带ARM编译器,不带具体芯片的库文件包,你需要先到Keil官网下载STM32F1系列的DFP,双击安装到Keil的安装目录下。
Keil5另一个常见的坑是C51和MDK共存。如果你同时用51单片机和STM32,建议把C51和MDK装在同一个Keil安装目录下,这样你打开一次Keil,就能自由切换。装的时候顺序其实无所谓,只要路径一致,最后生成的UV4.exe会被共用的C51和ARM编译器识别。如果装成了两个独立目录,会出现“打开一个另一个就没了”的错觉,其实是两个进程共用了一个IDE,工程属性里的编译器选项不同导致的。
2.2 STM32芯片包离线安装:没有网络的应急方案
芯片包安装不上,最常见的原因就是服务器连接慢或者公司网络限制。解决方法是下载离线包。到Keil官网的DFP页面,找到STM32F1系列的pack文件(通常是Keil.STM32F1xx_DFP.x.x.x.pack),下载完后直接双击,Keil会自动识别为插件包并完成安装。
还有一个细节:如果双击pack没有反应,试着用管理员身份运行Keil,再把pack文件放到一个纯英文路径的目录里,避免中文目录导致的解压失败。安装成功后,你在新建工程时就能看到STMicroelectronics目录下出现了STM32F103C8、F103ZET等具体型号,这时才算环境就绪。
2.3 下载调试器的选型:ST-Link、J-Link、DAP
买开发板时,商家通常有两种方案:板载ST-Link/V2,或者外接DAP下载器。我自己建议新手选择板载ST-Link版本,因为它不需要额外接线,一根USB线就既供电又下载和调试。
如果你用的是不带板载调试器的板子,就需要一个外接调试器,推荐DAP-Link或ST-Link V2。J-Link虽然强,但盗版克隆版本在学习场景下性价比有限,而且newer版本的MDK会检测固件,出现“The connected J-Link is defective”的提示,不如直接用ST-Link生态。选调试器的时候注意区分SWD和JTAG模式,STM32默认支持这两种下载方式,但SWD只用两根线(SWDIO和SWCLK),特别适合板子空间有限、引脚紧张的初学者。
3. 新建第一个工程:标准库还是HAL库?
3.1 标准库新建工程的完整解剖
很多菜鸟跟着教程建工程,总是少文件,编译报错一堆。标准库新建工程时,最怕的是库文件、头文件路径、宏定义三者对不上。以标准外设库V3.5为例,一个最简工程需要这几个部分:
- 启动文件(startup_stm32f10x_hd.s或md.s),记得区分芯片型号密度;
- 内核相关文件(core_cm3.c/h),这些是CMSIS核心;
- RCC系统初始化相关代码,尤其在用外部晶振时要配置PLL倍频;
- 标准库的src目录下,你用到哪些外设,就把对应的.c文件加进来,不要全加;
- 宏定义:STM32F10X_HD、USE_STDPERIPH_DRIVER这两个必须写对。
头文件路径也别漏了,包括Libraries下的CMSIS文件夹和FWlib的inc文件夹。我见过很多新手在路径设置里把include路径填成了lib文件夹的上级目录,编译就会爆出一堆“stm32f10x.h: No such file or directory”。
3.2 点亮第一颗LED:GPIO的完整配置流程
点亮LED是嵌入式界的“Hello World”,但这一步背后的机制值得吃透。GPIO有输入输出、复用、模拟四种模式,F103的每个端口都挂在APB2总线上,所以用GPIO前必须先开时钟。下面是标准库版本的一个完整示例:
#include "stm32f10x.h" void Delay(void) { for (uint32_t i = 0; i < 1000000; i++); } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay(); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay(); } }这里有个新玩家常见的疑问:为什么点灯之前要开时钟?因为STM32为了低功耗,外设时钟默认是全部关闭的,你如果不使能GPIOC的时钟,寄存器写的值根本无效,灯就永远不亮。这个“先开时钟再操作外设”的思路,贯穿整个F103学习生涯。
3.3 寄存器、标准库、HAL库:三套方案的取舍
学习F103会遇到一个问题:我应该学库还是学寄存器?我的答案很直接:
- 如果你目标是搞懂原理、做底层驱动,寄存器版是最透彻的路径,你要自己去写RCC寄存器,自己去查GPIO配置位;
- 如果你目标是快速上手、做项目、毕业设计,标准库版是平衡点,它把寄存器操作封装成函数,但不隐藏关键流程;
- 如果你目标是走ST生态、用CubeMX快速生成多平台代码,HAL库是趋势,但它的层级厚,出错后排查难度更高。
就F103而言,标准库材料最丰富,且代码量适中,我推荐新手先用标准库入门,把GPIO、定时器、串口调通,再逐步迁移到HAL。很多人一上来就学HAL,结果连RCC时钟树在哪配置都不清楚,遇到问题完全无处下手。
4. 吃透定时器:时序控制与波形生成的核心武器
4.1 定时器工作的本质:预分频、计数、重载
STM32F103内部的定时器资源很丰富,有基本定时器TIM6/TIM7、通用定时器TIM2/TIM3/TIM4/TIM5,还有高级定时器TIM1/TIM8。学习定时器时,永远要抓住三个数:预分频系数(PSC)、计数周期(ARR)、时钟源频率。
PSC和ARR的关系可以用生活中的例子来类比:“预分频”就像把钟摆的摆动次数先除以N,再把每次摆动计数到上限后重新归零。换算公式很简单:定时器时钟 = 输入时钟 / (PSC+1),溢出时间 = (ARR+1) / 定时器时钟。比如72MHz输入,PSC=71,那么定时器时钟就是1MHz,也就是1微秒计一次;ARR=999,就是1毫秒产生一次更新事件,也就是1kHz的中断频率。
4.2 三种核心模式配置:定时中断、PWM输出、输入捕获
定时器中断是最基础用法,我建议新手把“1毫秒进一次中断”调通,用全局变量做计时基准。这一套搞懂,你后面的超声波测距、按键消抖、OLED刷新帧率管理,全都用得上。
PWM输出则是电机控制、舵机驱动、调光灯的底子。配置上需要把GPIO复用为定时器通道对应的引脚,再配置TIM_CounterMode、TIM_Pulse和ARR。PWM的占空比 = 比较值 / ARR。以舵机控制为例,20ms周期,0.5ms到2.5ms范围内的高电平对应舵机角度,很多人直接把PSC和ARR填错,导致输出频率不对,舵机乱抖,一查周期根本不是20ms。
输入捕获模式是测频率和脉宽的利器,常用于超声波测距回波时间测量、红外遥控解码等场景。思路是把定时器的某个通道配置为捕获模式,当引脚检测到上升沿时记录CNT值,下一次上升沿再记录,两次的差值乘以定时器时钟周期,就得到信号周期。
5. 串口通信:调试交互和数据上云的基础
5.1 USART初始化必须注意的时钟和引脚映射
F103的USART1挂载在APB2总线,USART2/3挂在APB1总线,这两条总线的时钟频率不同(APB1默认36MHz,APB2默认72MHz),波特率配置时的计算基准也不同。新手最容易在这上面翻车:USART1在72MHz下算波特率正常,同样的代码切到USART2,波特率就乱了。
另一个大坑是引脚复用。STM32的USART引脚不是随便选的,USART1固定是PA9(TX)、PA10(RX),USART2是PA2、PA3等,而且这些引脚默认可能是JTAG/SWD复用引脚,使用前要配置GPIO模式为AF_PP(复用推挽)或AF_OD(复用开漏)。很多新手把PA9配成普通推挽输出,串口发不出数据,就是这个原因。
5.2 串口对接ESP8266与“巴法云”物联网
买板子后,很多人第一个“物联网”项目就是把数据送到云平台。ESP8266模块通过串口和STM32通信,协议是你自己定的——通常是用一个帧头加数据长度加校验位的格式。用串口中断接收不定长数据时,一个经典方案是:每收一个字节进中断,存入环形缓冲区,主循环解析缓冲区内的数据帧。
巴法云这种物联网平台本质上提供了一个MQTT服务器,你把ESP8266接入网络后,让ESP8266和服务器保持连接,STM32只需要通过串口把数据发给ESP8266,ESP8266内部固件会自动向MQTT主题发布消息。我当时做这个项目时,最大的体会是分帧解析的重要性——ESP8266返回的字符串里常夹杂着“OK”“busy”之类的响应,如果没做好状态机,解析会很头疼。
6. 横纵扩展:从热搜词看常用实战方向
6.1 超声波测距:核心是时间差,不是“量距离”
HC-SR04超声波模块,触发方式是给Trig引脚一个10us以上的高电平,模块自动发射声波并输出Echo引脚高电平,高电平持续时间就是声音往返的总时间。距离(厘米) = Echo高电平时间(us) / 58,空气声速约340m/s,往返距离自然要除以2。
实现过程中,最简单的测高电平时间方式是“轮询等待Echo引脚变高,再延时等待变低,用定时器计时”。这样会阻塞主循环,但如果应用不太复杂也够用。更好的方式是输入捕获:配置好定时器的输入捕获通道,捕获上升沿和下降沿,用两次捕获的CNT差值。新手直接拿轮询方式做也行,但要清楚这个方案会有几十微秒到上百微秒的误差。
6.2 ILI9341屏幕读ID返回0xA1A1的异常排查
ILI9341是常见TFT LCD驱动芯片,读ID本应是0x9341,但很多新手会读出0xA1A1。这个问题的根源,通常是SPI时序中“D/CX位”没处理好。ILI9341的命令和数据操作由DC引脚决定,如果你读ID的指令发送时,DC电平在前导字节和后续字节之间切换错误,控制器收到的不是完整命令序列,就会返回错误ID或0xA1A1。
0xA1A1这个特定值,一般是因为液晶控制器在未正确初始化的状态下,数据线默认电平恰好组合成0xA1,或者SPI模式下读寄存器前没切换为读状态。排查时先确认初始化序列里“Sleep Out”“Display Off”这些命令是否按厂家手册一字不差地执行,再去检查SPI模式是不是模式0(空闲低电平,上升沿采样),ILI9341对SPI模式比较挑剔。
6.3 DS1302时钟模块和数码管显示:驱动思路迁移
DS1302是常用的实时时钟芯片,用三线接口(SCLK、I/O、CE/CET)通信。搜到的热词里的“普中A2开发板+DS1302+数码管时钟”项目,本质上就是一个时钟芯片驱动和一个显示驱动的组合工程。这里要提醒新手的是,DS1302对时序的时序要求很严格,读取数据时在SCLK下降沿之后I/O线上才能有效,很多人的数据读到都是0xFF,就是信号建立时间不够。
数码管本身是低成本显示方案,动态扫描时必须注意刷新率,一般在1kHz以上才不会闪烁。用定时器中断做扫描,同时完成时分秒的更新,这类小项目是最合适的综合练习。
6.4 CAN通信:为什么突然连不上?
F103自带CAN控制器,在工业控制和车身电子中广泛使用。热词里提到“CAN通信突然连不上”,这几乎是每位工程师都经历过的噩梦。根因通常有四类:
- 波特率配置不一致。CAN有两个时钟段(同步段、相位段1、相位段2),必须保证网络中所有节点的位时序参数一致;
- 终端电阻缺失。CAN总线两端只要各接一个120欧电阻,这是基本要求,少了一端电阻信号反射会导致总线错误;
- 高字节序/位序错配。CAN标准帧ID和数据字节序在不同厂商协议中定义不同;
- 总线关闭(Bus Off)。某个节点发送错误过多,控制器自动从正常状态切换到离线状态,需要重新初始化才能恢复。
排查用CAN分析仪最省事,看不到波形时先用回环模式测试本机收发。如果硬件上没有CAN收发器(一般开发板都集成TJA1050或者MCP2551),但如果你是自己做的板子,先查VCC和GND是不是接反了。
7. 新手最容易踩的坑:故障排查速查表与避坑心得
7.1 下载烧录失败的“三大元凶”
“VS Code里编译成功,却怎么也烧录不进开发板”是很多人在从Keil切到VSCode后遇到的情况。其实问题不在编译,而在烧录配置。VSCode的Embedded IDE插件或者PlatformIO需要在配置文件里指定烧录工具、接口类型、芯片型号。烧录失败原因通常有三个:
- 连接类型下载器型号配置错了,比如代码选“ST-Link”,实际插的是DAP;
- 接线松了,SWDIO和SWCLK两根线接触不良会随机失败;
- 芯片读保护开启。如果你之前烧过加密程序,或者误操作设置了RDP,调试器无法连接,需要先用“under reset”模式或ST-Link Utility去解除保护。
7.2delay函数卡死:别急着改代码,先查时钟
热词“stm32延时函数delay卡死”是非常典型的入门问题。ESDelay、标准库自带的SysTick延时函数、或者自己写的循环延时,卡死的现象背后往往只有一个核心原因:SysTick定时器的时钟源和预期不符,或者中断优先级冲突。
F103的SysTick默认使用HCLK/8作为时钟源,而很多人配置外部晶振后忘了调整SystemInit里的时钟分频参数,导致延时时间比预期长几十倍,看起来就像死循环卡住。还有一种情况是SysTick的中断优先级和别的中断一样高,中断互相抢占把SysTick的计时线程卡死了。解决办法很土但有效:先把SysTick的优先级设为最低,再看延时的实际时间,用示波器或者逻辑分析仪测一下某个GPIO翻转周期。
7.3 STM32芯片第一脚怎么确认?
这个问题看似基础,却难倒过不少焊板子的人。贴片封装的STM32,第一脚通常用三种方式标记:芯片一角上的小圆点、正面斜切角周边长方形色块、封装丝印上的“白点”。注意,STM32F103的圆形凹点在丝印上有时出现在2脚附近,不要想当然把“圆点”当第1脚,一定要以官方数据手册的Bottom View透视图为准。
7.4 STM32禁用JTAG:哪些引脚不能随意复用?
F103有几组默认引脚兼容JTAG仿真器:PA13(SWDIO)、PA14(SWCLK)、PA15(JTDI)、PB3(JTDO)、PB4(JTRST)。如果你在代码里把PA15或PB3当普通GPIO使用,会发现它们怎么都输不出电平——因为这些引脚上电后默认作为JTAG复用功能。
解决办法是在代码里先调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE),禁用JTAG功能,但这样会同时关掉SWD调试接口。如果还用SWD调试,就用GPIO_Remap_SWJ_JTAGDisable只关JTAG保留SWD。这就是为什么很多人一禁用JTAG后下载器就连接不上了的来源。
结语:几个实操项目建议
STM32F103开发板买回来,只是学习链条的第一步。我建议按下面这个顺序做一遍,能让你对F103的理解上一个台阶:
- 先做“LED呼吸灯”,用PWM调光,同时测一下PWM的频率和占空比;
- 第二步做“串口收发命令控制LED”,把串口中断、状态机、帧校验都过一遍;
- 第三步做一个传感器项目,比如超声波测距或者DHT11温湿度,把ADC或者定时器输入捕获用上;
- 第四步再做一个小屏项目,用ILI9341或者OLED显示数据,把SPI/I2C联合起来用。
在踩过一轮坑之后,你再去翻标准库源码,会有完全不同的收获。嵌入式学习从来不是看书看会的,你就拿一块板子、一根USB线、一个示例工程,慢慢折腾。折腾通了,这板子的价值就回本了。如果后面想深入研究USB设备、RTOS、甚至Linux驱动的部分,F103也提供了足够的基础。
有点可惜了,没能早些告诉大家:这块板子买到手,你花时间最大的,从来不是看视频,而是静下心把时序图和参考手册对着读,把一个外设调通。这一点,越早体会到,学得越快。