第一部分:核心概念与准备工作
在开始写代码之前,你需要理解几个核心概念。
什么是 STM32?
STM32 是意法半导体推出的基于 ARM Cortex-M 内核的 32 位微控制器系列,它功能强大、外设丰富、性价比高,是嵌入式开发领域非常主流的选择。
STM32 编程的核心思想:寄存器 vs. 库函数
直接操作 STM32 的寄存器(GPIO、USART、TIM 等)就像直接用汇编语言操作硬件,非常繁琐且容易出错,厂商和社区提供了更高级的抽象方式。
- 寄存器操作:直接读写硬件寄存器,优点是代码效率高,缺点是开发慢、可读性差、移植性差。
- 标准库/外设库:ST 官方提供的一套 C 语言库,将复杂的寄存器操作封装成简单的函数调用(如
GPIO_SetBits()),这是最经典的开发方式,学习它能让你深刻理解硬件工作原理。 - HAL 库:ST 推出的现代硬件抽象层库,优点是跨系列(F1, F4, L4 等代码兼容性好)、代码结构清晰、错误处理机制完善,是目前 ST 官方主推的方式。
- LL 库:介于寄存器和 HAL 之间的一套轻量级库,直接调用寄存器,但提供了更友好的函数接口,性能接近寄存器操作。
- RTOS:实时操作系统,如 FreeRTOS,用于管理复杂的任务。
本教程将以最经典的 标准库 为例,因为它能让你最直观地理解 STM32 的工作原理,是学习其他所有方式的基础。
开发环境搭建
你需要两个主要软件:
-
IDE (集成开发环境):用于编写、编译、调试代码。
- Keil MDK (ARMCC/ARMCLANG):最传统、最稳定、支持最广的 STM32 开发工具,有免费版。
- STM32CubeIDE:ST 官方推出的免费 IDE,基于 Eclipse 和 GCC,集成了 STM32CubeMX 工具,非常方便。
- VS Code + PlatformIO:现代化的轻量级方案,插件丰富,跨平台。
-
芯片支持包:
- STM32CubeMX:ST 官方图形化配置工具,可以帮你自动生成初始化代码(支持 HAL/LL 库)。
- STM32标准外设库:如果你选择标准库,需要单独下载这个库文件。
推荐新手路线:
- 硬件:一块 STM32F103C8T6 的“蓝丸”开发板(便宜、资料多)。
- 软件:安装 STM32CubeIDE 和 STM32CubeMX(它们通常捆绑安装)。
第二部分:点亮一个 LED(最经典的入门程序)
我们将以 STM32F103C8T6 为例,通过标准库实现让板载 LED 闪烁。
步骤 1:硬件分析
- 查看你的开发板原理图,找到 LED 连接到了哪个引脚。
- 对于常见的“蓝丸”板,LED 通常连接在 PC13 引脚。
- STM32 的 GPIO 引脚可以配置为推挽输出、开漏输出等模式,点亮 LED,我们需要将 PC13 设置为推挽输出,然后输出低电平(因为大多数板子是 LED 低电平点亮)。
步骤 2:项目创建与代码编写(使用纯标准库,不依赖 CubeMX)
-
创建项目文件夹:
- 创建一个文件夹,
STM32_LED_Blink。 - 在里面创建
user文件夹(存放你的代码)和Libraries文件夹(存放标准库)。
- 创建一个文件夹,
-
添加标准库文件:
- 从 ST 官网下载标准库(
STM32F10x_StdPeriph_Lib_V3.5.0)。 - 将以下文件夹和文件复制到你的项目目录中:
Libraries/STM32F10x_StdPeriph_Driver:核心驱动库。Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x:设备支持文件(如stm32f10x.h,system_stm32f10x.c)。Libraries/CMSIS/CM3/CoreSupport:CMSIS 核心文件。
- 从 ST 官网下载标准库(
-
编写代码: 在
user文件夹下创建main.c和stm32f10x_it.c(中断文件,暂时不用,可以留空)。main.c代码详解:/* 包含头文件 */ #include "stm32f10x.h" // STM32F10x 的寄存器定义和外设访问结构体 #include "misc.h" // 杂项函数(如 NVIC 配置) /* 函数声明 */ void delay(volatile uint32_t count); int main(void) { /* 1. 使能 GPIOC 的时钟 */ // STM32 的所有外设(如 GPIO, USART, TIM)在使用前都必须先开启其对应的时钟 // APB2ENR 寄存器的第 4 位控制 GPIOC 的时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // 使用位操作或 |= 来设置,不影响其他位 /* 2. 配置 PC13 引脚为推挽输出,最大速度 50MHz */ // GPIOC 的 CRL 寄存器控制低 8 位引脚 (PC0-PC7) // CRH 寄存器控制高 8 位引脚 (PC8-PC15) // PC13 是高 8 位,所以操作 CRH 寄存器 // CRH 寄存器的每一位组 (4 bits) 控制一个引脚 // 对于 PC13,它位于 CRH 的 [13*4 : 13*4+3] = [52:55] 位 // 配置模式: 00=输入, 01=推挽输出, 10=开漏输出, 11=复用功能 // 配置速度: 00=2MHz, 01=10MHz, 10=50MHz // 我们需要配置为 01 (推挽输出) 和 10 (50MHz),所以值为 0b1011 (0xB) // 先清零原来的设置 (0xFFFFFFF0) GPIOC->CRH &= ~(0xF << 4 * (13 - 8)); // 再设置新的值 (0xB << 4 * (13 - 8)) GPIOC->CRH |= (0xB << 4 * (13 - 8)); /* 3. 主循环 */ while (1) { /* 点亮 LED: 设置 PC13 为低电平 */ // ODR 寄存器的第 13 位控制 PC13 的输出电平 // 0: 低电平 (点亮), 1: 高电平 (熄灭) GPIOC->BSRR = GPIO_BSRR_BR13; // 使用 BSRR 寄存器可以原子性地置位(清零)引脚,非常安全 delay(500000); // 延时 /* 熄灭 LED: 设置 PC13 为高电平 */ // 使用 BSRR 寄存器原子性地置位(置位)引脚 GPIOC->BSRR = GPIO_BSRR_BS13; delay(500000); // 延时 } } /* 简单的延时函数 */ void delay(volatile uint32_t count) { volatile uint32_t i; for (i = 0; i < count; i++); // 循环空转,消耗 CPU 时间 // volatile 关键字防止编译器优化掉这个空循环 }
步骤 3:编译与烧录
- 编译:在 STM32CubeIDE 中创建一个 C/C++ Executable 项目,将上述文件添加到工程中,并配置好包含路径(Include Paths)指向你添加的库文件,然后编译,生成
.hex或.elf文件。 - 烧录:使用 ST-Link/V2 下载器将程序烧录到 STM32 芯片中,STM32CubeIDE 集成了这个功能。
如果一切顺利,你就能看到板载 LED 开始闪烁了!
第三部分:进阶学习与关键模块
点亮 LED 只是开始,STM32 的强大在于其丰富的外设。
串口通信
串口是单片机与 PC 或其他设备通信最常用的方式。
- 核心步骤:
- 使能时钟:使能 USART1 的时钟(通常是 APB2ENR 的第 14 位)和 GPIOA 的时钟(因为 USART1 的 TX/RX 通常在 PA9/PA10)。
- 配置 GPIO:将 PA9 配置为复用功能推挽输出,PA10 配置为浮空输入,它们的复用功能都是 USART1。
- 配置 USART:通过 USART1 的寄存器(如
CR1,CR2,BRR)设置波特率(如 115200)、数据位(8)、停止位(1)、校验位(无)等。 - 使能 USART:设置
UE位使能 USART,设置TE和RE位使能发送和接收。 - 发送数据:向
DR(数据寄存器) 写入要发送的字符。 - 接收数据:检查
SR(状态寄存器) 的RXNE(读数据寄存器非空) 标志位,如果置位,则从DR读取数据。
定时器
定时器用于精确定时、产生 PWM 波形、或作为外部事件计数器。
- 核心步骤 (以 TIM2 为例):
- 使能时钟:使能 TIM2 的时钟(通常是 APB1ENR 的第 0 位)。
- 配置预分频器:
PSC寄存器,它将来自总线的时钟频率进行分频。TIMx_CLK / (PSC + 1)得到定时器计数时钟。 - 配置自动重装载值:
ARR寄存器,计数器从 0 开始向上计数,当计数值等于ARR时,产生中断事件并清零。 - 计算定时时间:
定时时间 = ((PSC + 1) * (ARR + 1)) / TIMx_CLK。 - 使能定时器:设置
CEN位开始计数。 - 中断配置:如果需要定时器中断,还需配置
DIER寄存器使能更新中断,并在NVIC中配置中断向量。
中断
中断是提高 CPU 效率的关键,当某个事件(如定时器溢出、串口收到数据)发生时,CPU 会暂停当前任务,去执行一个预先写好的中断服务函数。
- 核心步骤:
- 配置外设:如上所述,配置好串口或定时器,并使能它们的中断。
- 配置 NVIC:NVIC 是 ARM Cortex-M 内核自带的中断控制器。
- 使能对应的中断通道(如
USART1_IRQn)。 - 设置中断优先级。
- 使能对应的中断通道(如
- 编写中断服务函数:在
stm32f10x_it.c中,函数名必须是固定的(如void USART1_IRQHandler(void))。 - 清除中断标志:在 ISR 中,必须手动清除导致中断的标志位,否则会一直进入中断。
第四部分:从标准库到 HAL 库的过渡
当你熟悉了标准库后,强烈建议学习 HAL 库,ST 已经不再大力推荐标准库了。
HAL 库的特点:
- 统一性:
HAL_GPIO_WritePin(),HAL_UART_Transmit()等函数在不同系列的 STM32 上调用方式基本一致。 - 初始化简化:使用 STM32CubeMX 图形化配置,一键生成初始化代码。
- 错误处理:函数返回
HAL_StatusTypeDef类型(HAL_OK,HAL_ERROR),便于错误检测。
用 HAL 库点亮 LED 的代码示例:
/* main.c */
#include "main.h" // CubeMX 生成的头文件
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void) {
HAL_Init(); // 初始化 HAL 库
SystemClock_Config(); // 配置系统时钟 (由 CubeMX 生成)
MX_GPIO_Init(); // 初始化 GPIO (由 CubeMX 生成)
while (1) {
// 点亮 LED
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // RESET = 低电平
HAL_Delay(500);
// 熄灭 LED
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // SET = 高电平
HAL_Delay(500);
}
}
// 由 CubeMX 生成的 GPIO 初始化代码
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能 GPIOC 时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 配置 PC13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上拉下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;// 低速
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
// ... 其他由 CubeMX 生成的代码 ...
可以看到,HAL 库的代码更简洁,硬件配置的工作被交给了 CubeMX,开发者可以更专注于业务逻辑。
总结与学习路径
- 打好基础:熟悉 C 语言,特别是指针、结构体、位操作。
- 理解硬件:学习数字电路基础,了解 GPIO、UART、Timer 等外设的基本原理。
- 从标准库开始:亲手用标准库实现 LED 闪烁、串口打印、定时器中断,这个过程虽然繁琐,但能让你对 STM32 的寄存器和工作方式有最深刻的理解。
- 掌握工具:熟练使用 STM32CubeMX 进行引脚和时钟配置,这是现代 STM32 开发的必备技能。
- 转向 HAL 库:在理解了底层原理后,转向 HAL 库,提高开发效率和代码的可移植性。
- 探索高级主题:学习 DMA(直接内存访问,解放 CPU)、ADC(模数转换)、I2C/SPI(通信协议)、RTOS(实时操作系统)等。
STM32 的学习曲线相对陡峭,但只要你一步一个脚印,多动手实践,多看官方参考手册和原理图,很快就能上手并掌握它,祝你学习顺利!
