高品质WS2812B LED灯带
开发WS2812B灯带笔记
目录
什么是WS2812B灯
WS2812B是一种智能控制LED光源,将控制电路和RGB芯片集成在5050个元件包中。其内部包括智能数字端口数据锁存器和信号重塑放大驱动电路。还包括精密内部振荡器和电压可编程恒流控制部分,有效地确保像素点光色高度一致。俗称IC灯。
WS2812B灯带连接原理图
主要通过一条数据线来控制每个灯的颜色 ,每个灯珠控制着下个灯珠。
WS2812B灯带的数据和数据传输是啥样的?
数据传输协议采用单一的NZR通信模式。在像素通电复位后,DIN端口从控制器接收数据,第一个像素收集初始24位数据,然后发送到内部数据锁存器,其他数据通过内部信号重塑放大电路的重塑,通过DO端口发送到下一个级联像素。在对每个像素进行传输后,该信号减少24位。像素采用自动整形传输技术,使像素级联数不限制信号的传输,只依赖于信号传输的速度。
通俗点讲,就是每个灯接受24位的数据为每个灯的颜色像素(RGB888)。比如,你发送了3组24位的数据,第一个灯珠接收了第一组24位数据后,会把后面的两组数据发给第二个灯珠,第二个灯珠接受了第二组24位数据后,会把第三组数据发给第三个灯珠,以此类推。
灯珠接受的24位数据按图里来看,是8位绿色,8位红色,8位蓝色组成。
补充:什么是RGB?
RGB颜色空间以R(Red:红)、G(Green:绿)、B(Blue:蓝)三种基本色为基础,进行不同程度的叠加,产生丰富而广泛的颜色,所以俗称三基色模式。
RGB空间是生活中最常用的一个颜色显示模型,电视机、电脑的CRT显示器等大部分都是采用这种模型。自然界中的任何一种颜色都可以由红、绿、蓝三种色光混合而成,现实生活中人们见到的颜色大多是混合而成的色彩。
肉眼可以识别世界上的所有颜色,而RGB几乎可以组合成世界上所有的颜色!
以我们所用的RGB24为例,RGB24的每个元素在计算机内存中占用1个字节,1个字节等于8个bit位,所以RGB每个元素的取值范围为:0~255。
那么三色组合起来256x256x256=16777216(一千六百七十七万七千二百一十六)种颜色。
那么WS2812B灯的24bit数据如何构造?
既然是24bit数据代表三种颜色,我们就要首先知道一个bit的意义是什么,我们传统意义上来说1个bit代表一个数据位,但是对于数据位bit的理解好像就是“1”或者“0”在数电里我们很容易把高低电平跟逻辑1和逻辑0对应起来,但是表示灯珠的逻辑电平不是简单的高低电平。在数值上0xFFFFFF就是24bit的1,0×000000就是24bit的0.这里有8个bit代表颜色G分量,G7G6G5G4G3G2G1G0,有8个bit代表R分量R7R6R5R4R3R2R1R0,有8个bit代表B分量B7B6B5B4B3B2B1B0,当不同分量组合时候就会有不同的数据产生,这个数据背后其实是逻辑电平。
但是我们IC灯的逻辑“1”并不是简简单单的高电平,IC灯的逻辑“0”也不是简简单单的低电平。
WS2812B灯的逻辑“1”和逻辑“0”
从上图可知,逻辑“1”和逻辑“0”并不是普通的高电平,低电平。而是由高低电平不同比例组成的。
逻辑“0”:由小部分的高电平和大部分的低电平组成
逻辑“1”:由大部分的高电平和小部分的低电平组成
下面讲讲这大部分和小部分的精确取值:
逻辑“0”:由220ns-380ns的高电平和580ns-1us的低电平组成
逻辑“1”:由由220ns-380ns的低电平和580ns-1us的高电平组成
代码部分
到这我们差不多就知道了WS2812B灯的原理和时序了,下面讲讲编程的部分。
实现ns级别延时
- 硬件延时NOP实现
- 滴答定时器中断实现
- 普通定时器实现
首先因为对时序要求比较严格,所以需要实现ns级别的延时。
我用的板子是gd32f450,系统频率是200mhz。
1/200Mhz = 0.005 ms = 5 ns
所以一个nop的延时是5ns。
#define DELAY_5ns __NOP() /* 一个nup为5ns 1/200Mhz = 0.005 ms = 5 ns*/
#define DELAY_50ns DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns;DELAY_5ns
#define DELAY_250ns DELAY_50ns;DELAY_50ns;DELAY_50ns;DELAY_50ns;DELAY_50ns
#define DELAY_300ns DELAY_50ns;DELAY_50ns;DELAY_50ns;DELAY_50ns;DELAY_50ns;DELAY_50ns
#define DELAY_600ns DELAY_300ns;DELAY_300ns
编写逻辑“0”和逻辑“1”的函数
void Bit_0(void)
{
rt_pin_write(IC_LED_GREEN_REG,1);
DELAY_300ns;
rt_pin_write(IC_LED_GREEN_REG,0);
DELAY_600ns;
}
void Bit_1(void)
{
rt_pin_write(IC_LED_GREEN_REG,1);
DELAY_600ns;
rt_pin_write(IC_LED_GREEN_REG,0);
DELAY_300ns;
}
最简单的驱动函数
void RGB_Set(void)
{
for(uint8_t i = 0;i<8;i++)
{
Bit_1();
}
for(uint8_t i = 0;i<8;i++)
{
Bit_0();
}
for(uint8_t i = 0;i<8;i++)
{
Bit_0();
}
}
这样就生成了24bit的数据,111111110000000000000000,绿灯
但是这样的操作方法显然不适合在项目里使用,所以我们要写一些函数来设置
比如我们一般是按RGB值来设定灯的颜色,我们输入RGB三颜色的值就可以。但是这样的话又会又一个问题,我们输入的是10进制的值,0-255。但是我们所需要的却是需要把这个值换成2进制,因此,需要根据这些来编写代码
首先需要控制函数,以后项目需要控制IC灯只需要调用这个函数输入相对应的RGB值就行了,
int ic_led_ctrl(int r_val, int g_val, int b_val)
{
decimal_to_binary(r_val,g_val,b_val);
return 0;
}
接着我们要实现把10进制的RGB值转化为2进制的RGB值
/* 连接数组 */
int connection_arr(int *r,int *g,int *b)
{
int i = 1;
int *rgb = rgb_arr;
while (i <= 8)
{
*rgb = *g;
rgb++;
g++;
i++;
}
while (i <= 16)
{
*rgb = *r;
rgb++;
r++;
i++;
}
while (i <= 24)
{
*rgb = *b;
rgb++;
b++;
i++;
}
return 0;
}
/* 反转数组 */
int reverse(int *x) {
int* p, temp, * i, * j;
int m = (8 - 1) / 2;
i = x; /* 数组首元素地址 */
j = x + 8 - 1; /* 数组末尾元素地址 */
p = x + m; /* 中间元素地址 */
for (i = x; i <= p; ++i, --j) {
temp = *i;
*i = *j;
*j = temp;
}
return 0;
}
/* 十进制转换成二进制 */
static void decimal_to_binary(int r_num,int g_num, int b_num)
{
int r_arr[8] = {0}; /* 用一个长度为32的数组接收最后的二进制数,里面的0是为数组设置初始值 */
int g_arr[8] = {0};
int b_arr[8] = {0};
int index = 0;/* 用来定义数组的下标 */
r_arr[index] = r_num % 2; /* 先获取输入数字的第一个二进制位 */
g_arr[index] = g_num % 2;
b_arr[index] = b_num % 2;
index++; /* 下标下移一位 */
while (1) {/* 使用while循环来获取剩下的二进制位 */
/*十进制转换为二进制思想:
1.对十进制数进行除以2的操作
2.对所获得的商进行取余的操作,所获得的数就是二进制每一位
3.当num<2时,即num除以二不会再有大于1的数字,num%2必为0时
此时说明num已经完全转换位二进制数,则跳出循环*/
r_num = r_num / 2;
g_num = g_num / 2;
b_num = b_num / 2;
r_arr[index] = r_num % 2;
g_arr[index] = g_num % 2;
b_arr[index] = b_num % 2;
index++;
if (g_num < 2 && r_num <2 && b_num < 2){
break;
}
}
reverse(r_arr);
reverse(g_arr);
reverse(b_arr);
connection_arr(r_arr,g_arr,b_arr);
}
当做完上面这些,我们只需要在主函数中写入RGB的值就能实现灯带的点亮了。
void RGB_Set(void)
{
int i = 0;
for(i = 0; i < 24; ++i) {
if(rgb_arr[i] == 1)
{
Bit_1();
} else {
Bit_0();
}
}
}