ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32F103开发板入门实战:从开箱到独立项目的完整指南

STM32F103开发板入门实战:从开箱到独立项目的完整指南

STM32 开发板到手之后,第一件事往往不是急着写代码,而是先把“板子能不能跑起来”这件事确认清楚。我见过太多人,板子买回来插上电脑,灯不亮、串口不识别、Keil 报一堆错,折腾两天热情就没了。STM32F103 这块板子算是嵌入式入门的经典款,资料多、价格低、外设全,但它的坑也不少,尤其是对刚接触 ARM 架构、从 51 单片机转过来的人。这篇内容我打算把 STM32F103 开发板从开箱到跑通第一个工程、再到能自己写点小项目的完整路径讲清楚,包括工具链怎么选、工程怎么建、常见问题怎么排查,以及后面可以往哪些方向深入。不管你是电子专业的学生、想转嵌入式的程序员,还是单纯想玩玩硬件,只要跟着这条线走,基本能少走很多弯路。

1. 拿到板子之后先别写代码,把这几件事确认了

1.1 开发板到手后的物理检查与供电确认

STM32F103 开发板市面上版本很多,常见的有最小系统板、带外设的综合板、以及各种“学习板”。不管哪种,拿到手第一件事是看板子上的丝印和跳线帽。很多板子出厂时跳线帽的位置是随机的,尤其是 BOOT0 和 BOOT1 这两个引脚,它们决定了芯片从哪里启动。BOOT0 接高电平、BOOT1 接低电平是串口下载模式;两个都接低才是从 Flash 启动正常运行。我遇到过有人板子买回来一直跑不起来,最后发现是 BOOT0 被跳到了 3.3V,芯片一直在等串口下载,根本不执行程序。

供电方面,STM32F103 的工作电压是 2.0V 到 3.6V,典型值 3.3V。大部分开发板支持 USB 供电和外部电源供电两种方式。用 USB 供电时要注意,有些便宜板子的 USB 口只做数据通信,供电能力不足,插上之后芯片能识别但跑起来会复位。判断方法很简单:用万用表量一下 3.3V 稳压芯片的输出,如果低于 3.2V,换一个供电能力强的 USB 口或者用外部 5V 电源。另外,板子上的电源指示灯亮不代表电压一定正常,最好还是量一下。

还有一个容易被忽略的点:STM32F103 的每个电源引脚旁边都应该有去耦电容,通常是 100nF。有些低成本板子为了省成本会省略或者用劣质的,导致芯片在高频运行时不稳定。如果你发现程序跑着跑着就死机,排除代码问题之后,可以看看板子上电源引脚附近有没有足够的电容。没有的话,自己焊一个 100nF 的贴片电容上去,问题往往就解决了。

1.2 芯片型号识别与数据手册的对应关系

STM32F103 只是一个系列名,具体型号有很多,比如 STM32F103C8T6、STM32F103RCT6、STM32F103ZET6 等等。型号里的字母和数字是有含义的:C 代表 48 引脚,R 代表 64 引脚,Z 代表 144 引脚;8 代表 64KB Flash,C 代表 256KB Flash,E 代表 512KB Flash;T 代表 LQFP 封装,6 代表工业级温度范围。你拿到板子之后,先看清楚芯片上激光刻的型号,然后去下载对应的数据手册和参考手册。

这里有个关键点:STM32F103 的参考手册(Reference Manual)是通用的,但数据手册(Datasheet)是按具体型号分的。参考手册讲的是外设怎么用、寄存器怎么配,数据手册讲的是这个具体型号有多少引脚、多少 Flash、电气参数是什么。很多人只看参考手册,结果配置引脚的时候发现某个功能在这个型号上没有,就是因为没看数据手册。我建议把这两份文档都下载下来,放在手边随时查。

另外,STM32F103 的固件库有标准外设库(Standard Peripheral Library)和 HAL 库两种。标准库更接近寄存器操作,代码效率高,适合学习底层原理;HAL 库抽象程度高,跨系列移植方便,适合快速开发。对于刚入门的人来说,我建议先用标准库,因为它的代码结构清晰,能让你看清楚每个外设是怎么配置的。等你熟悉了之后,再转 HAL 库或者 LL 库都可以。

1.3 调试器与下载方式的选型逻辑

STM32F103 支持多种下载方式:串口下载(通过 BOOT0 进入系统存储器)、SWD 下载(通过 SWDIO 和 SWCLK 两根线)、JTAG 下载(需要更多引脚)。串口下载最简单,只需要一个 USB 转 TTL 模块,但每次下载都要手动切换 BOOT0 跳线,而且不能在线调试。SWD 下载需要 ST-Link 或者 J-Link 调试器,但支持在线仿真、断点调试、查看寄存器,是实际开发中最常用的方式。

如果你买的开发板板载了 ST-Link,那直接插 USB 就能用。如果没有,建议单独买一个 ST-Link V2,价格不贵,大概十几块钱,但能省很多事。J-Link 更强大,但价格也更高,而且市面上有很多山寨版本,固件容易掉。对于 STM32F103 来说,ST-Link 完全够用。连接的时候注意,SWDIO 对应 PA13,SWCLK 对应 PA14,GND 必须共地,3.3V 可以不接(如果板子自己供电的话)。

注意:有些开发板的 SWD 接口顺序不是标准的,连接之前一定要看板子的原理图或者丝印,接反了可能烧调试器。

我个人的习惯是,板子到手之后先用串口下载一个最简单的 LED 闪烁程序,确认芯片能跑、时钟配置正确、GPIO 能输出。然后再接 ST-Link,测试在线调试功能。这样分步验证,出了问题也容易定位。

2. 开发环境搭建:Keil、VSCode 还是 STM32CubeIDE

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

Keil MDK 是国内 STM32 开发最常用的 IDE,资料多、教程全、社区活跃。安装 Keil MDK 本身不复杂,但有几个坑要注意。首先,Keil MDK 和 Keil C51 是可以共存的,但安装顺序有讲究:先装 C51,再装 MDK,这样两个环境都能正常使用。如果先装了 MDK 再装 C51,可能会覆盖一些公共文件,导致 MDK 的编译器出问题。

安装完 Keil MDK 之后,还需要安装 STM32F1 系列的芯片包(Device Family Pack)。这个包包含了芯片的启动文件、外设寄存器定义、Flash 算法等。没有它,Keil 里选不到 STM32F103 的型号,编译也会报错。芯片包可以从 Keil 官网下载,也可以从 ST 官网下载。安装的时候注意版本,太老的版本可能不支持新的芯片型号,太新的版本可能和你的 Keil 版本不兼容。一般来说,选比 Keil 版本稍晚一点发布的芯片包比较稳妥。

还有一个常见问题:Keil 的编译器版本。Keil MDK 5.x 默认使用 ARM Compiler 5(AC5),但有些新版本的芯片包可能要求 ARM Compiler 6(AC6)。如果你编译的时候报“compiler version not supported”之类的错误,需要在 Keil 的 Options for Target -> Target 里切换编译器版本。AC5 和 AC6 的语法有些差异,尤其是内联汇编和某些宏定义,切换之后可能需要改代码。

2.2 VSCode + 插件方案的适用场景与配置要点

VSCode 本身不是 IDE,但通过安装插件可以变成强大的嵌入式开发环境。常用的插件组合是:Cortex-Debug(用于调试)、STM32 VS Code Extension(ST 官方插件)、以及 Keil Assistant(用于调用 Keil 的编译工具链)。这种方案的好处是编辑体验好、代码补全强、界面现代,适合已经熟悉 VSCode 的开发者。

配置 VSCode 开发 STM32 的关键是 launch.json 和 tasks.json 这两个文件。launch.json 告诉调试器怎么连接目标板、用什么调试器、加载什么符号文件;tasks.json 告诉 VSCode 怎么编译工程。如果你用 Keil 的编译工具链,需要在 tasks.json 里调用 Keil 的命令行工具 UV4.exe。如果你用 arm-none-eabi-gcc,那就要自己写 Makefile 或者用 CMake。

这里有个坑:VSCode 的 Cortex-Debug 插件默认使用 GDB 调试,但 ST-Link 的 GDB Server 有时候会和 Keil 的 ST-Link 驱动冲突。解决办法是安装 ST-Link 的独立驱动,或者在调试配置里指定使用 OpenOCD。OpenOCD 是一个开源的调试工具,支持多种调试器和目标芯片,配置起来稍微复杂一点,但稳定性更好。

提示:如果你刚开始学 STM32,不建议一上来就用 VSCode 方案。先把 Keil 或者 STM32CubeIDE 用熟,理解编译、下载、调试的流程之后,再折腾 VSCode 会轻松很多。

2.3 STM32CubeIDE 与 CubeMX 的配合使用

STM32CubeIDE 是 ST 官方推出的免费 IDE,基于 Eclipse 和 GCC 工具链。它最大的优势是集成了 CubeMX,可以通过图形化界面配置引脚、时钟、外设,然后自动生成初始化代码。对于新手来说,这能大大降低入门门槛,因为你不需要手动查寄存器手册就能把外设配起来。

CubeMX 的使用逻辑是:先选芯片型号,然后配置时钟树(比如 HSE 用 8MHz 晶振,PLL 倍频到 72MHz),再配置外设(比如 USART1 异步模式、GPIO 输出模式),最后生成代码。生成的代码里,初始化部分都在 main.c 的 SystemClock_Config 和 MX_xxx_Init 函数里,你只需要在 while(1) 循环里写自己的逻辑就行。

不过 CubeMX 生成的代码有个特点:它把初始化代码和用户代码分开了,用户代码要写在 /* USER CODE BEGIN/ 和 /USER CODE END */ 之间,否则重新生成代码的时候会被覆盖。这个机制一开始可能会让人觉得别扭,但习惯了之后其实很方便,因为你可以随时回到 CubeMX 改配置,重新生成代码,而不会丢失自己写的逻辑。

STM32CubeIDE 还集成了调试功能,支持 ST-Link 和 J-Link,可以直接在 IDE 里打断点、看变量、看寄存器。对于不想折腾环境的人来说,这是一个比较省心的选择。缺点是 Eclipse 的界面相对笨重,代码补全不如 VSCode 流畅,而且生成的代码体积比标准库大一些。

3. 第一个工程:从点亮 LED 到串口输出

3.1 新建工程时的时钟配置与启动文件选择

新建 STM32F103 工程的时候,有几个关键选择会影响后续开发。首先是启动文件的选择,Keil 里通常有 startup_stm32f10x_hd.s、startup_stm32f10x_md.s、startup_stm32f10x_ld.s 等几个版本,分别对应大容量、中容量、小容量产品。STM32F103C8T6 属于中容量,Flash 是 64KB,应该选 md 版本。选错了启动文件,编译可能通过,但运行会出问题,因为中断向量表的偏移和堆栈大小不一样。

时钟配置是另一个重点。STM32F103 最高可以跑到 72MHz,但默认的内部时钟(HSI)只有 8MHz。要跑到 72MHz,需要配置 PLL:HSE(外部晶振,通常是 8MHz)经过 9 倍频得到 72MHz,然后作为系统时钟。同时还要配置 AHB、APB1、APB2 的分频系数。APB1 最高 36MHz,APB2 最高 72MHz,如果超频了,外设可能工作不正常。比如 USART1 挂在 APB2 上,如果 APB2 是 72MHz,那波特率计算就是 72MHz 除以分频系数;如果 APB2 配错了,串口输出的就是乱码。

用标准库配置时钟的典型代码是这样的:

void RCC_Configuration(void) { RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() != 0x08); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); }

这段代码的逻辑是:先开 HSE,等它稳定;然后配置 PLL 为 HSE 的 9 倍频;再开 PLL,等它锁定;最后把系统时钟切到 PLL,并配置各总线的分频。每一步都有等待标志位的循环,这是必须的,因为时钟切换需要时间,不等稳定就切会导致芯片跑飞。

3.2 GPIO 初始化的标准流程与常见错误

点亮 LED 的本质是控制 GPIO 输出高低电平。STM32F103 的 GPIO 有 8 种模式:输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、开漏复用、推挽复用。控制 LED 一般用推挽输出,因为推挽输出的驱动能力强,能直接驱动 LED。

GPIO 初始化的标准流程是:先开 GPIO 的时钟(比如 PA 口挂在 APB2 上,要调用 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)),然后定义 GPIO_InitTypeDef 结构体,设置引脚、速度、模式,最后调用 GPIO_Init 函数。这里最常见的错误是忘记开时钟,结果 GPIO 配置不生效,LED 死活不亮。我刚开始学的时候也犯过这个错,查了半天代码没发现问题,最后才发现是时钟没开。

另一个常见错误是引脚号写错。STM32 的引脚号是 GPIO_Pin_0 到 GPIO_Pin_15,对应 PA0 到 PA15。如果你要控制 PA5,就写 GPIO_Pin_5。但有些人会写成 GPIO_Pin_5 对应的物理引脚是第 5 脚,这是不对的。PA5 在 LQFP48 封装里可能是第 15 脚或者别的,具体要看数据手册的引脚定义表。

LED 的接法也有讲究。如果 LED 是阳极接 3.3V、阴极接 GPIO,那 GPIO 输出低电平的时候 LED 亮,这叫灌电流接法;如果 LED 是阳极接 GPIO、阴极接地,那 GPIO 输出高电平的时候 LED 亮,这叫拉电流接法。STM32 的 GPIO 灌电流能力比拉电流强,所以灌电流接法更常见。写代码的时候要注意逻辑电平,别搞反了。

3.3 串口通信的波特率计算与重定向 printf

串口是嵌入式开发中最重要的调试手段。STM32F103 有 3 个 USART 和 2 个 UART,其中 USART1 挂在 APB2 上,USART2 和 USART3 挂在 APB1 上。配置串口的时候,波特率、数据位、停止位、校验位都要设置。最常用的是 115200-8-N-1,即波特率 115200,8 位数据位,无校验,1 位停止位。

波特率的计算是:波特率 = fPCLK / (16 * USARTDIV),其中 fPCLK 是串口所在总线的时钟频率。比如 USART1 挂在 APB2 上,APB2 是 72MHz,要得到 115200 的波特率,USARTDIV = 72000000 / (16 * 115200) = 39.0625。标准库的 USART_Init 函数会自动计算这个值,你只需要填 115200 就行。但如果你改了系统时钟,忘了改波特率,串口输出的就是乱码。

重定向 printf 是调试中非常实用的技巧。标准库的 printf 默认输出到控制台,在嵌入式里没有控制台,需要重定向到串口。方法是重写 fputc 函数:

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

然后在 Keil 的 Options for Target -> Target 里勾选“Use MicroLIB”。MicroLIB 是 ARM 提供的精简版 C 库,支持 printf 重定向,而且代码体积小。如果不勾选 MicroLIB,就需要自己实现 _sys_write 等底层函数,比较麻烦。

注意:重定向 printf 之后,串口发送是阻塞的,如果串口被占用或者波特率不对,程序会卡在 while 循环里。调试的时候如果发现程序跑着跑着不动了,先检查串口是不是卡住了。

4. 那些年我们一起踩过的 STM32 坑

4.1 下载失败与芯片被锁的排查链路

STM32 下载失败是最常见的问题之一,表现是 Keil 或者 ST-Link Utility 提示“No target connected”或者“Flash download failed”。排查的时候按这个顺序来:先检查硬件连接,SWDIO、SWCLK、GND 是否接好,目标板是否供电;然后检查调试器驱动是否安装,设备管理器里有没有识别到 ST-Link;再检查 Keil 里的调试配置,是否选对了调试器型号和接口(SWD 还是 JTAG);最后检查芯片是否被读保护或者写保护了。

芯片被锁的情况通常是因为程序里禁用了 SWD 引脚,或者设置了读保护。STM32F103 的 SWD 引脚是 PA13 和 PA14,如果程序里把这两个引脚配置成了普通 GPIO 或者复用功能,SWD 就连不上了。解决办法是:把 BOOT0 接高电平,让芯片进入系统存储器启动模式,然后用 ST-Link Utility 连接,解除读保护,再重新下载程序。如果读保护也解不了,可能需要用串口下载或者擦除整个 Flash。

还有一种情况是芯片进入了低功耗模式,SWD 也连不上。这时候可以尝试在连接设置里把“Connect under reset”打开,让调试器在芯片复位的时候连接。或者手动按住复位键,点击下载,然后松开复位键,利用这个时间窗口连接。

4.2 中断不响应与优先级配置的陷阱

中断是 STM32 的核心功能之一,但配置起来容易出问题。最常见的是中断不响应,原因可能有几个:中断使能没开、NVIC 配置不对、中断服务函数名写错、优先级分组没设置。STM32F103 的中断优先级分为抢占优先级和响应优先级,通过 NVIC_PriorityGroupConfig 函数设置分组。分组决定了抢占优先级和响应优先级各占几位。如果分组设置不对,可能导致高优先级中断被低优先级中断阻塞。

中断服务函数的名字必须和启动文件里的向量表一致。比如 USART1 的中断服务函数是 USART1_IRQHandler,如果你写成 USART1_IRQhandler(小写 h),编译器不会报错,但中断触发的时候会跳到默认的无限循环里,程序就死了。这个错误很隐蔽,因为编译能通过,下载也能成功,就是中断不工作。

还有一个坑是中断标志位没清除。STM32 的中断标志位需要在中断服务函数里手动清除,否则中断会一直触发,程序卡在中断里出不来。比如串口接收中断,收到数据之后要调用 USART_ClearITPendingBit 清除标志位。如果忘了清,主循环就永远执行不到。

4.3 延时函数卡死与时钟树的关系

延时函数卡死是新手经常遇到的问题。很多人用 SysTick 做延时,配置的时候没注意时钟源。SysTick 的时钟源可以是 HCLK 的 1/8 或者 HCLK 本身。如果系统时钟是 72MHz,SysTick 用 HCLK/8,那计数频率就是 9MHz。如果延时函数里按 72MHz 算,延时就会差 8 倍。更严重的是,如果时钟配置失败,HCLK 不是预期的频率,SysTick 的计数就不对,延时可能变得极长,看起来就像卡死了。

还有一种情况是延时函数里用了 while 循环等待标志位,但标志位永远不置位。比如等待 HSE 就绪的 while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET),如果外部晶振没起振,这个循环就出不来了。外部晶振不起振的原因可能是晶振坏了、负载电容不匹配、或者焊接不良。可以用示波器量一下晶振引脚,看有没有波形。没有波形的话,先换晶振,再检查电容。

提示:调试的时候可以在延时循环里加一个超时计数,超过一定次数就退出,并返回错误码。这样即使时钟有问题,程序也不会完全卡死,方便定位问题。

4.4 串口乱码与波特率误差的定量分析

串口乱码的原因通常是波特率不匹配。除了前面说的时钟配置错误,还有一个容易被忽略的因素:波特率误差。STM32 的 USART 波特率是通过分频得到的,分频系数是整数加小数,实际波特率和理论波特率之间会有误差。如果误差超过 2%,通信就可能出错。

以 72MHz 时钟、115200 波特率为例,USARTDIV = 72000000 / (16 * 115200) = 39.0625。标准库会把 39.0625 拆成整数部分 39 和小数部分 0.0625,小数部分乘以 16 得到 1,所以 BRR 寄存器的值是 39 * 16 + 1 = 625。实际波特率 = 72000000 / (16 * 39.0625) = 115200,误差为 0。但如果时钟是 72MHz、波特率是 9600,USARTDIV = 72000000 / (16 * 9600) = 468.75,BRR = 468 * 16 + 12 = 7500,实际波特率 = 72000000 / (16 * 468.75) = 9600,误差也是 0。但如果时钟是 72MHz、波特率是 921600,USARTDIV = 72000000 / (16 * 921600) = 4.8828125,BRR = 4 * 16 + 14 = 78,实际波特率 = 72000000 / (16 * 4.875) = 923076,误差约 0.16%,还在可接受范围内。

但如果时钟不是 72MHz,比如用了内部 HSI 的 8MHz,那波特率误差就会大很多。所以串口通信一定要用外部晶振,而且时钟配置要准确。如果实在要用内部时钟,建议把波特率降低,比如用 9600 或者 4800,误差会小一些。

5. 从跑通例程到独立做项目,中间还差什么

5.1 外设驱动开发的通用套路

跑通 LED 和串口之后,下一步是学其他外设:定时器、ADC、SPI、I2C、CAN 等。每个外设的配置流程其实都差不多:开时钟、配 GPIO、配外设参数、使能外设、写中断服务函数(如果需要)。以定时器为例,配置流程是:开 TIM 时钟、配 GPIO(如果是 PWM 输出)、配时基结构体(预分频、自动重装载值、计数模式)、配输出比较结构体(PWM 模式、占空比)、使能定时器、使能输出比较通道。

学外设的时候,我建议先看参考手册里对应章节的功能描述,理解这个外设能做什么、有哪些模式、寄存器怎么配。然后看标准库的例程,理解代码是怎么实现的。最后自己改参数,观察现象变化。比如定时器 PWM,改预分频值看频率变化,改比较值看占空比变化。这样学下来,比单纯抄代码理解得深得多。

5.2 用模块化思维组织工程代码

刚开始写代码的时候,很多人把所有代码都堆在 main.c 里,几百行还能忍,上千行就乱了。正确的做法是按功能模块拆分文件,比如 led.c/led.h、uart.c/uart.h、timer.c/timer.h。每个模块对外提供初始化函数和操作函数,内部实现细节放在 .c 文件里,.h 文件只放函数声明和宏定义。

模块化还有一个好处是方便复用。比如你写了一个串口驱动,下一个项目直接把这个 .c 和 .h 文件拷过去就能用,只需要改一下引脚定义和波特率。我自己的习惯是建一个“驱动库”文件夹,把常用的 LED、串口、定时器、按键、OLED 等驱动都放进去,新项目直接拿来用,省很多时间。

头文件里要用条件编译防止重复包含:

#ifndef __LED_H #define __LED_H #include "stm32f10x.h" void LED_Init(void); void LED_On(void); void LED_Off(void); void LED_Toggle(void); #endif

这个宏定义的名字要和文件名对应,比如 led.h 就用 __LED_H,避免不同头文件之间冲突。

5.3 调试手段的进阶:SWO、逻辑分析仪与 RTT

除了串口打印,还有一些更高级的调试手段。SWO(Serial Wire Output)是 SWD 接口的一个引脚,可以输出调试信息,速度比串口快,而且不占用串口资源。配置 SWO 需要在 Keil 里打开 ITM 功能,然后在代码里用 ITM_SendChar 输出。不过 SWO 需要调试器支持,ST-Link V2 支持,但有些山寨版可能不支持。

逻辑分析仪是分析时序问题的利器。比如 I2C 通信失败,用逻辑分析仪抓一下 SCL 和 SDA 的波形,就能看出是起始条件不对、地址不对、还是 ACK 没收到。市面上几十块钱的 8 通道逻辑分析仪配合 PulseView 软件,足够分析大部分低速数字信号。SPI、I2C、UART 的时序都能抓。

RTT(Real Time Transfer)是 SEGGER 公司推出的一种调试技术,通过 J-Link 调试器在目标板和电脑之间传输数据,速度比串口快很多,而且不需要占用串口。如果你用的是 J-Link,可以试试 RTT,配合 RTT Viewer 软件,打印调试信息非常方便。不过 ST-Link 不支持 RTT,所以用 ST-Link 的话还是老老实实用串口吧。

5.4 可以拿来练手的几个小项目方向

学完基础外设之后,最好的巩固方式是自己做几个小项目。第一个推荐的是“按键控制 LED”,看似简单,但涉及 GPIO 输入、消抖、状态机,能学到不少东西。第二个是“串口命令行”,通过串口发送指令控制 LED 或者读取传感器数据,涉及串口接收中断、缓冲区管理、字符串解析。第三个是“定时器 PWM 调光”,用 PWM 控制 LED 亮度,涉及定时器配置、占空比计算。第四个是“OLED 显示”,用 I2C 或 SPI 驱动 OLED 屏幕,显示文字和图形,涉及通信协议、字库取模、图形绘制。

如果这些都能独立做出来,那基本可以尝试更复杂的项目了,比如“超声波测距”(定时器输入捕获)、“步进电机控制”(定时器 PWM + 方向控制)、“CAN 通信”(CAN 外设配置 + 报文收发)、“USB 设备”(USB 外设 + 描述符配置)。每个项目都会遇到新的问题,解决这些问题的过程就是成长的过程。

提示:做项目的时候养成写文档的习惯,把遇到的问题、解决方法、关键代码都记下来。过一段时间回头看,你会发现这些记录比任何教程都有价值。

6. 关于学习节奏和心态的一点个人体会

STM32 的学习曲线不算陡,但也不平。前两周可能会觉得很多东西不理解,寄存器、时钟树、中断优先级,一堆概念堆在一起。这很正常,我当初也是这么过来的。我的建议是不要试图一次搞懂所有东西,先跑通例程,看到现象,再回头理解原理。比如时钟树,一开始不用完全搞懂,知道 HSE 倍频到 72MHz 就行,等遇到串口乱码或者定时器不准的时候,再深入看时钟树的分频关系,理解会更深。

另外,不要只盯着 STM32F103。这颗芯片虽然经典,但确实有点老了。学完 F103 之后,可以看看 STM32F4 或者 STM32G0,它们的外设更丰富、性能更强、价格也不贵。HAL 库的跨系列兼容性这时候就体现出来了,F103 上写的 HAL 代码,移植到 F4 上可能只需要改几个宏定义。如果对无线通信感兴趣,可以看看 ESP32 系列,它集成了 Wi-Fi 和蓝牙,和 STM32 配合使用能做出很多有意思的东西。

最后说一句,嵌入式开发是一个需要动手的领域,看十遍教程不如自己焊一块板子、写一遍代码。板子买回来了,就赶紧插上电,从点亮第一个 LED 开始。遇到问题不要怕,每一个坑都是经验,踩过的坑越多,后面走的路越顺。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进