多位(几个LED就是几位)通过引脚级联,接一个LED的DOUT引脚到另一个LED的DIN引脚,通过这种级联的方式,只需要使用一个IO口(单片机引脚)就能控制尽可能多的LED。
每个LED都集成了一颗驱动芯片在里面,让我们的LED变得智能和寻址,每一个内部都有恒流驱动,所以LED颜色非常一致,即使电压有小幅抖动,电压变化也是一样的。
不需要外部电阻——限流电阻,使LED灯的布局设计变得简单。
单线通信,能够最大限度的减少单片机IO口的压力,另外这款RGB灯使用了WS2812B驱动芯片,让外围电路只需要一颗电容就能够满足电路需求,从而最大可能的让电路变得简单优美。
注意: 800Kbps,相当于1.25us传输一比特数据。
引脚功能描述:
NO. | Symbol | 功能描述 |
---|---|---|
1 | VDD | LED的供电电源,Vdd 范围 +3.5~+5.3 V |
2 | DOUT | 控制信号数据输出引脚 |
3 | VSS | 地 |
4 | DIN | 控制信号数据输入引脚 |
除了灯珠以外只需要额外增加一个0.1uF的电容即可。
STM32F103RET6核心板 | WS2812B模块 |
---|---|
PA6 | DIN |
VCC | +5V |
GND | GND |
数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DOUT端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。
像素点采用自动整形转发技术,使得像素点的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
因为数据被内部锁存,所以只要不改变颜色值(模块持续供电),颜色是不会发生改变的,设置颜色的脉冲也不需要持续提供(单片机发生复位也无影响),只需要在修改颜色值的时候,发送一遍即可。
Treset:复位时间
由上图可知,我们要发送 '0' ,需要将GPIO引脚置高并持续0.4 us(400 ns),然后GPIO置低并持续0.85 us(850 ns),此过程即完成0 code的发送,具体代码实现如下:
void send_0(void)
{
IN_H;
Wait400ns;
IN_L;
Wait850ns;
}
我们要发送 '1' ,需要将GPIO引脚置高并持续0.85 us(850 ns),然后GPIO置低并持续0.4 us(400 ns),此过程即完成1 code的发送,具体代码实现如下:
void send_1(void)
{
IN_H;
Wait850ns;
IN_L;
Wait400ns;
}
所以本程序的难点即是求取400 ns 和 850 ns 相对精确的延时时间。
单片机里的延时函数一般通过执行一些无意义的循环进行延时,比如定义如下函数:
void delay(unsigned char i)
{
while(--i);
}
我们这里需要的延时周期很小,才1.25us,因为函数的调用,需要入栈和出栈,所以如果使用上面的延时函数的方式的话,那么一进一出就接近几百ns的时间就没了,所以为了精确控制,我们这里延时函数的定义如下:
#define Wait10nop {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
#define Wait250ns {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
#define Wait400ns {Wait250ns;Wait10nop;} //388
#define Wait850ns {Wait250ns;Wait10nop;Wait10nop;Wait10nop;Wait10nop;__NOP();__NOP();__NOP();__NOP();__NOP();} //860
我们在main函数中使用如下方式测试这个延时函数:
while(1)
{
IN_H;
//Wait250ns;
Wait850ns;
IN_L;
//Wait250ns;
Wait850ns;
}
然后用示波器观察与模块DIN引脚相连的GPIO输出的脉冲信号,查看其高电平是否与咱们预定义的一致,如果不一致,增加或减少空指令进行调整。
注意:一个 __NOP(); 空指令的耗时大约:1000/72 ≈ 14 ns 的时间,自己可以在上面定义的基础上,根据需要随意增加或者减少 __NOP(); 空指令的个数。
“注意空指令前面是两个“_”。
经过示波器测量,不断调整,上面定义的 Wait250ns 宏定义的耗时如下图所示。
经过示波器测试,上面的 Wait400ns 耗时为 388 ns , Wait850ns 耗时为 860 ns,满足上面"0"和"1"的时间区间范围。
注意: 数据传输顺序按GRB顺序传输,并且高位在前。
void ws2812_rgb(u8 ws_num,u8 ws_r,u8 ws_g,u8 ws_b)
{
ws_data[(ws_num-1)*3]=ws_g;
ws_data[(ws_num-1)*3+1]=ws_r;
ws_data[(ws_num-1)*3+2]=ws_b;
}
ws_data[] 数组用于记录待传输的RGB数据,每一个灯珠的颜色占用三个字节,因为数据传输顺序按GRB的顺序传输,所以赋值的时候注意先后顺序,上面函数是设置某一个灯珠的颜色值。
ws_data[] 数组中颜色值设置完毕之后,就要把这个数组的数据发送到模块中,具体的实现函数如下:
void ws2812_refresh(u8 ws_count)
{
u8 ws_ri=0;
for(;ws_ri<ws_count*3;ws_ri++)
{
if((ws_data[ws_ri]&0x80)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x40)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x20)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x10)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x08)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x04)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x02)==0) send_0(); else send_1();
if((ws_data[ws_ri]&0x01)==0) send_0(); else send_1();
}
//延时一段时间
ws2812_reset();
}
ws_data[] 数组中的每一个字节按位发送,因为高位在前,所以先发送每个字节的高位,获取最高位的值的方法为:ws_data[ws_ri]&0x80 。
N位的模块,一次就要发送 N * 3 字节的数据。
注意: 上图中D1的数据是通过单片机发送,D2,D3,D4通过像素内重塑放大传输。
main函数中,每隔1S,点亮一个LED,当8个LED都点亮一次之后,所有LED点亮一次,然后再开启下一次循环。
main函数的具体实现如下所示:
int main(void)
{
int times = 0;
//初始化
//延时函数初始化
delay_init();
uart_init(115200); //串口1:Debug,初始化为115200
ws2812_init();
printf("System Init OK ...\r\n");
while(1)
{
times++;
if(times > 8)
times = 0;
switch(times)
{
case 0:
ws2812_rgb(1, WS_RED);
ws2812_rgb(2, WS_GREEN);
ws2812_rgb(3, WS_BLUE);
ws2812_rgb(4, WS_WHITE);
ws2812_rgb(5, WS_PURPLE);
ws2812_rgb(6, WS_YELLOW);
ws2812_rgb(7, WS_BROWN);
ws2812_rgb(8, WS_BLUE);
ws2812_refresh(8);
break;
case 1:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(1, WS_RED);
ws2812_refresh(8);
break;
case 2:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(2, WS_GREEN);
ws2812_refresh(8);
break;
case 3:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(3, WS_BLUE);
ws2812_refresh(8);
break;
case 4:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(4, WS_WHITE);
ws2812_refresh(8);
break;
case 5:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(5, WS_PURPLE);
ws2812_refresh(8);
break;
case 6:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(6, WS_YELLOW);
ws2812_refresh(8);
break;
case 7:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(7, WS_BROWN);
ws2812_refresh(8);
break;
case 8:
memset(ws_data,0,WS_ARRAY_SIZE*sizeof(u8));
ws2812_rgb(8, WS_BLUE);
ws2812_refresh(8);
break;
}
delay_ms(1000);
}
}
颜色的RGB值和名称可以参考下面链接:
https://code.ziqiangxuetang.com/try/color.py
程序中颜色预定义如下:
#define WS_DARK 0,0,0
#define WS_WHITE 255,255,255
#define WS_RED 255,0,0
#define WS_GREEN 0,255,0
#define WS_BLUE 0,0,255
#define WS_YELLOW 255,255,0
#define WS_PURPLE 255,0,255
#define WS_CYAN 0,255,255
#define WS_BROWN 165,42,42
大家可以根据自己的喜欢,随意替换颜色。
—— The End ——