5

【.NET 与树莓派】控制彩色灯带(WS28XX)

 2 years ago
source link: https://www.cnblogs.com/tcjiaan/p/15687336.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【.NET 与树莓派】控制彩色灯带(WS28XX)

彩色灯带,相信不用老周多说,大家都知道,没准你家里的灯墙里面就有。老周的茅屋是早期建造的,所以没有预留的灯槽,明灯的话是不好看的,因此老周家里没使用灯带。不过,像柜子后面,显示器后面,书桌边沿这些地方,可以贴彩色灯带。书架上也贴了一些,因为那个书架是圣诞树形状的,没办法一条灯带贴完,只能把它剪开 N 段,测算好每一段的距离,再用烙铁加线重新焊接起来(嫌麻烦的话可以直接买连接头,不用焊接)。

买灯带时老周没有买驱动器,毕竟一开始老周就计划自己写个程序来控制灯带的色彩。装饰用的灯带,5V 电压足矣。如果灯较多可以考虑独立供电,5V电源比较方便好弄,手机充电头、锂电池+5V稳压板即可轻松解决。其实,两米长度内直接用开发板供电都没啥问题。要是五米一卷的,用树莓派的5V引脚供电没有问题,但用 ESP32 的话,电压好像有点不够,亮度降低。

老周的卧室里面目前使用的装饰灯是用 ESP 32 做的,两颗18650电池供电(需要稳压模块调到5V电压)。主要是看中 ESP32有 WiFi 功能,上面写个 UDP 服务器,通过接收到的命令来切换灯带颜色。

控制命令比较简单,全用文本格式,客户端只要发送满足格式要求的命令到 ESP32 即可。比如,发送

SET+255 125 127

首先识别出“SET+”,后面三个数值用空格分隔,依次表示 RGB 三个值。例如,让灯变成蓝色

SET+0 0 255

=================================================================================

彩色灯带使用 WS28XX 作为IC,常见的像 WS2812B。每个灯珠都可以单独控制,灯带剪开后只需要串联好就行,灯带末端不需要连接回路,WS28XX 自带回路。

WS28XX 的时序其实很简单,+5V 和 GND 是供电接口,剩下一根数据线来传输信号,所以这货是单数据线控制的。当N个灯珠串联起来后,第一个灯珠的 Din 输入信号,Dout 输出信号;第一个灯珠 Dout 联第二个灯珠的 Din;第二个灯珠的 Dout 联第三个灯珠的 Din ……

我们都知道,RGB三个值,各为一个字节,8位,三个值合起来 24 位,所以每个灯珠的数据为24位,3个 byte。对于每个二进制位,WS28XX 是通过单周期内高电平的持续时间来判断 0 或 1。

请看下面的高清无码美图。

上面这个就是 WS28XX 的时序图。在一个脉冲周期内,如果:

高电平持续时间为 0.4 微秒,低电平持续时间为 0.85 微秒,那么就是0;

高电平持续时间为 0.85 微秒,低电平持续时间为 0.4 微秒,那么就是1。

故一个周期的总时长为 1.25 微秒,即频率为 1000000 / 1.25 = 800 KHz,即 0.8 MHz

请记住这个频率,后面有用。

WS28XX的信号是连续发的,中间不需要停顿;如果出现超过 50 微秒的低电平,那么WS28XX会认为你的信号发送完毕。如果数据线上有数据来就会从第一个灯珠开始进行处理(等于更新整个灯带的数据)。

例如,有四个灯珠串联,每个灯的RGB有24位,那么,你需要向WS28XX连续发送 24 * 4 = 96 位数据,数据发送中不要停顿,所有数据都是连着发的—— 96位连续发,中间不用停顿;全部发完后再输出50微秒左右的低电平表示结束发送。

比如,红色,RGB 是 255,0,0,那么这24位就是:

1111 1111 0000 0000 0000 0000

如果有三个灯珠,第一个灯珠设置为绿色,第二个灯珠为白色,第三个灯珠为蓝色,那么三组RGB为:

【0,255,0】【255,255,255】【0,0,255】。于是,72位二进制为:

0000 0000 1111 1111 0000 0000 1111 1111 1111 1111 1111 1111 0000 0000 0000 0000 1111 1111

在发送的时候,这72位是连续发送的,中间不需要延时,发完后把电平拉低,50 us后WS28XX就会认为你已经发完数据。

 正如你所看到的,WS28XX 的通信协议比较简单。但是,问题出在它的时间很短。你如果纯手动写代码来改变电平的高低,要求程序有很高的性能。低配的单片机可能不够快,像树莓派这样的开发板,虽然处理器肯定比单片机快,但是代码传递到系统驱动,再由驱动传到底层硬件。而且每次切换电平需要来回两次通信,花的时间太长,都有可能超出 1.25 us 的周期。

所以,一般不采取直接写GPIO电平的方式通信,而是借助硬件上所支持的协议。能够做这事的硬件协议有俩:

1、PWM。这个估计大家能理解,前面老周说要记住那个通信频率,现在用上了。把PWM频率设为 800 KHz,然后一个周期的时长就是 1.25 us。最后你一定猜到了,发送1时,高电平持续0.85 us,占空比 68%;发送0时,高电平持续0.4 us,占空比 32%。于是呢,不断地改变占空比,就能给WS28XX发信号。许多方案是和DMA一起使用的,就是为了提高速度。.NET Iot 封装的 PWM 不支持 DMA 方式,因此这个方案跳过。

2、SPI,这个方案是目前最优秀方案。.NET Iot 封装的库也是采用 SPI 协议。这是个巧妙利用,将SPI的 MOSI 口与灯带的 Din 连接,让 SPI 时序来控制灯珠。

为啥不用 IIC (I2C)呢?因为 IIC 要从机地址啊,我们又不是真的使用 IIC 设备,所以这里不适合。根据官方的示例,使用SPI时要设置以下几个参数:

a、速率 2400 KHz,正好是 800 K 的三倍。于是乎,SPI 中 3 个二进制位对应WS28XX中 1 个二进制位。要发送 1 ,SPI 写入二进制 110;要发送 0 ,SPI 写入二进制 100。一个灯珠的数据是3字节24位,那么 SPI 就得写入72位(9个字节)。

 b、SPI 模式选择 Mode0。

c、数据长度为8位。

因为树莓派是支持硬件 SPI 的,所以 SPI 方案是没有问题的。若开发板没有硬件 SPI(靠软件模拟),就不能使用SPI方案了,速度跟不上。

 =======================================================================

知道原理后,咱们可以动手做了。

第1步,引用 Iot.Device.Bindings 包(Nuget),依赖包 System.Device.Gpio 会自动引入,所以不用管它。

第2步,using 以下命名空间:

using System.Device.Spi;
using System.Drawing;
using Iot.Device.Graphics;
using Iot.Device.Ws28xx;

第3步,初始化 SpiDevice 对象。

SpiConnectionSettings setting = new(0)
{
    Mode = SpiMode.Mode0,
    ClockFrequency = 2400_000,  //注意速率
    DataBitLength = 8
};

using SpiDevice spidev = SpiDevice.Create(setting);

把 ClockFrequency 设置为 2400,000 hz,原因前面解释过。

第4步,老周买的灯带是 WS2812B 芯片的,所以,实例化 WS28XX 时用 Ws2812b 类,大部分灯带都是这个。

// 30表示使用30个灯珠,这个按实际来传值,要用到50个灯珠,就传50
const int LED_NUM = 30;
Ws28xx leds = new Ws2812b(spidev, LED_NUM);

老周现用来测试的灯带是五米长的,150 个灯,但为了省电,只选择了 30 个灯做实验,你可以根据实际情况改代码。咱们这里用的灯带,所以只考虑宽度,高度忽略;如果用的是点阵屏,则要考虑高度,即几行几列。

第5步,准备七种常用颜色,待会咱们做色彩轮换效果。

Color[] colors =
{
    Color.Blue,         // 蓝色
    Color.Yellow,       // 黄色
    Color.White,        // 白色
    Color.Red,          // 红色
    Color.Green,        // 绿色
    Color.Pink,         // 粉色
    Color.Orange        // 橙色
};

第6步,色彩轮换,七种颜色轮着显示。

// Color数组中正在使用的索引
int theIndex = 0;

// 进入循环,开始溜灯
while (true)
{
    // 当表示索引的值超出数组范围后,重回0
    if (theIndex > colors.Length - 1)
    {
        theIndex = 0;
    }
    // 获取位图对象
    BitmapImage image = leds.Image;
    // 循环修改每个灯珠的颜色
    for (int x = 0; x < LED_NUM; x++)
    {
        // 更新像素点(对应一个灯珠)
        image.SetPixel(x, 0, colors[theIndex]);
        // 调用更新,才会刷新灯带显示
        leds.Update();
        Thread.Sleep(10);
    }
    theIndex++;
    Thread.Sleep(1000);
}

可以把一个灯珠视为一个像素,先从 Image 属性中获得对 BitmapImage 对象的引用,然后用 SetPixel 方法来设置每个灯的颜色。这里因为用的是灯带,所以 y 坐标都是 0,仅改变 x 坐标上的值。

修改完颜色后,如果想灯珠马上更新,请调用 Update 方法。

最后,强调一下:接线时,树莓派的 MOSI 接灯带的数据接口(Din 或 Di)。

367389-20211214172322090-1800147501.png

 如果SPI不能用,请执行 sudo raspi-config,然后进入“Interface Options”,确认 SPI 总线启用。

367389-20211214172534058-632303040.png

 看看最终效果。

【补充】如果出现程序运行几秒钟就“卡死”的情况,那是SPI被系统降频了所致。若遇到此问题,可以在 config.txt 文件中修改 GPU 超频参数。

sudo nano /boot/config.txt

加上这两行:

core_freq=250
core_freq_min=250
core_freq=500
core_freq_min=250

这样配置后,至少保证了 GPU 内核在空闲时的频率不低于 250 MHz。

除了变色、流水,还有比较常用的特效是颜色渐变。颜色渐变算法我们可以自己写,老周会在下一篇水文中演示颜色渐变效果。 


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK