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灭。
配置步骤如下:
- 使能GPIOB端口的时钟
- 配置PB0为推挽输出模式
- 设置输出速度(一般用50MHz就够了)
- 控制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被拉到低电平。
配置步骤:
- 使能GPIOA端口的时钟
- 配置PA0为输入模式
- 配置为上拉输入
- 读取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和按键这两个实验做一遍,亲手配置一下寄存器,感受一下推挽输出和上拉输入的区别。代码写多了,自然就熟练了。
下一节咱们会讲中断,到时候按键就不用轮询了,效率会高很多。先把这个基础打牢吧。