3. STM32 GPIO 基础:从模式到实战
GPIO,说白了就是芯片的「手脚」。你要让单片机跟外界打交道,全靠这些引脚。我刚开始学 STM32 的时候,总觉得 GPIO 太简单——不就是输出高电平和低电平吗?后来做项目才发现,这里面的坑还真不少。
这一章,咱们就把 GPIO 彻底讲透。从工作模式到寄存器操作,再到 HAL 库的封装,最后用两个实战案例收尾。嗯,保证你学完就能上手。
3.1 GPIO 的八种工作模式
STM32 的 GPIO 有 8 种模式。别被数字吓到,其实就分四大类:输入、输出、复用、模拟。我整理了一张表,你一看就明白。
| 模式 | 类型 | 典型用途 | 内部结构 |
|---|---|---|---|
| 浮空输入 | 输入 | 按键检测(外部上拉) | 无上下拉,电平由外部决定 |
| 上拉输入 | 输入 | 按键检测(默认高电平) | 内部接上拉电阻 ~40kΩ |
| 下拉输入 | 输入 | 按键检测(默认低电平) | 内部接下拉电阻 ~40kΩ |
| 模拟输入 | 输入 | ADC 采样 | 施密特触发器关闭,模拟信号直通 |
| 推挽输出 | 输出 | LED、蜂鸣器 | PMOS+NMOS 互补输出 |
| 开漏输出 | 输出 | I²C 总线、电平转换 | 只有 NMOS,高电平需外部上拉 |
| 复用推挽输出 | 复用 | USART_TX、SPI_MOSI | 输出由片上外设控制 |
| 复用开漏输出 | 复用 | I²C_SCL、I²C_SDA | 输出由片上外设控制,需外部上拉 |
3.2 寄存器操作 vs HAL 库
很多初学者会纠结:到底用寄存器还是 HAL 库?我的答案是——两个都要会。
寄存器操作快、直接,但代码可读性差。HAL 库封装得好、移植方便,但执行效率低一些。咱们来看个对比。
3.2.1 寄存器操作:点亮 LED
假设 LED 接在 PA5 上,低电平点亮。用寄存器操作,只需要三行代码:
// 开启 GPIOA 时钟
RCC->AHB1ENR |= (1 << 0);
// 配置 PA5 为推挽输出
GPIOA->MODER &= ~(0x3 << 10); // 先清零
GPIOA->MODER |= (0x1 << 10); // 设为输出模式
// 输出低电平,点亮 LED
GPIOA->ODR &= ~(1 << 5);
你看,直接操作寄存器,效率极高。但问题也很明显——你得记住每个寄存器的位定义。我刚开始学的时候,经常翻参考手册翻到眼花。
3.2.2 HAL 库操作:同样的功能
// 定义 GPIO 句柄
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 开启时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置 PA5
GPIO_InitStruct.Pin = GPIO_PIN_5;
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);
// 输出低电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
代码多了好几行,但可读性明显更好。而且 HAL 库帮你处理了很多边界情况,比如配置冲突检查。
3.3 实战一:点亮 LED
咱们用 STM32CubeIDE 建个新工程,目标板是 STM32F103C8T6(蓝 pill 板)。LED 接在 PC13 上,低电平点亮。
3.3.1 步骤拆解
- 使能时钟:GPIOC 挂在 APB2 总线上,要开启对应的时钟。
- 配置模式:PC13 设为推挽输出,速度设为 2MHz 就够了。
- 控制电平:写 BSRR 寄存器或调用 HAL_GPIO_WritePin。
3.3.2 完整代码(HAL 库)
#include "stm32f1xx_hal.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
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);
// 初始状态:熄灭(高电平)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}
void LED_On(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
}
void LED_Off(void)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}
void LED_Toggle(void)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
3.4 实战二:按键输入
按键比 LED 稍微复杂一点。因为按键有机械抖动,不处理的话,一次按下可能触发多次中断或多次检测。
3.4.1 硬件连接
按键接在 PA0 上,另一端接地。内部上拉,所以没按下时是高电平,按下后变低。
3.4.2 软件消抖
消抖的方法很多,最简单的就是延时再检测。我一般用 10~20ms 的延时。
#include "stm32f1xx_hal.h"
#define KEY_PIN GPIO_PIN_0
#define KEY_PORT GPIOA
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = KEY_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct);
}
uint8_t Key_Scan(void)
{
// 检测是否按下(低电平)
if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET)
{
// 延时消抖
HAL_Delay(10);
// 再次确认
if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET)
{
// 等待按键释放
while (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET);
return 1; // 有效按下
}
}
return 0; // 未按下或抖动
}
3.5 GPIO 知识结构总览
下面这张图,把 GPIO 的核心知识点串起来了。你可以把它当作本章的「地图」。
3.6 本章小结
GPIO 是嵌入式开发的「基本功」。这一章咱们讲了:
- 8 种工作模式:输入、输出、复用、模拟,每种都有适用场景。
- 寄存器 vs HAL 库:寄存器快但繁琐,HAL 库慢但易用。我建议你两手抓。
- 两个实战:LED 输出和按键输入,覆盖了 GPIO 最常用的两种场景。
你想想看,后面做 LoRa 通信、GPS 定位,本质上还是在操作 GPIO——只不过换成了 SPI、UART 这些复用功能。基础打牢了,后面就顺了。