启明云端乐鑫科技一级代理商及方案商头像
RMT 与 WS2812 灯带:可寻址 RGB 全彩控制封面图

RMT 与 WS2812 灯带:可寻址 RGB 全彩控制

之前我们用 GPIO 输出点亮过单色 LED,用也通过多个灯的轮流亮灭实现了流水灯。但一颗灯只能亮一种颜色,想做更加丰富的效果就不行了。这篇文章我们来实现进阶的——WS2812 全彩灯带,一颗灯珠就能调出 1600 万种颜色,一整串灯各自亮各自的颜色,做跑马灯、彩虹渐变都不在话下,这种灯我们可以在路边的树上,玩具上等地方都有应用。

这里我们用 ESP-IDF 的 led_strip 组件 + RMT 驱动实现,同时也搞清楚 RGB 顺序、供电、共地这些容易出现问题的地方。

1. 开始前准备

1.1 硬件准备

名称数量说明
ESP32-P4 开发板1 块用 GPIO2 输出数据信号
WS2812 灯带 / 灯环1 条建议使用RGB模块,我们直接接线就行
5V 独立电源1 个灯带多了必须外接,别全靠开发板供电
杜邦线(母对母)若干信号线 + 电源线 + 地线

1.2 软件准备

不需要新装任何东西,直接复用第 1 课的环境

如何还没有搭建环境可以参照第一课【ESP32入门】Windows 下 ESP-IDF V6.1 环境搭建(上):EIM 离线安装 + VS Code 配置 + 驱动安装_eim-gui-windows-x64-CSDN博客)

1.3 基础概念

1.3.1 WS2812 是什么:一颗灯珠就是一个小工位

普通 LED 就是个发光二极管,通电就亮。WS2812 不一样——它把红、绿、蓝三颗灯珠加一个控制芯片,封在一个小方块里。

最妙的是"可寻址":一整串灯,信号从第一颗进,传完了自动传给下一颗。不用每颗灯都拉一根线出来,三根线(VCC、GND、S)就能控制一整条灯带。

1.3.2 RGB 顺序:不是 RGB,是 GRB

在WS2812 的数据顺序是 GRB不是 RGB。也就是说你发 24 位颜色数据:前 8 位绿色,中间 8 位红色,最后 8 位蓝色。顺序搞反了的话,想要红色结果亮成绿色。但是用 led_strip 组件的时候,它会帮忙处理这个顺序,你只管给 R、G、B 三个值就行。在WS2812的数据手册中,我们可以看到GRB的顺序,如下:
在这里插入图片描述

1.3.3 单线协议:靠高电平长短区分 0 和 1

WS2812 的通信方式很特别——它没有时钟线,只有一根数据线,对应的就是模块的S引脚。它是靠高电平持续的时间长短来区分发的是 0 还是 1:
在这里插入图片描述

  • 发 0:高电平约 0.4us,然后低电平
  • 发 1:高电平约 0.8us,然后低电平
  • 发完一整串,拉低超过 50us,告诉灯珠"这一帧结束了,亮吧"

这个时序精度要求很高,差 100ns 都可能认错。所以不能用普通 GPIO 软件翻转来做——CPU 调度有抖动,时间不准。ESP32 的 RMT 外设就是干这个的:你给它一个时间清单,硬件自动按时翻转引脚,不占 CPU。

1.3.4 供电

WS2812 满亮的时候,一颗灯珠需要 60mA 左右电流。

  • 8 颗灯全白 ≈ 480mA

USB 口才供得起 500mA,多接几颗灯就不够了。所以灯带长了,必须外接 5V 独立电源。

还有个关键点:开发板和灯带必须共地。信号线的参考电平是 GND,两个 GND 不连在一起,信号就是"对空气"发的,灯珠根本认不出来。

1.3.5 判断3.3V 单片机能否直接驱动

虽然现在的开发板通常同时带有 5V 和 3.3V 两种供电引脚,但供电电压不等于 IO 口的逻辑电平。即使我们将 WS2812 的电源(VCC)接在开发板的 5V 引脚上,目前主流单片机(如 ESP32等)引脚输出的逻辑高电平依然只有 3.3V。用 3.3V 的信号去控制 5V 供电的灯珠,就存在无法正常驱动的风险。在WS2812的规格书中我们可以看到如下图电气参数:
在这里插入图片描述
从图中可以看到,高电平输入最小值为 0.55VDD,只要计算出的阈值小于 3.3V 即可正常驱动。已知表中 VDD = 5V,计算可得 0.55 × 5V = 2.75V < 3.3V,因此证明该灯珠可以直接使用 3.3V 逻辑电平驱动。在其他产品中,如果计算出的阈值大于 3.3V(无法直接驱动),则需要使用电平转换芯片或模块(如 74HCT245 等)对 S 引脚进行电平转换。

2. 案例一:点亮红绿蓝单色

先从最简单的开始:让第一颗灯分别亮红、绿、蓝三种颜色,循环切换。

2.1 接线

断电状态下接线:

线ESP32P4灯带 / 电源作用
信号线GPIO22DIN / SRMT 数据信号
地线GNDGND(电源地)共地,必须接
电源线接开发板5V5V (电源正)灯带供电

这里我们使用的模块只有6颗灯珠,所以没有使用独立电源模块供电了。
在这里插入图片描述

2.2 打开工程

使用课程提供的 WS2812_RGB 工程压缩包,解压到纯英文路径下,用 VS Code 直接打开工程文件夹也可以。(WS2812_RGB项目下载)

2.2 代码解释

完整代码先放出来,然后我们拆开讲:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"

#define LED_NUM     6       // 灯的数量
#define LED_GPIO    22      // 数据引脚

void app_main(void)
{
    // 配置灯带参数
    led_strip_config_t strip_config = {
        .strip_gpio_num = LED_GPIO,    // 数据输出引脚
        .max_leds = LED_NUM,           // 灯带总灯数
    };
    led_strip_rmt_config_t rmt_config = {
        .resolution_hz = 10 * 1000 * 1000,  // RMT 时钟分辨率 10MHz
    };

    // 创建灯带实例
    led_strip_handle_t strip;
    led_strip_new_rmt_device(&strip_config, &rmt_config, &strip);

    // 主循环:红、绿、蓝轮流亮
    while (1) {
        for (int i = 0; i < LED_NUM; i++) {
            led_strip_set_pixel(strip, i, 255, 0, 0);  // 亮红色
        }
        led_strip_refresh(strip);       // 把数据发出去
        vTaskDelay(pdMS_TO_TICKS(1000));  // 保持 1 秒

        for (int i = 0; i < LED_NUM; i++) {
            led_strip_set_pixel(strip, i, 0, 255, 0);  // 亮绿色
        }
        led_strip_refresh(strip);
        vTaskDelay(pdMS_TO_TICKS(1000));

        for (int i = 0; i < LED_NUM; i++) {
            led_strip_set_pixel(strip, i, 0, 0, 255);  // 亮蓝色
        }
        led_strip_refresh(strip);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

第一段:配置灯带参数

led_strip_config_t strip_config = {
    .strip_gpio_num = LED_GPIO,    // 数据输出引脚
    .max_leds = LED_NUM,           // 灯带总灯数
};
led_strip_rmt_config_t rmt_config = {
    .resolution_hz = 10 * 1000 * 1000,  // RMT 时钟分辨率 10MHz
};

这里配了两个结构体:

  • strip_config 是灯带基本参数:数据从哪个引脚输出、一共有多少颗灯
  • rmt_config 是 RMT 驱动参数:.resolution_hz = 10MHz 意思是 RMT 的时间精度是每格 100ns。WS2812 的时序要求是 0.4us 和 0.8us,刚好是 4 格和 8 格,这个分辨率刚好够精度

第二段:创建灯带实例

led_strip_new_rmt_device(&strip_config, &rmt_config, &strip);

这一行就把 RMT 驱动、时序编码全封装好了。你不用自己算 0.4us 还是 0.8us,组件内部都帮你算好了。

第三段:设置颜色 + 刷新

led_strip_set_pixel(strip, i, 255, 0, 0);  // 第 i 颗灯,R=255, G=0, B=0
led_strip_refresh(strip);                   // 把数据发出去

注意是两步操作:

  • set_pixel 只是把颜色写到内存缓冲区里,灯还不会变
  • refresh 才真正把数据通过 RMT 发出去,灯才会亮

和之前 PWM 的 set_duty + update_duty 是一个思路——先准备好,再一次性生效。这样做的好处是万一你设到一半被打断了,旧的输出还在,不会出现跳变。
在这里插入图片描述

3. 案例二:颜色渐变

最后来个好看点的——整排灯做彩虹渐变,颜色慢慢流动。

3.1 打开工程

使用课程提供的 WS2812_Rainbow 工程压缩包,解压到纯英文路径下,用 VS Code 直接打开工程文件夹也可以。(WS2812_Rainbow项目下载)

3.2 代码解释

#define BRIGHTNESS  50    // 亮度百分比:0~100,这里是 50% 亮度

uint8_t hue = 0;  // 色相值,0~255 循环
while (1) {
    for (int i = 0; i < LED_NUM; i++) {
        // 每颗灯的色相错开一点,形成渐变
        uint8_t h = hue + i * (256 / LED_NUM);
        uint8_t r, g, b;
        // 简单的 HSV 转 RGB(近似版)
        h = h % 256;
        if (h < 85) {
            r = 255 - h * 3; g = h * 3; b = 0;
        } else if (h < 170) {
            h -= 85;
            r = 0; g = 255 - h * 3; b = h * 3;
        } else {
            h -= 170;
            r = h * 3; g = 0; b = 255 - h * 3;
        }
        // 亮度调节:乘以亮度系数,范围 0~100%
        r = r * BRIGHTNESS / 100;
        g = g * BRIGHTNESS / 100;
        b = b * BRIGHTNESS / 100;
        
        led_strip_set_pixel(strip, i, r, g, b);
    }
    led_strip_refresh(strip);
    
    hue += 2;  // 色相慢慢增加,颜色就流动起来了
    vTaskDelay(pdMS_TO_TICKS(30));
}

逐段解释:

1. 色相错开形成彩虹条

uint8_t h = hue + i * (256 / LED_NUM);

整排灯不是同一个颜色,而是每颗灯的色相都错开一段距离。6 颗灯的话,每颗错开 256/6 ≈ 42,这样就形成了一条从红到紫的彩虹条。

2. HSV 转 RGB(简化版)

if (h < 85) {
    r = 255 - h * 3; g = h * 3; b = 0;
} else if (h < 170) {
    h -= 85;
    r = 0; g = 255 - h * 3; b = h * 3;
} else {
    h -= 170;
    r = h * 3; g = 0; b = 255 - h * 3;
}

这一段是把色相值转成 RGB 三个通道的亮度。原理是把色相环分成三段:

  • 0~85:红 → 黄
  • 85~170:黄 → 绿 → 青
  • 170~255:青 → 蓝 → 紫 → 红

这个是简化版,精度一般,但做个彩虹流动效果完全够用。想要更精确的颜色,可以用标准的 HSV 转 RGB 算法。

3. 亮度调节

#define BRIGHTNESS  50    // 亮度百分比:0~100
r = r * BRIGHTNESS / 100;
g = g * BRIGHTNESS / 100;
b = b * BRIGHTNESS / 100;

WS2812 本身不支持直接调亮度,我们用软件方式:算出 RGB 值之后,整体乘以一个亮度系数。BRIGHTNESS 设成 50,就是所有颜色都减半——相当于亮度砍半。

这样做的好处是:

  • 调亮度不用改颜色逻辑,改一个数字就行
  • 亮度低一点,整体电流也小,灯带不会太烫
  • 晚上用的时候不会晃眼

想调亮度就改 BRIGHTNESS 这个宏:100 是最亮,0 是全灭。

4. 颜色流动起来

hue += 2;
vTaskDelay(pdMS_TO_TICKS(30));

每 30ms 整体色相往前推 2,整条彩虹就像在缓缓流动。想调速度的话:

  • hue += 2 里的数越大,流得越快
  • vTaskDelay 的延时越小,流得越快

下面这张流程图就是这段代码的执行顺序,方便你整体理解:
在这里插入图片描述
在这里插入图片描述

4. 注意事项

  • WS2812相比较与之前的LED灯珠,功耗会大很多,如果灯带比较长或者灯珠数量比较多的情况下必须外接电源,外接电源的情况下注意GND需要共地;
  • 由于灯珠功耗比较大的原因,在使用中模块的发热一般比其他模块高,实际应用中需要注意对其他温度敏感模块的影响:
  • WS2812灯珠的亮度比较高,在使用中建议将亮度调到50%以下。

5. 参考文档

WS2812规格书

espressif/led_strip • v3.1.1 - ESP Component Registry

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_42679566/article/details/169260060

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--