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()函数用来配置引脚模式,参数可以是OUTPUTINPUTINPUT_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配置步骤:

  1. 选择一个PWM通道(共16个,0-15)
  2. 设置PWM频率
  3. 设置PWM分辨率(位数,比如8位就是0-255)
  4. 将通道绑定到GPIO引脚
  5. 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的亮度曲线要匹配好,不然颜色会偏。

好,这节课就到这里。有问题随时问我。