☰
STM32F103入门到实战:开发环境搭建与外设编程指南
2026/10/3 16:45:46 网站建设 项目流程

1. 拿到板子先别急着点灯,搞懂STM32F103到底是个啥

STM32F103这块板子,在嵌入式圈子里基本等同于学开车时的教练车——不是最快的,不是最炫的,但胜在皮实、资料多、踩坑有人陪。你手里这块开发板,核心芯片大概率是STM32F103C8T6或者RCT6、ZET6这些型号,C8T6是48脚,RCT6是64脚,ZET6是144脚,资源依次递增。C8T6有64KB Flash、20KB RAM,RCT6有256KB Flash、48KB RAM,ZET6直接拉到512KB Flash、64KB RAM。对于刚入门的人来说,C8T6完全够用,但如果你打算后面跑RTOS或者做带屏幕的项目,RCT6会更从容一些。

这块芯片的内核是ARM Cortex-M3,最高主频72MHz。72MHz是什么概念?大概就是每秒钟能执行7200万条左右的指令(理想情况下),放在2007年刚发布那会儿算是相当能打,放到现在做控制类任务依然绰绰有余。它自带的东西挺全:多个定时器(高级定时器TIM1、TIM8,通用定时器TIM2到TIM5,基本定时器TIM6、TIM7)、ADC(12位,最快1微秒转换一次)、DAC(部分型号有)、USART、SPI、I2C、CAN、USB Device(注意,F103的USB只能做设备,不能做Host,这个后面会细说)、SDIO等等。基本上你能想到的嵌入式外设,它都给你塞进去了。

为什么说STM32F103适合入门?三个原因。第一,资料多到离谱。中文的、英文的、视频的、文档的,你随便搜一个外设名字,能出来几十篇教程。第二,标准库和HAL库两条路都能走。标准库更接近寄存器操作,学起来对底层理解更深;HAL库封装度高,上手快,ST官方主推。第三,社区活跃。你遇到问题,大概率别人已经遇到过了,搜一下就有答案。我个人的建议是:先花两周时间用标准库把GPIO、定时器、串口、中断这几个基础外设摸一遍,然后再切到HAL库或者LL库去做项目。这样你对底层有感觉,后面用CubeMX生成代码的时候不会一脸懵。

买板子的时候通常会附带一些配件:USB线(注意是Mini-USB还是Type-C还是Micro-USB,别买错了)、杜邦线若干、可能还有OLED屏或者LCD屏、传感器模块之类的。先检查一下板子上的跳线帽是不是都在正确位置,特别是BOOT0和BOOT1这两个引脚。BOOT0决定芯片从哪里启动:接GND是从Flash启动(正常运行模式),接VCC是从系统存储器启动(串口下载模式)。BOOT1一般接GND就行。很多新手拿到板子发现下载不进去程序,八成是BOOT0没接对。

注意:STM32F103的IO口大部分是5V容忍的,但并不是全部。比如PA0到PA7、PB0到PB1这些带ADC功能的引脚,在作为模拟输入使用时不能接5V。具体哪些引脚是FT(Five-volt Tolerant)的,去翻对应型号的数据手册(Datasheet),别偷懒。

2. 开发环境搭建:Keil、CubeMX、VSCode三条路怎么选

2.1 Keil MDK:最经典但也最让人又爱又恨

Keil MDK(现在叫Keil MDK-ARM)是绝大多数STM32教程里用的IDE。它的优点是:编译器优化做得好,调试器集成度高,J-Link、ST-Link都能直接用,而且网上教程一抓一大把。缺点是:界面老旧,代码补全功能约等于没有,而且它是收费软件。当然,很多人用的是评估版或者社区版,代码大小限制在32KB以内。对于F103C8T6的64KB Flash来说,32KB的限制意味着你写到一半可能就超了。不过对于学习阶段的小项目,32KB基本够用。

安装Keil的时候有个坑:Keil5默认不包含STM32F1的器件支持包(Device Family Pack),你需要单独去Keil官网或者ST官网下载STM32F1xx_DFP这个包,安装之后才能在新建工程时选到STM32F103的型号。另外,Keil5和Keil4可以共存,但要注意安装路径不要冲突。如果你之前装过Keil4做51单片机,想再装Keil5做STM32,建议装在不同目录下,否则可能会互相干扰。

还有一个常见问题:Keil5的代码编辑体验太差,很多人会搭配VSCode来写代码,然后用Keil来编译和下载。具体做法是:在VSCode里安装Keil Assistant插件,配置好Keil的安装路径和工程文件路径,就可以在VSCode里编辑代码,然后一键调用Keil的编译和下载功能。这个方案我用了好几年,体验比纯Keil好很多。

2.2 STM32CubeMX + HAL库:ST官方主推的现代化方案

CubeMX是一个图形化配置工具,你只需要在界面上点一点,选择芯片型号、配置时钟树、使能外设、设置引脚功能,它就能自动生成初始化代码。生成的代码基于HAL库(Hardware Abstraction Layer),封装程度高,可移植性强。比如你要用串口,在CubeMX里使能USART1,设置波特率115200,生成代码后直接调用HAL_UART_Transmit()就能发数据,不用自己去配寄存器。

但HAL库也有缺点:代码效率比标准库低一些,因为多了一层封装;有些时候HAL库的默认配置不一定符合你的需求,需要手动改;而且HAL库的版本更新比较频繁,不同版本之间可能有API变化。我个人的建议是:新手先用CubeMX + HAL库快速做出东西来,建立信心,然后再回头用标准库或者LL库去理解底层。

CubeMX生成代码时有个选项叫“Copy only necessary library files”,建议勾上,这样生成的工程体积小,不会把整个HAL库都拷进来。另外,如果你用Keil作为工具链,CubeMX可以直接生成Keil工程文件(.uvprojx),打开就能编译。

2.3 VSCode + PlatformIO:极客路线

PlatformIO是VSCode的一个插件,它把编译器、调试器、库管理都集成在一起了。你只需要在platformio.ini文件里写几行配置,比如:

[env:bluepill_f103c8] platform = ststm32 board = bluepill_f103c8 framework = arduino upload_protocol = stlink

然后PlatformIO就会自动下载对应的工具链和库,编译下载一键完成。它支持Arduino框架、STM32Cube框架、CMSIS框架等多种开发方式。如果你之前玩过Arduino,用PlatformIO过渡到STM32会非常顺滑。

但PlatformIO的缺点是:国内下载库和工具链可能比较慢,需要配置镜像源;而且它的调试功能虽然能用,但不如Keil和IAR那么成熟。另外,PlatformIO的社区虽然活跃,但中文资料相对少一些。

2.4 工具链选择对比

方案上手难度代码效率调试体验适合人群
Keil MDK中等高好学生、传统嵌入式开发者
CubeMX + HAL低中好新手、快速原型开发
VSCode + PlatformIO中等中高中等有Arduino基础、喜欢现代化工具
IAR Embedded Workbench高最高最好企业级开发、对代码大小敏感

我个人的建议是:如果你是完全零基础,先用CubeMX + Keil的组合,把第一个工程跑起来。等你对STM32的工程结构、启动流程、时钟配置有了基本概念之后,再尝试VSCode + PlatformIO或者纯寄存器开发。

3. 第一个工程:从点亮LED到串口打印

3.1 硬件连接确认

拿到板子后,先别急着写代码。第一步是确认硬件连接。用USB线把板子接到电脑上,观察板子上的电源指示灯是否亮起。如果不亮,检查USB线是不是只能供电不能传数据的那种(有些便宜的USB线只有电源线没有数据线)。如果板子上有多个USB口,确认哪个是供电+下载口,哪个是USB Device口。

然后检查ST-Link或者J-Link调试器是否连接正确。如果你用的是板载ST-Link(很多开发板自带),直接用USB线连接电脑即可。如果你用的是外置ST-Link,需要连接SWDIO、SWCLK、GND、3.3V这四根线。注意:SWDIO和SWCLK不要接反,GND一定要共地。

注意:STM32F103的SWD接口默认是启用的,但如果你在代码里禁用了SWD或者JTAG引脚(比如把PA13、PA14配置成了普通IO),下次下载程序时可能会连不上。解决办法是:按住复位键,点击下载,然后松开复位键。或者把BOOT0接VCC,从系统存储器启动,这样芯片会忽略你的代码,直接进入下载模式。

3.2 新建工程与时钟配置

以CubeMX为例,新建工程,选择STM32F103C8T6(或者你板子上的具体型号)。然后配置时钟源:在RCC选项卡里,把HSE(高速外部时钟)设置为Crystal/Ceramic Resonator。大部分F103开发板外部都焊了8MHz的晶振。然后在Clock Configuration选项卡里,把PLL Source选为HSE,PLL Mul选为9倍频,这样系统时钟就是8MHz × 9 = 72MHz。AHB Prescaler设为1,APB1 Prescaler设为2(APB1最高36MHz),APB2 Prescaler设为1(APB2最高72MHz)。

为什么APB1要分频?因为STM32F103的APB1总线最高只能跑36MHz,而APB2可以跑72MHz。如果你把APB1也设成72MHz,定时器、串口这些挂在APB1上的外设可能会工作不正常。这个细节很多新手会忽略,导致串口波特率不对或者定时器周期不对。

3.3 GPIO输出:点亮第一颗LED

在CubeMX里找到板子上LED对应的引脚。以常见的BluePill板为例,板载LED接在PC13上,低电平点亮。在Pinout视图里点击PC13,选择GPIO_Output。然后在GPIO选项卡里,把PC13的GPIO output level设为Low(这样上电就亮),GPIO mode设为Output Push Pull,GPIO Pull-up/Pull-down设为Pull-up,Maximum output speed设为Low。

生成代码后,在main函数的while(1)循环里写:

HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(500);

编译下载,你应该能看到LED以1Hz的频率闪烁。如果没亮,先检查PC13的配置是否正确,再检查板子上的LED是不是接在别的引脚上。有些板子的LED接在PA5或者PB12上,具体看原理图。

3.4 串口配置与printf重定向

串口是调试过程中最重要的工具。在CubeMX里使能USART1,模式设为Asynchronous,波特率115200,8位数据位,1位停止位,无校验。然后生成代码。

为了能用printf输出调试信息,需要重定向fputc函数:

#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }

然后在Keil的Target选项里勾选“Use MicroLIB”,否则printf可能无法正常工作。如果你用的是GCC工具链(比如PlatformIO),需要自己实现_write函数。

配置好之后,在main函数里写:

printf("Hello STM32! System Clock: %lu Hz\r\n", HAL_RCC_GetHCLKFreq());

用USB转TTL模块连接PA9(TX)和PA10(RX),注意TX接RX,RX接TX,GND共地。打开串口助手,波特率115200,你应该能看到打印信息。

注意:STM32F103的USART1挂在APB2上,USART2、USART3挂在APB1上。如果你用USART2,波特率计算时的时钟源是APB1的时钟(36MHz),而不是72MHz。这个在配置的时候CubeMX会自动处理,但如果你手动写寄存器,就要注意这个区别。

4. 进阶外设实战:定时器、ADC、CAN和USB

4.1 定时器:不只是延时

STM32F103的定时器功能非常强大。除了基本的定时中断,还能做PWM输出、输入捕获、编码器接口等。以TIM2为例,它是一个32位通用定时器,挂在APB1上。假设APB1时钟是36MHz,TIM2的时钟就是36MHz × 2 = 72MHz(因为APB1预分频系数不为1时,定时器时钟会倍频)。

要配置一个1kHz的定时中断,计算过程如下:定时器时钟72MHz,预分频系数(Prescaler)设为72-1=71,这样定时器计数频率就是1MHz。自动重装载值(AutoReload Register)设为1000-1=999,这样每1000个计数溢出一次,溢出频率就是1MHz / 1000 = 1kHz。在CubeMX里配置TIM2,Clock Source选Internal Clock,Prescaler填71,Counter Period填999,然后使能TIM2全局中断。

在中断回调函数里写:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); } }

这样LED就会以500Hz的频率翻转,肉眼看起来是常亮的。你可以把Counter Period改成7200-1,这样溢出频率就是100Hz,LED闪烁就看得清了。

PWM输出也很常用。比如控制舵机,需要50Hz的PWM,脉宽在0.5ms到2.5ms之间。配置TIM3的通道1为PWM Generation CH1,Prescaler设为72-1,Counter Period设为20000-1,这样PWM周期就是20ms(50Hz)。然后设置Pulse为1500,对应1.5ms脉宽,舵机就会转到中位。

4.2 ADC:读取模拟传感器

STM32F103的ADC是12位的,转换范围0到4095,对应电压0到3.3V。配置ADC1的通道0(PA0),连续转换模式,使能DMA传输。这样ADC会不停地转换,DMA自动把结果搬到内存里,不占用CPU。

uint16_t adc_value; HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc_value, 1); float voltage = adc_value * 3.3f / 4095.0f;

如果你要读光敏电阻或者热敏电阻,注意分压电路的设计。比如光敏电阻和10k电阻串联,光敏电阻接VCC,10k接GND,中间点接PA0。光照越强,光敏电阻阻值越小,PA0电压越高。

注意:STM32F103的ADC输入阻抗不是无限大,如果信号源内阻太大,转换结果会不准。一般建议信号源内阻小于10kΩ。如果内阻太大,可以在前面加一个电压跟随器(运放)。

4.3 CAN通信:为什么突然连不上

CAN总线在汽车电子和工业控制里用得很多。STM32F103自带CAN控制器,但需要外接CAN收发器(比如TJA1050或者SN65HVD230)。配置CAN的时候,波特率计算比较复杂。以500kbps为例,APB1时钟36MHz,Prescaler设为4,BS1设为5个时间单元,BS2设为2个时间单元,SJW设为1。这样波特率 = 36MHz / (4 × (1 + 5 + 2)) = 36MHz / 32 = 1.125Mbps?不对,重新算:36MHz / 4 = 9MHz,9MHz / (1 + 5 + 2) = 1.125MHz,还是不对。正确的公式是:波特率 = APB1时钟 / (Prescaler × (1 + BS1 + BS2))。要得到500kbps,Prescaler × (1 + BS1 + BS2) = 36MHz / 500k = 72。如果Prescaler=4,那么1+BS1+BS2=18,BS1=15,BS2=2,SJW=1。这样波特率 = 36MHz / (4 × 18) = 500kbps。

CAN突然连不上的常见原因:终端电阻没接(CAN总线两端需要各接一个120Ω电阻)、波特率不匹配、CAN收发器供电不对、或者CAN_H和CAN_L接反了。我遇到过最坑的一次是:CAN收发器的STB引脚(Standby)悬空了,导致收发器一直处于待机模式,怎么都发不出数据。后来把STB接地就好了。

4.4 USB Device:F103的USB能做什么

STM32F103的USB外设是USB 2.0 Full Speed(12Mbps),只能做Device,不能做Host。也就是说,你可以把STM32模拟成一个USB设备,比如虚拟串口(CDC)、键盘、鼠标、U盘(MSC)等,但不能接U盘或者键盘。

用CubeMX配置USB Device,选择Communication Device Class(CDC),生成代码后,电脑上会多出一个虚拟串口。你可以用这个串口和STM32通信,速度比UART快很多。但要注意:F103的USB和CAN共用一部分RAM,如果你同时用USB和CAN,可能会遇到RAM不够的问题。具体来说,USB缓冲区占用了512字节的专用RAM,CAN也有一部分专用RAM,两者加起来可能会超过F103的RAM限制。

另外,F103的USB时钟必须精确为48MHz。如果外部晶振是8MHz,PLL配置为8MHz × 9 = 72MHz作为系统时钟,USB时钟需要72MHz / 1.5 = 48MHz。这个1.5分频是F103特有的,CubeMX会自动处理。如果你用的是12MHz晶振,PLL配置会不一样,USB时钟也要重新算。

5. 常见问题排查与避坑指南

5.1 下载失败:No Target Connected

这是新手遇到最多的问题。排查步骤:

  1. 检查ST-Link驱动是否安装。在设备管理器里看有没有“STMicroelectronics STLink dongle”或者类似设备。如果没有,去ST官网下载ST-Link驱动安装。
  2. 检查SWDIO和SWCLK是否接对。SWDIO接PA13,SWCLK接PA14,GND接GND,3.3V接3.3V。
  3. 检查BOOT0是否接GND。如果BOOT0接VCC,芯片会从系统存储器启动,不运行你的代码,但ST-Link应该还是能连上。
  4. 如果之前下载的程序禁用了SWD引脚,按住复位键,点击下载,然后松开复位键。
  5. 检查Keil的Debug设置里,是否选对了调试器(ST-Link Debugger),以及Port是否选为SW。

5.2 串口乱码:波特率不对

串口乱码最常见的原因是波特率不匹配。检查以下几点:

  1. 代码里设置的波特率和串口助手设置的波特率是否一致。
  2. 系统时钟配置是否正确。如果HSE没起振,系统会默认使用HSI(8MHz内部RC振荡器),导致实际波特率偏差很大。用示波器或者逻辑分析仪测一下PA9的波形,看看波特率是不是115200。
  3. 如果用的是USB转TTL模块,检查模块的晶振是不是12MHz。有些便宜的模块用的是1.5MHz的陶瓷谐振器,波特率误差很大。

5.3 定时器延时不准

HAL_Delay()函数是基于SysTick定时器的,默认1ms中断一次。如果你在中断服务函数里调用了HAL_Delay(),会导致死锁,因为SysTick中断优先级低于当前中断,永远进不来。解决办法是:在中断里不要用HAL_Delay(),改用简单的循环延时或者硬件定时器。

另外,如果你修改了系统时钟,HAL_Delay()的延时也会变。比如你把系统时钟从72MHz改成36MHz,HAL_Delay(1000)实际延时会变成2秒。因为SysTick的配置是基于系统时钟的,CubeMX生成的代码里会自动处理,但如果你手动改了时钟配置,就要重新初始化SysTick。

5.4 CAN通信突然断开

CAN总线在强电磁干扰环境下可能会出错。如果CAN突然连不上,检查:

  1. 终端电阻是否接好。CAN总线两端各需要一个120Ω电阻,总线上其他节点不需要接。
  2. 共地问题。如果两个CAN节点之间没有共地,差分信号可能超出共模范围,导致通信失败。
  3. 波特率是否一致。所有节点的波特率必须完全相同。
  4. CAN收发器是否损坏。用示波器看CAN_H和CAN_L的差分波形,正常应该有明显的差分信号。

5.5 常见问题速查表

问题现象可能原因解决方法
下载失败BOOT0接错BOOT0接GND
下载失败SWD引脚被禁用按住复位键下载
串口乱码波特率不匹配检查时钟配置和串口助手设置
LED不亮引脚配置错误检查原理图和GPIO配置
定时器不中断中断未使能检查NVIC配置
ADC读数跳动参考电压不稳加滤波电容
CAN连不上终端电阻缺失两端各接120Ω
USB识别不了时钟不对检查USB时钟是否为48MHz

6. 从开发板到项目:下一步怎么走

板子上的外设都摸了一遍之后,你可能会想:接下来做什么?我的建议是找一个具体的项目来做,哪怕很简单。比如:

  • STM32鱼缸控制器:用DS18B20测水温,用继电器控制加热棒,用OLED显示当前温度,用按键设置目标温度。这个项目涵盖了GPIO、定时器、单总线、I2C、中断等多个外设。
  • 超声波测距:用HC-SR04测距,用OLED显示距离,用蜂鸣器做报警。这个项目可以练习输入捕获和定时器。
  • CAN总线数据采集:用两个STM32板子通过CAN通信,一个发送传感器数据,一个接收并显示。这个项目可以深入理解CAN协议。
  • USB虚拟串口:把STM32做成一个USB转串口工具,可以替代CH340。这个项目可以学习USB CDC协议。

做项目的过程中,你会遇到各种问题,这些问题才是真正让你成长的东西。看教程只能让你知道“怎么做”,但只有自己踩过坑,才知道“为什么这么做”和“怎么做更好”。

提示:做项目的时候,养成写文档和注释的习惯。把每个模块的接线、配置、遇到的问题和解决方法都记下来。过几个月回头看,你会感谢当时的自己。

最后分享一个我个人的习惯:每次拿到一个新的芯片或者开发板,我都会先花半天时间把数据手册和参考手册翻一遍,重点看引脚定义、时钟树、电源要求和外设框图。不用全部看懂,但至少知道遇到问题的时候该去翻哪一章。这个习惯让我在后面的开发中省了很多时间。STM32F103的参考手册有1000多页,但你不需要从头读到尾,把它当成字典用就行。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询