☰
STM32F103开发板入门到进阶:学习路径、外设实操与避坑指南
2026/10/5 6:36:59 网站建设 项目流程

1. 这块板子到底能带你走多远

STM32F103开发板到手的那一刻,很多人第一反应是插上USB线,看看电脑能不能认出来。能认出来,灯亮了,心里就踏实了一半。但接下来呢?面对Keil里密密麻麻的寄存器定义、标准库和HAL库的选择、各种外设的初始化代码,很容易陷入一种“知道它很强,但不知道从哪下手”的状态。我当年拿到的第一块STM32F103C8T6最小系统板,在抽屉里躺了整整两周,就是因为不知道第一步该干什么。

这篇文章就是写给刚拿到STM32F103开发板、想系统学下去的人。我不会只给你列一堆教程链接,而是把这块板子的核心价值、学习路径、实操细节和常见坑点全部拆开讲清楚。STM32F103系列基于ARM Cortex-M3内核,主频72MHz,拥有丰富的外设资源——GPIO、USART、SPI、I2C、ADC、定时器、CAN、USB等,这些外设不是摆设,每一个都能独立撑起一个完整的项目。你手里的这块板子,可能是普中、正点原子、野火或者某家小厂的兼容板,但核心芯片的架构是一样的,学会一块,换一块板子只是改改引脚定义的事。

适合谁看?如果你是电子、自动化、计算机相关专业的学生,正在为课程设计或毕业设计发愁;如果你是嵌入式新手,想从51单片机进阶到32位MCU;如果你是工作后想转行做嵌入式开发的工程师,这块板子都是性价比极高的入门选择。STM32F103的生态极其成熟,中文资料铺天盖地,社区活跃,遇到问题几乎都能搜到答案。但资料多也有坏处——信息过载,新手容易迷失。所以我会按照“先跑通、再理解、后扩展”的逻辑,把学习路径理清楚。

2. 开发板选型与上手前的硬件确认

2.1 你手里的板子属于哪一类

市面上常见的STM32F103开发板大致分三种。第一种是最小系统板,比如C8T6核心板,只有芯片、晶振、复位电路和排针,价格十几块钱,适合已经有一定基础、需要自己外接模块的人。第二种是学习型开发板,比如正点原子Mini板、普中A2,板载了LED、按键、数码管、DS1302时钟芯片、红外接收头、蜂鸣器等外设,配套教程完整,适合零基础入门。第三种是工控型开发板,接口丰富,带RS485、CAN收发器、继电器输出,偏向实际项目开发。

拿到板子后,先确认几个关键信息:芯片型号是F103C8T6、F103ZET6还是F103RCT6?这决定了Flash和RAM的大小,也影响后续代码空间是否够用。C8T6只有64KB Flash和20KB RAM,跑复杂协议栈会比较紧张;ZET6有512KB Flash和64KB RAM,基本随便造。晶振频率是8MHz还是12MHz?这直接影响系统时钟配置和延时函数的准确性。板子上的下载接口是JTAG还是SWD?现在绝大多数板子都支持SWD,只需要四根线——VCC、GND、SWDIO、SWCLK。

2.2 必备工具与软件环境搭建

硬件方面,除了开发板本身,你还需要一个下载调试器。ST-Link V2是最常见的选择,价格二十块左右,支持SWD模式,能下载程序也能在线调试。J-Link性能更好但价格贵,新手用ST-Link完全够用。USB转TTL模块也是必备的,用来查看串口打印信息,CH340或CP2102芯片的都可以。杜邦线若干,建议买公对母和母对母各一排。

软件方面,Keil MDK5是STM32开发的主流IDE,配合STM32F1的Device Family Pack使用。安装Keil5之后,需要单独安装芯片包,否则新建工程时找不到F103的器件。芯片包可以从Keil官网下载,也可以直接用Pack Installer在线安装。另一个选择是STM32CubeIDE,ST官方推出的免费IDE,集成了CubeMX配置工具,适合喜欢图形化配置的人。我个人的建议是:新手先用Keil加标准库,把底层逻辑搞清楚;等熟悉了再转CubeMX加HAL库,提高开发效率。

注意:Keil5和Keil4可以共存,但安装路径要分开。如果你之前装过Keil4用于51单片机开发,不要直接覆盖安装,否则两个环境都可能出问题。安装Keil5时选择自定义路径,比如D:\Keil_v5,避免和旧版本冲突。

2.3 芯片包安装与新建工程的正确姿势

芯片包安装失败是新手遇到的第一个拦路虎。常见原因是网络问题导致Pack Installer下载中断,或者Keil版本太老不兼容最新的Pack文件。解决办法是手动下载Pack包,双击安装。下载时注意选择对应版本,F1系列选Keil.STM32F1xx_DFP,最新版本通常是2.4.0左右。

新建工程时,标准库的目录结构要清晰。我习惯这样组织:

Project/ ├── CMSIS/ # 内核相关文件 ├── FWLIB/ # 标准外设库 │ ├── inc/ │ └── src/ ├── USER/ # 用户代码 │ ├── main.c │ ├── stm32f10x_it.c │ └── ... ├── HARDWARE/ # 硬件驱动 └── SYSTEM/ # 系统级配置

添加文件到工程时,别忘了在Options for Target的C/C++选项卡里添加头文件路径。很多人编译报错“cannot open source input file”,就是因为路径没加对。另外,宏定义里要加上USE_STDPERIPH_DRIVER,否则标准库的函数声明不会被包含进来。

3. 从点灯到串口:核心外设的实操路径

3.1 GPIO输出:点亮第一颗LED

点灯是嵌入式的“Hello World”,但STM32的点灯比51单片机复杂得多。51单片机只需要配置一个寄存器,STM32需要开启时钟、配置GPIO模式、设置输出速度。以PA5接LED为例,标准库的配置代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_5); // 输出高电平

这里的关键是时钟使能。STM32的外设默认时钟是关闭的,不开启时钟,配置寄存器写了也没用。这是新手最容易忽略的一步。另外,GPIO_Mode_Out_PP是推挽输出,能输出强高低电平;如果是开漏输出,高电平需要外部上拉。LED通常接推挽输出就够了。

实操心得:如果你点灯不亮,先别怀疑代码。用万用表量一下LED正极对地电压,如果是3.3V,说明GPIO输出正常,问题在LED或限流电阻;如果是0V,检查时钟和GPIO配置;如果是1.几伏,可能是开漏模式没上拉。这个排查顺序能帮你快速定位问题。

3.2 串口通信:打通与电脑的对话通道

串口是调试利器,没有串口,你就像在黑暗中摸索。STM32F103有3个USART,USART1挂在APB2总线上,最高速率4.5Mbps。配置串口需要设置波特率、数据位、停止位、校验位和硬件流控。常用配置是115200-8-N-1。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // PA9 TX, PA10 RX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE);

TX引脚配置为复用推挽输出,RX引脚配置为浮空输入或上拉输入。很多人TX和RX接反了,导致收不到数据。记住:开发板的TX接USB转TTL模块的RX,开发板的RX接模块的TX,交叉连接。

重定向printf到串口是提高调试效率的关键。在Keil中,需要勾选Use MicroLIB,然后重写fputc函数:

int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return ch; }

这样就能直接用printf打印变量值、调试信息,比用LED闪烁判断状态高效得多。

3.3 定时器:精准延时的核心

STM32F103有多个定时器,TIM1和TIM8是高级定时器,TIM2到TIM5是通用定时器,TIM6和TIM7是基本定时器。定时器可以用于精准延时、PWM输出、输入捕获测频率、编码器接口等。

用定时器做1ms延时中断的配置:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE);

计算过程:系统时钟72MHz,预分频72,得到1MHz计数频率,即每个计数1微秒。自动重装载值999,计数1000次溢出,溢出周期1毫秒。这个计算逻辑要理解清楚,以后改延时时间就是改这两个参数。

注意:TIM2挂载在APB1总线上,APB1的时钟频率是36MHz,但定时器时钟有倍频机制,当APB1预分频系数不为1时,定时器时钟为APB1时钟的2倍,即72MHz。这个细节在计算定时时间时容易搞错。

3.4 中断系统:让CPU不再空转

轮询方式写程序,CPU大部分时间都在空转等待。中断能让CPU在事件发生时再处理,效率高得多。STM32的中断系统涉及NVIC(嵌套向量中断控制器),需要配置中断优先级。

NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);

STM32F103的中断优先级分为抢占优先级和响应优先级。抢占优先级高的可以打断正在执行的低优先级中断,响应优先级只在同时发生时决定谁先执行。数值越小优先级越高。新手常见错误是把所有中断都设成最高优先级,导致中断嵌套混乱。

中断服务函数的名称必须和启动文件中的向量表一致,比如TIM2的中断函数是TIM2_IRQHandler,写错了就不会进中断。函数体要尽量短,不要在里面做延时或复杂运算。

4. 进阶外设与项目实战方向

4.1 ADC采样:读取模拟量的正确方式

STM32F103的ADC是12位逐次逼近型,有18个通道,最高采样率1MHz。配置ADC需要设置通道、采样时间、转换模式。采样时间越长,精度越高,但速度越慢。对于一般传感器信号,55.5个周期的采样时间足够。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟72/6=12MHz GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE); // 校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1));

ADC使用前必须校准,否则精度很差。校准分两步:复位校准和启动校准,都要等待完成。读取值时先软件触发转换,等待EOC标志置位,再读数据寄存器。

4.2 I2C与SPI:连接外部模块的桥梁

I2C和SPI是连接传感器、显示屏、存储芯片的常用接口。STM32F103的硬件I2C有已知的稳定性问题,很多开发者选择用GPIO模拟I2C。模拟I2C的好处是引脚灵活、时序可控,缺点是占用CPU时间。

以驱动OLED屏幕为例,I2C模拟的时序关键点:起始条件SCL高时SDA拉低,停止条件SCL高时SDA拉高,数据在SCL高电平期间保持稳定。延时函数用微秒级,太快了模块反应不过来,太慢了刷新率低。

SPI是全双工高速接口,驱动ILI9341液晶屏时常用。有热词提到“stm32使用ili9341读id是a1a1”,这说明屏幕控制器ID读取正常,但显示可能有问题。ILI9341的ID是0x9341,读出来是0xA1A1可能是时序问题或者读ID命令不对。检查SPI模式(CPOL和CPHA)、时钟频率和片选信号。

4.3 CAN通信:工业现场的可靠总线

CAN总线在汽车电子和工业控制中应用广泛。STM32F103自带CAN控制器,需要外接CAN收发器如TJA1050。配置CAN涉及波特率计算、滤波器设置、发送邮箱和接收FIFO。

波特率计算公式:BaudRate = APB1Clock / (Prescaler * (1 + BS1 + BS2))。APB1时钟36MHz,要得到500kbps,可以设Prescaler=4,BS1=8,BS2=7,则36M / (4 * (1+8+7)) = 562.5k,接近500k。实际调试时用示波器看波形,微调参数。

CAN通信突然连不上,常见原因:终端电阻没接(120欧姆)、波特率不匹配、滤波器配置错误导致接收不到、总线短路或断路。排查时先用示波器看CAN_H和CAN_L的差分波形,正常应该有明显的电平翻转。

4.4 超声波测距与步进电机控制

超声波模块HC-SR04的测距原理:Trig触发发送,Echo返回高电平,高电平持续时间就是声波往返时间。距离 = 时间 * 340 / 2。用定时器输入捕获功能测量Echo高电平时间最准确。

五线四相步进电机驱动,常用ULN2003或L298N。五线四相中,红线是公共端接正极,其余四根线按顺序接驱动输出。步进电机有单四拍、双四拍、八拍三种驱动方式,八拍步距角最小,运行最平滑。STM32用GPIO输出脉冲序列,定时器中断控制换相速度。

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

5.1 下载失败与芯片识别问题

ST-Link连不上芯片,提示“No target connected”,先检查接线:SWDIO、SWCLK、GND、3.3V四根线是否接好。如果接线没问题,可能是芯片进入了低功耗模式或者读保护状态。用ST-Link Utility连接,如果提示读保护,执行全片擦除解除保护。

还有一种情况是芯片的BOOT0引脚被拉高,芯片从系统存储器启动,不运行用户程序。检查BOOT0和BOOT1跳线帽,正常运行时应都接GND。

5.2 程序跑飞与HardFault定位

程序下载后不运行,或者运行一段时间后死机,可能是HardFault。HardFault的原因很多:数组越界、空指针访问、堆栈溢出、中断优先级配置错误。定位方法是在HardFault_Handler中打断点,查看LR寄存器的值判断是从哪里跳转过来的。

Keil的调试功能可以查看调用栈,但需要编译时开启调试信息。另外,栈溢出是常见原因,STM32F103的默认栈大小是0x400,如果局部变量太大或者递归太深,就会溢出。在启动文件里可以修改Stack_Size。

5.3 串口乱码与延时函数卡死

串口打印乱码,九成是波特率不对。检查系统时钟配置,外部晶振是8MHz还是12MHz,PLL倍频系数是否正确。如果系统时钟不对,所有基于时钟的外设都会出问题。

延时函数delay卡死,通常是因为中断优先级配置不当,或者延时函数里用了中断但中断没使能。还有一种情况是编译器优化等级太高,把延时循环优化掉了,用volatile修饰循环变量可以解决。

5.4 常见问题速查表

问题现象可能原因排查方法
LED不亮时钟未使能、GPIO模式错误检查RCC配置和GPIO_Init
串口无输出TX/RX接反、波特率错误交换TX/RX、确认时钟
下载失败接线松动、读保护检查SWD线序、全片擦除
程序跑飞栈溢出、指针越界查看HardFault、增大栈
ADC值跳动未校准、参考电压不稳执行校准、加滤波电容
CAN不通终端电阻缺失、波特率不匹配接120欧姆、示波器看波形
定时器不中断中断未使能、优先级错误检查NVIC和TIM_ITConfig
I2C无应答上拉电阻缺失、地址错误接4.7k上拉、确认从机地址

实操心得:遇到问题先别急着改代码,用万用表和示波器量一下硬件信号。很多“软件问题”其实是硬件连接问题。我遇到过好几次串口不通,最后发现是杜邦线内部断了,换了根线就好了。

6. 学习路径与项目扩展建议

6.1 分阶段学习路线

第一阶段(1-2周):GPIO输入输出、串口通信、定时器延时。目标是能点亮LED、按键控制、串口打印调试信息。

第二阶段(3-4周):中断系统、ADC采样、PWM输出。目标是能做呼吸灯、读取电位器、控制电机速度。

第三阶段(5-6周):I2C/SPI通信、OLED显示、传感器驱动。目标是能驱动显示屏、读取温湿度传感器。

第四阶段(7-8周):CAN通信、USB设备、文件系统、RTOS。目标是能做数据记录仪、USB键盘、多任务系统。

6.2 项目实战方向推荐

智能鱼缸控制器:STM32采集水温、控制加热棒、定时喂食、OLED显示状态、串口上报数据。这个项目综合了ADC、GPIO、定时器、串口、OLED,难度适中,适合练手。

CAN总线数据采集节点:STM32通过CAN接收传感器数据,通过串口转发到电脑,或者存储到SD卡。这个项目贴近工业应用,能学到CAN通信和文件系统。

USB HID设备:把STM32做成USB键盘或鼠标,或者自定义HID设备与电脑通信。STM32F103的USB外设支持全速设备,配合标准库的USB例程可以快速实现。

步进电机闭环控制:用定时器输出PWM驱动步进电机,编码器反馈位置,PID调节。这个项目涉及定时器、中断、PID算法,适合想深入控制领域的人。

6.3 代码组织与版本管理

项目代码超过一定规模后,要有清晰的组织结构。硬件驱动放在HARDWARE目录,每个模块一个.c和.h文件。系统级配置放在SYSTEM目录,比如延时、串口重定向。用户应用逻辑放在USER目录。

版本管理用Git,每次实现一个功能就提交一次,提交信息写清楚改了什么。这样出问题可以回退,也方便对比不同版本的差异。.gitignore文件里排除编译生成的Objects和Listings目录。

最后分享一个小技巧:在main.c的开头定义一个全局的调试开关,比如#define DEBUG_ENABLE 1,然后在需要打印调试信息的地方用#if DEBUG_ENABLE包裹。发布版本时把开关关掉,所有调试代码自动移除,不占空间也不影响性能。这个习惯我从第一个项目保持到现在,非常实用。

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

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

立即咨询