3、GPIO基础控制:数字输出控制LED、数字输入读取按键、内部上拉电阻使用、GPIO矩阵映射
好,咱们进入第三章。这一章我打算把ESP32的GPIO基础操作讲透。说白了,就是让灯亮起来,让按键能被识别。听起来简单,但里面有不少门道。我当年刚接触ESP32时,就在内部上拉电阻上栽过跟头,后来才明白——嗯,这些细节决定了你的产品稳不稳。
3.1 数字输出:控制LED亮灭
先来个最基础的——控制LED。ESP32的GPIO输出电平只有两种:高(3.3V)和低(0V)。你写个digitalWrite()就能搞定。但我建议你别小看这一步,因为输出电流有限制。
来看代码:
// 定义LED引脚
#define LED_PIN 2
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); // 初始状态:灭
}
void loop() {
digitalWrite(LED_PIN, HIGH); // 亮
delay(1000);
digitalWrite(LED_PIN, LOW); // 灭
delay(1000);
}
这里有个小技巧:ESP32开发板上的LED通常接在GPIO2,但不同板子可能不一样。我习惯在代码开头用宏定义,方便后期改板子。你想想看,要是硬编码数字,换板子时得满世界找——多累啊。
3.2 数字输入:读取按键状态
按键输入比输出稍微复杂点。核心问题是:按键没按下时,引脚是悬空的,电平不确定。这时候就需要上拉或下拉电阻。
ESP32内部自带上拉电阻,省了外接元件。但要注意——内部上拉电阻约45kΩ,对于长线缆或强干扰环境可能不够。我在做工业控制项目时,就遇到过按键误触发,后来换成外部10kΩ上拉才解决。
按键读取的标准写法:
#define BUTTON_PIN 0
#define LED_PIN 2
bool lastState = HIGH;
bool ledState = LOW;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP); // 启用内部上拉
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, ledState);
}
void loop() {
bool currentState = digitalRead(BUTTON_PIN);
// 检测下降沿(按下)
if (lastState == HIGH && currentState == LOW) {
delay(20); // 消抖
if (digitalRead(BUTTON_PIN) == LOW) { // 再次确认
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}
}
lastState = currentState;
}
注意看,我用了INPUT_PULLUP模式。这样按键没按时读HIGH,按下时读LOW。为什么?因为内部上拉电阻把引脚拉到了3.3V,按键接地后引脚被拉到0V。这个逻辑别搞反了——我曾经在项目里搞反过,结果调试了一下午才发现。
3.3 内部上拉电阻的使用细节
内部上拉电阻到底好不好用?我的看法是:原型验证时完全够用,量产产品要谨慎。
来看个对比表格:
| 方案 | 阻值 | 优点 | 缺点 |
|---|---|---|---|
| 内部上拉 | ~45kΩ | 省元件、省PCB空间 | 阻值大、抗干扰弱 |
| 外部上拉 | 4.7kΩ~10kΩ | 抗干扰强、可定制 | 多一颗电阻 |
| 外部下拉 | 10kΩ | 逻辑更直观(按下读HIGH) | ESP32内部无下拉 |
我个人习惯:开发阶段用内部上拉,打样时换成外部10kΩ上拉。为什么?因为内部上拉在长线缆场景下容易受电磁干扰。我记得有一次做智能窗帘的按键面板,线长2米,内部上拉时按键经常误触发,换成外部4.7kΩ后问题消失。
3.4 GPIO矩阵映射:ESP32的独门绝技
这部分是ESP32最让我喜欢的功能——GPIO矩阵映射。说白了,你可以把任意外设功能映射到任意GPIO引脚上。不像传统单片机,I2C必须用固定引脚,UART必须用固定引脚。ESP32打破了这种限制。
为什么会这样?因为ESP32内部有一个可编程的IO MUX矩阵,它相当于一个数字交叉开关。你只需要调用一个函数,就能把UART的TX信号路由到GPIO4,把RX路由到GPIO5。
来看实际用法:
#include <driver/gpio.h>
// 将UART2的TX映射到GPIO17,RX映射到GPIO16
void setup() {
// 方法1:使用Arduino框架的Serial2
Serial2.begin(115200, SERIAL_8N1, 16, 17);
// 方法2:使用ESP-IDF的底层函数
// pinMatrixOutAttach(17, U2TXD_OUT_IDX, false, false);
// pinMatrixInAttach(16, U2RXD_IN_IDX, false);
}
void loop() {
Serial2.println("Hello from GPIO16/17!");
delay(1000);
}
这里要注意:不是所有GPIO都支持所有功能。比如GPIO6~GPIO11被内部Flash占用,不能乱用。GPIO34~GPIO39只能输入,不能输出。我刚开始用ESP32时就踩过这个坑——想把LED接到GPIO34,结果死活不亮,查了手册才发现它只能读不能写。
矩阵映射还有一个妙用——引脚复用。比如你有一个I2C传感器和一个UART模块,但只剩3个空闲引脚。你可以让I2C的SCL和UART的TX共用同一个引脚?不行,因为它们是不同协议。但你可以通过矩阵映射,在不同时间段把同一个引脚分配给不同功能。当然,这需要软件配合,不是所有场景都适用。
最后,我整理了一个常用GPIO功能速查表:
| GPIO编号 | 可用功能 | 注意事项 |
|---|---|---|
| 0 | 输入/输出/ADC2 | 上电时不能拉低(影响启动) |
| 1 | UART0 TX | 调试串口,慎用 |
| 2 | 输入/输出 | 开发板LED常用 |
| 3 | UART0 RX | 调试串口,慎用 |
| 4~5 | 输入/输出 | 常用,无特殊限制 |
| 6~11 | ⚠️ 不可用 | 内部Flash占用 |
| 12 | 输入/输出 | 上电时电平影响Flash电压 |
| 13~15 | 输入/输出 | 常用 |
| 16~17 | 输入/输出 | 常用,可映射UART |
| 34~39 | 仅输入 | 无内部上拉,只能输入 |
嗯,这一章的内容就这些。总结一下:数字输出控制LED要加限流电阻,数字输入要处理消抖和上拉,内部上拉适合原型但量产建议用外部,GPIO矩阵映射是ESP32的杀手锏——灵活分配引脚,让布线更自由。下一章我们会把这些知识用到实际项目里,做一个按键控制LED的智能窗帘演示。