0%

STM32再出发——从Keil转到VSCode+CubeMX

前言

大一上的时候,我曾短暂学习过 STM32,当时用的是 keil5 的开发方式。Keil 的代码补全基本没有、所有内容全靠硬写,或者说硬抄。由于当时面临很多压力,所以我暂时终止了STM32的学习。

这次重新出发,我决定换一套更现代化的工具链:

  • STM32CubeMX —— 图形化配置引脚和外设,生成初始化代码
  • VS Code + STM32 扩展 —— 现代化的代码编辑器,补全、跳转、Git 集成
  • HAL 库 —— 用库函数操作外设,先建立工程能力,以后再深入底层

最终效果:通过 VS Code 管理项目,只用写核心的功能代码,一键编译,一键烧录到开发板,LED 灯点亮并闪烁。


工具链概览

先搞清楚每样东西是干什么的:

工具 作用
STM32CubeMX 图形化配置芯片引脚、时钟、外设,生成初始化 C 代码
VS Code 代码编辑器,用于编写业务逻辑
STM32 VS Code Extension ST 官方插件,集成编译、烧录、调试功能
GCC Toolchain 编译器(arm-none-eabi-gcc),把代码编译成机器码
ST-Link 调试器/烧录器,通过 SWD 接口连接电脑和开发板

整个工作流程:

1
CubeMX 配置引脚 → 生成代码 → VS Code 编写LED灯闪烁的逻辑 → 编译 → 烧录 → 跑起来!

安装步骤

安装 STM32CubeMX

  1. 去 ST 官网 下载 STM32CubeMX(需要注册 ST 账号,免费)
  2. 运行安装程序,一直下一步
  3. 安装过程中它会自动下载芯片支持包(STM32Cube FW_F1 等),等它下载完就行

安装完成后桌面上会有个 STM32CubeMX 的图标。

安装 VS Code 与 STM32 扩展

  1. 去 VS Code 官网 下载安装
  2. 安装完成后,打开 VS Code,点左侧的扩展图标(或按 Ctrl+Shift+X)
  3. 搜索 STM32,找到 STMicroelectronics 官方出的 STM32 VS Code Extension,点击安装
  4. 安装完成后,VS Code 会自动检测并建议安装一些配套工具,确认即可

这个扩展帮我们集成了编译和烧录的功能,不用自己手动配 CMake 和命令行。

ST-Link 是连接电脑和开发板的调试器。插上电脑后,如果设备管理器里显示黄色感叹号,说明没装驱动。

解决方法:

  1. 下载 ST-Link 驱动(en.stsw-link009.zip)
  2. 解压后,64 位系统运行 dpinst_amd64.exe
  3. 安装完成后,重新插拔 ST-Link
  4. 打开设备管理器,应该能看到 STMicroelectronics STM32 STLink,没有黄色感叹号

如果不想单独装驱动,安装 STM32CubeProgrammer 也会自动装好 ST-Link 驱动。


创建第一个项目

在 CubeMX 中新建项目

  1. 打开 STM32CubeMX
  2. 点击 “New Project”
  3. 在搜索框输入 STM32F103C8T6(我用的是 Keysking大佬开发的集成开发板),双击选中
  4. 弹窗问是否初始化外设,点 “No”

配置引脚

在 CubeMX 的 Pinout View视图中,芯片的每个引脚都可以点击配置。image-20260724202056262

配置 LED 灯引脚(PA7):

  1. 在芯片图上找到 PA7,点击它

  2. 选择 GPIO_Outputimage-20260724202119625

  3. 在左侧的 Categories面板下面的System Core里找到 GPIO 设置,点击后中间会跳出Configuration界面,如下图所示

  4. image-20260724202248835

  5. 选中PA7引脚,下方跳出具体的设置界面image-20260724202524255

  6. 此处本来GPIO output level是low,也就是低电平,把它改成High,高电平即可

  7. image-20260724202648342

  8. 其他保持默认:推挽输出、无上下拉、低速度

配置调试接口

依旧是Categories面板下面的System Core,找到 SYS 设置,点击后中间会跳出SYS Mode and Configuration界面,如下图所示:屏幕截图 2026-07-24 211145

  • Debug 选择 Serial Wire
  • 这样 PA13/SWDIO 和 PA14/SWCLK 会被自动配置,用于 ST-Link 调试

这一步很重要!否则烧录一次后,下次 SWD 就被禁用了,再也烧不进新程序。

选择工具链

切换到 Project Manager 标签页:image-20260724203047284

设置项 值
Project Name stm32code-demo
Project Location 选一个纯英文目录–此处我选的是D盘下的stm32project目录
Toolchain / IDE CMake(关键!一定要选 CMake,而不是其他的)
Default Compiler/Linker GCC

点击右上角 **”Generate Code”**,生成代码。弹窗问要不要打开项目,选 **”Open Project”**,它会自动用 VS Code 打开。


在 VS Code 中编写代码

项目打开后,目录结构大致是这样:

1
2
3
4
5
6
7
8
9
10
stm32code-demo/
├── Core/
│ ├── Inc/
│ │ └── main.h ← 引脚宏定义在这里
│ └── Src/
│ └── main.c ← 主程序
├── Drivers/ ← HAL 库和 CMSIS
├── cmake/ ← CMake 配置
├── CMakeLists.txt ← CMake 主文件
└── stm32code-demo.ioc ← CubeMX 配置文件(不要删)

认识 main.c

CubeMX 生成的 main.c 骨架代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(void)
{
HAL_Init(); // 1. 初始化 HAL 库
SystemClock_Config(); // 2. 配置系统时钟
MX_GPIO_Init(); // 3. 初始化 GPIO(在这里配置引脚)

/* USER CODE BEGIN 2 */
// 4. 用户初始化代码写在这里
/* USER CODE END 2 */

while (1)
{
/* USER CODE BEGIN WHILE */
// 5. 主循环 —— 反复执行的逻辑
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
}

关键点:**USER CODE BEGIN 和 USER CODE END 之间的代码**,CubeMX 重新生成时不会覆盖。你自己的逻辑都写在这之间。

写点灯代码

让 LED 一直亮: 不需要写任何代码。MX_GPIO_Init() 里已经通过 HAL_GPIO_WritePin() 设好了初始电平,灯自然会亮。

让 LED 闪烁: 在 while(1) 里写翻转逻辑:

1
2
3
4
5
6
7
8
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_7); // PA7 翻转电平
HAL_Delay(500); // 等 500ms
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}

如果用到了 User Label,也可以写成 HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);

关于 USER CODE 区块

这是 CubeMX 最贴心的设计之一。每次 CubeMX 重新生成代码时:

  • ✅ USER CODE BEGIN/END 之间的内容会被保留
  • ❌ 外部的代码会被完全覆盖

所以养成习惯:所有自己写的代码,都放在 USER CODE 区块里。

常用的 USER CODE 位置:

区块 位置 用途
USER CODE BEGIN 2 MX_GPIO_Init() 之后 初始化设置
USER CODE BEGIN WHILE while(1) 内部开头 主循环逻辑
USER CODE BEGIN 3 while(1) 内部结尾 主循环末尾
USER CODE BEGIN 0 main.c 顶部函数区 自定义函数

解读 MX_GPIO_Init —— CubeMX 生成的初始化代码

前面提到,CubeMX 帮我们生成了引脚配置代码。但”能用”和”理解”是两回事,我们来看看 MX_GPIO_Init() 函数里每段代码到底在干什么。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};

/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);

/*Configure GPIO pin : PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

逐行解析

代码 作用
GPIO_InitTypeDef GPIO_InitStruct = {0}; 定义一个 GPIO 配置结构体,{0} 把所有成员初始化为 0,避免使用未初始化的值
__HAL_RCC_GPIOA_CLK_ENABLE() 使能 GPIOA 端口的时钟。STM32 的外设默认时钟是关闭的,使用前必须开启时钟,否则寄存器写不进。这条是一个宏,背后操作的是 RCC 寄存器
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET) 在配置之前先把引脚输出设为低电平,保证初始时 LED 是灭的,不会出现瞬间闪一下
GPIO_InitStruct.Pin = GPIO_PIN_7 指定要配置的引脚是第 7 号引脚(PA7)
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP 输出模式:推挽输出(Push-Pull)。这是 GPIO 最常用的输出模式——可以输出高/低电平,驱动能力强
GPIO_InitStruct.Pull = GPIO_NOPULL 上下拉:无。LED 是输出引脚,不需要上下拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW 输出速度:低速。LED 翻转速度很慢(几百 ms 一次),低速就够用了。速度越快功耗越高、电磁干扰越大
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct) 将上面配置的结构体写入寄存器,GPIO 才真正按我们的设置工作。GPIOA 参数表示操作 A 组引脚

关于 GPIO 模式

GPIO_MODE_OUTPUT_PP 是最常用的输出模式,还有一些其他模式你以后会遇到:

模式 含义 典型用途
GPIO_MODE_OUTPUT_PP 推挽输出 驱动 LED、普通数字信号输出
GPIO_MODE_OUTPUT_OD 开漏输出 I2C 通信、电平转换
GPIO_MODE_INPUT 浮空输入 读取按键、传感器信号
GPIO_MODE_IT_RISING 上升沿触发中断 外部中断
GPIO_MODE_IT_FALLING 下降沿触发中断 按键中断
GPIO_MODE_AF_PP 复用推挽输出 UART、SPI 等外设功能引脚

目前只需掌握前两个输出模式,后续文章中会接触到更多。

关键理念:CubeMX 生成 → 解析代码 → 脱离 CubeMX

CubeMX 帮我们生成了这段代码,我们可以直接拿来用,但这不意味着可以跳过理解。建议的学习路径是:

  1. 先用 CubeMX 生成 —— 快速搭建项目,不纠结寄存器细节
  2. 再解析生成的代码 —— 搞清楚每行配置在做什么
  3. 最后能不用 CubeMX 也写出来 —— 真正掌握后,即使工作中没有 CubeMX 也能手写驱动

这也是后续所有博客都会保持的风格——不只是展示 CubeMX 的操作步骤,还会解析生成的代码。


编译

写好了代码,怎么编译成开发板能运行的机器码?

通过 VS Code 编译

在 VS Code 底部状态栏,ST 扩展提供了按钮:

  1. 先点 齿轮图标 “生成” → 开始编译
  2. 观察底部终端面板的输出
  3. 看到类似下面的输出就是编译成功了:
1
2
3
4
5
[build] Memory region         Used Size  Region Size  %age Used
[build] RAM: 1584 B 20 KB 7.73%
[build] FLASH: 4628 B 64 KB 7.06%
[driver] 生成完毕: 00:00:01.235
[build] 生成已完成,退出代码为 0

其中 RAM 和 FLASH 显示了固件占用的空间,生成完毕 和 退出代码为 0 代表编译成功。

编译产物在 build/Debug/ 目录下:

  • stm32code-demo.elf — 可执行文件(包含调试信息)
  • stm32code-demo.bin — 纯二进制文件(用于烧录)

常见编译错误

错误 原因 解决
undefined reference to XXX_Pin 引脚宏被删了但代码还在引用 去 CubeMX 重新配引脚并生成代码
arm-none-eabi-gcc: not found 工具链没装上 重新安装 STM32 VS Code Extension
CMake Error: toolchain file not found 工具链路径不对 确保 STM32 扩展已正确安装

烧录

编译通过后,把程序下载到开发板上。

硬件连接

ST-Link 和开发板通过 SWD 接口连接,只需要 4 根线:

ST-Link 开发板
SWDIO PA13
SWCLK PA14
GND GND
3.3V VCC

有些 ST-Link 还多一根 NRST(复位)线,也可以接上,但不是必须的。

驱动问题排坑

插上 ST-Link 后,如果 VS Code 检测不到设备,大概率是驱动没装。

排坑步骤:

  1. 打开设备管理器(Win + X → 设备管理器)
  2. 看有没有 STM32 STLink 出现
  3. 如果有黄色感叹号 → 运行 dpinst_amd64.exe 安装驱动
  4. 装好驱动后,重新插拔 ST-Link
  5. 设备管理器里应该显示正常

烧录到开发板

驱动正常后,在 VS Code 中:

  1. 点击左侧的 运行和调试 图标(🦖 或 Ctrl+Shift+D),打开运行和调试面板
  2. 确认下方 STM32Cube设备与开发板 列表中能看到 STM32 STLink(STM32设备列表
  3. 点击上方的 运行和调试 按钮,选择 STM32Cube:STM32 Launch STLink GDB Server选择Launch STLink GDB Server
  4. 此时 ST-Link 开始连接开发板,终端会输出连接日志
  5. 点击 VS Code 上方调试工具栏中的 继续 按钮(▶️ 图标)点击继续按钮运行程序
  6. 程序开始运行,LED 灯闪烁 🎉

如何确认烧录成功

烧录完成后,如果代码写的是闪烁逻辑,会看到:

  • LED 以 500ms 的间隔一亮一灭 💡
  • 这说明:程序成功烧录 → 芯片运行 → GPIO 正常翻转

如果灯不亮:

现象 可能原因
完全不亮 接线反了?引脚号对不对?初始电平设的是 High 吗?
一直亮不闪 while(1) 里没写 TogglePin 或 Delay
亮一下就不亮了 可能芯片复位了,检查电源

总结

与传统 Keil 开发的对比

对比项 Keil 方案 VS Code + CubeMX 方案
界面 老旧,2000 年代风格 现代化,美观
代码编辑 无补全 智能补全、跳转定义
配置方式 手动写寄存器或配图形界面 CubeMX 统一图形化配置
项目迁移 难跨平台 基于 CMake,跨平台
费用 收费(商业版) 全部免费
学习曲线 陡峭(要学很多很多很多细节) 平缓(HAL 封装好了,只关心核心功能)

工作流总结

1
2
3
4
5
6
7
1.  打开 CubeMX → 配引脚 → Generate Code
2. 在 VS Code 中打开项目
3. 在 USER CODE 区域写业务逻辑
4. Ctrl+Shift+P → STM32: Build
5. 插上 ST-Link 连接开发板
6. Ctrl+Shift+P → STM32: Download
7. 🎉 灯亮了!

后续学习路线

点灯是嵌入式开发的 **”Hello World”**,下一步可以学:

  • 外部中断 —— 按键控制 LED
  • 定时器/PWM —— 呼吸灯、舵机控制
  • UART 串口 —— 和电脑通信,打印调试信息
  • ADC —— 读取传感器数值
  • I2C/SPI —— 驱动 OLED 屏幕、传感器模块

每条路线跑通后,我都会写博客记录下来,欢迎继续关注 😄


参考资源