3、ESP32 GPIO基础:从按键到LED的输入输出实战

各位同学,今天我们进入一个非常核心的环节——GPIO。说白了,就是ESP32的那些金属引脚,怎么让它们听话,怎么让它们感知外部世界。

我个人习惯把GPIO比作芯片的「手脚」。有的负责「摸」(输入),有的负责「动」(输出)。今天我们就用按键和LED,把这两件事彻底搞明白。

3.1 认识ESP32的GPIO引脚

ESP32芯片有34个GPIO引脚,但不是所有都能随便用。我刚开始做项目时,就踩过这个坑——选了一个内部已经接死的引脚,折腾半天没反应。

这里我整理了一张常用引脚表,建议你保存下来:

引脚编号 功能说明 注意事项
GPIO0 下载模式选择 低电平进入下载模式,慎用
GPIO2 板载LED 很多开发板默认接了这个
GPIO4、GPIO5 普通IO 我最常用的两个引脚
GPIO12 启动电压选择 上拉会改变Flash电压,别乱用
GPIO13-GPIO19 普通IO 大部分可安全使用
GPIO34-GPIO39 仅输入 不能输出,只能读取
⚠️ 避坑指南: 我曾经在量产项目中用了GPIO12做按键输入,结果部分板子死活无法启动。查了两天才发现,GPIO12的上拉电阻影响了Flash电压。从那以后,我选引脚都会先查数据手册。

3.2 两个核心函数:digitalWrite 和 digitalRead

Arduino框架下,GPIO操作被封装得非常简单。你只需要记住两个函数:

  • pinMode(pin, mode) —— 设置引脚模式(输入/输出)
  • digitalWrite(pin, value) —— 写数字信号(HIGH/LOW)
  • digitalRead(pin) —— 读数字信号(返回HIGH/LOW)

嗯,这里要注意:pinMode 必须在 setup() 里先调用,否则引脚默认状态是不确定的。我见过新手直接写 digitalWrite,结果LED死活不亮——其实就是忘了设模式。

3.3 实战:按键控制LED

好,理论说完了,我们直接上代码。这个例子的逻辑很简单:

  1. 按键按下 → GPIO读取到低电平 → LED点亮
  2. 按键松开 → GPIO读取到高电平 → LED熄灭

你想想看,这不就是最基础的「输入决定输出」吗?

硬件连接

  • LED正极 → GPIO2(板载LED)
  • 按键一端 → GPIO4
  • 按键另一端 → GND
  • GPIO4内部上拉(代码里设置)

完整代码

// 引脚定义
const int ledPin = 2;      // 板载LED
const int buttonPin = 4;   // 按键输入

void setup() {
  Serial.begin(115200);
  
  // 设置引脚模式
  pinMode(ledPin, OUTPUT);      // LED为输出
  pinMode(buttonPin, INPUT_PULLUP);  // 按键为输入,启用内部上拉
  
  Serial.println("按键控制LED已启动");
}

void loop() {
  // 读取按键状态
  int buttonState = digitalRead(buttonPin);
  
  // 按键按下时,引脚为低电平
  if (buttonState == LOW) {
    digitalWrite(ledPin, HIGH);  // 点亮LED
    Serial.println("按键按下,LED点亮");
  } else {
    digitalWrite(ledPin, LOW);   // 熄灭LED
  }
  
  delay(50);  // 简单消抖
}
💡 关键点解析:
  • INPUT_PULLUP 启用内部上拉电阻,这样按键悬空时引脚为高电平
  • 按键接地,按下时引脚被拉低到GND
  • delay(50) 是为了消除机械按键的抖动

3.4 我踩过的坑:按键抖动

第一次做这个实验时,我发现LED有时候会「闪一下」或者「乱跳」。为什么呢?

其实机械按键在按下和松开的瞬间,金属触点会弹跳几次,产生多个高低电平变化。如果不处理,程序会误认为按了好多次。

我曾经在一个门禁项目中,因为没做消抖处理,导致按键一次触发三次开门信号。嗯,后来被客户投诉了。

最简单的消抖方法就是加一个 delay(20-50ms),等电平稳定后再读取。更专业的做法是用定时器或状态机,但初学者先用delay就够了。

3.5 进阶:用按键切换LED状态

上面的例子是「按下亮,松开灭」。但实际项目中,我们更常用「按一下切换状态」。比如按一次开灯,再按一次关灯。

这里需要引入一个「状态变量」来记住当前LED是亮还是灭:

bool ledState = false;  // 记录LED状态
int lastButtonState = HIGH;  // 记录上一次按键状态

void loop() {
  int currentButtonState = digitalRead(buttonPin);
  
  // 检测按键从松开到按下的变化(下降沿)
  if (lastButtonState == HIGH && currentButtonState == LOW) {
    // 切换LED状态
    ledState = !ledState;
    digitalWrite(ledPin, ledState);
    Serial.println(ledState ? "LED已打开" : "LED已关闭");
    
    delay(50);  // 消抖
  }
  
  lastButtonState = currentButtonState;
}
🔧 我的小技巧:bool 类型来存状态,比用 int 更省内存,代码也更清晰。另外,ledState = !ledState 这种取反写法,比 if...else 简洁多了。

3.6 总结一下今天的内容

  • GPIO引脚分输入和输出,用 pinMode 设置
  • digitalWrite 输出高低电平,digitalRead 读取外部电平
  • 按键需要内部上拉(INPUT_PULLUP)或外部上拉电阻
  • 机械按键有抖动,必须做消抖处理
  • 用状态变量可以实现「按一次切换一次」的效果

说实话,GPIO是嵌入式开发最基础也最重要的部分。你想想看,所有传感器、执行器,最终都是通过GPIO和芯片交互的。把今天这个实验做熟,后面学I2C、SPI、PWM都会轻松很多。

下一章我们会讲PWM调光,到时候用呼吸灯效果来展示。嗯,那个更有意思。