拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103开发板入门到实战:环境搭建、点灯串口与避坑指南

STM32F103开发板入门到实战:环境搭建、点灯串口与避坑指南

1. 拿到板子先别急着点灯,搞清楚你手里这块东西到底是什么

STM32F103开发板到手的那一刻,大部分人的第一反应是插上USB线,打开Keil,找个点灯例程烧进去看看。这个冲动我完全理解,但如果你真想把STM32学明白,而不是停留在“烧个例程发朋友圈”的阶段,我建议你先花二十分钟把板子上的东西认全。STM32F103这颗芯片属于ARM Cortex-M3内核,主频72MHz,常见封装有LQFP48、LQFP64、LQFP100、LQFP144几种,你买到的开发板大概率是C8T6(48脚)或者ZET6(144脚)这两种之一。C8T6便宜、体积小,适合做小项目;ZET6引脚多、外设全,适合学习和扩展。拿到板子第一件事,翻过来看背面丝印,确认芯片型号,再看晶振是8MHz还是12MHz,这直接决定了你后面配置时钟树的参数。

很多人忽略的一个细节是:STM32芯片的第一脚怎么确认。这个问题在热搜里出现过,说明确实有人被坑过。芯片表面会有一个小圆点或者一个斜切的角,那个位置对应的就是第1脚。如果你把芯片焊反了,通电瞬间芯片就会发烫,基本就废了。开发板上一般不会焊反,但如果你自己画板子或者买的是核心板,这个细节必须确认。另外,开发板的供电方式也要搞清楚,是USB直接供电还是外部DC供电,有没有AMS1117-3.3稳压芯片,VDD和VDDA是否分开滤波。这些看起来是硬件细节,但后面你调ADC的时候如果VDDA没处理好,采样值跳得你怀疑人生。

我个人的习惯是,拿到一块新板子先做三件事:第一,用万用表蜂鸣档测一下VCC和GND有没有短路;第二,插上USB,量一下3.3V输出是否稳定;第三,看板子上的LED接在哪个引脚,按键接在哪个引脚,串口TX/RX有没有引出。这三步做完,你心里就有底了。STM32F103的开发板市面上很多种,有普中、正点原子、野火这些常见品牌,也有各种 unnamed 的最小系统板。不管哪种,核心逻辑是一样的:电源、时钟、复位、下载接口、外设引出。把这五样搞清楚,后面学起来就不会慌。

还有一点,热搜里有人问“apt32101开发板怎么选jlink类型”,这说明下载器的选择也是个高频问题。STM32F103支持SWD和JTAG两种调试接口,SWD只需要SWCLK和SWDIO两根线,占用引脚少,速度也够用,我强烈建议新手直接用SWD模式。J-Link、ST-Link、DAP-Link都可以,ST-Link性价比最高,原厂的和国产的都能用。如果你买的是带板载ST-Link的开发板,那就更省事了,一根USB线搞定供电和下载。

2. 开发环境搭建:Keil、CubeMX、VS Code到底怎么选

2.1 Keil MDK的安装与芯片包配置

Keil MDK是STM32开发的老牌工具链,资料多、教程全,缺点是界面丑、代码补全弱。安装Keil5的时候有一个经典坑:Keil5本身不包含STM32的器件支持包,你需要单独安装芯片包。热搜里“stm32芯片包安装”能上榜,说明这个问题困扰了不少人。具体操作是去Keil官网下载对应的Device Family Pack,比如Keil.STM32F1xx_DFP.2.4.0.pack,双击安装即可。如果你同时要开发C51和STM32,热搜里“keil5兼容c51和stm32安装”也是个常见需求。解决办法是装两个版本的Keil,分别放在不同目录,或者用Keil5的Pack Installer管理。我个人的建议是:如果只是学STM32,就别折腾C51了,直接用Keil5专注一个平台,省得环境冲突。

安装完芯片包之后,新建工程时选择STM32F103C8或者ZET6,Keil会自动加载启动文件和系统初始化代码。这里有一个细节:启动文件的选择。startup_stm32f10x_md.s对应中等容量(64KB或128KB Flash),startup_stm32f10x_hd.s对应大容量(256KB以上)。选错了会导致中断向量表偏移不对,程序跑飞。C8T6用md,ZET6用hd,这个必须匹配。

2.2 STM32CubeMX:图形化配置的利与弊

CubeMX是ST官方推出的图形化配置工具,可以自动生成初始化代码,支持HAL库和LL库。对于新手来说,CubeMX能大幅降低入门门槛,你只需要在界面上点一点,时钟树、GPIO、UART、SPI、I2C都能自动生成。但我的经验是:不要完全依赖CubeMX。原因很简单,如果你连时钟树怎么配、寄存器怎么算都不清楚,后面遇到问题根本无从排查。我建议的做法是:前期用CubeMX生成代码,但生成之后一定要逐行看一遍,搞清楚每个配置对应哪个寄存器,HAL库函数底层做了什么。这样学下来,你既有效率,又有深度。

CubeMX配置时钟的时候,有一个参数叫HCLK,也就是AHB总线时钟。STM32F103最高72MHz,来源是外部8MHz晶振经过PLL倍频。具体计算过程是:8MHz先经过PREDIV1分频(通常不分频),然后PLLSRC选择HSE,PLLMUL选择9倍频,得到72MHz。如果你用的是12MHz晶振,PLLMUL就要选6倍频。这个计算过程在CubeMX的时钟树界面里会实时显示,但你要理解背后的逻辑,而不是只看结果。

2.3 VS Code + PlatformIO:轻量级方案

热搜里有人提到“vs code里编译成功,却怎么也烧录不进开发板”,这个问题我太熟悉了。VS Code本身只是一个编辑器,编译和烧录靠的是插件和工具链。如果你用的是PlatformIO,编译成功但烧录失败,大概率是以下几个原因:下载器驱动没装好、烧录算法选错了、芯片被读保护了、或者BOOT0引脚状态不对。STM32F103的BOOT0决定启动模式,BOOT0=0从Flash启动,BOOT0=1从系统存储器启动。正常烧录时BOOT0应该接地。如果你不小心把BOOT0拉高了,程序烧进去也不会运行。

PlatformIO的好处是跨平台、插件生态丰富、支持自动下载依赖。缺点是国内网络环境下下载工具链可能比较慢,而且遇到问题排查起来比Keil麻烦。我的建议是:新手先用Keil或者CubeIDE把基础打牢,等熟悉了之后再尝试VS Code + PlatformIO。如果你非要用VS Code,那一定要把STM32CubeProgrammer或者OpenOCD配置好,烧录失败的时候用命令行工具单独测试,排除是IDE的问题还是硬件的问题。

3. 从点灯到串口:第一个真正能跑起来的项目

3.1 GPIO输出:LED点灯背后的寄存器操作

点灯是嵌入式的Hello World,但很多人只是复制粘贴代码,根本没搞清楚GPIO的工作原理。STM32F103的GPIO有8种模式:输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、开漏复用、推挽复用。点灯用的是推挽输出模式,输出速度一般选2MHz或50MHz。配置GPIO需要操作三个寄存器:CRL/CRH(配置寄存器)、ODR(输出数据寄存器)、BSRR(位设置/清除寄存器)。用HAL库的话,就是HAL_GPIO_Init()和HAL_GPIO_WritePin()两个函数。

但我要说的是:你一定要至少手动配置一次寄存器。比如让PA5输出高电平,直接写GPIOA->BSRR = GPIO_PIN_5;让PA5输出低电平,写GPIOA->BRR = GPIO_PIN_5。这样你才能理解BSRR和BRR的区别,以及为什么STM32要设计这两个寄存器。BSRR是原子操作,设置和清除可以同时进行,不会被打断。这在多任务或者中断环境下非常重要。

热搜里有个“stm32电量一个led小灯”,我猜是“点亮一个LED小灯”的笔误。这个需求太基础了,但基础不代表简单。你要搞清楚:LED是共阳极还是共阴极,限流电阻多大,GPIO驱动能力够不够。STM32F103单个GPIO最大输出20mA,LED一般需要5-10mA,所以直接驱动小功率LED没问题,但如果是大功率LED或者多个LED并联,就要加三极管或者驱动芯片。

3.2 串口通信:USB虚拟串口与UART收发

串口是调试利器,也是STM32最常用的通信接口之一。STM32F103一般有3个USART和2个UART,USART1挂在APB2总线上,最高时钟72MHz;USART2和USART3挂在APB1总线上,最高时钟36MHz。配置串口需要设置波特率、数据位、停止位、校验位、硬件流控。波特率的计算公式是:baud = fCK / (16 * USARTDIV),其中USARTDIV是一个浮点数,整数部分写在BRR的高12位,小数部分写在低4位。比如72MHz时钟下要得到115200波特率,USARTDIV = 72000000 / (16 * 115200) = 39.0625,整数部分39,小数部分0.0625*16=1,所以BRR = 0x271。

热搜里“stm32 usb虚拟串口发送数据”是个高频需求。STM32F103自带USB外设,可以配置成CDC设备,也就是虚拟串口。这样做的好处是不需要额外的USB转串口芯片,一根USB线就能实现供电和通信。但USB虚拟串口的代码比普通UART复杂得多,涉及USB协议栈、端点配置、描述符定义。新手建议先用UART转USB模块,等熟悉了再折腾原生USB。

我实测下来,STM32F103的USB虚拟串口在Windows和Linux下都能免驱识别,但有一个坑:USB时钟必须配置成48MHz。STM32F103的USB外设要求时钟精度较高,一般用外部8MHz晶振经过PLL倍频到72MHz,再经过1.5分频得到48MHz。如果你用的是内部RC振荡器,USB大概率枚举失败。所以,玩USB一定要用外部晶振。

3.3 定时器:从延时函数到PWM输出

热搜里“stm32延时函数delay卡死”是个经典问题。很多人用for循环做延时,结果优化等级一变,延时就不准了。正确的做法是用SysTick定时器或者通用定时器做精确延时。SysTick是Cortex-M3内核自带的24位递减计数器,配置简单,适合做毫秒级延时。通用定时器TIM2-TIM5功能更强,支持PWM输出、输入捕获、编码器模式等。

STM32F103的定时器分为高级定时器(TIM1、TIM8)、通用定时器(TIM2-TIM5)、基本定时器(TIM6、TIM7)。高级定时器支持互补输出和死区控制,适合驱动电机;通用定时器适合PWM、测频、测周;基本定时器只能做定时中断。热搜里“stm32定时器捕获测频率”和“stm32定时器模式”都是定时器的典型应用。输入捕获测频率的原理是:被测信号接到定时器的捕获引脚,定时器在信号上升沿或下降沿触发捕获,记录当前计数值,两次捕获值之差就是信号周期,取倒数就是频率。这个方法测量范围广,精度取决于定时器时钟和预分频系数。

PWM输出的配置稍微复杂一点,需要设置ARR(自动重装载值)和CCR(捕获比较值),占空比 = CCR / ARR。比如ARR=999,CCR=300,占空比就是30%。PWM频率 = 定时器时钟 / ((ARR+1) * (PSC+1))。假设72MHz时钟,PSC=71,ARR=999,PWM频率就是72000000 / (72 * 1000) = 1000Hz。这个计算过程你要烂熟于心,后面调电机、调LED亮度都靠它。

4. 进阶实战:那些让你踩坑又让你成长的典型项目

4.1 超声波测距:输入捕获的经典应用

热搜里“stm32超声波测距”是个很受欢迎的项目。HC-SR04超声波模块的工作原理是:Trig引脚给一个10us以上的高电平脉冲,模块内部发出8个40kHz的方波,然后Echo引脚拉高,高电平持续时间就是超声波往返的时间。距离 = 高电平时间 * 声速 / 2。声速取340m/s,也就是34000cm/s。如果Echo高电平时间是580us,距离就是580 * 0.000001 * 34000 / 2 = 9.86cm。

实现方式有两种:一种是轮询,用定时器计数,等待Echo变高再变低;另一种是用输入捕获,Echo接到定时器的捕获通道,上升沿捕获一次,下降沿捕获一次,两次差值就是高电平时间。输入捕获的方式更精确,不占用CPU,但配置稍微复杂。我建议先用轮询方式把逻辑跑通,再用输入捕获优化。注意HC-SR04是5V供电,Echo输出也是5V,而STM32的GPIO是3.3V容忍,但为了安全,最好加一个电平转换或者分压电阻。我见过有人直接把5V Echo接到STM32引脚,短期能用,长期有风险。

4.2 智能台灯:PWM调光与ADC采样

“基于stm32的智能台灯”这个项目很适合练手,涉及PWM调光、ADC采样光敏电阻、按键控制、串口通信等多个模块。PWM调光就是用定时器输出不同占空比的方波,控制LED的亮度。ADC采样光敏电阻的分压值,根据环境光强度自动调节亮度。按键用来手动切换模式,串口用来输出调试信息。

ADC配置有几个关键点:采样时间、分辨率、参考电压。STM32F103的ADC是12位逐次逼近型,参考电压一般是3.3V,所以最小分辨率是3.3V / 4096 = 0.8mV。采样时间越长,转换结果越稳定,但转换速度越慢。光敏电阻的阻值变化范围大,分压电阻要选合适,否则ADC读数要么饱和要么接近零。我一般用10kΩ的固定电阻和光敏电阻串联,光敏电阻在亮处约1kΩ,暗处约100kΩ,分压范围大概在0.3V到3V之间,正好覆盖ADC的测量范围。

4.3 Modbus通信:agile_modbus在STM32上的移植

热搜里“agile_modbus stm32”说明有人在工业场景下用STM32做Modbus通信。Modbus是工业领域最常用的通信协议之一,分为RTU和TCP两种。STM32上一般跑Modbus RTU,通过RS485收发器连接总线。agile_modbus是一个轻量级的Modbus协议栈,支持主站和从站,代码量小,适合嵌入式平台。

移植agile_modbus的关键是提供三个回调函数:发送数据、接收数据、获取时间戳。发送和接收用UART的DMA或者中断实现,时间戳用SysTick或者定时器。RS485收发器需要控制DE/RE引脚切换收发方向,发送前拉高DE,发送完拉低DE。这个切换时机很关键,切换太早数据发不完,切换太晚总线冲突。我一般用UART的TC中断来判断发送完成,然后在TC中断里切换方向。

4.4 STM32控制伺服电机:485通信与定时器PWM

“stm32控制伺服电机485”这个需求在工业控制中很常见。伺服电机一般支持RS485或者CAN通信,通过Modbus协议或者厂商自定义协议控制位置、速度、力矩。STM32这边需要配置UART+RS485收发器,定时器输出PWM作为辅助控制信号。伺服电机的控制周期一般是1ms到10ms,对实时性要求较高。我建议用定时器中断触发控制循环,在中断里发送控制指令和读取反馈数据。注意中断服务函数要尽量短,耗时操作放到主循环或者DMA里处理。

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

5.1 烧录失败:从硬件到软件逐层排查

烧录失败是新手遇到最多的问题,没有之一。我整理了一个排查顺序,按这个顺序走,基本能定位到问题:

排查步骤检查内容常见问题解决方法
1供电USB线只供电不传数据换一根带数据线的USB线
2下载器驱动设备管理器有黄色感叹号重装ST-Link或J-Link驱动
3接线SWCLK/SWDIO接反或接触不良检查杜邦线,确认引脚定义
4BOOT引脚BOOT0被拉高BOOT0接地,复位后重试
5芯片读保护提示“Flash read protected”用STM32CubeProgrammer解除读保护
6烧录算法选错了Flash大小根据芯片型号选择对应算法
7时钟配置HSE起振失败检查晶振和负载电容

热搜里“load ... error: fla”这个报错,大概率是Flash算法选错了或者芯片被读保护了。Keil的报错信息有时候很模糊,这时候用STM32CubeProgrammer单独连接一下,如果能连上,说明硬件没问题,问题在Keil配置;如果连不上,那就是硬件或者驱动的问题。

5.2 程序跑飞:HardFault的定位方法

STM32程序跑飞最常见的原因是数组越界、空指针、栈溢出、中断优先级配置错误。HardFault发生时,芯片会进入死循环,这时候你需要查看LR寄存器和PC寄存器来确定出错位置。在Keil里,进入调试模式,查看Call Stack窗口,能看到函数调用关系。如果Call Stack是乱的,那就在HardFault_Handler里加一句while(1),然后查看SCB->CFSR寄存器的值,根据错误标志位判断是总线错误、存储器错误还是用法错误。

我个人的经验是:栈溢出是新手最容易忽略的问题。STM32F103的默认栈大小在启动文件里定义,一般是0x400(1KB)。如果你在函数里定义了大数组,比如uint8_t buf[2048],栈直接爆掉。解决办法是把大数组定义成全局变量或者用malloc动态分配,或者增大栈大小。启动文件里的Stack_Size改成0x800或者0x1000。

5.3 串口乱码:波特率与时钟的匹配问题

串口乱码的原因通常有三个:波特率不匹配、时钟配置错误、硬件流控没关。波特率不匹配最常见,比如你代码里设的是115200,串口助手设的是9600,那肯定乱码。时钟配置错误比较隐蔽,比如你用的是8MHz晶振,但代码里按12MHz算波特率,结果实际波特率偏差超过3%,通信就不稳定了。硬件流控如果没关,RTS/CTS引脚状态不对,数据也发不出来。

排查方法:先用示波器或者逻辑分析仪量一下TX引脚的实际波特率,跟理论值对比。如果没有仪器,就换一个已知正确的波特率试试,比如9600。另外,串口助手的编码格式也要注意,GBK和UTF-8混用也会显示乱码。

5.4 中断不触发:NVIC配置与优先级分组

STM32的中断优先级分为抢占优先级和响应优先级,通过NVIC_SetPriorityGrouping()设置分组。分组不同,抢占和响应的位数分配不同。比如分组2是2位抢占、2位响应,分组3是3位抢占、1位响应。如果两个中断的抢占优先级相同,响应优先级高的先执行;如果抢占优先级不同,高的可以打断低的。配置中断的时候,别忘了使能NVIC通道,也就是NVIC_EnableIRQ()。很多人只写了中断服务函数,忘了使能NVIC,结果中断永远不触发。

6. 学习路径与资源推荐:从入门到能做项目

6.1 分阶段学习路线

STM32的学习曲线比较陡,我建议分四个阶段:

第一阶段:GPIO、UART、定时器。目标是能点灯、能串口收发、能定时中断。这个阶段大概需要一到两周。

第二阶段:ADC、DMA、SPI、I2C。目标是能读传感器、能驱动屏幕、能读写EEPROM。这个阶段需要两到三周。

第三阶段:USB、CAN、FSMC、SDIO。目标是能做USB设备、能组网通信、能驱动外部存储器。这个阶段需要一个月左右。

第四阶段:RTOS、文件系统、网络协议栈。目标是能跑FreeRTOS、能读写SD卡文件、能实现简单的TCP通信。这个阶段需要持续学习。

热搜里“基于stm32的毕业设计”和“stm32项目”说明很多人是为了做项目而学。我的建议是:不要为了做项目而跳过基础。基础不牢,项目做到一半就会卡住,到时候回头补基础更浪费时间。

6.2 工具与资源清单

  • 开发工具:Keil MDK、STM32CubeMX、STM32CubeProgrammer、VS Code + PlatformIO
  • 调试工具:ST-Link V2、J-Link、逻辑分析仪、示波器
  • 参考文档:《STM32F10x参考手册》、《Cortex-M3权威指南》、ST官方数据手册
  • 社区资源:ST官方论坛、电子工程世界、CSDN、GitHub上的开源项目

我个人最推荐的入门方式是:买一块带板载ST-Link的开发板,跟着官方例程走一遍,然后自己改代码实现新功能。不要一上来就啃参考手册,那样容易劝退。先跑起来,再深入原理,这个顺序对新手最友好。

6.3 常见误区与避坑建议

第一个误区:只看视频不动手。嵌入式是实践性极强的学科,看十遍视频不如自己写一遍代码。第二个误区:遇到问题就换方案。比如串口调不通就换SPI,SPI调不通就换I2C,最后什么都没学透。正确的做法是死磕一个问题,查手册、查论坛、用调试工具,直到解决为止。第三个误区:不重视硬件。软件写得再好,硬件有问题照样跑不起来。学会看原理图、用万用表、用示波器,这些技能和写代码一样重要。

我在实际项目中踩过最大的坑是:忽略了电源纹波对ADC的影响。当时用开关电源给STM32供电,ADC采样值跳了十几个LSB,后来换成LDO,加了滤波电容,问题才解决。所以,如果你要做精密测量,电源一定要干净。

最后再分享一个小技巧:善用STM32的硬件错误异常和断言机制。在关键代码里加assert_param(),在HardFault_Handler里打印出错地址,能帮你快速定位问题。这个习惯一旦养成,调试效率至少提升一倍。

返回列表