STM32F103这块板子我是蹲了大半年才下决心买的,说实话周围的人挺不理解——iPhone都出到16了,我还在掏钱买一颗2006年发布、主频只有72MHz的Cortex-M3单片机。但你真去翻一圈嵌入式招聘JD,或者看两眼毕业设计的选题清单,会发现F103这三个字依旧高频出现。这篇文章不卖课、不写广告,纯粹从一个刚拆箱的新手视角,把“开发板到手之后怎么学STM32”这件事从头到尾捋一遍:环境怎么搭、第一段代码跑什么、哪些坑十个新手九个踩、以及最后怎么靠这块板子练出能拿得出手的项目。我尽量把话说人话,能上代码的上代码,能给出排查步骤的给步骤,你看完照着走就行。
1. 为什么我劝你第一步先玩F103:这块“老古董”依然是入门最优解
1.1 F103在生态里的位置:资料多到能淹死人,反而不是坏事
F103是意法半导体基于Cortex-M3内核的经典MCU系列,常见的STM32F103C8T6有64KB Flash、20KB RAM,旗舰的ZET6是512KB Flash、64KB RAM。单看性能,它连现在一颗手机处理器零头的零头都赶不上,但它占了两个无法替代的优势:一是外设齐全到离谱,USART、SPI、I2C、ADC、定时器、CAN、USB全都有,你能想到的嵌入式基本功它都能练;二是生态成熟得像老城区,搜索引擎一搜,从报错到例程到毕业设计源码,几万条资料铺在你面前。
很多人觉得资料多是好事,但刚入门时恰恰会被“多”淹没。我的建议很直接:把F103当成你的第一块板子,是因为你搜到的10条教程里,至少有8条能直接跑通。你学I2C时搜“STM32 I2C例程”,出来的多半是F103的代码;你学定时器输入捕获,B站和博客上铺天盖地也是F103的演示。这种“几乎所有问题都有人踩过并写下了答案”的确定性,是新手最稀缺的东西。
1.2 同价位开发板横评:ESP32、Arduino、3588凭什么被我排除
下单之前我也纠结过要不要上别的板子,毕竟现在选择太多了。如果你正面临同样的纠结,听我说说排除理由:
- ESP32-C3等无线MCU:Wi-Fi和蓝牙是它的强项,做IoT项目确实方便,但它的开发资源高度依赖平台管理器,光是“esp32开发板包下载”就能卡住新手一晚上。而且ESP32很多外设的驱动封装太“黑盒”,你很难从寄存器层面理解单片机到底在干什么。
- Arduino系列:五分钟点亮LED确实爽,但Arduino把GPIO、中断、定时器全部抽象成了
digitalWrite和delay。用它做作品非常合适,用它学单片机底层就是在给自己埋坑——你要是只会调用封装,换到裸机环境依然无从下手。 - RK3588这类高算力开发板:它是跑Linux的应用处理器,难度层级完全不一样。你连MCU的裸机逻辑都没建立,直接上Linux板卡只会变成“照着教程敲命令”,内核设备树、交叉编译、驱动模型一堆概念砸过来,心态直接崩。
所以我最终锁定了F103的开发板。它那种“笨拙感”恰恰是优点:寄存器要自己查、时钟要自己配、中断要自己设计,你把每一步都搞清楚后,再回去看ESP32和Arduino,一眼就能看穿它们底下的封装逻辑。
1.3 挑开发板时看哪几个硬件细节
买开发板也别闭眼乱选,同一个F103系列,板子和板子的差距比你想的大。我看中三件事:
- 芯片型号:优先选STM32F103C8T6或ZET6,C8T6便宜,ZET6引脚多、外设资源全。像“STM32F103VET6”这种大芯片,后续做项目(尤其是带LCD屏或者多路PWM)不用换板。
- 调试下载方式:最好带板载ST-Link或SWD接口,没有的话至少要带USB转串口芯片。千万别买只留一个下载口、连原理图都不给的板子,后患无穷。
- 板上资源:LED、按键、可拆卸跳线帽、引出全部GPIO排针,最好预留TFT-LCD接口。F103玩到中后期一定会碰屏幕,板载接口能省一堆杜邦线。
价格方面,几十块到一百出头都是合理区间。省下的钱不如买一个独立ST-Link V2下载器,那玩意儿才是陪你最久的工具。
2. 开箱后第一小时:别急着敲代码,先把这三件事做对
2.1 工具链和驱动,别把顺序搞反了
我记得自己第一次踩坑就是环境安装:先装了各种杂七杂八的插件,结果芯片包没装,编译报错一堆,折腾到怀疑人生。正确顺序应该这样:
- 安装Keil MDK(建议5.x版本),安装时勾选需要的组件,这一步绕不开。装完先别急着建工程。
- 安装STM32F1系列器件支持包。Keil 5以后的版本,芯片支持包和IDE是分开的,你需要在Pack Installer里找到“STM32F1xx Device Support”,或者到官网下载离线pack包手动双击安装。这一步对应“stm32芯片包安装”的高频问题,注意是离线包而非在线等他慢慢转。
- 装ST-Link驱动。插上ST-Link后系统如果识别不了,去官网下最新的驱动包重装一遍。
- 装CH340等USB转串口驱动。如果你的板载下载器是国产物料,大概率要手动安装。
顺序反了的典型症状是:Keil能打开但“Device”里找不到STM32F103,或者Download时报“No ST-Link detected”。遇到别慌,按这个顺序倒回去重装,十有八九能解决。
如果你习惯用现代编辑器,VSCode + EIDE或PlatformIO也能搭STM32开发环境,但那更适合已经有基础的人。刚开始学,我强烈建议留在Keil里:网上十个教程九个是Keil界面,你有任何操作失误都更容易对照排查。
2.2 芯片第一脚怎么确认:新手烧板高发区的正确姿势
你可能觉得“第一脚”这种问题很弱智,但把它拿出来说真不是废话——我见过不止一个新手把SWD的四根线插反,或者杜邦线接到相邻引脚上,然后对着冒烟的板子发呆。
确认第一脚的办法有三个,按优先级排序:
- 看丝印:芯片表面的圆形凹点或缺角旁边,就是1脚。F103大多在左上角。
- 看原理图:开发板卖家都会给原理图,找到MCU封装的引脚序号,和实物丝印一一对应。
- 看板载标识:开发板丝网上通常会用小圆圈标注1脚,排针附近也有文字编号。
接到ST-Link时,遵循SWDIO、SWCLK、GND、3V3“四线法”,芯片3V3接下载器3V3。接到开发板的USB串口下载时,TXD接芯片的RX1,RXD接芯片的TX1,交叉接,别同名单接。
2.3 标准库还是HAL库:先把这个选择题做了
现在的STM32教程分两大流派:老一点的教学多用标准库(Standard Peripheral Library),新一点的用STM32CubeMX + HAL库。我刚开始学的时候也是犹豫了半天,最后的结论是:先把标准库学会,再切换到HAL。
为什么?标准库是对寄存器的轻量封装,你写GPIO_SetBits时脑子里还能隐约想到背后有个ODR寄存器在变。HAL库封装层次更深,隐藏了寄存器和寄存器映射,新手容易“会用但不懂”。标准库虽然官方不再更新,但F103的老项目、老代码、绝大多数教材都还是它,你能搜到的经典例程,基本还是这个套路。
下面这段代码,就是标准库点亮板载LED的核心逻辑,你先跑通它,再往后走:
#include "stm32f10x.h" void Delay(volatile uint32_t n) { while (n--); } 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(500000); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay(500000); } }这段代码干的事就三件:打开GPIOC的时钟、把PC13配成推挽输出、然后循环拉高拉低。跑通它,你就理解了“寄存器外设驱动”的基本套路——任何外设无非就是在做“开时钟、配模式、操作它”三步。
3. 从点灯到实用外设:学习顺序决定你三个月后的水平
3.1 GPIO点灯只是入场券:中断是第一道分水岭
很多新手点完灯就以为自己会单片机了,这是最大的错觉。点灯只证明你学会了GPIO输出,离入门还差一个关键概念:中断。
CPU在一个死循环里翻转LED,和在工作时随时响应外部事件,这是两个境界。中断是什么?通俗讲就是你正在做饭,门铃响了,你放下锅去开门,拿完快递再回来接着炒菜。门铃就是中断源,接快递的过程就是中断服务函数。F103里有个NVIC中断控制器,专门管理这些“门铃”的优先级和开关。
学完GPIO输出,紧接着学GPIO输入(按键检测)和外部中断EXTI。拿F103的PA0做按键实验,按下触发一次中断,LED翻转一次。中断服务函数不要放耗时操作,否则会卡住主程序,这是所有MCU工程师都懂的铁律。你把中断学会,后面定时器、串口、ADC全都会顺很多。
3.2 定时器、串口、ADC:这三个外设串起来才算入门
F103最值得花时间的就是它的定时器资源。别看这颗芯片老,它有多个高级定时器,能做定时中断、PWM输出、输入捕获。我的学习顺序是这样的:
- 定时器定时中断:替代delay,让程序能在精确时刻做事情,比如每1ms翻转一次IO。
- 定时器PWM输出:控制电机转速、舵机角度、LED亮度。对应“stm32控制伺服电机485”这种需求,本质就是PWM信号加通信协议。
- 定时器输入捕获:测量外部信号的频率和脉宽。你做“stm32超声波测距”,就是发一个触发脉冲,然后利用输入捕获测Echo引脚高电平的持续时间,距离就等于声速乘以时间除以2。
然后就是串口UART。别只是收发字符串,要会重定向printf到串口。在Keil里勾选MicroLIB后,写这几行代码就能用printf调试:
int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }这一步务必搞懂,后面的PID调试、数据打印全靠它。printf不仅能打印“hello world”,还能把传感器数值、PWM占空比、温度曲线全部输出到上位机,配合VOFA+或者匿名助手,你能以图形化方式看到变量的实时变化。串口调试PID就是这么玩的:一边让小车电机转,一边用串口打印速度反馈,观察曲线如何收敛。
3.3 超声波、屏幕这类项目习题,正好用来消化外设
我见过很多人学完外设最后忘了,原因是只做“实验”不做“项目”。我的经验是:点灯只是认识GPIO,你至少要用两块模块做点小东西,知识才会焊死。
比如HC-SR04超声波测距模块,这其实是定时器输入捕获的经典练习。你不需要理解声波物理,只要搞明白:Trig引脚拉高10us触发,Echo引脚会返回一个高电平,高电平的持续时间就是超声波往返时间。用定时器输入捕获去量这个时间,算出来距离,再通过串口打印出来,一个最简单的测距仪就完成了。
再比如stm32使用ILI9341驱动的TFT屏,最典型的问题是读ID读回来是0xA1A1。这通常说明数据线和电源没问题,但初始化序列不对,或者读ID指令和屏厂不匹配。遇到这种问题真正的锻炼价值是:你得学会看数据手册、查初始化参数、用逻辑分析仪确认时序。这不比背一百个API有用多了?
还有DS3231这种I2C高精度时钟模块,用来做万年历、时钟屏。I2C的学习本身就涉及时序、上拉电阻、应答位,配合DS3231现实场景学起来非常快。我建议把“超声波测距+OLED显示”或者“DS3231时钟+TFT屏”作为中期目标,这两个小项目能覆盖中断、定时器、I2C、USART四大核心外设。
4. 新手期最容易卡住你的四个坑(我当年每个都踩过)
4.1 delay卡死:主频配置与优化等级的爱恨情仇
“stm32延时函数delay卡死”绝对是热搜级别的坑,我当年也卡了整整一晚上。现象很诡异:代码编译下载成功后,LED不闪,板子像死了一样。
排查链路是这样的:
- 先看时钟配置:F103上电默认走HSI内部8MHz时钟,如果你没在系统启动时切换到HSE外部8MHz晶振并PLL倍频到72MHz,而你的delay是拿72000000这种参数算出来的,那么延时会变成实际时间的9倍,看起来就像卡死。
- 再看delay实现方式:如果用简单
while(n--);,变量请写成volatile uint32_t。被优化后的空循环可能直接被编译器删掉,变成无限循环或干脆不延时。 - 再看优化等级:Keil里Options -> C/C++的Optimization如果开到
-O2,某些循环会被优化出问题。新手阶段先保持默认-O0,排除一个变量再加优化。 - 最后看delay是否和中断冲突:如果在中断服务函数里调用了一个依赖全局变量的非重入delay,会发生不可预知的卡死。中断里面不要用delay,要用标志位或定时器。
如果点灯程序只是“不闪”,别一上来怀疑芯片坏了,从时钟、编译优化、循环变量这三个位置查起,九成问题出在这。
4.2 芯片包安装失败、ST-Link连不上:开发环境三个隐形雷
环境问题是劝退新手的第一大杀手,很多人在“还没开始学”就已经放弃了。常见症状和根因如下:
| 症状 | 根因 | 解决思路 |
|---|---|---|
| Keil里找不到STM32F103 | 器件支持包没装 | 手动安装离线pack包,注意版本 |
| Download时报No Target Connected | ST-Link驱动异常 | 重装驱动,换USB口 |
| 下载时提示“Cannot access target” | SWD接线松、目标板没上电 | 检查四根线顺序,给板子单独供电 |
| 下载经常失败但重启就好 | 线太长、接触不良 | 换短杜邦线,或直接焊接 |
| keil5安装后C51工程也打不开 | 兼容包未装 | 若需要51和STM32共存,安装C51支持包 |
我唯一想强调的点是:ST-Link的SWD线能短就短。曾经我被一根30厘米的杜邦线坑了三天,总是下载到一半失败,最后换了一根15厘米的线立刻稳定。高速信号在长导线上的信号完整性问题,新手阶段感受不到,但迟早会懂。
4.3 SWD和JTAG被禁用后程序下不进去:新手的终极噩梦
“stm32禁用jtag”这个话题跟着F103一直热到现在。原因也很简单:PA13、PA14、PA15这几个引脚默认是SWD/JTAG调试口,很多人学到GPIO复用后想解放这几个引脚,于是调用了GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);,全禁用了。然后——再也连不上调试器了。
解决办法分两种情况:
- 如果你还有串口ISP能力:把板上的BOOT0跳线拉高,BOOT1拉低,重新上电,芯片会进入系统存储器启动模式。此时用串口下载工具擦除Flash(全片擦除)即可,调试口就恢复了。擦完记得把BOOT0跳回低电平。
- 如果你用的是ST-Link:在Keil的Flash Download设置里把“Reset under Reset”或“Connect under Reset”勾上,有时候能救回来。但最稳妥的还是ISP擦除。
所以我的建议是:学GPIO复用时,可以试着重映射,但先别把SWJ全部禁用。你至少要保留SWD口,否则每次调试都要跳BOOT0,会崩溃的。想释放那几个引脚,成熟的板子一般会把调试口通过排针单独引出来,或者在试用完JTAG之后,只禁JTAG而保留SWD(GPIO_Remap_SWJ_JTAGDisable)。
4.4 USB设备、CAN这种高级外设,别在错误时机硬刚
热词里有个“stm32 如何做usb设备”,很多人拿到板子第一件事就想把它模拟成鼠标键盘。想法很好,但F103的USB是全速设备,要求系统的48MHz时钟必须精准,而48MHz通常由PLL输出分频得到——这意味着你得先把RCC时钟树彻底搞明白,否则USB要么枚举失败,要么疯狂掉线。
我的建议是排在“点灯、串口、定时器、中断”之后再做USB。一旦永安串口都熟了,再做USB HID鼠标或者虚拟串口,你只需要对着官方例程改改描述符就行。类似的还有“stm32 can通信突然连不上”这类问题,CAN总线的坑主要在于:末端要有120Ω终端电阻、收发器要正常供电、CANH和CANL不能接反、波特率必须和大家一致。连不上时按这个顺序用万用表和示波器排查,几分钟就能定位。
很多新手觉得CAN、USB这些名字听着高级就想先学,但它们的调试难度完全建立在前面的基础功底上。如果你连定时器的时钟分频都算不利索,去调USB的48MHz只会雪上加霜。
5. 把F103玩到能独立做项目的程度:我的进阶路线图
5.1 从开发板到自制板:最小系统设计的几个硬指标
开发板的作用是让你低成本学习,但真正找工作或者做产品,你不可能带着一块大开发板出门。玩到第二个月,我开始自己画F103的最小系统板。
一个能稳定跑起来的最小系统包括:STM32F103C8T6主控、8MHz外部晶振和两个20pF负载电容、复位电路、BOOT0下拉电阻、3.3V电源电路(一般用AMS1117-3.3)、SWD调试接口,以及分布在VDD附近的0.1uF去耦电容。这套组合下来,板子就能跑。
有几个细节值得多说一句:
- 晶振离芯片越近越好,走线短而直。
- 每个电源引脚旁边都要放去耦电容,C8T6的VDD和VDDA一个都不能漏。漏了可能出现工作不稳定、ADC读数飘的怪问题。
- 复位脚要加上拉电阻和接地电容,不要悬空。
- 想画四层板就尽量双层起步,市面上的开发板布局就是最好的参考。PCB工艺用嘉立创几块钱就能打样,完全不是门槛。
做完最小系统板之后,你会发现之前学的外设全都串起来了:你用定时器PWM驱动电机,用串口下载调试,用ADC采集电流,一个真正属于自己的“产品原型”慢慢成型。
5.2 从标准库迁到HAL/CubeMX的正确时机
标准库学到能看懂GPIO_Init这些结构体配置,并且能独立写一个串口收发程序后,我建议开始接触STM32CubeMX和HAL库。为什么要转?因为新出的STM32系列(比如G0、H7)官方只支持HAL库,而且CubeMX能根据你想用的外设自动生成初始化代码,省去大量查手册配寄存器的时间。
常见的学习曲线是:一开始会觉得HAL库很啰嗦,HAL_GPIO_WritePin和HAL_UART_Transmit这类函数封装了一层又一层。但好处是代码结构统一、换芯片时迁移方便,而且CubeMX生成的工程把时钟树、引脚复用和中断优先级都配置好了,你只需要填业务逻辑。
我的判断标准很明确:只要你能独立说出“GPIO_Mode_Out_PP”背后的物理含义,就可以切换到HAL了。因为你已经知道底层是什么,用HAL就是在“抄近路”,而不是在黑盒里裸奔。迁移时机选在做一个稍微复杂一点的项目时,比如鱼缸控制器:一个定时器控制加热棒通断、一个ADC读取水温、一个串口接ESP8266联网上报到巴法云,这种项目用CubeMX先初始化,快得多。
5.3 用VSCode搭建开发环境:尽早脱离Keil的舒适区
等你熟练了Keil,可以试试用VSCode + EIDE插件 + arm-none-eabi-gcc + OpenOCD来构建STM32工程。这一步不是炫技,而是提前适应现代嵌入式开发流程:代码跳转、Git版本管理、格式化、CMake构建,都比Keil舒服得多。
配置过程中几乎人人都会碰到的就是调试器设置,对应热词里“vscode stm32调试powerlink如何设置launch.json”。不管项目里有没有powerlink协议栈,你都需要一个能正常启动GDB调试的launch.json:
{ "version": "0.2.0", "configurations": [ { "name": "STM32 Debug", "type": "cortex-debug", "request": "launch", "servertype": "openocd", "configFiles": [ "interface/stlink-v2.cfg", "target/stm32f1x.cfg" ], "executable": "build/led.elf" } ] }关键就在configFiles两行:第一行是指定用ST-Link作为调试器,第二行是指定芯片目标。很多人的launch.json报错,十有八九是这两行配置和你的下载器/芯片不匹配。
5.4 用项目收尾:鱼缸控制器、平衡小车、Modbus从站
最后一个阶段,我称之为“带着目的去学”。纯学外设的尽头是迷茫,项目才是让知识互相咬合的齿轮。以下三个方向我都试过或拆解过,按难度递增排列:
- 鱼缸控制器:DS18B20测温、继电器控制加热棒、OLED显示、按键设置温度阈值、可选的联网上报。覆盖GPIO、ADC或单总线、定时器、显示、人机交互,做完你对“系统设计”四个字有体感。
- 两轮平衡小车:MPU6050姿态解算、编码器测速、定时器PWM驱动电机、PID闭环控制,这就是热词“stm32串口调试pid”的最佳实战场。项目本身要处理电机转向、零点漂移、PID调参,能把前面所有知识点打得稀碎再重组。
- Modbus从站:用F103实现一个Modbus RTU从站,响应主站的寄存器读写,上位机用Modbus Poll控制灯的亮灭或电机的转速。“agile_modbus stm32”这种热词说明很多工业自动化方向的人在干这件事,搞懂它,你去碰CANopen、EtherCAT这些工业总线时不会慌。
做完这三个项目,你看开发板的角度会彻底改变:它不再是“学F103的开发板”,而是一个你能随意组装、替换、裁剪的积木。
我个人玩F103最深的体会是:这颗芯片本身没什么了不起的技术含量,真正有价值的是它逼你养成的调试习惯——读手册前先百度、写代码前先想时钟、连不上先量电压。这不是某个外设的实验报告,而是一整套嵌入式工程师的肌肉记忆。接下来我打算把这类踩坑记录、例程改动和最小系统打板过程持续整理成系列内容,保持更新,想一起学STM32的朋友可以守着后续的动态,每篇都会像这次一样,给足代码和排查思路。