很多朋友第一次玩esp8266时,都是从点亮一颗LED开始的。这就像学习编程时的"Hello World",简单直接。但当你成功让LED亮起来后,有没有想过让它变得更"聪明"一些?比如实现那种柔和渐变的呼吸灯效果,而不是简单的亮灭交替。
呼吸灯在智能家居中特别实用。想象一下,你的智能音箱在待机时LED缓慢明灭,就像在呼吸一样;或者智能门锁在低电量时LED以急促的呼吸频率提醒你。这些细腻的交互,都离不开PWM(脉冲宽度调制)技术。
我刚开始玩esp8266时,也只会用digitalWrite()让LED开关。直到有一次要给工作室做个氛围灯,才发现简单的亮灭实在太生硬了。后来学会了PWM,才真正打开了物联网设备交互设计的大门。
要实现呼吸灯效果,你需要准备以下硬件:
这里有个小经验:选LED时尽量选散光型的,不要选聚光型的。因为呼吸灯效果在散光LED上看起来会更柔和自然。我曾经用过高亮聚光LED做呼吸灯,结果亮度变化时特别刺眼,效果很不理想。
接线其实很简单,但有几个关键点需要注意:
特别注意:esp8266的工作电压是3.3V,虽然大部分LED在3.3V下也能工作,但为了保险起见,一定要加限流电阻。我刚开始玩的时候,有一次忘记加电阻,结果连续烧了三个LED才反应过来。
PWM全称是脉冲宽度调制,听起来很高大上,其实原理很简单。想象你在用开关快速地点亮和熄灭LED,如果开关速度足够快,人眼就会看到LED保持亮的状态,只是亮度取决于"开"和"关"的时间比例。
在esp8266中,PWM通过快速切换GPIO的高低电平来实现。比如设置PWM值为128(范围0-255),就表示在一个周期内,有一半时间是高电平,一半时间是低电平,LED看起来就是半亮状态。
esp8266的PWM有几点需要注意:
实测发现,esp8266的PWM在低亮度时线性度不太好。比如设置PWM值为10时,可能比值为20时看起来还亮。这是因为LED本身的特性,不是硬件问题。后面我会教大家如何通过代码补偿这个问题。
先来看最简单的实现方式:
cpp复制const int ledPin = D5; // LED连接的引脚
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
// 渐亮
for(int brightness = 0; brightness <= 255; brightness++){
analogWrite(ledPin, brightness);
delay(10);
}
// 渐暗
for(int brightness = 255; brightness >= 0; brightness--){
analogWrite(ledPin, brightness);
delay(10);
}
}
这段代码通过两个for循环,让LED从暗到亮,再从亮到暗,形成呼吸效果。delay(10)决定了呼吸的速度,数值越大呼吸越慢。
基础版虽然能用,但有两个问题:
改进后的代码使用指数曲线和millis()非阻塞实现:
cpp复制const int ledPin = D5;
unsigned long previousMillis = 0;
int brightness = 0;
bool rising = true;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if(currentMillis - previousMillis >= 10) {
previousMillis = currentMillis;
if(rising) {
brightness += 5;
if(brightness >= 255) {
brightness = 255;
rising = false;
}
} else {
brightness -= 5;
if(brightness <= 0) {
brightness = 0;
rising = true;
}
}
// 使用指数曲线使呼吸更自然
int pwmValue = brightness * brightness / 255;
analogWrite(ledPin, pwmValue);
}
// 这里可以添加其他非阻塞代码
}
这个版本呼吸效果更接近真实的呼吸节奏,而且不会阻塞其他操作。我在智能家居项目中就用这个方案,效果相当不错。
呼吸速度主要由两个参数控制:
建议的调节方法:
但要注意,间隔时间不能小于5ms,否则PWM效果会变差。我曾经试过1ms的间隔,结果LED出现了明显的闪烁。
如果要控制多个LED实现呼吸灯,有几种方案:
这里给出一个双LED交替呼吸的代码示例:
cpp复制const int ledPin1 = D5;
const int ledPin2 = D6;
int brightness1 = 0;
int brightness2 = 255;
bool rising1 = true;
bool rising2 = false;
void setup() {
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
}
void loop() {
// LED1呼吸
if(rising1) {
brightness1 += 3;
if(brightness1 >= 255) rising1 = false;
} else {
brightness1 -= 3;
if(brightness1 <= 0) rising1 = true;
}
// LED2呼吸(与LED1相反)
if(rising2) {
brightness2 += 3;
if(brightness2 >= 255) rising2 = false;
} else {
brightness2 -= 3;
if(brightness2 <= 0) rising2 = true;
}
analogWrite(ledPin1, brightness1 * brightness1 / 255);
analogWrite(ledPin2, brightness2 * brightness2 / 255);
delay(10);
}
这种交替呼吸效果很适合作为设备的状态指示,比如网络连接中、数据传输中等场景。
既然esp8266有WiFi功能,我们何不做一个可以通过手机控制的呼吸灯?这里简单介绍下思路:
核心代码片段:
cpp复制#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
const int ledPin = D5;
int breathSpeed = 10;
void handleRoot() {
String html = "<form action='/set'>"
"速度: <input type='range' name='speed' min='1' max='50' value='"+String(breathSpeed)+"'>"
"<input type='submit' value='设置'>"
"</form>";
server.send(200, "text/html", html);
}
void handleSet() {
breathSpeed = server.arg("speed").toInt();
server.send(200, "text/plain", "设置成功");
}
void setup() {
pinMode(ledPin, OUTPUT);
WiFi.begin("你的SSID", "你的密码");
while(WiFi.status() != WL_CONNECTED) delay(500);
server.on("/", handleRoot);
server.on("/set", handleSet);
server.begin();
}
void loop() {
server.handleClient();
// 呼吸灯代码(使用breathSpeed变量控制速度)
static int brightness = 0;
static bool rising = true;
if(rising) {
brightness += 5;
if(brightness >= 255) rising = false;
} else {
brightness -= 5;
if(brightness <= 0) rising = true;
}
analogWrite(ledPin, brightness * brightness / 255);
delay(breathSpeed);
}
上传代码后,在浏览器中输入esp8266的IP地址,就能看到一个滑动条,可以实时调节呼吸灯的速度了。这个项目我在工作室用来控制氛围灯,朋友们都觉得很有意思。
虽然呼吸灯看起来简单,但在实际项目中,特别是电池供电的设备上,还是需要注意功耗问题。下面分享几个实测有效的优化技巧:
修改PWM频率的方法:
cpp复制void setup() {
pinMode(D5, OUTPUT);
// 设置PWM频率为500Hz
analogWriteFreq(500);
}
在电池供电的智能门锁项目中,通过这些优化技巧,我将原本只能工作1周的电池寿命延长到了近1个月。特别是在夜间,降低呼吸灯的亮度变化范围,对省电效果非常明显。