深夜加班回到家,打开刺眼的白炽灯总让人感到不适。有没有想过用温暖的光线迎接自己?今天我们就用ESP32和WS2812灯带,打造一个能调节色温、亮度的智能床头灯。这个项目不仅实用,还能让你深入理解ESP32的RMT外设如何精准控制WS2812这类时序敏感的LED。
我们的智能床头灯需要实现以下功能:
| 组件 | 型号 | 数量 | 备注 |
|---|---|---|---|
| 主控芯片 | ESP32-WROOM-32 | 1 | 推荐使用开发板方便调试 |
| LED灯带 | WS2812B | 1米 | 60灯/米,5V供电 |
| 电源 | 5V/3A开关电源 | 1 | 需根据灯带长度调整电流 |
| 电容 | 1000μF 10V | 1 | 电源滤波 |
| 电阻 | 470Ω 1/4W | 1 | 数据线限流 |
| 按键 | 6×6mm轻触开关 | 3 | 模式切换/亮度调节 |
提示:WS2812灯带长度不宜超过1米(60灯),否则需要考虑额外供电和信号增强。
ESP32的RMT(Remote Control)外设最初设计用于红外遥控,但其灵活的信号生成能力使其成为驱动WS2812的理想选择。每个WS2812灯珠需要24位数据(G7-G0 R7-R0 B7-B0),每位数据用高低电平持续时间表示0或1:
| 数据位 | T0H | T0L | T1H | T1L |
|---|---|---|---|---|
| 时间(ns) | 350 | 800 | 700 | 600 |
c复制// RMT配置结构体示例
rmt_config_t config = {
.rmt_mode = RMT_MODE_TX,
.channel = RMT_CHANNEL_0,
.gpio_num = GPIO_NUM_18,
.clk_div = 2, // 80MHz APB时钟分频
.mem_block_num = 1,
.tx_config = {
.carrier_freq = 0,
.carrier_level = RMT_CARRIER_LEVEL_LOW,
.idle_level = RMT_IDLE_LEVEL_LOW,
.carrier_duty_percent = 33,
.carrier_en = false,
.loop_en = false,
.idle_output_en = true,
}
};
我们封装一个易用的LED驱动库,隐藏RMT底层细节:
c复制typedef struct {
led_strip_t *strip;
uint16_t led_num;
bool is_on;
uint8_t brightness; // 0-100
uint16_t color_temp; // 2700-6500K
} smart_light_t;
esp_err_t smart_light_init(smart_light_t *light, uint8_t gpio, uint16_t led_num) {
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, RMT_CHANNEL_0);
config.clk_div = 2;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(led_num, (led_strip_dev_t)config.channel);
light->strip = led_strip_new_rmt_ws2812(&strip_config);
light->led_num = led_num;
light->brightness = 50;
light->color_temp = 4000;
return ESP_OK;
}
将色温(2700K-6500K)转换为RGB值:
c复制void color_temp_to_rgb(uint16_t temp_kelvin, uint8_t *r, uint8_t *g, uint8_t *b) {
temp_kelvin = CLAMP(temp_kelvin, 2700, 6500);
float temp = temp_kelvin / 100.0f;
if (temp <= 66) {
*r = 255;
*g = (uint8_t)(99.4708025861 * log(temp) - 161.1195681661);
} else {
*r = (uint8_t)(329.698727446 * pow(temp - 60, -0.1332047592));
*g = (uint8_t)(288.1221695283 * pow(temp - 60, -0.0755148492));
}
if (temp >= 66) {
*b = 255;
} else if (temp <= 19) {
*b = 0;
} else {
*b = (uint8_t)(138.5177312231 * log(temp - 10) - 305.0447927307);
}
// 亮度调整
*r = (*r * light->brightness) / 100;
*g = (*g * light->brightness) / 100;
*b = (*b * light->brightness) / 100;
}
模拟日出过程(30分钟线性变化):
c复制void sunrise_effect(smart_light_t *light, uint32_t duration_ms) {
const uint32_t start_time = xTaskGetTickCount();
const uint32_t end_time = start_time + pdMS_TO_TICKS(duration_ms);
while (xTaskGetTickCount() < end_time) {
float progress = (float)(xTaskGetTickCount() - start_time) / pdMS_TO_TICKS(duration_ms);
// 色温从1800K到6500K
uint16_t temp = 1800 + (uint16_t)(4700 * progress);
// 亮度从0%到70%
uint8_t brightness = (uint8_t)(70 * progress);
uint8_t r, g, b;
color_temp_to_rgb(temp, &r, &g, &b);
set_all_leds(light, r, g, b);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
使用FreeRTOS任务处理按键输入:
c复制void button_task(void *arg) {
smart_light_t *light = (smart_light_t *)arg;
bool btn_pressed[3] = {false};
while (1) {
// 读取按键状态
btn_pressed[0] = gpio_get_level(BTN_MODE_PIN) == 0;
btn_pressed[1] = gpio_get_level(BTN_UP_PIN) == 0;
btn_pressed[2] = gpio_get_level(BTN_DOWN_PIN) == 0;
// 模式切换
if (btn_pressed[0] && !prev_btn[0]) {
light->current_mode = (light->current_mode + 1) % MODE_COUNT;
apply_light_mode(light);
}
// 亮度增加
if (btn_pressed[1] && light->brightness < 100) {
light->brightness += 5;
update_light(light);
}
// 亮度减少
if (btn_pressed[2] && light->brightness > 5) {
light->brightness -= 5;
update_light(light);
}
memcpy(prev_btn, btn_pressed, sizeof(btn_pressed));
vTaskDelay(pdMS_TO_TICKS(50));
}
}
基于ESP-IDF的WiFi和Web服务器实现:
c复制void start_web_server(smart_light_t *light) {
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
// 配置URI处理函数
httpd_uri_t light_on = {
.uri = "/api/light/on",
.method = HTTP_POST,
.handler = light_on_handler,
.user_ctx = light
};
httpd_uri_t light_off = {
.uri = "/api/light/off",
.method = HTTP_POST,
.handler = light_off_handler,
.user_ctx = light
};
// 启动服务器
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &light_on);
httpd_register_uri_handler(server, &light_off);
}
}
当灯关闭时进入轻量级睡眠模式:
c复制void enter_light_sleep() {
// 配置唤醒源(GPIO或定时器)
esp_sleep_enable_ext0_wakeup(BTN_MODE_PIN, 0);
// 关闭不需要的外设
esp_wifi_stop();
rmt_driver_uninstall(light->channel);
// 进入睡眠
esp_light_sleep_start();
// 唤醒后重新初始化
smart_light_init(&light);
esp_wifi_start();
}
使用双缓冲技术减少闪烁:
c复制typedef struct {
uint8_t r[LED_NUM_MAX];
uint8_t g[LED_NUM_MAX];
uint8_t b[LED_NUM_MAX];
} led_buffer_t;
led_buffer_t buffers[2];
uint8_t active_buffer = 0;
void swap_buffers() {
active_buffer = 1 - active_buffer;
for (int i = 0; i < light->led_num; i++) {
light->strip->set_pixel(light->strip, i,
buffers[active_buffer].r[i],
buffers[active_buffer].g[i],
buffers[active_buffer].b[i]);
}
light->strip->refresh(light->strip, 100);
}
使用开源建模软件设计灯罩:
注意:灯带背面需要粘贴铝制散热条,延长WS2812寿命。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 灯带部分不亮 | 电源不足 | 增加5V电源注入点 |
| 颜色异常 | 数据时序错误 | 检查RMT时钟分频设置 |
| 随机闪烁 | 电源干扰 | 增加1000μF电容 |
| WiFi断开 | 信号干扰 | 远离灯带布置天线 |
通过ESP-ADF实现本地语音识别:
c复制void voice_control_init() {
audio_pipeline_handle_t pipeline;
audio_element_handle_t i2s_reader, vad, asr;
// 创建音频处理流水线
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline = audio_pipeline_init(&pipeline_cfg);
// 配置语音活动检测(VAD)
vad_cfg_t vad_cfg = DEFAULT_VAD_CONFIG();
vad = vad_init(&vad_cfg);
// 配置语音识别(ASR)
asr_cfg_t asr_cfg = DEFAULT_ASR_CONFIG();
asr = asr_init(&asr_cfg);
// 注册命令回调
asr_register_cmd(asr, "开灯", light_on_cmd);
asr_register_cmd(asr, "关灯", light_off_cmd);
}
添加BH1750光传感器自动调节亮度:
c复制void ambient_light_task(void *arg) {
smart_light_t *light = (smart_light_t *)arg;
i2c_dev_t dev = {0};
bh1750_init_desc(&dev, BH1750_ADDR_LO, I2C_NUM_0, GPIO_NUM_21, GPIO_NUM_22);
bh1750_setup(&dev, BH1750_MODE_CONTINUOUS, BH1750_RES_HIGH);
while (1) {
float lux;
bh1750_read(&dev, &lux);
// 根据环境光照调整亮度
uint8_t new_brightness = (uint8_t)((lux / 1000.0) * 70 + 30);
new_brightness = CLAMP(new_brightness, 30, 100);
if (abs(new_brightness - light->brightness) > 5) {
light->brightness = new_brightness;
update_light(light);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
经过实际测试,这个智能床头灯在以下方面表现优异:
遇到的主要挑战是WiFi信号受灯带干扰问题,最终通过以下方式解决:
下一步可以考虑添加蓝牙Mesh组网功能,实现多个灯具的同步控制。