ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32F103入门实战:点灯、串口调试与项目实践

2026/10/4 20:25:15 拓冰建站 浏览量
STM32F103入门实战:点灯、串口调试与项目实践 板子终于到手了。STM32F103这块芯片在嵌入式圈子里真的算“国民级”存在开发板、最小系统板、教学视频里露脸最多的就是它。买这块板子的理由也很简单资料多、外设全、价格便宜哪怕以后不吃这行饭拿来当业余爱好折腾也完全不亏。这篇就写给和我一样刚拿到板子、想学STM32但不知道从哪下手的人把我从拆箱、建工程、点灯、串口调试到后面准备尝试几个项目的完整过程捋一遍顺便把踩过的坑都写在里面。1. 开发板刚到手先别急着点灯把板卡信息核对一遍1.1 芯片丝印、板载外设和芯片第一脚的确认方法很多新人拿到开发板的第一反应是赶紧上电看灯亮不亮。其实更该做的是先把板卡信息核对一遍。先说芯片STM32F103有很多封装和容量版本常见的比如C8T664KB Flash、RCT6256KB Flash、ZET6512KB Flash后缀直接决定你能跑多大的程序。芯片表面的丝印一般会写清楚型号和产地批次比如“STM32F103RCT6”这行字就是型号信息。带有“T”表示LQFP封装“C”和“R”表示引脚数量C是48脚R是64脚Z是144脚。搞清楚这几点后面找资料、选工程模板才不会选错。然后打开卖家提供的原理图PDF别嫌麻烦这张图是整个学习过程最重要的地图。重点看几个位置供电部分通常是AMS1117稳压到3.3V、USB转串口芯片CH340或CP2102系列、用户LED接在哪个引脚、按键接在哪、SWD调试口引出的是哪几个信号、排针是否把USART1/I2C/SPI都引出来了。很多板子原理图里都会标注跳帽的作用比如BOOT0选择、电源选择、串口选择之类等你后面遇到“为什么程序没跑起来”“为什么串口没反应”回来翻原理图往往一眼就能找到答案。芯片上哪个脚是第1脚也常有人问。LQFP封装在芯片一角有一个小圆点或者一个内凹的斜切标记这个位置就是第1脚然后逆时针数引脚就是第2、第3……一直到最后一脚。板子上的丝印有时也会画一个小的圆点和芯片上的圆点对应。如果你拿到的是散件自己焊的板子上电之前最好用万用表量一下3.3V和GND之间有没有短路毕竟焊错一个脚、锡连到相邻引脚这种事太常见了。1.2 BOOT0和BOOT1两个跳帽决定程序怎么启动STM32的启动方式全靠BOOT0和BOOT1两个引脚的电平决定常见开发板上会用跳帽或拨码开关来切换。它的逻辑很简单BOOT1BOOT0启动位置实际用途x0主闪存Flash正常运行你自己烧写的程序01系统存储器用芯片自带的Bootloader下载程序11SRAM调试用程序掉电就丢BOOT0拉低、BOOT1随便是最常用的状态所有正常运行的固件都从主闪存启动。BOOT0拉高、BOOT1拉低是进入系统存储器也就是芯片出厂自带的串口下载程序。在早期没有ST-Link、J-Link的年代很多人就是用这个模式配合串口工具把程序烧进Flash的。用ST-Link在线调试时如果你发现烧录没问题但程序不跑十有八九就是跳帽被拨到了BOOT11/BOOT01的SRAM启动位置。1.3 电源和调试器怎么接才不会出问题开发板供电一般有三种方式USB口直接供电、外部5V接到排针、通过ST-Link/V-USB的3.3V供电。最省心的做法是电脑USB线插在开发板的USB口上板载稳压芯片会把5V转成3.3V。但要注意有的板子USB口和串口共用一个口插上去之后电脑会识别到一个串口设备这很正常别以为板子坏了。调试器我建议直接用ST-Link V2二十几块就能买到。接线只需要四根SWDIO、SWCLK、GND、3.3V。注意很多国产ST-Link上面标的3.3V其实是从目标板取电再反供出来的你要是不确定就只接SWDIO、SWCLK和GND目标板自己供电这样最安全。J-Link也能用但在Keil里驱动版本容易出幺蛾子新人阶段一份ST-Link足够。2. 搭建开发环境和第一个工程Keil还是VSCode标准库还是HAL2.1 工具链选择的自洽性比工具本身好坏更重要关于开发环境网上吵得最凶的就是Keil和VSCode孰优孰劣。我的看法是新人阶段用哪个都能学但一定要保证一套工具链从头到尾自洽不要今天看A教程用Keil明天看B教程用VSCode后天又换成PlatformIO那样光环境配置就能磨掉一个周末。Keil MDK的优点是集成度高下载、调试、查看外设寄存器都在一个界面里对寄存器原理的理解很有帮助。缺点是界面老旧、代码补全弱、工程文件乱。VSCode的思路则是用编辑器配合arm-none-eabi-gcc编译器和OpenOCD做下载调试。好处是代码提示漂亮、Git集成方便、配置文件一目了然适合有点Linux基础或者后期研究代码的你。但坏处也明显你需要自己配编译命令、烧录命令、调试配置比如如何生成ELF文件、如何指定调试器、如何写launch.json。还有一件事是GCC环境下要懂一点链接脚本也就是.ld文件它决定了程序段和数据的地址分布。比如Flash 64KB的芯片要自己改RAM区大小链接脚本写错程序可能直接跑飞。所以我通常建议新人第一周老老实实用Keil把单片机本身搞明白之后再转VSCode也不迟。2.2 标准库和HAL库到底该怎么选F103这颗芯片流行多年积累了两套主要固件库标准外设库Standard Peripheral Library简称SPL和HAL库。标准库比较接近寄存器操作代码里直接看到GPIOA、USART1这种外设结构体写起来很直接。HAL库配合STM32CubeMX图形化配置点几下就能生成初始化代码用起来更省事但封装层数多新手经常看不到背后的寄存器是怎么变的一旦出了问题反而不知道从哪里查。我给新人的建议是如果你手上教程是正点原子或野火那套经典标准库教程就踏踏实实跟着标准库走。如果你更希望先用CubeMX快速搭好工程、后面再看寄存器细节也用HAL库。千万别标准库和HAL混着抄两个库的初始化思路、函数命名、中断处理方式都不一样混着用会越看越乱。F103其实有两套甚至更多启动文件和内存映射方案只要你固定一套不死磕混搭后面学习效率高得多。2.3 用标准库手写一个点灯工程需要做对哪三件事第一件事是选择正确的器件型号比如STM32F103C8T6然后添加对应的启动文件startup_stm32f10x_md.smd对应中等容量128KB及以下就选md256KB以上选hd选错启动文件最典型的症状是程序编译通过但仿真时PC指针跳到奇怪地址。第二件事是定义编译宏一般要在C/C选项中添加STM32F10X_MD,USE_STDPERIPH_DEVICE前者让固件库知道你是哪颗芯片后者确保标准库的外设驱动文件参与编译缺少这两个宏编译报一大堆红色错误非常正常。第三件事才是真正点亮LED。LED一般接在一个GPIO上比如PB5代码思路是先打开GPIOB的时钟这一步是STM32和51单片机最大的不同。51单片机上电所有外设就绪而STM32为了省电每个外设的时钟都默认关闭。点灯代码大概是这样的GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOB, GPIO_Pin_5); Delay(300); GPIO_ResetBits(GPIOB, GPIO_Pin_5); Delay(300); }很多人按这个写完发现灯不亮最常见原因就是没开时钟或者LED其实接在别的引脚上。所以回到第一小节说的先查原理图确认LED引脚再写代码。GPIO_Mode_Out_PP是推挽输出GPIO_Speed_50MHz表示输出翻转速度对于点灯来说用什么速度都行但接高速外设时速度配置错了信号就会变形。3. 从点灯切换到串口开发期间最重要的“眼睛”3.1 USART引脚不是随手接的先看复用关系点灯只是让单片机“会动”串口才是真正让单片机“会说话”。写程序最怕的事情不是逻辑错而是根本不知道程序跑到哪一步。这时候串口就相当于开发者的眼睛能把调试信息、变量值、运行状态全打在电脑屏幕上。F103的每一个串口都有默认引脚不能随便接。最常用的USART1使用PA9作为发送TXPA10作为接收RX。注意此时这两个引脚要配置成复用推挽输出和浮空输入而不是普通的GPIO输出串口默认TX默认RX挂在哪个总线常见坑USART1PA9PA10APB2TX要和对方RX交叉接USART2PA2PA3APB1注意开启APB1时钟USART3PB10PB11APB1重映射后可到PD8/PD9串口接线有一条铁律两个设备之间必须TX接RXRX接TX。比如开发板的PA9发送到USB转串口的RXDPA10接收从USB转串口的TXD。很多新人拿着杜邦线把PA9和PA10直接短路屏幕上当然什么都收不到。还有的板子会把USART1和板载的USB串口芯片连在一起这时候你不需要额外接杜邦线直接用USB线连接电脑和开发板即可。3.2 printf重定向以及“只打印一次”的坑串口初始化完成后想让printf直接打印到串口需要重定向底层输出函数。使用标准库时把printf的输出重定向到串口发送函数代码思路如下int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }这段代码要求你已经把USART1初始化好了并且波特率、数据位、停止位配置正确电脑端选择对应波特率打开串口助手。为了能输出中文串口助手的编码一般选GBK或UTF-8和编辑器的编码保持一致否则就是乱码。F103的串口本身只是发字节不关心字节编码真正决定中文显示效果的是电脑端的解码方式。“printf只打印一次”这个问题也常遇到。原因多半是fputc里没有等待发送寄存器空或者你直接把一个非常长的字符串一次print出去串口发送缓冲区和移位寄存器跟不上后续字符丢失。解决办法就是发送一个字节后等TXE标志置位再发下一个。还有一种情况是Keil里默认开启了半主机模式printf会被转到调试器的半主机通道而不是串口程序看起来卡在printf上。如果发现点击运行后程序总在printf处不动可以在工程选项里关闭微库(MicroLIB)或者重定向时禁用半主机相关代码。3.3 串口接收从轮询到中断再到环形缓存接收数据比发送稍难一点。最简单的做法是轮询也就是不断读RXNE标志位串口收到一字节就存下来。但轮询会让CPU一直干等接收数据期间干不了别的活所以实际项目里基本都用中断。中断接收思路是配置USART1接收中断每次收到一字节就进入中断服务函数把数据存进一个缓冲区void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); if (rx_len sizeof(rx_buf) - 1) { rx_buf[rx_len] ch; } } }中断方式的好处是主程序不必死等串口平时该干嘛干嘛收到数据后靠一个标志位提醒主循环去处理。想做更稳定一点可以把这个缓冲区改成环形队列用一个写指针和一个读指针循环控制防止高频数据把缓冲区顶爆。但环形队列也有个麻烦主循环处理数据和中断写入数据会同时访问缓冲区必要时得做临界区保护比如在读取时暂时关闭串口中断。初学者用单字节标志位加上简单的数组缓存就够了先把串口跑通再考虑高效。3.4 串口调试常见问题的排查顺序现象大概率原因处理方向电脑串口助手没有反应串口号选错、波特率不一致、TX/RX接反换串口号、统一波特率、交叉接线输出乱码波特率不一致或编码不匹配查时钟和分频编码统一GBK或UTF-8能收不能发发送引脚没配成复用推挽或TXE标志没等检查GPIO模式重定向代码检查数据多一段少一段缓冲区溢出或中断太频繁调大缓冲区用环形队列中文全是问号电脑端编码不对串口助手编码切到GBK/UTF-8串口是嵌入式调试的基石如果你能熟练用串口打印运行状态后面调试I2C、SPI、CAN都会轻松不少。很多高级调试手段其实到最后都不如一个串口println来得直接。4. 按键、外部中断和Delay这些简单功能最容易卡住新手4.1 按键电路和GPIO输入模式怎么选按键看起来比LED还简单但涉及的原理不少。按键的常见接法有两种一端接GND一端接GPIO配置成上拉输入按下时读到低电平或者一端接3.3V一端接GPIO配置成下拉输入按下时读到高电平。区别就在于单片机内部的上下拉电阻到底在按下之前把引脚拉成什么状态。F103的GPIO输入模式支持浮空、上拉和下拉。按键用上拉输入代码是GPIO_Mode_IPU这样不按键时引脚通过内部上拉电阻保持高电平。也可以外部加一个10k上拉电阻再接按键效果一样。这里建议优先使用内部上拉省事。按下瞬间按键引脚会抖动大概几百微秒到几毫秒机械触点的金属弹跳会产生连续电平变化。如果程序不做消抖一次按键可能会被识别成三四次。消抖最常见的做法是检测到电平变化后延时20毫秒再读一次确认还是按下状态才执行逻辑。但有经验的开发者会提醒你delay消抖会阻塞主循环如果一个系统里同时有串口接收、屏幕刷新一次20ms的阻塞可能让整个系统卡顿。进阶一点的方案是用定时器轮询比如每2ms扫描一次按键连续10次都是同一个电平再确认状态变化。这种方法把消抖放在时基上主循环不会被拖死。对新手来说先用延时消抖把按键功能跑通理解之后再改成定时扫描。4.2 外部中断的配置以及EXTI线路共用的限制按键还可以接成外部中断按下瞬间触发中断省去主循环一直扫描电平的消耗。F103的外部中断控制器把GPIO引脚分成16条线EXTI0对应PA0/PB0/PC0等所有编号为0的引脚EXTI1对应所有编号为1的引脚以此类推。这里有个关键限制同一时刻PA0和PB0不能同时作为外部中断源用因为它们共用EXTI0线。配置外部中断的步骤是先开GPIO时钟和AFIO时钟配置GPIO为输入模式再用GPIO_EXTILineConfig函数把指定引脚的EXTI线路连接过来最后初始化EXTI。中断服务函数也有讲究EXTI0使用EXTI0_IRQHandlerEXTI1使用EXTI1_IRQHandler而EXTI5到EXTI9共用一个EXTI9_5_IRQHandlerEXTI10到EXTI15共用一个EXTI15_10_IRQHandler。进入中断后要手动清除挂起标志也就是调用EXTI_ClearITPendingBit否则中断会一直被触发程序就像卡死一样。4.3 Delay卡死从时钟树开始排查“延时函数delay卡死”是ST群里出现频率极高的问题。很多人的第一反应是延时函数写错了其实大部分情况是时钟配置或者工程配置的问题。F103的延时函数通常基于SysTick系统定时器原理是每秒触发一次、在主循环里维护一个毫秒计数器。如果芯片实际运行频率和SystemCoreClock变量不一致延时时间就会完全不准。比如外部晶振8MHz但代码里默认系统时钟72MHz结果实际运行速度只有预期的一部分延时看起来就像被拖慢了。常见的卡死场景可以分几类。第一类上电后程序停在启动文件SystemInit处这是HSE高速外部时钟启动失败常见原因是外部晶振没焊好、晶振负载电容不对、或者你改代码把时钟切成HSE但硬件上没有8MHz晶振。第二类在你自己的延时函数里while循环出不去原因是SysTick没有开启或者中断优先级配置冲突。第三类在中断服务函数里调用了一个会阻塞的延时函数比如在定时器中断里调用delay(100)中断服务函数没执行完主循环被卡住看起来就是整个系统死掉。第四类循环变量溢出比如你用一个uint8_t i做for(i0;i300;i)i到255之后又回0循环永远不会结束。这类细节调试起来非常隐蔽但只要把变量类型改成uint16_t就能马上解决。遇到delay卡死建议第一件事不是改延时函数而是先把工程简化为“最简点灯工程”不开中断、不初始化串口、只用延时翻转LED。如果最简工程正常再把功能一点点加回去一次只加一个模块问题自然浮出水面。5. 基础打完之后往哪些方向开项目更过瘾5.1 超声波测距HC-SR04从IO操作到定时器捕获串口、按键、GPIO和中断这四样玩熟之后就可以往项目方向走了。超声波测距是很好的第一站模块便宜、代码量适中、现象直观很适合拿来练手。HC-SR04模块有四个脚VCC接5VGND接地Trig是触发脚Echo是回波脚。使用时先在Trig脚给一个10微秒以上的高电平脉冲模块内部会发一串超声波然后把Echo拉高当超声波碰到障碍物返回后Echo拉低。所以Echo高电平持续的时间就是超声波往返的时间。距离的计算公式是距离厘米 高电平时间微秒/ 58。更严谨的写法是声速约340m/s往返时间除以2再换算成厘米。用F103来做最简单的方式是GPIO轮询但更推荐用定时器输入捕获。把TIM2配置成输入捕获模式用定时器产生一个1微秒的计数时基捕获Echo上升沿时记录计数值捕获Echo下降沿时再记录一次两次相减就是高电平时间。测频率也用同一个套路不用测脉宽只需要连续捕获两个上升沿计算两次捕获值之间的差值就能算出信号频率。这里有个坑很多HC-SR04模块的Echo输出其实是5V电平STM32的GPIO引脚不能直接承受5V严格来说要用电阻分压或者电平转换电路。如果你只是自己学习、连接线很短有人直接接也能工作但这种侥幸心理不值得养成建议串一个1k电阻或者用两个电阻分压。5.2 给TFTLCD屏写驱动读到0xA1A1说明什么玩屏幕是STM32学习绕不开的一环常见的就是ILI9341驱动的3.5寸或者2.8寸TFT屏。很多人用屏幕厂商提供的例程最后都能点亮但如果你自己写驱动第一个需求往往就是读LCD控制器的ID。ILI9341这个型号的控制器读回来的ID应该是0x9341。如果你读出来的ID显示0xA1A1或者类似这种“对称数”基本可以判断不是芯片控制器本身的问题而是单片机和你屏幕之间的SPI通信没对齐。0xA1A1这个值很典型它表示你在读ID时屏幕其实没有正确返回有效数据或者说返回的字节正好被高位低位顺序搞混了。排查思路按顺序走先确认SPI的极性相位和屏幕要求一致ILI9341的SPI一般要求模式0也就是CPOL0、CPHA0。再看数据位顺序必须是MSB first。如果都没问题检查复位引脚和片选引脚复位引脚要先拉低十几毫秒再拉高不然控制器还处于未复位状态。最后是读ID的时序一般要先写命令寄存器0xD3或者0x04然后连续发送空字节把数据读回来错一个字节就会得到全0xFF或者0xA1之类的假数据。串口打印和屏幕显示两手抓其实是不错的学习路线串口负责调试信息屏幕负责最终人机交互界面做一个小小的温湿度计、音乐播放器或者游戏掌机都够玩很久。5.3 USB设备让F103扮演鼠标键盘或虚拟串口F103内置了USB全速设备外设可以用来实现USB鼠标、USB键盘、USB虚拟串口。它的引脚是固定的PA11接USB的D-PA12接USB的D不能随意换。配置USB功能时还有一个硬性要求USB外设需要48MHz时钟。如果你用8MHz外部晶振PLL出来72MHz系统时钟之后USB预分频器分频得到48MHz如果你用的是别的晶振就要仔细算时钟树否则电脑会一直报“无法识别的USB设备”。从项目角度来说USB方向适合想做“电脑小外设”的人比如做一个自定义快捷键键盘、做一个USB转CAN工具、做一个虚拟示波器上位机对接的仪器。这个方向难度比普通GPIO大但带来的成就感也更大。你可以先从ST官方USB库里的HID鼠标例程开始改把按键扫描和USB发送连起来很快就能让电脑上的鼠标指针跟着你的按键走。5.4 CAN通信突然连不上大概率不是软件问题如果你的目标是车规级或者工业控制方向CAN总线是必须接触的。F103的bxCAN控制器只能说是个基础版本外部还得接一个CAN收发器芯片通常用TJA1050或者SN65HVD230。很多人在实验室里两台F103开发板对接CAN跑得好好的隔天重新上电就连不上了。这时候先别急着怀疑代码CAN通信有三样物理硬指标必须满足总线两端都要有120Ω终端电阻网络里的CANH和CANL不能接反所有节点的地必须共地。终端电阻一端的说法很常见但在CAN总线上终端电阻不是随便加一颗就行标准是总线的两端各有一颗120Ω电阻。如果只是两个板子点对点对接那每块板子各有一个120Ω电阻一个接在一端一个接在另一端如果中间有第三个节点就不再额外加电阻。调试时如果CAN一直进不了邮箱重发次数不断增加可以先用万用表量一下CANH和CANL之间的电阻两颗120Ω并联结果应该是60Ω左右。如果是这个值说明总线物理层基本没问题再回头查波特率和帧格式。以上这些基础检查做完代码层面的波特率分频、采样点配置、过滤器设置才有排查的意义。5.5 更多方向从电机控制到物联网都能塞进这块板子F103的中等容量其实能干不少事情。比如驱动五线四相步进电机用定时器产生脉冲串控制步进角度配合ULN2003驱动板就能做一个简易小车或者云台。再比如接个ESP8266或者ESP32模块通过AT指令发送MQTT数据就能把开发板的温湿度数据传到云平台做一套物联网环境监测的demo。还有照片级别的玩法用GC032A摄像头模块和F103摄像头接口做个图像采集装置虽然帧率不高但原理完整。开发板买回来不是只为了点灯玩两天关键是每做一个小项目都要记录问题把串口日志和接线图存在本地过一个月回头看进步会非常快。6. 几个小教训以及给同样刚入门的人的个人建议操作上最容易出问题的往往不是代码而是接线和供电。拔插杜邦线的时候先把电源断开别带电操作我有一次就是带电插了一个按键模块结果PA0所在引脚被一个错误的信号拉坏虽然芯片还能用但那个引脚怎么都不对劲了。另外调试器连接线不要太长超过20厘米之后SWD很容易出现识别不到芯片的情况短距离直接连接基本不会出这种问题。还有一点整个学习过程一定要保留一套“最小点灯工程”。这个工程不需要花哨只包含时钟初始化、GPIO初始化、SysTick延时、LED翻转四个部分。每当你遇到疑难杂症先把代码回退到这个工程确认芯片本身没坏、调试器能连上、能下程序然后再逐步加新功能。很多看起来神秘莫测的Bug最后都被证明是某个驱动代码跟主循环冲突导致的。我给新人的最后一个建议是学习节奏比学习资料重要得多。现在STM32的教程多到根本看不完真正缺的不是资料而是“每天或者每周末固定往下推两步”的执行力。今天跑通串口过两天加上按键下周研究定时器捕获一个月下来基础框架就搭起来了。别囤一堆课程和PDF收藏夹然后吃灰把一块F103开发板从头到尾折腾完比买十块更贵的开发板有用得多。