用 Keil 写 STM32 的老哥,估计都经历过这些:工程模板配到怀疑人生、代码补全约等于没有、串口调试全靠串口助手反复拔线、想用个 Git 管理代码还得小心翼翼不把 Keil 的临时文件提交上去。最开始我也是这么过来的,直到换了 Vscode + PlatformIO 这套组合,才感觉 STM32 开发总算有点现代 IDE 的样子了。这篇文章就把我从环境搭建到串口调试的完整过程,以及踩过的各种坑,一次性说清楚。
这个方案到底强在哪?首先是项目管理清爽了,PlatformIO 用 platformio.ini 一个文件搞定板卡、框架、烧录器配置,工程迁移特别方便;其次是代码体验确实好,Vscode 的补全、跳转、格式化、Git 集成都是碾压级体验;最重要的一点是跨平台,Windows、Linux、macOS 下同一套流程,比如在 Linux 服务器上跑个 Docker 顺便编固件,这套流程也能无缝衔接。不管你是刚买了一块 STM32 最小系统板的入门玩家,还是被 Keil 模板和编译速度折磨的进阶选手,这篇文章都能帮你少走很多弯路。
1. Vscode + PlatformIO 环境搭建与工程创建
1.1 Vscode 安装没有太多花活,但有三个细节值得注意
Vscode 的安装本身没什么幺蛾子,官网下载安装包一路下一步就行。但有几个细节直接影响后面的体验。
第一点是建议在安装时勾选“添加到 PATH”和“将 Code 注册为受支持文件的编辑器”,前者方便后面在终端里直接敲code .打开项目,后者解决双击工程文件时系统不知道用什么打开的问题。
第二点是扩展插件的安装。Vscode 市场里的插件质量参差不齐,但 PlatformIO IDE 这个插件是核心,直接在插件市场搜索 “PlatformIO IDE” 安装即可。安装过程需要下载 PlatformIO Core,这一步在国内网络环境下经常处于“转圈圈”的状态,很多人的第一道坎就在这里。
第三点是我个人的习惯,建议在设置里把files.eol设置为\n,把editor.renderWhitespace设置为all。前者避免在 Windows 上代码行尾混入\r\n导致编译告警或 Git 差异混乱,后者让你一眼看出每行是不是多了多余空格。这些细节在后期排查问题时会省不少时间。
1.2 PlatformIO 创建工程慢?这是第一次用几乎必踩的坑
装好插件后,新建工程时选完板卡和框架,你会发现进度条卡在 “Downloading Platform ...” 这一步一动不动,等个十几分钟算正常的,偶尔直接失败退出。这不是你的操作有问题,而是 PlatformIO 在拉取平台包和工具链时访问的是国外的 CDN,网络稍不稳定就会卡死。
解决办法是给 PlatformIO 配置国内镜像源。在用户目录下找到.platformio文件夹,里面有个platformio.ini配置文件,添加下面的内容:
[platformio] packages_dir = C:/.platformio/packages [env] platform_packages = platformio/framework-stm32cubef1@^1.8.0在platformio.ini中加上这一行核心配置:
[platformio] registry_url = https://pio.registry.kulesz.pl实际上 PlatformIO 在 6.x 版本以后,镜像配置方式变成了在用户目录的.platformio目录下放一个platformio.ini,里面写上:
[platformio] registry_url = https://kulesz.pl但我这里要补充一点:不同版本的 PlatformIO 配置方式略有差异,最简单的办法其实是“多试几次”。反正 PlatformIO 支持断点续传,多触发几次下载通常能把它磨下来。另外一个有效的办法是开代理,但代理这块我们就不展开讲了,自己体会。
1.3 板卡和框架怎么选?STM32F103C8T6 的标准答案
新建工程时,Boards 搜索框里输入 STM32F103C8T6,能看到很多相似选项,比如BluePill F103C8、STM32F103C8、generic STM32F103C8T6等。很多人在这里开始纠结,导致选错板卡,后面编译出来的代码烧进去跑不起来。
我的建议是选generic STM32F103C8T6。原因很简单:这个板卡定义是最通用的,它对应的是芯片本身,而不是某个商家的开发板。如果你选了某个品牌的开发板定义,可能会有厂商自定义的 LED 引脚、自定义时钟配置等,反而不通用。板上带的 LED 引脚每个板子不同,自己用ld脚本或代码定义就好。
Framework 这里有两个选项:STM32Cube和Arduino。如果你之前用过 HAL 库,选STM32Cube,用起来和 STM32CubeMX 生成的代码风格一致。如果你只想要上手快、最快看到点灯效果,选Arduino,PlatformIO 会帮你自动处理底层初始化,但后面写稍微复杂一点的工程时会觉得被框架束缚住了。这篇文章我们以STM32Cube为主,这也是绝大多数转过来的老铁实际使用的框架。
选完之后,PlatformIO 会生成一个基础的工程结构。默认目录如下:
src/main.cpp—— 主程序文件platformio.ini—— 工程配置文件include/—— 用户头文件目录lib/—— 用户库目录.vscode/—— Vscode 自动生成的配置
1.4 platformio.ini 配置模板
整个工程的核心就是platformio.ini,我贴一份实测没毛病的配置:
[env:genericSTM32F103C8] platform = ststm32 board = genericSTM32F103C8 framework = stm32cube upload_protocol = stlink monitor_speed = 115200 build_flags = -D HSE_VALUE=8000000 -D VDD_VALUE=3300这里解释一下关键项。platform = ststm32代表使用意法半导体官方平台包。board指定板卡。upload_protocol = stlink指定烧录用 ST-Link,这是最常用也最便宜的调试器。monitor_speed设置串口监视器波特率,如果你后面用串口调试,这一步千万别忘了,两边不一致读出来的全是乱码。
build_flags里的HSE_VALUE是一个隐藏大坑,后面我会专门讲。很多人的串口乱码、定时器时间不对,都是这个值不正确导致的。
2. 点灯工程:GPIO 输出背后的原理
2.1 时钟是个大前提
点灯看着简单,但 Step 1 往往是“系统跑不起来”或者“下载不进去”。写点灯代码之前,我建议你先理清这个逻辑:STM32 内部几乎所有外设都要在时钟开启之后才能工作。GPIO 属于挂在 APB 总线上的外设,所以要先用__HAL_RCC_GPIOB_CLK_ENABLE()之类的方式打开对应 GPIO 端口的时钟。
PlatformIO 用 STM32Cube 框架时,系统时钟树的初始化会在SystemClock_Config()函数里完成。比如 F103 默认外部晶振是 8MHz,通过 PLL 倍频到 72MHz 作为系统主频。所以在主函数最开始,你要调用:
HAL_Init(); SystemClock_Config();这两行不写,后面的点灯代码烧进去基本没反应。
2.2 点灯代码:直接可抄的版本
在src/main.cpp里写测试代码:
#include "stm32f1xx_hal.h" void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); while (1) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1); HAL_Delay(500); } }注意引脚号这里,我写的是GPIOB, GPIO_PIN_1,对应的是我手上的最小系统板载 LED 所接的 PB1。你的板子接哪个引脚,要查你自己的原理图。这是新手最容易想当然的地方:以为板载 LED 一定是 PC13,结果烧上去没反应,折腾半天还以为是代码问题。
2.3 为什么 SystemClock_Config 里的 HSE_VALUE 会导致串口乱码和延时不准
这是我踩过最深的坑之一。F103 的 HAL 库在计算系统时钟时会用到HSE_VALUE这个宏,它默认值是 8MHz。如果你的板子上焊的是 8MHz 晶振,恭喜你,没问题。但现在市面上很多 STM32F103 核心板用的是 8MHz 晶振,但也有相当一部分用 12MHz 晶振,还有一些模块用的是内部 HSI 时钟。
如果你板子上实际是 8MHz 晶振,但platformio.ini里没有定义HSE_VALUE,默认就是 8MHz,一切正常。如果板子实际是 12MHz 晶振,而HSE_VALUE仍旧是默认的 8MHz,HAL 计算 PLL 分频倍频参数时就错了,主频不是你以为的 72MHz,而是 48MHz 或者别的值。后果就是 HAL_Delay 的时间不对、串口波特率对不上(因为波特率是按照实际时钟算的)、定时器溢出不準。
所以配置里我写了:
build_flags = -D HSE_VALUE=8000000这块一定要根据你自己的晶振频率调整。不确定的话,可以先接上 ST-Link 用 STM32CubeProgrammer 读一下芯片的 RCC 配置,或者直接用示波器量 MCO 引脚输出。最笨的办法是先用默认值编译点灯,测一下 HAL_Delay(1000) 实际间隔是不是一秒,不对再调整。
2.4 烧录失败的几个常见原因
代码写好了,编译通过,结果烧录时报Cannot connect to target,或者No ST-Link detected,这种问题我前后被折磨了一个多星期。排查方向大致如下:
优先检查 ST-Link 驱动。Windows 下 ST-Link 驱动装不上或者版本不对,最常见现象是设备管理器里能看到一个带感叹号的未知设备。解决方法是去意法半导体官网下载最新的 ST-Link 驱动,注意别下载成 ST-Link Utility 的旧驱动。
然后检查接线。ST-Link 那边虽然有防呆,但很多淘宝的 ST-Link 是非官方版本,引脚定义五花八门。我推荐至少接四根线:SWDIO、SWCLK、GND、3.3V。部分山寨 ST-Link 的 SWDIO 和 SWCLK 丝印反了,出现这种情况只能换线序测试。
再检查目标板供电。部分核心板只靠 ST-Link 供电时,如果板上还有其他外设模块耗电,电压会跌落导致连接不稳定。这种情况下单独给板子接上一根 USB 供电,多半就能正常烧录了。
还有一点容易忽略的是upload_protocol配置。如果你手里是 J-Link,那就要写upload_protocol = jlink;如果你用的又是串口 ISP 下载,那用stlink肯定连不上。每次换调试器之前,先检查platformio.ini里的协议是不是和硬件对得上。
3. 串口调试:printf 重定向与串口打印实战
3.1 UART 初始化的基本套路
串口调试的核心就两步:第一步让 UART 外设工作起来,第二步把 printf 的数据流重定向到 UART 发送接口。UART 的初始化代码和 GPIO 类似,先开时钟,再配置引脚复用,最后配置 UART 参数。
以最常用的 USART1 为例,代码框架如下:
void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart1); }同时需要注意 GPIO 复用配置,PA9 作为 TX,PA10 作为 RX:
__HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);注意GPIO_MODE_AF_PP这个配置,F103 的 USART_TX 必须配成复用推挽输出,RX 配成浮空输入或复用开漏,不然收发都有问题。
3.2 printf 重定向:比 Keil 里省心但也没省到哪去
Keil 里用微库可以轻松实现 printf 重定向,PlatformIO 里 GCC 环境下做法不同。坑在于,如果你直接裸写一个fputc就调用printf,可能会遇到“编译过了但运行时硬 fault”的情况。原因在于 GCC 的printf默认实现会做一些内存分配相关的操作,而 STM32 的默认堆栈设置可能不够用,尤其是用 HAL 库之后。
最稳妥的做法是:
#include <stdio.h> int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } int fputc(int ch, FILE *f) { return __io_putchar(ch); }同时需要在platformio.ini的build_flags里加上:
build_flags = -D HSE_VALUE=8000000 -u _printf_float --specs=nano.specs --specs=nosys.specs--specs=nano.specs是必须的,它告诉链接器用精简版 C 库,这个库对内存和栈的要求低很多。-u _printf_float是为了支持%f格式化浮点数,不加的话打印浮点数会输出?或者不输出。--specs=nosys.specs提供了一些系统级的 stub 函数,避免链接器报_sbrk之类的未定义符号错误。
如果这些配置都加了,printf 还是不好使,检查一下huart1这个全局变量是不是在你 printf 调用之前已经初始化完成。如果是在只定义未初始化的时候调用,HAL_UART_Transmit 会卡死在超时等待里。
3.3 用 PlatformIO 自带 Serial Monitor 还是用第三方串口助手
PlatformIO 在 Vscode 底部有一个串口监视器按钮,它本质上是调用了pio device monitor命令。好处是没有任何驱动安装的额外步骤,而且和monitor_speed配置联动。点击底部小插头图标,或者终端里执行:
pio device monitor就能看到串口输出。需要注意的是,如果此时屏幕上有乱码,大概率是波特率不匹配,检查一下platformio.ini里的monitor_speed是不是和代码里huart1.Init.BaudRate一致。
我个人的习惯是:简单调试用 Vscode 内置的串口监视器,采集数据、保存日志或需要发送特殊协议时就外接一个串口调试助手。市面上常用的有 SSCOM、XCOM、MobaXterm 自带串口等。选串口助手时要注意一点:部分软件发送数据时会自动附加回车换行,这个在调试 AT 指令或 Modbus 协议时会多出字节,导致协议解析失败。建议先在一个文本编辑器里打两行十六进制数据,看看发送效果,再决定要不要用。
3.4 串口乱码和丢数据的排查套路
串口打印出错时,先别急着换线换芯片,按照这个顺序排查:
第一,确认波特率。代码里的BaudRate和监视器里的波特率一致。这一点我和朋友联调时翻车过很多次,很多时候就是一个人用了 9600,另一个人用 115200,输出全是乱码。
第二,确定晶振频率。这其实是乱码的隐藏元凶,HSE_VALUE 不对,UART 实际波特率偏差超出容忍范围,115200 就变成 104857 这种奇怪值了。这就是为什么我在前面强调必须在 build_flags 里显式声明 HSE_VALUE。
第三,检查 TX/RX 交叉接线。串口通信要交叉连接,开发板的 TX 接 USB 转 TTL 的 RX,开发板的 RX 接 USB 转 TTL 的 TX。同向连接是新手最常见的接线错误之一。
第四,排除供电问题。USB 转 TTL 的 3.3V 和外部电源同时供电时,如果两边电压有微小差异,可能导致部分电平长期处于不确定状态。用万用表量一下开发板供电电压 3.3V 是否稳定。
4. PlatformIO 常见编译问题和稳定性优化
4.1 编译慢?PlatformIO 的编译缓存够用吗
第一次编译 STM32Cube 框架的工程,会感觉像是在看 PPT。原因是 HAL 库编译量巨大,CPU 和磁盘 IO 都被拉满。但从第二次开始,PlatformIO 会启用编译缓存,未修改的源文件不会重新编译,速度会快很多。
如果你觉得还是慢,可以做两件事。第一件事是在platformio.ini里启用build_cache参数:
build_cache = .pio/build_cache这个参数会启用共享缓存目录,多个工程之间可以复用同一份编译产物。对于 STM32F103 这种 HAL 库是公共依赖的场景,效果非常明显。第二件事是在 Vscode 里把 PlatformIO 的编译任务并行数调高,这个在Advanced设置里找pio builder threads相关选项。
另外,尽量把工程放在 SSD 上,.pio目录的 IO 压力很大。把工程直接放在机械硬盘上首编,是很多人没有怀疑到的性能瓶颈。
4.2 编译报错:这些错误不用慌
在 PlatformIO 从 Keil 迁移过来时,常见编译报错基本都是 C 语言层面的习惯差异。
比如 Keil 里默认支持#include "stm32f1xx_hal.h"直接引用,但在 PlatformIO 中偶尔会报找不到文件,这时检查 include 路径是否写对了。PlatformIO 的 include 搜索路径不会自动包含 Keil 里常见的那些自定义文件夹,你可以通过build_flags = -I lib/xxx手动添加。
再比如,Keil 的 ARMCC 编译器对 C 语言的语法检查相对宽松,但 GCC 编译器会更严格。像函数声明缺失、隐式转换、结构体未初始化等,在 Keil 里可能只是警告,但在 GCC 下直接就是 error。解决办法就是按标准 C 语法来写,函数先声明再使用,结构体变量用{0}初始化。
4.3 工程目录与版本管理建议
我强烈建议一创建工程就把目录纳入 Git 管理。.pio目录和.vscode目录中有一部分是平台自动生成的,不建议提交到版本控制中。需要提交的关键文件是platformio.ini、src/、include/、lib/和test/。
在项目根目录创建.gitignore,写入:
.pio/ .vscode/如果后续你用 STM32CubeMX 生成过初始化代码,注意 CubeMX 的工程文件也和 PlatformIO 的工程结构不同,建议 CubeMX 单独建一个目录,把生成的 Core、Drivers 等拷贝到 PlatformIO 的 include 和 src 结构里,并统一维护。这样后面升级 HAL 库或者换芯片型号会省很多事。
4.4 从 PlatformIO 的配置层面避开的一堆小问题
有人会把build_flags里的-D STM32F103xB写错。PlatformIO 的 ststm32 平台包会基于板卡定义自动添加正确的芯片宏,但如果你手动覆盖了,可能导致 HAL 库内部的某些条件编译分支出错,常见的现象是#error "Please select first the target STM32F1xx device used in your application"。遇到这种错误,先检查自己有没有在 build_flags 里乱加-D STM32F103xE之类的宏。除非你确实在做芯片型号迁移,否则让 PlatformIO 自己管理即可。
还有一个常见问题是framework = stm32cube下默认生成的链接脚本是基于板卡定义自动选择的。如果你改用外部内存或者要自定义内存布局,记得创建自定义.ld文件并写入board_build.ldscript。
5. 实战经验:完整跑通一个“点灯 + 串口打印”的例程
5.1 一个完整的流程参考
说了这么多原理和坑,最后我分享一下我实际跑通的最小工程流程,每一步都经过了反复验证。
第一步,创建工程,板卡选generic STM32F103C8T6,框架选stm32cube。
第二步,在platformio.ini里写入以下配置:
[env:genericSTM32F103C8] platform = ststm32 board = genericSTM32F103C8 framework = stm32cube upload_protocol = stlink monitor_speed = 115200 build_flags = -D HSE_VALUE=8000000 --specs=nano.specs --specs=nosys.specs -u _printf_float第三步,编写主程序,实现 PB1 引脚 LED 翻转和 USART1 初始化后的 printf 输出。
第四步,用 ST-Link 连接 SWDIO、SWCLK、GND、3.3V,点击底部工具栏的上传箭头。
第五步,编译成功后,打开串口监视器,看到板子周期性输出日志,同时 LED 熄灭点亮交替。
这套流程整个跑下来,如果顺利,大约十分钟以内就能完成。如果中间任何一步卡住,请对照前面几章的排查点逐个检查。
5.2 一个实用的日志输出技巧
调试时我习惯封装一个简单的日志函数,避免到处直接调用HAL_UART_Transmit:
#include <stdarg.h> #include <stdio.h> void log_printf(const char *fmt, ...) { char buf[256]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), HAL_MAX_DELAY); }这样写有个好处:调试时可以直接控制开关。在代码里加一个编译宏,发布版本时直接把整个函数体替换成空操作,不用逐个删改调用点。
5.3 串口接收中断的坑:回调函数里及时搬运数据
串口发送搞定了,很多人开始研究串口接收。F103 的 HAL 库接收中断有几个容易犯错的地方。
开启接收中断的接口是:
HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);注意,这是“接收单个字节后触发一次回调”的机制。第一次启动后,如果不在回调函数里重新调用这个接口,接收完一个字节之后,后续数据就都不会再触发中断了。回调函数如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { received_data = rx_buffer; HAL_UART_Receive_IT(&huart1, &rx_buffer, 1); } }很多人在回调函数里做了数据处理,但忘了重新调用HAL_UART_Receive_IT,导致的现象是“第一个字节能收到,后面的全丢”。这个问题排查起来还挺隐蔽,我一度以为是 DMA 配置出了问题。
另外,在回调函数里尽量不要做耗时操作,比如调用HAL_Delay或者 printf 大量数据。中断回调里做重活会阻塞其他中断,时序要求高的场景很容易翻车。正确做法是把数据放到环形缓冲区里,主循环再统一处理。
5.4 项目管理:不要把所有代码堆在一个 main.cpp 里
新手很容易把所有代码堆进src/main.cpp,这在上手阶段问题不大,但工程稍微一复杂就变成一锅粥。PlatformIO 本身建议的目录划分是:驱动程序放lib/目录下的独立子文件夹,每层驱动包含自己的.h和.c文件。比如写一个 OLED 驱动,就新建lib/OLED/目录,在下面放OLED.h和OLED.cpp。
PlatformIO 会自动递归扫描lib/目录下的所有库并组织编译,这么管理的好处是代码复用率高、模块边界清晰。到了后期维护阶段,你只需要关注某个外设的驱动源码,而不需要在全局文件里搜索一个函数的实现。
6. STM32F103 之外的扩展:PlatformIO 还能玩什么
6.1 从 F103 到 ESP32、树莓派 Pico
PlatformIO 的价值不只是 STM32 这一家。它的核心设计理念是“一套工具链,多种平台”,同一个 Vscode 窗口下,你可以同时管理 STM32F103、ESP32、树莓派 Pico、Arduino UNO 等多个目标平台的工程。
比如你在做 ROS 2 相关的机器人项目时,经常会遇到需要同时开发 ESP32 和 STM32 的情况。STM32 负责电机闭环控制,ESP32 负责网络通信和 ROS 2 节点。PlatformIO 可以在同一个工作区里管理这两个工程,编译下载方式统一,日志输出方式统一,比来回切换 IDE 舒服太多。
6.2 配合 Docker 进行命令行编译
PlatformIO 提供完整的命令行接口,意味着它可以在无图形界面的 Linux 服务器中通过 Docker 容器完成编译。在docker环境中使用microros和ros2 humble时,经常需要在容器内部交叉编译 STM32 或 ESP32 的固件。PlatformIO 在这方面非常友好,一条命令:
docker run -v $(pwd):/project -it platformio/platformio pio run就能把当前目录的 PlatformIO 工程丢到 Docker 容器里编译,宿主机不用安装任何编译工具链。这在团队协作、CI/CD 场景下面特别好用,也是我要提一嘴的原因。
6.3 单元测试支持
PlatformIO 原生支持嵌入式单元测试框架 Unity,你可以在test/目录下写测试用例,用:
pio test -e native先在 PC 上跑一轮纯逻辑测试,再烧到板子上跑硬件相关的测试。这一套下来,代码质量明显会往上走一个台阶,尤其适合做电机控制、滤波算法这类需要反复调试数值逻辑的场景。
7. 我的个人体会总结
从 Keil 换到 Vscode + PlatformIO,最直观的感受是心态变好了。Keil 的工程管理和代码编辑体验停留在 2010 年代,而 PlatformIO 给出的是一个接近现代后端开发体验的嵌入式工作流。它没有牺牲 STM32 工程本身的灵活性,只是把模板化、重复性的工作压缩到了极致。
在踩过无数坑之后,我个人的总结是:这套组合最怕的不是技术问题,而是“以为自己配置错了”然后反复折腾配置,实际只是晶振频率写错了。老实说,我开发 STM32 这么久,百分之三十的时间花在调时钟、调波特率、查接线这些基础问题上,剩下的时间才是真正的业务逻辑开发。希望这份指南能帮你安全绕过一大半我刚入坑时踩过的雷。最后再分享一个小技巧,工程规模变大以后,编译前先看一眼 Vscode 左下角的 PlatformIO 状态栏,如果显示的小电脑图标不是绿色,多半是环境有问题,这时候点它重新加载一下,能避免很多莫名其妙的编译错误。