GPIO(通用输入输出)¶
GPIO(General Purpose Input/Output)是 STM32 最基础的外设,几乎所有功能都要通过 GPIO 引脚与外部世界交互。理解 GPIO 是学习一切外设的起点。
一、GPIO 的基本概念¶
什么是 GPIO?¶
GPIO 就是芯片上那些可以被软件控制的引脚。每个引脚可以被配置为:
- 输入:读取外部信号(按键、传感器)
- 输出:驱动外部设备(LED、继电器)
- 复用功能:交给其他外设使用(USART、SPI、I2C 等)
- 模拟功能:用于 ADC/DAC 模拟信号输入输出
引脚命名规则¶
STM32 的 GPIO 按组(Port)划分,每组最多 16 个引脚:
- GPIOA:PA0, PA1, PA2, ... PA15
- GPIOB:PB0, PB1, PB2, ... PB15
- GPIOC:PC0, PC1, PC2, ... PC13(蓝色小板上 PC13 接了 LED)
不同型号芯片的引脚数量不同(48 脚、64 脚、100 脚、144 脚等),但命名规则一致。
二、GPIO 的 8 种工作模式¶
这是 GPIO 最核心的知识点。STM32 的每个引脚都可以配置为以下 8 种模式之一:
输入模式(4 种)¶
| 模式 | CubeMX 选项 / HAL 宏 | 说明 | 典型应用 |
|---|---|---|---|
| 浮空输入 | GPIO_MODE_INPUT + No Pull |
引脚既不上拉也不下拉,电平完全由外部决定 | 外部已有确定电平的信号 |
| 上拉输入 | GPIO_MODE_INPUT + Pull-up |
内部接上拉电阻,默认高电平 | 按键检测(按下接地) |
| 下拉输入 | GPIO_MODE_INPUT + Pull-down |
内部接下拉电阻,默认低电平 | 按键检测(按下接 VCC) |
| 模拟输入 | GPIO_MODE_ANALOG |
关闭数字输入功能,直接采集模拟电压 | ADC 采集 |
输出模式(4 种)¶
| 模式 | CubeMX 选项 / HAL 宏 | 说明 | 典型应用 |
|---|---|---|---|
| 推挽输出 | GPIO_MODE_OUTPUT_PP |
可以输出高电平和低电平,驱动能力强 | LED、蜂鸣器 |
| 开漏输出 | GPIO_MODE_OUTPUT_OD |
只能拉低,高电平靠外部上拉 | I2C 总线、电平转换 |
| 复用推挽 | GPIO_MODE_AF_PP |
引脚由外设控制,推挽输出 | USART TX、SPI |
| 复用开漏 | GPIO_MODE_AF_OD |
引脚由外设控制,开漏输出 | I2C SDA/SCL |
推挽 vs 开漏,一张图搞懂
推挽输出:内部有两个 MOS 管(P-MOS 和 N-MOS),可以主动输出高电平和低电平。
- 输出 1:P-MOS 导通,N-MOS 关断 → 引脚接 VDD → 高电平
- 输出 0:P-MOS 关断,N-MOS 导通 → 引脚接 GND → 低电平
开漏输出:只有 N-MOS,没有 P-MOS。
- 输出 0:N-MOS 导通 → 引脚拉低 → 低电平
- 输出 1:N-MOS 关断 → 引脚悬空 → 需要外部上拉电阻才能输出高电平
什么时候用开漏?
- I2C 通信:多个设备共享总线,开漏 + 上拉的方式天然支持"线与"逻辑
- 电平转换:MCU 是 3.3V,外部设备是 5V,用开漏 + 5V 上拉就能输出 5V 信号
- 多设备共享信号线:任何一个设备都可以拉低总线,但不会冲突
三、GPIO 输出速度¶
配置输出模式时需要设置引脚的输出速度(翻转频率上限):
| 速度等级 | 最大翻转频率 | 适用场景 |
|---|---|---|
GPIO_Speed_2MHz |
2 MHz | LED、继电器等低速设备 |
GPIO_Speed_10MHz |
10 MHz | 普通通信接口 |
GPIO_Speed_50MHz |
50 MHz | SPI 高速通信、SDIO |
速度不是越快越好
更高的翻转速度意味着更大的电磁辐射(EMI)和更高的功耗。如果只是点个 LED,用 2MHz 就够了。
四、GPIO 相关寄存器¶
理解寄存器有助于理解库函数在做什么。STM32F103 每组 GPIO 有 7 个寄存器:
| 寄存器 | 全称 | 功能 |
|---|---|---|
| CRL | Configuration Register Low | 配置引脚 0~7 的模式和速度 |
| CRH | Configuration Register High | 配置引脚 8~15 的模式和速度 |
| IDR | Input Data Register | 读取引脚输入电平(只读) |
| ODR | Output Data Register | 设置引脚输出电平(读写) |
| BSRR | Bit Set/Reset Register | 原子操作:置位或复位某些引脚 |
| BRR | Bit Reset Register | 原子操作:复位某些引脚 |
| LCKR | Lock Register | 锁定引脚配置,防止意外修改 |
ODR vs BSRR
用 ODR 修改单个引脚时需要先读再改再写(读-改-写),如果中间发生中断可能导致数据错乱。BSRR 是原子操作,直接写入要设置/清除的位,不会影响其他引脚,推荐使用 BSRR。
五、CubeMX 配置 + HAL 库 GPIO 编程¶
点亮 LED(输出)¶
以 PC13 上的蓝色小板 LED 为例(低电平点亮):
CubeMX 配置步骤:
- 在 Pinout 视图中点击 PC13,选择
GPIO_Output - 在左侧 GPIO 配置面板中设置:
- GPIO output level: Low(点亮 LED)
- GPIO mode: Output Push Pull
- GPIO Pull-up/Pull-down: No pull-up and no pull-down
- Maximum output speed: Low
- User Label: LED(可选,方便生成宏定义)
- 生成代码
CubeMX 会自动在 gpio.c 中生成 MX_GPIO_Init() 函数:
// 由 CubeMX 自动生成的初始化代码(在 gpio.c 中)
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE(); // 自动开启时钟
/* Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 初始低电平
/* Configure GPIO pin : 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);
}
main.c 中 LED 就已经点亮了(初始电平为 Low)。如果你想在用户代码中控制:
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // LED 亮
/* USER CODE END WHILE */
}
LED 闪烁¶
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转 LED
HAL_Delay(500); // 延时 500ms
/* USER CODE END WHILE */
}
HAL_Delay 的原理
HAL_Delay() 基于 SysTick 定时器实现,单位是毫秒,比空循环延时精确得多。CubeMX 默认已配置好 SysTick。
按键检测(输入)¶
假设按键接在 PA0,按下时接地(低电平有效):
CubeMX 配置步骤:
- PC13 →
GPIO_Output(LED,同上) - PA0 →
GPIO_Input - PA0 的 GPIO 配置:Pull-up/Pull-down 选择 Pull-up(内部上拉,默认高电平)
- 生成代码
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) // 按键按下
{
HAL_Delay(20); // 消抖延时 20ms
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转 LED
// 等待按键释放
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
}
/* USER CODE END WHILE */
}
按键消抖
机械按键按下时会产生抖动(在 0 和 1 之间快速跳变,持续约 5~20ms)。消抖方法:
- 软件消抖:检测到按下后延时 10~20ms 再次确认(如上面代码)
- 硬件消抖:并联一个电容(通常 0.1μF)滤除抖动
用户代码必须写在 USER CODE 区域
CubeMX 重新生成代码时,只有 /* USER CODE BEGIN */ 和 /* USER CODE END */ 之间的代码会被保留,其他区域会被覆盖!
六、HAL 库 GPIO 常用函数速查¶
| 函数 | 功能 | 示例 |
|---|---|---|
HAL_GPIO_Init() |
初始化引脚(CubeMX 自动调用) | HAL_GPIO_Init(GPIOC, &GPIO_InitStruct) |
HAL_GPIO_WritePin() |
输出指定电平 | HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET) |
HAL_GPIO_TogglePin() |
翻转引脚电平 | HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13) |
HAL_GPIO_ReadPin() |
读取引脚电平 | HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) |
HAL_GPIO_LockPin() |
锁定引脚配置 | HAL_GPIO_LockPin(GPIOC, GPIO_PIN_13) |
HAL_GPIO_DeInit() |
将引脚恢复为默认状态 | HAL_GPIO_DeInit(GPIOC, GPIO_PIN_13) |
HAL 库函数的特点
- 函数少而精,比标准库更简洁
HAL_GPIO_WritePin()内部操作的是 BSRR 寄存器,天然原子操作HAL_GPIO_TogglePin()是 HAL 库独有的便利函数,标准库没有直接对应
七、常见问题¶
CubeMX 配置了 GPIO 但引脚没反应?
- 检查是否生成代码:修改配置后要重新 Generate Code
- 检查引脚冲突:CubeMX 中引脚显示黄色警告表示有冲突
- 检查硬件:LED 是接 VCC 还是接 GND?高电平亮还是低电平亮?
- 检查 User Label:如果设置了 Label(如
LED),可以用LED_GPIO_Port和LED_Pin宏代替硬编码
同一个引脚能同时做输入和输出吗?
不能。但是在推挽/开漏输出模式下,仍然可以通过 HAL_GPIO_ReadPin() 读取引脚的实际电平状态。