.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)。
如果SPI不能用,请执行 sudo raspi-config,然后进入“Interface Options”,确认 SPI 总线启用。
看看最终效果。
【补充】如果出现程序运行几秒钟就“卡死”的情况,那是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。
除了变色、流水,还有比较常用的特效是颜色渐变。颜色渐变算法我们可以自己写,老周会在下一篇水文中演示颜色渐变效果。
栏 目:.NET代码
本文地址:http://www.codeinn.net/misctech/213974.html