4、STM32 GPIO编程:GPIO模式配置、推挽输出与开漏输出、点亮LED与读取按键

好,咱们今天来聊聊GPIO。这玩意儿可以说是嵌入式开发里最基础、也最常用的外设了。我刚开始学STM32那会儿,第一个实验就是点亮LED,当时觉得挺简单的,不就是给个高电平嘛。后来做项目多了才发现,GPIO里面的门道还真不少。配置不对,轻则功能异常,重则烧片子。所以这一节,咱们把GPIO的几个关键模式掰开揉碎了讲清楚。

4.1 GPIO是什么?

GPIO,全称General Purpose Input Output,通用输入输出口。说白了,就是芯片上那些可以编程控制的引脚。你可以让它输出高电平或低电平,也可以读取它上面的电平状态。

每个GPIO端口通常有16个引脚,比如PA0到PA15。每个引脚都可以独立配置。嗯,这里要注意,不是所有引脚都能随便用的,有些引脚有特殊功能,比如用作ADC输入或者定时器输出。我建议你在画板子之前,先把芯片手册里的引脚功能表看一遍,省得后面踩坑。

4.2 GPIO的几种工作模式

STM32的GPIO模式比51单片机复杂不少。它一共有8种模式,但咱们常用的其实就几种。我列个表,你一看就明白。

模式 说明 典型应用
推挽输出 可以输出高电平和低电平,驱动能力强 驱动LED、蜂鸣器
开漏输出 只能输出低电平,高电平需要外部上拉 I2C总线、电平转换
浮空输入 引脚电平不确定,容易受干扰 基本不用
上拉输入 内部接上拉电阻,默认高电平 读取按键(按下为低)
下拉输入 内部接下拉电阻,默认低电平 读取按键(按下为高)
模拟输入 信号直接进入ADC或比较器 采集模拟电压

你想想看,这么多模式,选错了会怎样?我曾经在一个项目里,把I2C的SCL引脚配成了推挽输出,结果通信死活不正常。查了半天才发现,I2C协议要求开漏输出,推挽输出会把总线拉死。所以模式配置这事儿,真不能马虎。

4.3 推挽输出 vs 开漏输出

这两个模式是输出里最常用的,但区别很大。我详细说说。

推挽输出:内部有两个MOS管,一个负责拉高,一个负责拉低。输出高电平时,上管导通,下管截止;输出低电平时,上管截止,下管导通。所以推挽输出既能输出高电平,也能输出低电平,而且驱动电流比较大,一般能到20mA左右。点亮一个LED绰绰有余。

开漏输出:内部只有一个下拉的MOS管。输出低电平时,MOS管导通,引脚接地;输出高电平时,MOS管截止,引脚处于高阻态。这时候引脚的电平由外部上拉电阻决定。所以开漏输出本身不能输出高电平,必须靠外部上拉。

核心区别一句话总结:推挽输出能主动输出高低电平,开漏输出只能主动输出低电平,高电平靠外部上拉。

为什么要有开漏输出?我举个例子。I2C总线上挂了好几个设备,如果都用推挽输出,一个设备输出高电平,另一个设备输出低电平,那总线就短路了。开漏输出就不存在这个问题,因为高电平是靠上拉电阻实现的,任何设备都可以把总线拉低,但谁都不能主动拉高。这就是所谓的“线与”逻辑。

4.4 点亮LED:推挽输出的实战

好,理论说完了,咱们动手。点亮LED是嵌入式开发的“Hello World”。

假设LED接在PB0引脚上,阳极通过一个330欧姆的限流电阻接到PB0,阴极接地。那么PB0输出高电平时,LED亮;输出低电平时,LED灭。

配置步骤如下:

  1. 使能GPIOB端口的时钟
  2. 配置PB0为推挽输出模式
  3. 设置输出速度(一般用50MHz就够了)
  4. 控制PB0输出高电平或低电平

代码是这样的:

// 使能GPIOB时钟
RCC->AHB1ENR |= (1 << 1);   // 位1置1,使能GPIOB

// 配置PB0为推挽输出
GPIOB->MODER &= ~(0x3 << 0); // 先清除原来的配置
GPIOB->MODER |= (0x1 << 0);  // 设置为输出模式(01)

// 配置输出类型为推挽
GPIOB->OTYPER &= ~(1 << 0); // 0表示推挽输出

// 配置输出速度
GPIOB->OSPEEDR &= ~(0x3 << 0);
GPIOB->OSPEEDR |= (0x2 << 0); // 50MHz

// 配置上拉/下拉,输出模式一般不用
GPIOB->PUPDR &= ~(0x3 << 0); // 无上拉无下拉

// 点亮LED:PB0输出高电平
GPIOB->ODR |= (1 << 0);

// 熄灭LED:PB0输出低电平
// GPIOB->ODR &= ~(1 << 0);

小技巧:我个人习惯用位带操作或者直接操作BSRR寄存器来设置ODR,这样不会影响其他引脚。比如点亮LED可以用 GPIOB->BSRR = (1 << 0);,熄灭用 GPIOB->BSRR = (1 << 16);。BSRR的低16位是置位,高16位是复位,用起来很方便。

4.5 读取按键:上拉输入的实战

按键读取也是基本功。假设按键接在PA0引脚上,按键另一端接地。那么我们需要配置PA0为上拉输入模式。这样按键没按下时,PA0被内部上拉电阻拉到高电平;按键按下时,PA0被拉到低电平。

配置步骤:

  1. 使能GPIOA端口的时钟
  2. 配置PA0为输入模式
  3. 配置为上拉输入
  4. 读取IDR寄存器获取电平状态

代码示例:

// 使能GPIOA时钟
RCC->AHB1ENR |= (1 << 0);   // 位0置1,使能GPIOA

// 配置PA0为输入模式
GPIOA->MODER &= ~(0x3 << 0); // 00表示输入模式

// 配置为上拉输入
GPIOA->PUPDR &= ~(0x3 << 0);
GPIOA->PUPDR |= (0x1 << 0);  // 01表示上拉

// 读取按键状态
uint8_t key_state = (GPIOA->IDR >> 0) & 0x01;

if(key_state == 0) {
    // 按键按下
} else {
    // 按键未按下
}

注意:机械按键有抖动问题。按下和松开的时候,电平会跳变几次,持续几毫秒到十几毫秒。如果不做消抖处理,一次按键可能会被误判成多次。我建议用延时消抖或者定时器扫描的方式。最简单的办法是检测到电平变化后,延时20ms再读一次,确认电平确实变了。

4.6 避坑指南

做GPIO编程,有几个坑我踩过,分享给你:

  • 时钟没使能:这是新手最容易犯的错误。寄存器写半天,引脚没反应,查了半天发现时钟没开。STM32的外设默认时钟都是关闭的,必须手动使能。
  • 模式配置冲突:同一个引脚不能同时用作GPIO和复用功能。比如你想用PA9做GPIO输出,但PA9默认是USART1的TX。这时候需要先把复用功能关掉,或者重新映射。
  • 输出电流超限:虽然STM32的GPIO能输出20mA,但整个端口的电流总和有限制。我见过有人用一个端口驱动8个LED,结果芯片发热严重。建议单个引脚电流不超过10mA,整个端口不超过100mA。
  • 开漏输出忘加上拉:我曾经调试一个I2C设备,波形怎么看都不对。后来发现是忘了在SCL和SDA上加外部上拉电阻。开漏输出不加外部上拉,高电平就是浮空的,逻辑完全乱套。

4.7 小结

这一节的内容其实不难,但很重要。GPIO是芯片和外界交互的桥梁,配置对了,后面的开发就顺风顺水。我建议你拿到开发板后,先把LED和按键这两个实验做一遍,亲手配置一下寄存器,感受一下推挽输出和上拉输入的区别。代码写多了,自然就熟练了。

下一节咱们会讲中断,到时候按键就不用轮询了,效率会高很多。先把这个基础打牢吧。