4. CubeMX工程创建流程

GPIO 是 STM32 最基础、最常用的外设。说白了,它就是芯片和外界打交道的「手脚」。我刚开始学 STM32 时,觉得 GPIO 不就是点个灯、读个按键嘛,太简单了。直到后来做项目,因为中断没处理好,产品在客户现场频繁误触发……嗯,从那以后我再也不敢小看 GPIO 了。

这一章,我们就把 GPIO 的几种模式、HAL 库的 API 用法、以及按键中断与消抖这些实战问题,一次性讲透。

4.1 GPIO 的四种工作模式

STM32 的 GPIO 引脚,可以配置成四种基本模式。理解这四种模式,是驱动开发的第一步。

模式 典型用途 内部结构特点
输入模式 读取按键、传感器电平 施密特触发器开启,上下拉可选
输出模式 驱动 LED、蜂鸣器、继电器 推挽或开漏输出,速度可配
复用功能模式 USART、SPI、I2C 等外设引脚 引脚由片上外设控制,非 GPIO 寄存器
模拟模式 ADC 采样、DAC 输出 施密特触发器关闭,功耗最低
个人经验: 我习惯在 CubeMX 里先把所有用到的引脚模式配好,生成代码后再根据实际需求微调。尤其是复用功能,选错 AF 编号会导致外设不工作,排查起来很头疼。

4.2 HAL_GPIO_Init 详解

这个函数是 GPIO 配置的核心。它的原型长这样:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

第一个参数指定是哪一组 GPIO(GPIOA、GPIOB 等),第二个参数是一个结构体指针,里面包含了所有配置项。

GPIO_InitTypeDef 结构体关键成员:

  • Pin:引脚编号,可以是 GPIO_PIN_0 ~ GPIO_PIN_15,也可以用或运算组合多个引脚。
  • Mode:模式选择,如 GPIO_MODE_INPUT、GPIO_MODE_OUTPUT_PP、GPIO_MODE_AF_PP、GPIO_MODE_ANALOG。
  • Pull:上下拉配置,GPIO_NOPULL、GPIO_PULLUP、GPIO_PULLDOWN。
  • Speed:输出速度,GPIO_SPEED_FREQ_LOW/MEDIUM/HIGH/VERY_HIGH。
  • Alternate:复用功能编号,仅在复用模式下有效。
避坑指南: 我曾经在配置 USART 时忘了设置 Alternate 参数,结果串口怎么都发不出数据。查了半天才发现 AF 没配。记住:复用模式下,Alternate 必须和 CubeMX 里看到的 AF 编号一致。

4.3 读写引脚:HAL_GPIO_WritePin / ReadPin / TogglePin

这三个 API 是日常开发中使用频率最高的。用法非常直接:

// 写引脚:设置 PA5 输出高电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

// 读引脚:读取 PB3 的电平状态
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3);

// 翻转引脚:将 PC1 的电平取反
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);

你想想看,TogglePin 这个函数多方便。以前用标准库时,翻转引脚得先读再写,现在一行搞定。

小技巧: 我习惯把 LED 引脚封装成宏,比如 #define LED_TOGGLE() HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin),这样代码更简洁,移植也方便。

4.4 按键中断与消抖实战

按键处理是嵌入式开发中的经典场景。直接读引脚电平?不行,机械按键按下和释放时会产生抖动,不处理的话一次按下可能被误判成多次。

硬件消抖 vs 软件消抖:

  • 硬件消抖:在按键引脚上加 RC 滤波电路或施密特触发器。适合对成本不敏感、对实时性要求高的场合。
  • 软件消抖:检测到电平变化后,延时 10~20ms 再读一次。成本低,灵活,是大多数项目的首选。

我个人更推荐软件消抖,因为不需要额外元件,而且可以通过调整延时时间适应不同按键。

4.4.1 中断方式实现按键检测

轮询方式太浪费 CPU 了。用外部中断(EXTI)才是正经做法。配置步骤:

  1. 在 CubeMX 中将按键引脚配置为 GPIO_EXTI 模式,并使能中断。
  2. 设置触发边沿:上升沿、下降沿或双边沿。
  3. 在中断回调函数 HAL_GPIO_EXTI_Callback 中处理按键逻辑。

代码示例:

// 中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == KEY_Pin)
    {
        // 延时消抖
        HAL_Delay(20);
        if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET)
        {
            // 按键确实被按下,执行动作
            LED_TOGGLE();
        }
    }
}
注意: 中断回调函数里不要做耗时操作,比如 HAL_Delay(500)。中断服务程序应该短小精悍,否则会影响系统实时性。如果按键动作复杂,建议在回调中只设置一个标志位,在主循环中处理。

4.4.2 消抖的几种方案对比

方案 优点 缺点 适用场景
延时消抖 实现简单,代码量少 阻塞 CPU,不适合多任务 简单按键,非实时系统
状态机消抖 非阻塞,适合 RTOS 代码稍复杂 多按键,实时性要求高
硬件 RC 消抖 不占 CPU,可靠性高 增加 BOM 成本 工业控制,恶劣环境

我在一个智能家居项目中用过状态机消抖,同时处理 8 个按键,效果很好。如果你用的是 FreeRTOS,可以创建一个按键任务,用消息队列传递按键事件,这样更优雅。

4.5 GPIO 驱动开发知识体系

下面这张图总结了本章的核心内容,帮你建立整体认知:

GPIO 驱动开发知识体系 GPIO 驱动开发知识体系 GPIO 驱动开发 输入模式 输出模式 复用功能模式 模拟模式 HAL 库核心 API HAL_GPIO_Init HAL_GPIO_WritePin HAL_GPIO_ReadPin HAL_GPIO_TogglePin 实战应用:按键中断与消抖 延时消抖 状态机消抖 硬件 RC 消抖 EXTI 中断
核心要点回顾:
  • GPIO 四种模式:输入、输出、复用、模拟,各有各的用途。
  • HAL_GPIO_Init 是配置入口,结构体成员一个都不能漏。
  • WritePin / ReadPin / TogglePin 是日常三件套,熟记于心。
  • 按键中断一定要做消抖,延时 20ms 是经验值,但别在中断里做耗时操作。