今天我们不点LED,我们点一下WS2812B(其实也算LED)
WS2812B是可编程的RGB灯,也就是说不同于我们之前玩的固定颜色的灯,这个灯是可以通过我们的编程来改变颜色的。
一般的LED是通上电就亮了,但是WS2812B是上电了也不亮,要让它亮,我们需要通过一根数据线给它发送数据才行。
我们给WS2812B发送24bit的数据,也就是3个Byte,高位先发,分别代表GRB(记住顺序),通过设置GBR的数值来调整绿蓝红的大小,从而改变WS2812B的颜色。
然后从上面手册里截来的图我们可以发现,我们一次性可以发很多个3Byte,这是因为我们可以把很多个WS2812B串联起来,这样我们只需要一根数据线就能控制多个WS2812B!
比如我们发送3个3Byte的数据给三个串联起来的WS2812B,第一个3Byte会被第一个灯接收,然后第一个灯会把剩下的两个3Byte传下去。
在原理图里就像是下面这样。
第一个灯的DIN接到我们的MCU的引脚上,然后把第一个灯的DOU接到下一个灯的DIN……最后一个灯的DOU直接接地就行。
然后手册里推荐是WS2812B接个10nf(104)的电容。
接着看看参数,范围还是非常大的,容错空间很大。
我是直接用STC8G1K08A来操控WS2812B的,用的5V供电(3.3V也可以的)。
自己画了块板子,上面集成了STC32和STC8两个芯片,一次性就可以玩到两种单片机,过阵子我第二版测试没问题之后再开源出来。
我在板子上的STC8G1K08A这边直接接了个WS2812B,今天我们就来看看怎么点亮它,只要知道了原理,那么换个芯片也是可以轻松点亮的。
刚刚我们知道了我们只需要一根数据线就可以点亮一堆WS2812B,每个WS2812B要接收3个Byte,3Byte按照GRB的顺序来分配绿红蓝的数值,那么问题就只剩下一个,我们应该如何发送数据?
我们可以看看手册。
我们要发送的不是0就是1,剩下RESET码就是用来区分数据批次的。
比如说我一次性要控制三个灯,然后我要让它们亮个颜色A,那么我就发送3*3个Byte。过了一下子我要让它们换个颜色,那么我就需要发送RESET码,表示我这次发送的数据是新的一批,你可别再把数据往后面送了(因为WS2812B可以串联很多个,并且它们并不知道自己后面究竟串联了多少个)
RESET码就是拉低电平一段时间。
0码和1码都是先拉高电平再拉低电平,唯一的区别就是高低电平的持续时间。
先看RESET码,因为它只需要拉低电平,比较简单,只要我们把数据线拉低超过80us,那么就算是发送了RESET码。
我们可以在发送数据之前发一下RESET码(反正也才80us),也可以在每次发送完数据之后再发送RESET码。
接着是0码和1码。
0码的高电平时间是0.2~0.35us,低电平的时间是0.55~1.2us,然后我们要注意一点,每个码元的之间不能小于0.89us,也就是说我们发送0码或者1码的高低电平的时间加起来要超过0.89us。
我们就用表格中的典型值,高电平给0.3us,低电平给0.6us。
1码和0码差不多,高低电平是顺序一样,都是先高电平后低电平,不一样的是持续时间,持续时间其实也差不多,就是高低电平的时间反过来,所以我们1码的高电平时间定为0.6us,低电平时间定为0.3us。
现在我们知道如何发送三个码元(0码1码RESET码)之后就可以开始敲代码了。
唯一的难点在于我们如何控制时间,因为时间单位是us,甚至ns,ns级别就有点难控制了。
一个办法就是用定时器,另一个土办法,我们直接延时。
因为我用的芯片是STC8,所以可以直接拿他家的烧录程序来获取延时函数。
但是人算不如天算,最低精度是1us。
这也难不倒我,经过我一顿操作和计算,STC8G1K08A的主频为24MHz,一个_nop_()大概耗时是63+ns,其实我计算的结果应该是44ns,因为1/24 000 000 约等于是40ns,但是我拿着40一个_nop_()的结果去写代码,发现好像不对劲,最后定位在了一个_nop_()大概耗时是60+ns。
不过这都不是重点,重点是我们要点亮WS2812B。
所以我们之间说的0.3us那就是需要五个_nop_()来延时,但是以防万一,我用的是四个,比较时间范围是0.2~0.35us。
0.6us就用十个_nop_()就行。
然后RESET码因为80us算是时间比较长的,我们就用STC烧录软件提供的延时代码就行。
具体可以参考下面的代码。包含的头文件是STC8的官方库函数。
#include "STC8G_H_Delay.h"
#include "STC8G_H_GPIO.h"
#define WS2812B_IN P32
// 复位
void WS2812B_SendReset(void) {
unsigned char data i, j;
WS2812B_IN = 0; // 拉低80us
i = 2;
j = 219;
do {
while (--j)
;
} while (--i);
}
// 发送1码
void WS2812B_SendOne(void) {
WS2812B_IN = 1; // 拉高延时1us
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
WS2812B_IN = 0; // 拉低延时0.3us
_nop_();_nop_();_nop_();_nop_();
}
// 发送0码
void WS2812B_SendZero(void) {
WS2812B_IN = 1; // 拉高延时0.3us
_nop_();_nop_();_nop_();_nop_();
WS2812B_IN = 0; // 拉低延时1us
_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
}
// 纯红光
void WS2812B_SendRed(void) {
unsigned char i;
WS2812B_SendReset();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
for (i = 0; i < 8; ++i)
WS2812B_SendOne();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
}
// 纯绿光
void WS2812B_SendGreen(void) {
unsigned char i;
WS2812B_SendReset();
for (i = 0; i < 8; ++i)
WS2812B_SendOne();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
}
// 纯蓝光
void WS2812B_SendBlue(void) {
unsigned char i;
WS2812B_SendReset();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
for (i = 0; i < 8; ++i)
WS2812B_SendZero();
for (i = 0; i < 8; ++i)
WS2812B_SendOne();
}
void main(void) {
P3_MODE_OUT_PP(GPIO_Pin_2); // 设置P32为推挽输出
while (1) {
WS2812B_SendRed();
delay_ms(1000);
WS2812B_SendBlue();
delay_ms(1000);
WS2812B_SendGreen();
delay_ms(1000);
}
}
我是让WS2812B循环更换红色蓝色绿色三种颜色的灯。
效果还行。
我有个大胆的想法,既然WS2812B可以用一根数据线来操控多个,那我直接做个可以调颜色的发光棒,主控芯片就用STC8G1K08A(成本0.3元),然后多放几个WS2812B上去……
过阵子看看实现一下。