跳转至

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),可以主动输出高电平和低电平。

VDD ──┤ P-MOS ├──┐
                  ├── 引脚输出
GND ──┤ N-MOS ├──┘
  • 输出 1:P-MOS 导通,N-MOS 关断 → 引脚接 VDD → 高电平
  • 输出 0:P-MOS 关断,N-MOS 导通 → 引脚接 GND → 低电平

开漏输出:只有 N-MOS,没有 P-MOS。

外部上拉电阻 ── VDD
              ├── 引脚输出
GND ──┤ N-MOS ├──┘
  • 输出 0:N-MOS 导通 → 引脚拉低 → 低电平
  • 输出 1:N-MOS 关断 → 引脚悬空 → 需要外部上拉电阻才能输出高电平

什么时候用开漏?

  1. I2C 通信:多个设备共享总线,开漏 + 上拉的方式天然支持"线与"逻辑
  2. 电平转换:MCU 是 3.3V,外部设备是 5V,用开漏 + 5V 上拉就能输出 5V 信号
  3. 多设备共享信号线:任何一个设备都可以拉低总线,但不会冲突

三、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 配置步骤:

  1. 在 Pinout 视图中点击 PC13,选择 GPIO_Output
  2. 在左侧 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(可选,方便生成宏定义)
  3. 生成代码

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 配置步骤:

  1. PC13GPIO_Output(LED,同上)
  2. PA0GPIO_Input
  3. PA0 的 GPIO 配置:Pull-up/Pull-down 选择 Pull-up(内部上拉,默认高电平)
  4. 生成代码
/* 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 但引脚没反应?

  1. 检查是否生成代码:修改配置后要重新 Generate Code
  2. 检查引脚冲突:CubeMX 中引脚显示黄色警告表示有冲突
  3. 检查硬件:LED 是接 VCC 还是接 GND?高电平亮还是低电平亮?
  4. 检查 User Label:如果设置了 Label(如 LED),可以用 LED_GPIO_PortLED_Pin 宏代替硬编码

同一个引脚能同时做输入和输出吗?

不能。但是在推挽/开漏输出模式下,仍然可以通过 HAL_GPIO_ReadPin() 读取引脚的实际电平状态。