前言

上篇文章,我们点亮了LED,用的是推挽输出模式

当我们去查STM32的参考手册时,会发现GPIO有8种工作模式!那这么多模式,什么时候该用哪个?

这篇文章,我们就来梳理一下GPIO的8种工作模式,以及它们的应用场景。


为什么要了解GPIO的工作模式?

你可能会问:我只想点亮一个LED,为什么要了解这么多模式?

原因是:

  • 不同的外设,需要不同的GPIO模式
  • 按键输入、PWM输出、I2C通信、ADC采集……每种应用场景,都有对应的最佳GPIO模式
  • 如果模式选错了,外设可能不工作,或者工作不稳定

所以,了解GPIO的工作模式,是STM32开发的基础。


STM32 GPIO的8种工作模式

STM32的GPIO,可以分为两大类:输出模式输入模式,每类各有4种。

输出模式(4种)

模式 说明 典型应用
推挽输出 (Push-Pull) 能主动输出高、低电平 LED控制、普通数字信号输出、驱动芯片使能脚
开漏输出 (Open-Drain) 只能输出低电平,高电平需要外接上拉电阻 I2C总线、电平转换
复用推挽输出 GPIO 被外设接管,引脚功能由片上外设决定(如PWM、串口TX) PWM输出、USART_TX
复用开漏输出 GPIO 被外设接管,引脚功能由片上外设决定(如I2C) I2C_SDA、I2C_SCL

输入模式(4种)

模式 说明 典型应用
浮空输入 (Floating) 引脚电平不确定,易受干扰 外部信号输入(外部已有明确电平驱动)
上拉输入 (Pull-up) 内部上拉电阻,无外部信号时为高电平 按键输入(按键另一端接GND)、I²C 总线输入
下拉输入 (Pull-down) 内部下拉电阻,无外部信号时为低电平 按键输入(按键另一端接VCC)、检测高电平有效的触发信号
模拟输入 (Analog) 关闭数字缓冲器,直接连接ADC或比较器 电压采集、传感器信号采集

输出模式详解

1. 推挽输出(Push-Pull)

原理

  • 引脚可以主动输出高电平(3.3V)或低电平(0V)
  • 内部有两个MOS管(PMOS + NMOS):一个接VCC,一个接GND
  • 输出高电平时,上管导通;输出低电平时,下管导通

特点

  • ✅ 驱动能力强(能输出20mA左右)
  • ✅ 高低电平切换快
  • ✅ 不需要外接上拉电阻

应用场景

  • LED控制、继电器控制、蜂鸣器控制
  • 普通数字信号输出
  • 驱动芯片使能脚

配置方法(寄存器编程):

c

// 配置PA0为推挽输出、50MHz
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x3 << 0);   // CNF0=00(推挽输出),MODE0=11(50MHz)

2. 开漏输出(Open-Drain)

原理

  • 引脚只能主动输出低电平,高电平需要外接上拉电阻
  • 输出低电平时,内部MOS管导通,引脚被拉到低电平
  • 输出高电平时,MOS管截止,引脚靠外部上拉电阻拉到高电平

特点

  • ✅ 可以实现电平匹配(比如用5V上拉,就能输出5V电平)
  • ✅ 支持线与(多个开漏输出接在一起,任意一个输出低电平,总线就是低电平)
  • ❌ 高电平切换速度慢(受上拉电阻影响)

应用场景

  • I2C总线(SDA和SCL都是开漏输出)
  • 电平匹配(3.3V MCU控制5V设备)
  • 多个设备共享一根信号线

配置方法(寄存器编程):

c

// 配置PA0为开漏输出、50MHz
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x7 << 0);   // CNF0=01(开漏输出),MODE0=11(50MHz)

3. 复用推挽输出 / 复用开漏输出

原理

  • 和普通的推挽/开漏输出一样,但输出信号来自外设,不是来自GPIOx_ODR寄存器
  • 比如:PWM信号来自定时器,串口TX信号来自USART模块

应用场景

  • PWM输出(TIMx_CHx)
  • 串口发送(USARTx_TX)
  • SPI时钟和数据(SPIx_SCK、SPIx_MOSI)

配置方法(寄存器编程):

c

// 配置PA8为复用推挽输出、50MHz(TIM1_CH1)
GPIOA->CRH &= ~(0xF << 0);   // 清除CNF8和MODE8位
GPIOA->CRH |=  (0xB << 0);   // CNF8=10(复用推挽输出),MODE8=11(50MHz)

输入模式详解

1. 浮空输入(Floating)

原理

  • 引脚内部既不上拉,也不下拉
  • 引脚电平完全由外部电路决定
  • 如果外部没有接任何东西,电平是不确定的(可能是高,也可能是低)

特点

  • ✅ 外部可以灵活控制电平
  • ❌ 如果外部信号不稳定,容易误触发

应用场景

  • 外部有上拉/下拉电阻的场合
  • 用于读取数字传感器的输出(如DHT11)

配置方法(寄存器编程):

c

// 配置PA0为浮空输入
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x4 << 0);   // CNF0=01(浮空输入),MODE0=00(输入模式)

2. 上拉输入(Pull-up)

原理

  • 引脚内部有一个上拉电阻(约40kΩ),把引脚拉到高电平
  • 如果外部把引脚拉到低电平,引脚就读到低电平

特点

  • ✅ 默认高电平,适合按键接GND的场合
  • ✅ 抗干扰能力强

应用场景

  • 按键输入(按键另一端接GND)
  • 读取开关状态

配置方法(寄存器编程):

c

// 配置PA0为上拉输入
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x8 << 0);   // CNF0=10(上拉/下拉输入),MODE0=00(输入模式)
GPIOA->ODR |=  (1 << 0);     // ODR0=1,选择上拉

或者:

c

// 配置PA0为上拉输入
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x8 << 0);   // CNF0=10(上拉/下拉输入),MODE0=00(输入模式)
GPIOA->BSRR = (1 << 0);     // 置位 ODR(ODR写 1),选择上拉

更推荐第二种的写法,因为操作ODR的过程是"读-改-写",在中断或多任务中可能被打断,而BSRR是原子操作,不会被中断打断。


3. 下拉输入(Pull-down)

原理

  • 引脚内部有一个下拉电阻(约40kΩ),把引脚拉到低电平
  • 如果外部把引脚拉到高电平,引脚就读到高电平

特点

  • ✅ 默认低电平,适合按键接VCC的场合
  • ✅ 抗干扰能力强

应用场景

  • 按键输入(按键另一端接VCC)
  • 读取开关状态

配置方法(寄存器编程):

c

// 配置PA0为下拉输入
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
GPIOA->CRL |=  (0x8 << 0);   // CNF0=10(上拉/下拉输入),MODE0=00(输入模式)
GPIOA->BRR |= (1 << 0);     // 清除 ODR(ODR写 0),选择下拉

4. 模拟输入(Analog)

原理

  • 引脚直接连到ADC模块,用于采集模拟电压
  • 数字输入缓冲器被禁止,引脚的电平变化不会触发中断

特点

  • ✅ 专门用于ADC采集
  • ✅ 功耗低

应用场景

  • 电位器读数
  • 温度传感器
  • 电池电压检测

配置方法(寄存器编程):

c

// 配置PA0为模拟输入
GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
// CNF0=00,MODE0=00(模拟输入模式)

如何用寄存器配置GPIO模式?

关键寄存器

配置GPIO模式,主要用这两个寄存器:

  1. GPIOx_CRL(配置低8位引脚,Pin0~Pin7)
  2. GPIOx_CRH(配置高8位引脚,Pin8~Pin15)

每个引脚占用4位

  • CNF[1:0]:配置模式(输入/输出、推挽/开漏等)
  • MODE[1:0]:配置速度(输入模式下,这两位为00)

实际代码示例

示例:按键输入(上拉输入模式)

c

#include "stm32f10x.h"

int main(void)
{
    // 1. 开启GPIOA时钟
    RCC->APB2ENR |= (1 << 2);
    
    // 2. 配置PA0为上拉输入(假设按键接在PA0,另一端接GND)
    GPIOA->CRL &= ~(0xF << 0);   // 清除CNF0和MODE0位
    GPIOA->CRL |=  (0x8 << 0);   // CNF0=10(上拉/下拉输入),MODE0=00(输入模式)
    GPIOA->BSRR = (1 << 0);     // 置位 ODR(ODR写 1),选择上拉
    
    // 3. 主循环:检测按键
    while(1)
    {
        if ((GPIOA->IDR & (1 << 0)) == 0)
        {
            // PA0为低电平(按键按下)
            // 做点什么...
        }
    }
}

将上述代码改成使用官方宏定义的形式:

c

#include "stm32f10x.h"

int main(void)
{ 
    // 1. 开启GPIOA时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    
    // 2. 配置PA0为上拉输入(假设按键接在PA0,另一端接GND)
    GPIOA->CRL &= ~GPIO_CRL_CNF0;   // 清除CNF0位
    GPIOA->CRL |= GPIO_CRL_CNF0_1;   // CNF0=10(上拉/下拉输入)
    GPIOA->CRL &= ~GPIO_CRL_MODE0;   // MODE0=00(输入模式)
    GPIOA->BSRR = GPIO_BSRR_BS0;     // 置位 ODR(ODR写 1),选择上拉
    
    // 3. 主循环:检测按键
    while(1)
    {
        if ((GPIOA->IDR & GPIO_IDR_IDR0) == 0)
        {
            // PA0为低电平(按键按下)
            // 做点什么...
        }
    }

}

总结

这篇文章,我梳理了STM32 GPIO的8种工作模式:

输出模式

  • 推挽输出:最常用,能输出高/低电平
  • 开漏输出:用于I2C、电平匹配
  • 复用推挽/开漏:用于外设功能

输入模式

  • 浮空输入:外部有上拉/下拉时用
  • 上拉输入:按键接GND时用
  • 下拉输入:按键接VCC时用
  • 模拟输入:用于ADC采集

作者:一个焊板子的人欢迎关注获取更多硬件工程师学习笔记

如果觉得有帮助,欢迎点赞收藏,也欢迎在评论区交流讨论

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐