news 2026/10/2 16:42:41

STM32F103开发板从零上手:环境搭建、外设驱动与常见坑全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103开发板从零上手:环境搭建、外设驱动与常见坑全记录

板子到手的第一晚,我把它接上电脑,灯亮起来那一刻其实心里挺平静的。STM32F103这块芯片在圈子里火了多少年,资料多得翻不完,但真的自己把开发板买回来、开始跑第一个程序,跟看教程完全是两码事。这篇文章就记录我从拆箱到把常用外设跑通的完整过程,包括踩过的坑、反复试出来的参数、以及为什么我最终选了这条学习路线。如果你也是刚拿到板子不知道从哪下手的状态,这篇应该能帮你省不少弯路。

1. 内容整体设计与思路拆解

1.1 为什么选STM32F103而不是别的芯片

先说选型。现在市面上能玩的东西太多了,ESP32、K210、GD32、甚至树莓派Pico,每个都有自己的拥趸。但我最后还是选了STM32F103C8T6这一档,原因其实很朴素:资料密度。

F103的生态积累太夸张了。无论是CSDN上的博客、GitHub上的开源项目、还是各种技术群里的聊天记录,只要你搜"STM32F103 + 你想要的功能",几乎总能找到前人趟过的路。这对新手来说是巨大的优势——你不是在孤军奋战,你踩的每一个坑都有人踩过,而且大概率已经把解决方案写出来了。

更重要的是,F103的架构足够简单,但又足够完整。Cortex-M3内核,72MHz主频,64KB Flash,20KB SRAM,对于学习和做大多数普通项目完全够用。它不会像H7系列那样有复杂的双核、Cache、MPU配置把你劝退,也不会像51单片机那样除了点灯和串口之外什么都干不了。这是一个"跳一跳刚好够得着"的芯片,学习曲线最合理。

对比一下其他常见选项:

芯片/平台优势劣势适合人群
51单片机极简,上手最快外设太少,性能太弱纯入门了解寄存器
STM32F103资料最多,外设全面,价格适中性能不算强,生态偏老绝大多数电子爱好者
STM32F407性能更强,带DSP和FPU复杂度上升,资料相对F103少需要跑算法或界面
ESP32自带WiFi/蓝牙,价格便宜单片机基础属性偏弱,生态是另一套物联网方向

1.2 我规划的学习路线:先库后寄存器,但必须看得懂寄存器

刚开板的时候我纠结过一个问题:学标准库还是学HAL库?现在ST官方主推HAL,但网上的老教程又大多是标准库。我的选择是:从标准库入门,但要把寄存器看懂。

这个选择有几个考量。标准库的代码风格更接近底层,GPIO、串口、定时器的配置都是直接操作寄存器再包一层函数,你能清楚看到每一步在做什么。HAL库封装得更彻底,函数名虽然规范,但封装层级多了之后,出了问题不好排查。对于学习而言,能看懂底层发生了什么,比快速实现功能更重要。我见过太多用HAL库的同学,遇到外设初始化失败只会删掉重来,因为根本不懂库函数背后操作了哪些寄存器。

学习路径我给自己定了这么几个阶段:

  1. 点亮LED——理解GPIO输出、时钟使能、推挽和开漏的区别
  2. 按键输入——理解GPIO输入、上拉下拉、消抖
  3. 定时器——理解时基单元、预分频和自动重载的数学关系
  4. 串口通信——理解波特率、中断、printf重定向
  5. I2C/SPI屏幕——理解通信时序、从设备寄存器
  6. ADC采样——理解分辨率、参考电压、DMA

这个顺序是有讲究的。前两步建立"写代码控制硬件"的基本感觉;第三步开始接触"硬件本身有逻辑,你只是配置它"的思维转变;第四步开始建立"单片机对外通信"的能力;到第五、六步,你已经具备了做任何常见项目的基本功。

1.3 开发环境选型的思考

开发环境的坑比我想象的多。Keil还是主流,但VSCode + EIDE插件也越来越多人在用。我的建议是:前期用Keil,脾气摸透了再迁移VSCode。

原因很简单:Keil的代码提示和历史包袱虽然重,但网上任何一个教程截图都是Keil界面,你跟着操作不会迷茫。VSCode那条路,环境变量、编译链配置、烧录插件、launch.json,任何一个环节出点幺蛾子都会消耗大量精力,而这些精力本来应该花在学习单片机上。等基础打牢了,你自然知道怎么迁移。

2. 核心细节解析与实操要点

2.1 拿到板子第一件事:认料

别急着插电。先把板子上的关键器件认一遍。

芯片型号:STM32F103C8T6,这是最最常见的"蓝板",C8代表64KB Flash,T6代表LQFP48封装。翻到背面看丝印,确认具体型号。有朋友的板子是RCT6或ZET6,资源不同但玩法大同小异。

第一脚怎么确认:芯片上有个圆点或切角,那个方向就是第一脚的位置,然后逆时针数。如果你要从零画原理图或者飞线,这个必须确定。板子上一般会把1脚用方形的焊盘标示出来,留意一下。

板载外设:通常有一到两个LED(接在PC13或PB0/PB1上),一个复位按键,一个BOOT0跳线帽,一个USB转串口芯片(常见的CH340或CP2102),一个Type-C或Mini USB接口。LED和按键是第一批程序要用的,USB转串口决定了你能不能烧录和看调试信息,BOOT0跳线则是进入串口下载模式的开关。

电源路径:用USB供电时,5V经过板载稳压芯片(一般是AMS1117-3.3)变成3.3V给芯片供电。很多新手烧录失败,其实是因为用了一个只供电不通数据的USB线。这个排查点后面细说。

2.2 芯片包安装和工程创建的坑

我说一下在Keil里搭一个标准库工程的具体过程,顺便标注哪里容易出问题。

第一步,装芯片包。Keil5默认不带STM32的Device支持包,需要从Pack Installer里找STM32F1系列安装。这一步常见的坑是网络太慢装不上,或者装的是新版Pack但代码用的是旧版固件库,编译报各种语法错误。我建议下载Pack时顺手把版本记下来,后面出问题好排查。

第二步,准备标准外设库。ST已经停止维护标准库了,但网上随便都能下到3.5版本的FWLib。把整个Libraries文件夹拷贝到工程目录下。

第三步,新建工程目录结构。我习惯分成这样:

Project/ ├── User/ # main.c、stm32f10x_it.c 等主程序文件 ├── Core/ # 启动文件 startup_stm32f10x_hd.s、内核相关 ├── FWLib/ # 标准外设库的 src 和 inc ├── HardWare/ # 自己写的硬件驱动,比如 LED、KEY、UART └── System/ # system_stm32f10x.c

第四步,在Keil里配置。Device选择STM32F103C8,然后在Options for Target里设置宏定义STM32F10X_HD,这是告诉标准库"你用的是高密度器件"。忘了这个宏,编译就会出现stm32f10x_conf.h找不到之类的报错,特别坑。

第五步,设置烧录器。如果板载了DAP-Link或者你手上是ST-Link,在Debug选项卡里选择对应的CMSIS-DAP或ST-Link Debugger,再在Utilities或Flash Download里选好算法文件。F103C8对应的是STM32F10x Med-density Flash 64K。这步配错了,Keil会提示No Algorithm found,也是一道经典的坎。

2.3 第一个程序为什么选点灯

点灯不是玄学,它涉及的所有环节都是基本功:

  • 时钟使能:STM32的外设默认是关闭时钟的,你直接操作GPIO寄存器是无效的。必须先通过RCC使能对应GPIO口的时钟。这一步新手最容易忘。
  • GPIO模式配置:LED接的是推挽输出,意味着GPIO输出高电平就能驱动LED(如果LED接VCC则是输出低电平点亮)。推挽模式的本质是P-MOS和N-MOS交替导通,能主动提供高低电平。
  • 主循环延时:让LED一闪一闪需要延时。最简单的延时就是用循环空转,但这里就牵扯出后面的大坑——如果用系统滴答定时器SysTick或者定时器延时,比空循环靠谱得多。

我第一次点灯用的是标准库的轮询写法,核心代码就几句话,但这几句话背后是整条"时钟-寄存器-库函数映射"的理解链条。看懂了这几句,后面的所有外设都是类似的套路:开时钟、配模式、操作数据。

3. 实操过程与核心环节实现

3.1 从点灯到按键:GPIO输入的关键细节

点完灯,我下一个动作是接按键。这里有几个特别容易忽略的点。

上拉和下拉:按键电路一般有两种接法。按键一端接GND、一端接GPIO,这时GPIO内部要配置成上拉输入,这样平时读到高电平,按下时读到低电平。如果你的按键一端接3.3V、一端接GPIO,那就要配置成下拉输入。

常见的错误是模式配反了,结果按键按下去灯没反应,松开反而切换了。另一个坑是按键抖动。机械按键按下和松开的瞬间,触点会反复通断几毫秒到十几毫秒,如果你在循环里直接读电平,一次按下可能被程序误判成好几次。消抖的办法有两种:一是硬件上用RC电路,二是软件上检测到变化后延时10~20ms再确认电平。对学习项目来说,软件消抖就够了。

我把按键控制LED的代码写出来大家看一下:

// 按键初始化 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOB, &GPIO_InitStructure); // 主循环里做消抖判断 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { DelayMs(20); // 消抖延时 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0); // 等待释放 GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)); } }

这个写法有进步空间(阻塞在while里会拖慢主循环),但作为第一个按键程序,逻辑直观、容易看懂,后续可以改成状态机或者外部中断的版本,那都是后话。

3.2 定时器从"配置"到"捕获":数学关系必须自己算一遍

学到定时器这章,我强烈建议别只调库函数,把预分频和自动重载的公式自己算一遍。STM32F103的定时器时钟源通常是72MHz(外部晶振8MHz经过PLL倍频得到),如果你要产生1kHz的定时中断,可以设预分频PSC=71,自动重载ARR=999。

计算逻辑是这样的:定时器时钟除以(PSC+1)是计数频率,再除以(ARR+1)是最终中断频率。也就是72MHz / (71+1) / (999+1) = 1000Hz。这个公式看起来平淡无奇,但你把数字改着算过一遍之后,PWM频率、输入捕获测频率这些就全都通了。

定时器输入捕获是另一个高频需求,比如测一个外部方波的频率。捕获的意思是检测到跳变沿时,把当前计数器值存到捕获寄存器里,然后通过中断告诉你"我抓到了"。连续两次捕获的差值,除以计数频率,就是信号的周期。

有个细节要注意:当你配置定时器通道为输入捕获时,对应的GPIO要配置为浮空输入或上拉输入。我见过有人把GPIO配成复用推挽,结果根本捕获不到边沿。

超声波测距模块(比如HC-SR04)也是同样的原理。Trig引脚给一个10us以上的高电平触发,Echo引脚会返回一个电平,电平持续时间就是声波往返时间。用定时器捕获Echo的高电平宽度,除以58得到厘米数,就是这个模块测距的核心逻辑。这比单纯看教程有意思多了——你会发现定时器的知识点瞬间变成了一个可以玩的实际功能。

3.3 串口和printf:调试效率的分水岭

说实话,点了灯、通了按键之后,你对单片机还只是"能控制"。“会说话”的标志是串口,因为有了串口你才有了眼睛。

串口调试在STM32F103上用的是USART1,对应的引脚是PA9(TX)和PA10(RX)。管脚定义这东西千万别凭记忆,每一块板的丝印可能有出入,查原理图最靠谱。我自己的板子上,USART1通过板上跳线连到了USB转串口芯片,插上USB,电脑上就能识别到一个COM口。

配置USART的时候,除了波特率、数据位、停止位,还要注意是否打开中断、是否发送使能。波特率我一般用115200,速度够快,兼容性也好。

能把printf重定向到串口,是串口调试的质变。标准库的printf最终会调用fputc,你重写这个函数,让它往USART发送寄存器里写数据即可:

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

然后在魔术棒选项卡里勾选MicroLib,否则会因为编译器精简问题导致printf没有输出。这个坑也是经典,好多人重定向了打印不出来,就是忘了勾。

等你用printf把"定时器捕获到的频率是多少""ADC采到的电压是多少"打出来,你就知道这东西有多香了。之后再遇到"程序没反应"的问题,第一反应就不是瞎猜,而是加点打印看看卡在哪一行。

3.4 SPI驱动TFT屏幕:一个让人脑壳疼的经典故障

跑通串口之后,我开始接ILI9341的TFT屏幕,然后遇到了一个几乎所有人都见过的诡异问题:用SPI读LCD控制器的ID,读回来是0xA1A1。

正常来说,ILI9341的ID寄存器应该返回0x9341。读到0xA1A1什么意思?基本都是SPI通信时序不对,具体来说大部分情况是:

  • 工作模式配置错了。ILI9341在SPI模式下要求CPOL=0、CPHA=1,如果你用的SPI配置是别的极性相位,读出来的字节就全是错位垃圾。
  • 数据线没接对。MOSI和MISO是相对MCU说的,屏幕那边叫SDA(串行数据输入)和SDO(串行数据输出),如果板子的跳线或者线序有问题,读回的自然不对。
  • 没有按手册发"读命令"的前导字节。ILI9341的命令很多是16位格式,0x04是Read ID命令,发送这个命令之后需要等待一段tRR时间再读数据,没有延迟直接读也会拿到杂讯。

我当时排查了半天,最后发现是屏幕模块的SPI模式设置跳线默认接的是"3线SPI",也就是SDA双向复用、只有一根数据线,而我程序里用的是标准的4线SPI方式,所以读出来的ID整个是乱的。把跳线改成4线模式,再把CPOL/CPHA配好,ID才正常读出来。

这里就有一个通用经验:与陌生外设通信时,第一优先级不是把功能跑出来,而是先把基础的读取/ID确认跑通,证明时序和接线是对的,再往上堆功能。如果一上来就写一大堆初始化序列,出了问题根本无从排查。

4. 常见问题与排查技巧实录

4.1 编译成功但烧录不进去

这个问题的出现频率高得离谱。我整理一下排查顺序,按优先级来:

  1. 确认调试器是否被识别。Keil的Settings里能看到SW Device是否出现目标芯片。如果显示No Target,大概率是连接问题或目标板没上电。很多"蓝板"用ST-Link连接时没插外部供电,导致识别不到芯片。
  2. 确认烧录器类型。DAP-Link选CMSIS-DAP,ST-Link选ST-Link。选错了会直接提示找不到设备。
  3. 确认SWDIO和SWCLK两个引脚没被程序占用。程序里如果调用了GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE)把SWD引脚关了,JTAG/SWD就全废了,以后没法下载。好在F103有"空片自动进入ISP模式"的机制,或者按住复位键的同时点下载再松开,能从这种状态里救回来。这个问题在"stm32禁用jtag"的搜索词里出现频率也很高,真的是人人都踩过。
  4. 确认Flash算法选对。F103C8是Medium-density,选成High-density算法也能写,但有些工具会报错。

4.2 延时函数卡死

程序运行到Delay函数就卡住不动了,这通常不是延时函数本身的问题,而是时钟树配置错乱。

标准库工程中,SystemInit()会读取芯片的HSI/HSE配置,并且把时钟切换到PLL。如果外部高速晶振没有(或者配置里写了有),系统在等待HSE就绪的超时循环里出不来,表现就是程序启动就卡死。

我的板子是蓝色那种,板载8MHz晶振,一般没问题,但如果有人买的是带外部晶振但不焊接的板子,或者你把晶振旁边的两个电容焊错了位置,就会遇到这个卡死问题。排查方式是:在SystemInit里面或main最开始处点灯,看灯亮不亮。如果连灯都不亮,十有八九是时钟初始化没过。

4.3 芯片包安装异常和Keil5兼容C51的问题

用Keil5同时开发51和STM32很常见,但两个Pack混装可能出现冲突。解决方法是分别安装到同一个Keil5目录下,然后ARM的Pack和C51的Pack各自添加。具体操作是安装C51时把安装目录指向Keil5的根目录,安装完在Keil里Tools菜单能看到C51选项。如果装了C51但新建工程时没有51选项,重新以管理员身份运行Keil或者检查License。

如果Pack Installer下载很慢,可以手动去Keil官网下Pack文件(.pack后缀),双击就能安装。另外要注意版本兼容性——老的MDK4工程在MDK5下能不能编译,取决于你是否装了对应的Legacy Support包,那也是个容易忽略的东西。

4.4 常用问题速查表

我把几个高频问题的症状、原因、解决办法整理成了表格,方便收藏后直接查阅:

症状常见原因解决办法
编译报错找不到xx.h芯片包未装或宏定义缺失安装对应Device Pack,检查C/C++里宏定义
下载提示No Target connected调试器选错/线没接/板子没供电检查Debugger设置,接线,外部供电
No Algorithm foundFlash下载算法未选择在Debug设置里选择正确的Flash容量算法
程序能编译不能烧录SWD引脚被复用/BOOT0状态不对按住复位下载,或调整BOOT0再试
printf无输出未勾选MicroLib或fputc未重定向魔术棒勾选Use MicroLib,重写fputc
Delay卡死HSE启动超时/时钟树错误检查外部晶振,核对SystemInit
SPI读ID读回0xA1A1SPI模式不匹配/数据线接错核对CPOL/CPHA,确认4线/3线模式

4.5 一个和VSCode相关的小提醒

我看到很多人问"VSCode里编译成功,却烧录不进去"。这类问题通常是编译工具链和烧录工具是两个独立体系导致的。Keil里下载是"编译-链接-调用烧录器"一气呵成,但VSCode + EIDE或者PlatformIO里,编译是gcc或armcc完成的,烧录则是调用OpenOCD或pyOCD完成的,任何一环的配置断了,就出现"编译过但下不进"的奇怪状态。

排查方向是:先确认你的OpenOCD配置里interface和target是否匹配板子,再用命令行的方式手动烧录一次,看看具体报什么错。命令行能烧成功,说明问题在IDE和命令的衔接上;命令行都烧不进去,问题就在接线、调试器或者目标板本身。不要在IDE界面上猜,去命令行找日志,这是所有工具链问题的高效解法。

5. 我的实操心得与下一步打算

板子到手玩熟到现在,我最深的感受是:STM32的学习曲线陡在"不知道下一步该做什么"而不是"某个知识点多难"。跟着教程点灯、调串口都很顺利,真正卡人的是那些看似随机但必然出现的环境问题——读到0xA1A1、烧录器不被识别、延时函数卡死。这些坑每一个单拎出来都不难,难的是没人告诉你它们会出现。

我的建议是准备一个实验记录文档,每个外设从接线、初始化代码到踩过的坑,全部记录下来。别高估自己的记忆力,三个月后的你再去查一个SPI时序问题,绝对会感谢当初记录了细节的自己。

在这个基础上,后面我打算做几个更完整的小项目:用超声波模块做倒车雷达,配合OLED屏幕显示距离;用定时器PWM控制舵机和电机;再往后想把ESP8266接进来,让STM32连上网络,往云端上报数据。这些项目每一个都会把前面学的外设串起来,这才是学单片机的真正目的——不是会调某个外设,而是能组合外设变成能解决问题的能力。

如果你也刚拿到板子,或者正在纠结买什么开发板,不用犹豫了,就F103,把标准库吃透、把常用外设跑通,这个基础足够你撑起绝大多数DIY项目。后面遇到问题欢迎一起讨论,我踩过的那些坑,希望你能绕着走。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 16:41:51

教师考AI证书最容易踩哪些坑?为什么不能替代教师资格

AI证书证明的是特定范围内的AI知识或应用能力,不赋予教师资格,也不能证明持证人具备课堂管理、学科教学、学生评价和育人能力。近年来,面向教育从业者的AI相关培训证书大量涌现,不少教师出于提升教学效率、丰富专业履历的需求选择…

作者头像 李华
网站建设 2026/10/2 16:39:18

直流电机驱动板怎么选?艾思控双路/4路规格与接线实战解析

玩小车、搭履带底盘、做机械臂样机,电机驱动板是绕不开的一环。手头这款艾思控双路/4路电机驱动器,属于入门到中阶之间非常常见的一档,在很多学生竞赛项目和DIY作品里都能看到它的身影。我最早接触它是在给一台差速转向的轮式底盘换驱动板的时…

作者头像 李华
网站建设 2026/10/2 16:38:55

一堂嵌入式内存课:从布局对齐到泄漏排查的实战指南

做了十几年嵌入式开发,带过的项目从8位MCU到多核ARM Linux都有。这几年面试过不少人,也接手过不少别人留下的“能跑但有隐患”的代码,发现一个特别普遍的现象:大家写功能代码都很熟练,一碰到内存就开始含糊。你问他知道…

作者头像 李华