3、GPIO基础与LED控制:ESP32 GPIO引脚功能、数字输出控制LED、PWM调光原理、呼吸灯实验
各位同学,咱们今天来聊聊ESP32上最基础、也最常用的功能——GPIO控制。说白了,就是让芯片的引脚听话,让它输出高电平就高电平,让它输出低电平就低电平。你想想看,智能家居里那些灯光的开关、继电器的通断,底层都是这么干的。
3.1 ESP32的GPIO引脚功能概览
ESP32的引脚,可不是每个都能随便用的。我刚开始用ESP32的时候,就踩过这个坑——随便找了个引脚接LED,结果死活不亮。后来一查手册,好家伙,那个引脚是只读的输入引脚。
ESP32一共有34个GPIO引脚,编号从GPIO0到GPIO39。但要注意,并不是所有引脚都适合做数字输出。我习惯把引脚分成三类:
- 标准GPIO:GPIO0-GPIO19、GPIO21-GPIO23、GPIO25-GPIO27、GPIO32-GPIO39。这些引脚可以正常做数字输入输出。
- 受限GPIO:GPIO6-GPIO11,这些引脚连接到了Flash芯片,一般不建议用,用了可能会影响程序运行。
- 输入专用:GPIO34-GPIO39,这些引脚只能做输入,不能输出。嗯,这里要注意,千万别把它们当输出用。
重要提醒:GPIO0、GPIO2、GPIO5、GPIO12、GPIO15这几个引脚在启动时有特殊功能。比如GPIO0拉低会进入下载模式。我在项目中就遇到过,GPIO12接了上拉电阻,结果芯片死活启动不了,查了半天才发现是电压问题。
另外,ESP32的GPIO还支持多种复用功能,比如ADC、DAC、触摸传感器、I2C、SPI、UART等。但咱们这节课先聚焦在数字输出上。
3.2 数字输出控制LED——点亮你的第一盏灯
控制LED,是嵌入式开发的"Hello World"。说白了就是让引脚输出高电平,LED就亮;输出低电平,LED就灭。咱们来看代码:
// 定义LED引脚
const int ledPin = 2; // 我习惯用GPIO2,因为很多开发板上的板载LED就在这个引脚
void setup() {
// 设置引脚为输出模式
pinMode(ledPin, OUTPUT);
// 点亮LED
digitalWrite(ledPin, HIGH);
}
void loop() {
// 什么也不做,LED保持常亮
}
这段代码很简单,但有几个细节要注意:
pinMode()函数用来配置引脚模式,参数可以是OUTPUT、INPUT、INPUT_PULLUP等。digitalWrite()用来设置引脚电平,HIGH是3.3V,LOW是0V。- ESP32的GPIO输出高电平是3.3V,不是5V。如果你接的是5V的LED,记得串个限流电阻,不然可能会烧引脚。
小技巧:我一般会在LED上串一个220Ω到470Ω的电阻。电阻太小,LED太亮甚至烧坏;电阻太大,LED太暗。220Ω是个比较折中的值。
咱们再来个闪烁的例子,让LED一秒钟闪一次:
const int ledPin = 2;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH); // 亮
delay(500); // 等500ms
digitalWrite(ledPin, LOW); // 灭
delay(500); // 等500ms
}
你看,就这么几行代码,LED就开始呼吸了——不对,这是闪烁,不是呼吸。呼吸灯要更平滑,那就得用PWM了。
3.3 PWM调光原理——让LED亮度可调
PWM,全称是脉冲宽度调制。什么意思呢?就是让引脚快速地在高电平和低电平之间切换。你想想看,如果高电平的时间长,低电平的时间短,LED看起来就亮;反过来就暗。人眼有视觉暂留效应,只要切换频率够快(一般100Hz以上),我们就感觉不到闪烁,只感觉到亮度的变化。
PWM有两个关键参数:
- 频率:每秒切换的次数,单位Hz。频率太低会看到闪烁,频率太高会增加功耗。
- 占空比:高电平时间占整个周期的比例。占空比0%就是全灭,100%就是全亮。
ESP32的PWM实现方式和Arduino不太一样。Arduino用analogWrite()就行,但ESP32需要先配置PWM通道。我刚开始也觉得麻烦,但用习惯了反而觉得更灵活。
ESP32的PWM配置步骤:
- 选择一个PWM通道(共16个,0-15)
- 设置PWM频率
- 设置PWM分辨率(位数,比如8位就是0-255)
- 将通道绑定到GPIO引脚
- 用
ledcWrite()设置占空比
来看代码:
const int ledPin = 2;
const int freq = 5000; // PWM频率5kHz
const int ledChannel = 0; // 通道0
const int resolution = 8; // 8位分辨率,占空比范围0-255
void setup() {
// 配置PWM通道
ledcSetup(ledChannel, freq, resolution);
// 将通道绑定到引脚
ledcAttachPin(ledPin, ledChannel);
}
void loop() {
// 从暗到亮
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(ledChannel, dutyCycle);
delay(10);
}
// 从亮到暗
for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
ledcWrite(ledChannel, dutyCycle);
delay(10);
}
}
避坑指南:我曾经在项目里把PWM频率设成了50Hz,结果LED肉眼可见地在闪烁,客户投诉说产品有问题。后来我把频率提高到1kHz以上,问题就解决了。一般LED调光,建议频率在1kHz到10kHz之间。
3.4 呼吸灯实验——从理论到实践
呼吸灯,就是让LED像呼吸一样,慢慢地变亮,再慢慢地变暗。这个效果用PWM来实现再合适不过了。咱们刚才的代码其实已经实现了呼吸灯的效果,但还不够平滑。我来优化一下:
const int ledPin = 2;
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;
void setup() {
ledcSetup(ledChannel, freq, resolution);
ledcAttachPin(ledPin, ledChannel);
}
void loop() {
// 呼吸一次:从暗到亮再到暗
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(ledChannel, dutyCycle);
delay(5); // 延时短一点,呼吸更平滑
}
for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
ledcWrite(ledChannel, dutyCycle);
delay(5);
}
}
你可能会问,为什么不用delay(10)而用delay(5)?因为延时越短,呼吸的步进越细腻,看起来就越平滑。但也不能太短,否则呼吸太快,看起来就像在抽搐。
我个人习惯用指数曲线来做呼吸灯,这样更自然。因为人眼对亮度的感知不是线性的,而是对数的。用线性变化看起来会感觉亮得很快,暗得很慢。用指数曲线可以补偿这个效应:
void loop() {
// 指数呼吸灯
for (int i = 0; i <= 100; i++) {
// 用sin函数生成平滑的呼吸曲线
float brightness = (sin(i * 3.14159 / 100) + 1) / 2 * 255;
ledcWrite(ledChannel, (int)brightness);
delay(20);
}
}
这段代码用了sin()函数,生成一个从0到1再到0的平滑曲线,再映射到0-255的占空比范围。效果比线性呼吸好很多。
小技巧:如果你想让呼吸灯有不同节奏,可以调整delay()的值。比如delay(30)就是慢呼吸,delay(10)就是快呼吸。我在做智能台灯的时候,就用了三种呼吸模式:快速呼吸(提醒模式)、慢速呼吸(睡眠模式)、正常呼吸(待机模式)。
3.5 总结与扩展
好了,咱们这节课的内容就到这里。总结一下:
- ESP32的GPIO引脚有不同功能,用之前一定要查手册。
- 数字输出用
digitalWrite(),简单直接。 - PWM调光用
ledcWrite(),需要先配置通道和频率。 - 呼吸灯用PWM加延时循环就能实现,用指数曲线效果更好。
下节课咱们会讲按键输入和中断,到时候就能用按键来控制LED的亮灭了。嗯,那才是智能家居交互的起点。
课后你可以试试:把呼吸灯改成RGB三色呼吸灯,用三个PWM通道分别控制红绿蓝三个LED,看看能不能混出彩虹效果。我在项目里做过,效果很炫酷,但要注意三个LED的亮度曲线要匹配好,不然颜色会偏。
好,这节课就到这里。有问题随时问我。