4、STM32 GPIO编程:GPIO模式配置、推挽输出与开漏输出、按键输入与消抖
好,咱们今天聊聊GPIO。说实话,GPIO是嵌入式开发里最基础、也最容易被轻视的东西。我见过不少新手,上来就点灯,灯亮了就觉得GPIO搞定了。但实际项目中,GPIO用不好,轻则功能异常,重则烧芯片。咱们做轨道交通POS机,稳定性是第一位的,GPIO这块必须吃透。
4.1 GPIO模式配置:别小看这步
STM32的GPIO,每个引脚都可以独立配置成多种模式。你想想看,一个引脚既能当输入,又能当输出,还能搞成模拟功能,这背后其实是一堆寄存器在干活。
我个人习惯,在初始化GPIO时,一定先看原理图,确认这个引脚在板子上接了什么东西。是驱动一个LED?还是读取一个按键?还是跟别的芯片通信?不同的用途,模式选择完全不同。
STM32的GPIO模式主要有这么几种:
| 模式 | 说明 | 典型用途 |
|---|---|---|
| 输入模式 | 读取外部信号高低电平 | 按键、传感器信号 |
| 推挽输出 | 可输出高电平或低电平,驱动能力强 | LED、蜂鸣器、普通数字信号 |
| 开漏输出 | 只能输出低电平,高电平靠外部上拉 | I2C总线、多设备共享线路 |
| 复用功能 | 引脚由片上外设控制 | USART、SPI、定时器 |
| 模拟模式 | 引脚直接连到ADC等模拟外设 | 采集模拟电压 |
配置代码其实很固定,我一般用HAL库,结构清晰:
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE(); // 别忘了开时钟
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);
4.2 推挽输出与开漏输出:选对才能用对
这两个概念,很多初学者容易混淆。我简单说说我的理解。
推挽输出,说白了就是内部有两个管子,一个推上去输出高电平,一个拉下来输出低电平。谁干活谁出力,所以驱动能力强。点亮一个LED,用推挽输出最直接。
开漏输出,只有拉低的能力,没有推高的能力。想输出高电平?得靠外部接一个上拉电阻。那为什么还要用它?因为开漏输出可以实现“线与”功能——多个设备可以共用一个线路,谁拉低谁说话。I2C总线就是典型例子。
我在项目中遇到过一个问题:用开漏输出驱动一个LED,结果灯死活不亮。后来一查,忘了加上拉电阻,引脚根本拉不到高电平。嗯,这里要注意,开漏输出不是万能的,用之前想清楚外部电路。
两种模式的对比:
| 特性 | 推挽输出 | 开漏输出 |
|---|---|---|
| 输出高电平 | 内部直接驱动 | 需外部上拉电阻 |
| 输出低电平 | 内部直接驱动 | 内部直接驱动 |
| 驱动能力 | 强 | 弱(取决于上拉电阻) |
| 多设备共用 | 不适合 | 适合(线与) |
| 典型应用 | LED、普通IO | I2C、中断信号线 |
4.3 按键输入与消抖:别让抖动坑了你
按键输入,看起来简单——读引脚电平呗。但实际用起来,有个大坑:机械抖动。
你按一下按键,金属触点不是一下子就稳定接触的。它会弹跳几次,每次几毫秒到几十毫秒。如果你直接读引脚,可能会读到一连串的高低变化,程序会误以为你按了好多次。
为什么会这样?说白了就是物理接触的不可靠性。我刚开始做项目时,就因为这个被坑过——按一次按键,计数器跳了五六次。后来老老实实加了消抖。
消抖的方法有两种:
- 硬件消抖: 在按键两端并联一个电容(通常0.1uF),利用电容的充放电特性滤除毛刺。简单粗暴,适合对成本不敏感的场景。
- 软件消抖: 检测到电平变化后,延时10-20ms再读一次。如果电平一致,才认为是有效按键。这是最常用的方法,零成本。
我一般用软件消抖,代码也很简单:
// 假设按键接在PA0,低电平有效
#define KEY_PIN GPIO_PIN_0
#define KEY_PORT GPIOA
uint8_t Key_Scan(void)
{
if(HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET)
{
HAL_Delay(10); // 延时10ms消抖
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; // 无按键
}
另外,按键的输入模式也要注意。一般用上拉输入或下拉输入,取决于你的电路设计。如果按键一端接地,另一端接IO,那就用上拉输入,默认高电平,按下变低电平。
配置代码:
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉输入
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
好了,GPIO这部分就聊到这儿。推挽和开漏选对,按键消抖做稳,你的嵌入式程序就成功了一半。下一节咱们聊聊定时器,那个更有意思。