1. 课程导入

做嵌入式开发,第一件事就是把环境搭好。我见过不少新手,代码写得挺溜,结果卡在编译不过、下载失败这种基础问题上,一查全是工具链配置的坑。所以咱们第一章,先把地基打牢。

这一章我们主要搞定三件事:Keil MDK安装配置、STM32CubeMX工具链,以及一个很多人纠结的问题——LL库和HAL库到底选哪个?

本章核心目标:搭建一个能编译、能下载、能调试的完整开发环境,并理解两种官方库的适用场景。

STM32开发环境知识体系 STM32 开发环境 Keil MDK (IDE/编译器) STM32CubeMX HAL库 vs LL库 Keil MDK 安装 · 注册 · 配置 AC6/AC5编译器选择 CubeMX 图形化配置 代码生成 库选择策略 HAL: 可移植性优先 LL: 性能/资源优先 💡 开发流程建议 CubeMX生成初始化代码 → Keil MDK编写业务逻辑 根据项目需求选择HAL或LL,也可混合使用

1.1 Keil MDK:老牌IDE的安装与配置

Keil MDK,圈里人都叫它"Keil"。说实话,它的界面确实有点复古,但稳定性没得说。我在几个量产项目里都用它,没出过幺蛾子。

安装步骤

  1. 下载安装包:去ARM官网或Keil官网下载MDK-Arm最新版(目前是5.38或5.39)。注意别下成C51版,那是给51单片机用的。
  2. 运行安装程序:一路Next,路径建议用默认的 C:\Keil_v5。我曾经手贱改到D盘,结果后面装Pack包各种路径报错。
  3. 安装器件支持包:安装完后打开Keil,点 Pack Installer,找到你的芯片型号(比如STM32F103C8T6),点击Install。这一步很多人忘,结果编译时报"device not found"。
  4. 注册/激活:如果你有正版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官方出的图形化配置工具。说白了,就是帮你自动生成初始化代码,省得你对着寄存器手册一行行敲。我刚开始用的时候还不太信任它,后来发现——真香。

安装与基本使用

  1. 下载安装:去ST官网搜"STM32CubeMX",下载最新版。需要注册一个ST账号,免费的。
  2. 安装固件包:打开CubeMX,点 Help → Manage embedded software packages,选择你的芯片系列(如STM32F1),下载对应版本的固件包。注意版本号,我建议用最新的稳定版。
  3. 新建工程:点 New Project,在 Part Number 里搜你的芯片型号,双击选中。
  4. 配置外设:在Pinout视图里点选你要用的外设(比如USART1、I2C1、SPI1),配置引脚和参数。
  5. 生成代码:点 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的各个模块吃透。记住,嵌入式开发没有捷径,多动手、多踩坑,经验就是这么积累起来的。