为什么需要理解 GPIO 模式
使用 STM32 时,CubeMX 或 HAL 库给 GPIO 提供了 8 种模式:
1 2 3 4 5 6 7 8
| GPIO_MODE_INPUT 输入浮空 GPIO_MODE_INPUT_PULLUP 输入上拉 GPIO_MODE_INPUT_PULLDOWN 输入下拉 GPIO_MODE_ANALOG 模拟输入 GPIO_MODE_OUTPUT_PP 推挽输出 GPIO_MODE_OUTPUT_OD 开漏输出 GPIO_MODE_AF_PP 复用推挽 GPIO_MODE_AF_OD 复用开漏
|
选错模式轻则不工作,重则烧引脚。理解每种模式的内部电路,才能正确选型。
一、输出模式
1.1 推挽输出(Push-Pull)
1 2 3 4 5 6 7 8 9
| VDD │ ┌─── P-MOS ───┐ 输出高电平:P-MOS 导通, N-MOS 截止 → VDD │ │ │ ┌──────┤─── GPIO │ │ │ └─── N-MOS ───┘ 输出低电平:N-MOS 导通, P-MOS 截止 → GND │ GND
|
工作原理:两个 MOS 管交替导通,”推”(P-MOS 送 VDD)和”挽”(N-MOS 拉 GND),输出端始终是确定的电平。
1 2 3 4 5 6
| HAL 配置: GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
寄存器操作: GPIOx->CRL |= (0x2 << pin*4); // 输出模式, 推挽 GPIOx->ODR |= (1 << pin); // 输出高
|
特点:
| 特性 |
说明 |
| 驱动能力 |
强,通常 ±25mA/引脚 |
| 电平 |
明确的高/低,无中间态 |
| 速度 |
快,可达 50MHz |
| 线与 |
❌ 不支持,两个推挽输出不能短接 |
典型应用:
- LED 驱动(
LED_GPIO_Port → LED_Pin)
- 控制继电器/蜂鸣器
- 普通数字信号输出
1.2 开漏输出(Open-Drain)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| VDD VDD │ │ │ 外接上拉电阻 │ │ 4.7KΩ │ ┌───────────┤ │ │ │ │ │ ┌────┴────── GPIO │ │ │ │ └─── N-MOS ───┐ │ │ │ │ GND │ │ 输出高:N-MOS 截止 → 上拉电阻拉到 VDD 输出低:N-MOS 导通 → 拉到 GND
|
工作原理:只有 N-MOS 管,输出高电平时靠外部上拉电阻拉到 VDD。
1 2 3 4 5 6
| HAL 配置: GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 注意:必须外接上拉电阻或在内部使能上拉
寄存器操作: GPIOx->CRL |= (0x6 << pin*4); // 输出模式, 开漏
|
特点:
| 特性 |
说明 |
| 驱动能力 |
高电平由上拉电阻决定(弱),低电平由 N-MOS 决定(强) |
| 电平转换 |
上拉电阻接 5V 即可驱动 5V 设备(3.3V STM32 → 5V 外设) |
| 线与 |
✅ 多个开漏输出可直接并接,任一拉低即低 |
| 速度 |
受上拉电阻影响,RC 充放电限制频率 |
典型应用:
- I2C 总线(SDA/SCL 必须开漏,靠外部上拉)
- 1-Wire(DS18B20 单总线)
- 多设备共享信号线(多个芯片的 INT 中断引脚并接)
- 5V 电平转换(上拉电阻接 5V)
1 2 3 4 5 6 7 8 9 10
| 为什么 I2C 必须用开漏:
SDA ──┬────┬────┬──── VDD │ │ │ │ 4.7K DEV1 DEV2 DEV3 │ │ │ │ GND OD OD OD
所有设备都是开漏 → 任一设备可以拉低 SDA → 实现多主仲裁 如果推挽,两个设备同时输出不同电平 → 短路烧毁
|
二、输入模式
2.1 浮空输入
1 2 3 4 5 6 7 8 9 10 11 12
| GPIO │ ┌─────────┤ │ │ ESD 输入缓冲器 ──→ 内部数据总线 │ │ └─────────┤ │ GND
无上拉/下拉,引脚电平完全由外部信号决定。 悬空时电平不确定(受电磁干扰会乱跳)。
|
1 2 3
| HAL 配置: GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 无上下拉 GPIO_InitStruct.Pull = GPIO_NOPULL;
|
2.2 上拉输入
1 2 3 4 5 6 7 8 9 10 11 12
| VDD │ 内部上拉 (~40KΩ) │ ├────── GPIO ──── 输入缓冲器 │ 外部按键 │ GND
默认状态(按键未按下):上拉电阻 → GPIO 读到高电平 按键按下:GPIO 直接接地 → 读到低电平
|
1 2 3
| HAL 配置: GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP;
|
典型应用:
- 按键检测(按键接地,默认高电平,按下变低)
- I2C 空闲保持高电平
- UART RX 防悬空乱收数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 按键接线(上拉输入):
VDD ──── 内部上拉 ~40KΩ │ ├──── GPIO ──── MCU │ ┌──┤ │ │ │按键 │ │ └──┤ │ GND
按下 → 低电平 松开 → 高电平(上拉)
|
2.3 下拉输入
1 2 3 4 5 6 7 8 9 10 11 12
| 外部按键 │ VDD │ ├────── GPIO ──── 输入缓冲器 │ 内部下拉 (~40KΩ) │ GND
默认状态(按键未按下):下拉电阻 → GPIO 读到低电平 按键按下:GPIO 直接接 VDD → 读到高电平
|
1 2 3
| HAL 配置: GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN;
|
典型应用:
- 按键检测(按键接 VDD,默认低电平,按下变高)
- 使能引脚(默认禁用,外接高电平使能)
1 2 3 4 5 6 7 8 9 10 11
| 按键接线(下拉输入):
VDD │ ├──── 按键 ──── GPIO ──── MCU │ │ │ 内部下拉 ~40KΩ │ │ │ GND
按下 → 高电平 松开 → 低电平(下拉)
|
三、上拉 vs 下拉:按键怎么选
|
上拉输入 |
下拉输入 |
| 默认电平 |
高 |
低 |
| 按键接法 |
按键 → GND |
按键 → VDD |
| 抗干扰 |
较好(高电平驱动强) |
一般 |
| 省电 |
无按键时上拉电阻耗电 |
无按键时下拉电阻耗电 |
| 业界惯例 |
更常用 |
较少 |
经验法则:按键优先用上拉输入。几乎所有 MCU 的 GPIO 默认状态都是上拉或浮空,下拉支持较弱。STM32 每个 GPIO 内部上拉约 40KΩ。
四、推挽 vs 开漏:输出怎么选
| 场景 |
选推挽 |
选开漏 |
| LED |
✅ |
也能用 |
| 普通 IO 控制 |
✅ |
❌(需外加电阻) |
| I2C |
❌ |
✅ 必须 |
| 多设备共享线 |
❌ |
✅ 线与 |
| 电平转换 3.3→5V |
❌ |
✅ 上拉接 5V |
| 高速信号 |
✅ 50MHz |
慢(RC 限制) |
五、HAL 库配置速查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
|
六、常见错误
| 现象 |
原因 |
解决 |
| I2C 通信失败 |
配置成推挽输出 |
改为 GPIO_MODE_AF_OD |
| 按键读到乱跳 |
浮空输入,未加上拉 |
改为 GPIO_PULLUP |
| LED 亮度很暗 |
开漏输出未加上拉电阻 |
改为推挽,或外接上拉 |
| 引脚发热 |
两个推挽输出短接 |
检查电路,改用开漏 |
| 悬空引脚电平乱跳 |
未用引脚配置为浮空输入 |
设为模拟模式 GPIO_MODE_ANALOG |