100 篇文章精通 STM32F103(第 2 篇):GPIO 深度解析与按键检测实操
大家好!上一篇我们通过 “点亮 LED” 迈出了 STM32 学习的第一步,而控制 LED 的核心就是GPIO(通用输入输出口)。这一篇我们将深入 GPIO 的工作原理,掌握它的 8 种工作模式,并通过 “按键检测” 实操(输入模式)巩固知识,让你不仅 “会用” GPIO,更 “懂为什么这么用”。
一、GPIO 是什么?为什么它是 STM32 的 “手脚”?
GPIO(General Purpose Input/Output,通用输入输出口)是 STM32 与外部世界交互的 “桥梁”—— 它既能输出电信号(比如控制 LED 亮灭、驱动电机),也能读取外部电信号(比如检测按键是否按下、读取传感器数据)。
STM32F103 的 GPIO 分为多个端口(GPIOA、GPIOB...GPIOG),每个端口有 16 个引脚(Pin0-Pin15),不同型号的芯片引脚数量不同(如 STM32F103C8T6 有 GPIOA、GPIOB、GPIOC 共 3 个端口,37 个可用 GPIO 引脚)。
简单说:GPIO 就是 STM32 的 “手脚”—— 用 “手”(输入)接收外部信息,用 “脚”(输出)对外发出指令。
二、GPIO 硬件结构:从引脚到寄存器的 “信号通路”
要理解 GPIO 的工作模式,先得看懂它的硬件结构(简化版):
外部信号 → 引脚 → 保护二极管(防过压) → 上下拉电阻 → 输入缓冲器 → 内部寄存器(IDR)
↑
内部寄存器(ODR) → 输出控制器(推挽/开漏) → 引脚 → 外部设备
关键组件作用:
- 保护二极管:当引脚电压过高(>VDD+0.3V)或过低(<GND-0.3V)时导通,保护芯片内部电路;
- 上下拉电阻:默认情况下,未接外部信号的 GPIO 引脚电平是 “浮空” 的(不稳定,可能乱跳),上下拉电阻可将其稳定在高电平(上拉)或低电平(下拉);
- 输入缓冲器:将外部模拟信号转为数字信号(0 或 1),存入输入数据寄存器(IDR);
- 输出控制器:根据输出数据寄存器(ODR)的指令,控制引脚输出高 / 低电平,支持推挽或开漏两种模式。
三、GPIO 的 8 种工作模式:一文看懂 “输入 / 输出 / 复用 / 模拟”
STM32 的 GPIO 有 8 种工作模式,可分为4 类输入模式和4 类输出模式(含复用功能),不同模式对应不同应用场景。
1. 4 种输入模式(“接收外部信号” 用)
| 模式名称 | 核心特点 | 典型应用 |
|---|---|---|
| 输入浮空(Floating input) | 引脚电平完全由外部信号决定,无上下拉,易受干扰 | 需外部电路提供稳定电平的场景(如连接按键时外部接上下拉电阻) |
| 输入上拉(Input pull-up) | 内部接 30-50kΩ 上拉电阻到 VDD,未接外部信号时默认高电平 | 按键检测(外部按下时接地,电平变低) |
| 输入下拉(Input pull-down) | 内部接 30-50kΩ 下拉电阻到 GND,未接外部信号时默认低电平 | 同上(外部按下时接 VDD,电平变高) |
| 模拟输入(Analog input) | 跳过输入缓冲器,直接将引脚信号送入 ADC(模数转换器) | 读取模拟信号(如光敏电阻、温度传感器的电压值) |
关键区别:前 3 种是 “数字输入”(只能读 0 或 1),最后 1 种是 “模拟输入”(可读连续电压值,配合 ADC 使用)。
2. 4 种输出模式(“对外发信号” 用)
| 模式名称 | 核心特点 | 典型应用 |
|---|---|---|
| 推挽输出(Output push-pull) | 可直接输出高 / 低电平(高电平 = VDD,低电平 = GND),驱动能力强 | 控制 LED、继电器、直接驱动小负载 |
| 开漏输出(Output open-drain) | 只能输出低电平(接地),高电平需外部上拉电阻提供,可实现 “线与” 功能 | I2C 通信(SDA/SCL 线)、多个设备共用一根线 |
| 复用推挽输出(Alternate function push-pull) | 引脚功能被外设(如 UART 的 TX)占用,输出由外设控制,推挽模式 | UART 发送、SPI 时钟线(SCK) |
| 复用开漏输出(Alternate function open-drain) | 引脚功能被外设占用,输出由外设控制,开漏模式 | I2C 的 SDA/SCL(复用 I2C 外设时) |
推挽 vs 开漏:用 “水龙头” 比喻
- 推挽输出:像两个水龙头,一个接 VDD(高电平),一个接 GND(低电平),可主动切换出水(高)或排水(低),水流(电流)大;
- 开漏输出:只有一个接 GND 的水龙头,能排水(低电平),但不能主动出水(高电平),要出水得靠外部 “水桶”(上拉电阻)倒水,水流大小由水桶决定。
3. 输出速度:为什么要分 “低速 / 中速 / 高速”?
输出模式还需配置 “输出速度”(Low/Medium/High),对应最大切换频率:
- 低速(Low):2MHz;
- 中速(Medium):10MHz;
- 高速(High):50MHz。
作用:不是速度越快越好!高频切换会产生电磁干扰(EMI),且耗电更多。例如:
- 控制 LED 用 “低速” 即可(切换频率低,无干扰);
- 驱动高速 SPI 外设(如 OLED 屏幕)用 “高速”(保证信号同步)。
四、实操:用 GPIO 输入模式检测按键(从硬件到代码)
学会检测按键是 GPIO 输入模式的经典应用,我们用 “输入上拉模式” 实现:按键未按下时,引脚默认高电平;按下时,引脚接地变低电平,通过读取电平变化判断按键状态。
1. 硬件准备与连接
| 硬件 | 规格 | 作用 |
|---|---|---|
| 轻触按键 | 4 脚直插(或 2 脚贴片) | 提供开关信号 |
| STM32F103 核心板 | STM32F103C8T6 | 核心控制 |
| 杜邦线 | 公对公 / 公对母 | 连接电路 |
| 面包板(可选) | 基础款 | 搭建临时电路 |
接线方式(用 PB0 引脚检测按键):
- 按键一端 → STM32 的
PB0引脚; - 按键另一端 → STM32 的
GND引脚; - (无需外部电阻,因为我们用 “输入上拉模式”,内部已接上拉电阻)
简化接线图:PB0 ←→ 按键 ←→ GND
2. 用 STM32CubeIDE 配置 GPIO 输入模式
步骤 1:新建工程(或在上一篇工程基础上修改)
- 若新建工程:参考第 1 篇,选择 STM32F103C8T6,项目名设为 “Key_Detection”;
- 若修改旧工程:打开上一篇的 “LED_Blink” 工程,直接修改配置。
步骤 2:配置 PB0 为 “输入上拉模式”
- 进入 “Pinout & Configuration” 界面,在右侧引脚图中找到 “PB0”;
- 点击 PB0,下拉菜单选择 “GPIO_Input”;
- 左侧 “Configuration”→“GPIO”→“PB0”,配置参数:
- GPIO mode:Input pull-up(输入上拉);
- 其他参数默认(无需改速度,输入模式速度参数无效);
- 保留上一篇的 PA5 配置(推挽输出,控制 LED),点击 “Generate Code” 生成代码。
3. 编写按键检测代码(含消抖处理)
按键是机械结构,按下 / 松开时会有5-20ms 的抖动(电平快速跳变),直接读取会导致误判。解决方法:延时消抖—— 检测到电平变化后,延时 10ms 再读一次,两次结果一致才确认状态。
在main.c的while(1)循环中添加如下代码:
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 读取PB0引脚电平(GPIO_PIN_SET=高电平,GPIO_PIN_RESET=低电平)
uint8_t key_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
// 按键按下时(电平为低)
if (key_state == GPIO_PIN_RESET)
{
// 延时10ms消抖
HAL_Delay(10);
// 再次读取,确认按键真的按下
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
// 点亮LED(PA5输出高电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}
}
// 按键未按下时(电平为高)
else
{
// 熄灭LED(PA5输出低电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
}
}
/* USER CODE END 3 */
代码解释:
HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0):读取 PB0 引脚电平,返回GPIO_PIN_SET(1,高电平)或GPIO_PIN_RESET(0,低电平);- 消抖逻辑:第一次检测到低电平后,延时 10ms 再检测,若仍为低电平,才确认按键按下(过滤抖动);
- 功能:按键按下时 LED 点亮,松开时 LED 熄灭。
4. 烧录验证与问题排查
- 烧录程序:用 ST-Link 连接核心板,点击 “Run” 烧录(步骤同第 1 篇);
- 验证效果:按下按键,LED 点亮;松开按键,LED 熄灭,说明功能正常。
常见问题排查:
- 按键按下 LED 不亮:① 检查 PB0 引脚是否接对(别接成其他引脚);② 确认 GPIO 模式配置为 “Input pull-up”(若设为浮空,未按下时电平可能乱跳);③ 用万用表测 PB0 电平:未按下时应为 3.3V(上拉),按下时应为 0V(接地)。
- LED 乱闪(抖动未解决):① 延长消抖延时(如 20ms);② 检查按键是否质量差(机械抖动过大)。
五、GPIO 寄存器:HAL 库背后的 “真相”
HAL 库帮我们简化了 GPIO 操作,但理解底层寄存器能让你更灵活地控制引脚(比如在对速度要求高的场景下直接操作寄存器)。
STM32F103 每个 GPIO 端口有 7 个关键寄存器(以 GPIOA 为例):
| 寄存器名称 | 作用 | 关键位 |
|---|---|---|
| GPIOA_MODER | 模式寄存器(配置输入 / 输出 / 复用 / 模拟) | 每 2 位控制 1 个引脚(00 = 输入,01 = 输出,10 = 复用,11 = 模拟) |
| GPIOA_OTYPER | 输出类型寄存器(推挽 / 开漏) | 每 1 位控制 1 个引脚(0 = 推挽,1 = 开漏) |
| GPIOA_OSPEEDR | 输出速度寄存器 | 每 2 位控制 1 个引脚(00 = 低速,01 = 中速,11 = 高速) |
| GPIOA_PUPDR | 上下拉寄存器 | 每 2 位控制 1 个引脚(00 = 浮空,01 = 上拉,10 = 下拉) |
| GPIOA_IDR | 输入数据寄存器(只读) | 每 1 位对应 1 个引脚的电平(0 = 低,1 = 高) |
| GPIOA_ODR | 输出数据寄存器(可读可写) | 每 1 位控制 1 个引脚的输出电平 |
| GPIOA_BSRR | 位设置 / 清除寄存器(快速操作 ODR) | 高 16 位清除(0 无效,1 = 清 0),低 16 位设置(0 无效,1 = 置 1) |
举个例子:用寄存器实现 “PA5 输出高电平”
// 配置PA5为推挽输出(需先使能GPIOA时钟,后续章节讲时钟)
GPIOA->MODER &= ~(0x03 << (5*2)); // 清除原来的配置
GPIOA->MODER |= (0x01 << (5*2)); // 01=输出模式
GPIOA->OTYPER &= ~(0x01 << 5); // 0=推挽输出
// 输出高电平(两种方式)
GPIOA->ODR |= (0x01 << 5); // ODR第5位置1
// 或用BSRR(更高效,不影响其他位)
GPIOA->BSRR = (0x01 << 5); // 低16位第5位置1(置位)
HAL 库与寄存器的关系:HAL_GPIO_WritePin本质就是操作 ODR 或 BSRR 寄存器,HAL_GPIO_ReadPin就是读取 IDR 寄存器。
六、第 2 篇总结与下一篇预告
总结
这一篇我们掌握了:
- GPIO 的 8 种工作模式(4 输入 + 4 输出)及应用场景;
- 用 “输入上拉模式” 实现按键检测,理解了机械抖动与消抖处理;
- 初步认识 GPIO 寄存器,知道 HAL 库函数的底层原理。
下一篇预告
GPIO 的工作离不开时钟系统——STM32 所有外设(包括 GPIO)必须先 “使能时钟” 才能工作。第 3 篇我们将学习 STM32F103 的时钟树结构、如何配置系统时钟(从 8MHz 外部晶振到 72MHz 主频),以及时钟与外设性能的关系,让你明白 “为什么程序要先开时钟”。

被折叠的 条评论
为什么被折叠?



