1. STM32开发环境搭建:工欲善其事,必先利其器
做嵌入式开发,第一件事就是把环境搭好。我见过不少新手,代码写得挺溜,结果卡在编译不过、下载失败这种基础问题上,一查全是工具链配置的坑。所以咱们第一章,先把地基打牢。
这一章我们主要搞定三件事:Keil MDK安装配置、STM32CubeMX工具链,以及一个很多人纠结的问题——LL库和HAL库到底选哪个?
本章核心目标:搭建一个能编译、能下载、能调试的完整开发环境,并理解两种官方库的适用场景。
1.1 Keil MDK:老牌IDE的安装与配置
Keil MDK,圈里人都叫它"Keil"。说实话,它的界面确实有点复古,但稳定性没得说。我在几个量产项目里都用它,没出过幺蛾子。
安装步骤
- 下载安装包:去ARM官网或Keil官网下载MDK-Arm最新版(目前是5.38或5.39)。注意别下成C51版,那是给51单片机用的。
- 运行安装程序:一路Next,路径建议用默认的
C:\Keil_v5。我曾经手贱改到D盘,结果后面装Pack包各种路径报错。 - 安装器件支持包:安装完后打开Keil,点
Pack Installer,找到你的芯片型号(比如STM32F103C8T6),点击Install。这一步很多人忘,结果编译时报"device not found"。 - 注册/激活:如果你有正版License,在
File → License Management里输入。学生或个人开发者可以用评估版,代码限制在32KB以内——做小项目够用了。
⚠️ 避坑提醒:我曾经遇到过Keil突然打不开,折腾半天发现是Windows Defender把破解文件杀了。如果你用评估版,记得把Keil目录加入杀毒软件白名单。
编译器选择:AC5还是AC6?
Keil MDK自带两个编译器:ARM Compiler 5(AC5)和 ARM Compiler 6(AC6)。AC6基于LLVM,编译速度更快、优化更好。我个人习惯:新项目一律用AC6,老项目如果代码量大且稳定,才继续用AC5。
| 对比项 | AC5 | AC6 |
|---|---|---|
| 编译速度 | 较慢 | 快(约2-3倍) |
| 代码优化 | 一般 | 更好(-O2/-O3效果明显) |
| C++支持 | 有限 | 完整 |
| 兼容性 | 老项目稳定 | 部分旧代码需调整 |
切换方法:在工程选项 Target → ARM Compiler 里选择 Use default compiler version 6。
1.2 STM32CubeMX:图形化配置利器
STM32CubeMX是ST官方出的图形化配置工具。说白了,就是帮你自动生成初始化代码,省得你对着寄存器手册一行行敲。我刚开始用的时候还不太信任它,后来发现——真香。
安装与基本使用
- 下载安装:去ST官网搜"STM32CubeMX",下载最新版。需要注册一个ST账号,免费的。
- 安装固件包:打开CubeMX,点
Help → Manage embedded software packages,选择你的芯片系列(如STM32F1),下载对应版本的固件包。注意版本号,我建议用最新的稳定版。 - 新建工程:点
New Project,在Part Number里搜你的芯片型号,双击选中。 - 配置外设:在Pinout视图里点选你要用的外设(比如USART1、I2C1、SPI1),配置引脚和参数。
- 生成代码:点
Project Manager标签,设置工程名、路径、IDE类型(选MDK-ARM V5),然后点GENERATE CODE。
💡 我的习惯:生成代码后,我会在CubeMX里把 Project Settings → Code Generator 中的 Generate peripheral initialization as a pair of .c/.h files per peripheral 勾上。这样每个外设单独一个文件,代码结构更清晰。
1.3 LL库与HAL库:到底怎么选?
这个问题,几乎每个用STM32的人都会纠结。我直接说结论:没有绝对的好坏,只有合不合适的场景。
HAL库(硬件抽象层)
HAL库是ST主推的库,封装层次高,API函数名长但语义清晰。比如初始化一个UART,调用 HAL_UART_Init() 就行,内部帮你处理了时钟使能、GPIO配置、中断设置等一堆细节。
- 优点:可移植性好,换芯片时改改参数就行;代码易读,适合团队协作。
- 缺点:代码量大,效率偏低。我在一个电机控制项目里用过HAL,中断响应延迟比LL多了将近10个时钟周期。
- 适用场景:产品原型开发、对实时性要求不高的项目、初学者。
LL库(底层库)
LL库是轻量级库,直接操作寄存器,API函数名短小精悍。比如 LL_USART_Enable() 就是直接写寄存器位。
- 优点:代码小、执行快、资源占用少。我做过一个Bootloader,用LL库编译出来只有4KB,HAL库至少8KB起步。
- 缺点:可移植性差,换芯片得改不少代码;需要你对寄存器有一定了解。
- 适用场景:资源受限的MCU(如STM32G0系列)、实时性要求高的应用(如电机控制、传感器采集)、Bootloader。
| 对比维度 | HAL库 | LL库 |
|---|---|---|
| 代码体积 | 大(约LL的2-3倍) | 小 |
| 执行效率 | 中等 | 高(接近寄存器操作) |
| 学习曲线 | 平缓 | 较陡(需懂寄存器) |
| 可移植性 | 好 | 差 |
| 调试难度 | 低(封装层多) | 高(直接操作寄存器) |
| 官方支持力度 | 强(持续更新) | 中等 |
我的选择策略
我个人习惯是:项目前期用HAL快速验证,后期性能瓶颈部分用LL重写。比如一个传感器数据采集项目,初始化部分用HAL,中断服务函数里用LL直接操作寄存器,这样既保证了开发效率,又保证了实时性。
核心建议:如果你刚开始学STM32,从HAL库入手。等你对寄存器熟悉了,再尝试LL库。不要一上来就追求"底层操作",先把功能跑通再说。
1.4 第一个工程:点亮LED
环境搭好了,咱们跑个经典的点灯程序验证一下。这里我用LL库演示,因为代码更简洁,能让你看到底层是怎么工作的。
/* 使用LL库点亮PB0引脚的LED */
#include "stm32f1xx_ll_gpio.h"
#include "stm32f1xx_ll_bus.h"
int main(void)
{
/* 使能GPIOB时钟 */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB);
/* 配置PB0为推挽输出 */
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_0, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_0, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_0, LL_GPIO_SPEED_FREQ_LOW);
while(1)
{
/* 点亮LED */
LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_0);
/* 延时(简单循环,实际项目用定时器) */
for(volatile uint32_t i = 0; i < 100000; i++);
/* 熄灭LED */
LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_0);
for(volatile uint32_t i = 0; i < 100000; i++);
}
}
你看,LL库的代码很直白:LL_APB2_GRP1_EnableClock 使能时钟,LL_GPIO_SetPinMode 设置模式,LL_GPIO_SetOutputPin 输出高电平。每一步都对应一个寄存器操作,没有多余的封装。
💡 调试小技巧:第一次下载程序时,如果LED不亮,先检查三件事:1) 芯片供电是否正常;2) 引脚有没有被其他外设占用(CubeMX里可以看Pinout视图);3) 时钟配置是否正确。我遇到过好几次,都是因为忘了使能GPIO时钟。
好了,环境搭好了,第一个程序也跑通了。接下来我们会深入每个外设的底层驱动,从GPIO开始,一步步把STM32的各个模块吃透。记住,嵌入式开发没有捷径,多动手、多踩坑,经验就是这么积累起来的。