3. STM32 GPIO 基础:从模式到实战

GPIO,说白了就是芯片的「手脚」。你要让单片机跟外界打交道,全靠这些引脚。我刚开始学 STM32 的时候,总觉得 GPIO 太简单——不就是输出高电平和低电平吗?后来做项目才发现,这里面的坑还真不少。

这一章,咱们就把 GPIO 彻底讲透。从工作模式到寄存器操作,再到 HAL 库的封装,最后用两个实战案例收尾。嗯,保证你学完就能上手。

3.1 GPIO 的八种工作模式

STM32 的 GPIO 有 8 种模式。别被数字吓到,其实就分四大类:输入、输出、复用、模拟。我整理了一张表,你一看就明白。

模式 类型 典型用途 内部结构
浮空输入 输入 按键检测(外部上拉) 无上下拉,电平由外部决定
上拉输入 输入 按键检测(默认高电平) 内部接上拉电阻 ~40kΩ
下拉输入 输入 按键检测(默认低电平) 内部接下拉电阻 ~40kΩ
模拟输入 输入 ADC 采样 施密特触发器关闭,模拟信号直通
推挽输出 输出 LED、蜂鸣器 PMOS+NMOS 互补输出
开漏输出 输出 I²C 总线、电平转换 只有 NMOS,高电平需外部上拉
复用推挽输出 复用 USART_TX、SPI_MOSI 输出由片上外设控制
复用开漏输出 复用 I²C_SCL、I²C_SDA 输出由片上外设控制,需外部上拉
我的经验:做按键输入时,我习惯用上拉输入模式。这样按键没按下时引脚就是高电平,按下后变低。省掉一个外部上拉电阻,板子能小一点。

3.2 寄存器操作 vs HAL 库

很多初学者会纠结:到底用寄存器还是 HAL 库?我的答案是——两个都要会。

寄存器操作快、直接,但代码可读性差。HAL 库封装得好、移植方便,但执行效率低一些。咱们来看个对比。

3.2.1 寄存器操作:点亮 LED

假设 LED 接在 PA5 上,低电平点亮。用寄存器操作,只需要三行代码:

// 开启 GPIOA 时钟
RCC->AHB1ENR |= (1 << 0);

// 配置 PA5 为推挽输出
GPIOA->MODER &= ~(0x3 << 10);  // 先清零
GPIOA->MODER |= (0x1 << 10);   // 设为输出模式

// 输出低电平,点亮 LED
GPIOA->ODR &= ~(1 << 5);

你看,直接操作寄存器,效率极高。但问题也很明显——你得记住每个寄存器的位定义。我刚开始学的时候,经常翻参考手册翻到眼花。

3.2.2 HAL 库操作:同样的功能

// 定义 GPIO 句柄
GPIO_InitTypeDef GPIO_InitStruct = {0};

// 开启时钟
__HAL_RCC_GPIOA_CLK_ENABLE();

// 配置 PA5
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 输出低电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);

代码多了好几行,但可读性明显更好。而且 HAL 库帮你处理了很多边界情况,比如配置冲突检查。

核心观点:项目开发用 HAL 库,效率高、易维护。调试定位问题时,用寄存器思维去分析——比如直接读 ODR 寄存器看输出状态,比调 HAL 函数快得多。

3.3 实战一:点亮 LED

咱们用 STM32CubeIDE 建个新工程,目标板是 STM32F103C8T6(蓝 pill 板)。LED 接在 PC13 上,低电平点亮。

3.3.1 步骤拆解

  1. 使能时钟:GPIOC 挂在 APB2 总线上,要开启对应的时钟。
  2. 配置模式:PC13 设为推挽输出,速度设为 2MHz 就够了。
  3. 控制电平:写 BSRR 寄存器或调用 HAL_GPIO_WritePin。

3.3.2 完整代码(HAL 库)

#include "stm32f1xx_hal.h"

void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOC_CLK_ENABLE();

    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);

    // 初始状态:熄灭(高电平)
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}

void LED_On(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
}

void LED_Off(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}

void LED_Toggle(void)
{
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
注意:PC13 在 STM32F103 上默认是 JTAG_SWD 的调试引脚。如果你发现程序下载后 LED 不亮,先检查调试接口配置。我曾经在这上面浪费了半天——后来发现是调试器占用了引脚功能。

3.4 实战二:按键输入

按键比 LED 稍微复杂一点。因为按键有机械抖动,不处理的话,一次按下可能触发多次中断或多次检测。

3.4.1 硬件连接

按键接在 PA0 上,另一端接地。内部上拉,所以没按下时是高电平,按下后变低。

3.4.2 软件消抖

消抖的方法很多,最简单的就是延时再检测。我一般用 10~20ms 的延时。

#include "stm32f1xx_hal.h"

#define KEY_PIN     GPIO_PIN_0
#define KEY_PORT    GPIOA

void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = KEY_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct);
}

uint8_t Key_Scan(void)
{
    // 检测是否按下(低电平)
    if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET)
    {
        // 延时消抖
        HAL_Delay(10);

        // 再次确认
        if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET)
        {
            // 等待按键释放
            while (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET);
            return 1;  // 有效按下
        }
    }
    return 0;  // 未按下或抖动
}
我的习惯:按键扫描函数里,我加了「等待释放」的逻辑。这样一次按下只返回一次有效值,不会出现按一下灯闪好几下的情况。如果你做的是长按检测,那逻辑要另外写。

3.5 GPIO 知识结构总览

下面这张图,把 GPIO 的核心知识点串起来了。你可以把它当作本章的「地图」。

STM32 GPIO 知识结构图 STM32 GPIO 8 种工作模式 输入模式 输出模式 复用模式 模拟模式 寄存器操作 MODER OTYPER ODR BSRR HAL 库封装 HAL_GPIO_Init HAL_GPIO_WritePin HAL_GPIO_ReadPin HAL_GPIO_TogglePin 实战:点亮 LED 实战:按键输入 💡 学习路径建议 先理解 8 种工作模式 → 掌握寄存器操作原理 → 用 HAL 库快速开发 → 实战中巩固理解

3.6 本章小结

GPIO 是嵌入式开发的「基本功」。这一章咱们讲了:

  • 8 种工作模式:输入、输出、复用、模拟,每种都有适用场景。
  • 寄存器 vs HAL 库:寄存器快但繁琐,HAL 库慢但易用。我建议你两手抓。
  • 两个实战:LED 输出和按键输入,覆盖了 GPIO 最常用的两种场景。

你想想看,后面做 LoRa 通信、GPS 定位,本质上还是在操作 GPIO——只不过换成了 SPI、UART 这些复用功能。基础打牢了,后面就顺了。