这款LED灯最神奇的地方是只有一条控制线,即一条IO它可以控制一个和多个灯的亮灭和RGB颜色,当我第一次接触到这个时,我质疑了二进制的状态。直到看完协议,发现是时间顺序上的串行,我才豁然开朗。
多位(几个LED也就是几个)通过引脚级联,一个接一个LED的DOUT引脚到另一个LED的DIN引脚,通过这种级联,只需要使用一个IO口(单片机引脚)可以尽可能多地控制LED。
每个LED里面集成了驱动芯片,让我们LED变得智能和寻址,每个内部都有恒流驱动,所以LED颜色非常一致,即使电压有轻微的抖动,电压变化也是如此。
不需要外部电阻-限流电阻LED灯的布局设计变得简单。
单线通信可以最大限度地减少单片机IO口压,另外这个RGB灯使用了WS2812B(WS2812C跟B的01码的高低电平持续时间是一样的)驱动芯片,让外围电路能够满足电路的需求,使电路最大限度地简单美观。
特点
-
智能反接保护,电源反接不会损坏IC;
-
IC控制电路与LED点光源公用一个电源;
-
控制电路与RGB芯片集成在5050包装元件中,形成外部控制像素点;
-
内置信号整形电路,任何像素点收到信号后通过波形整形输出,确保线路波形畸变不会累积;
-
内置上电复位和掉电复位电路;
-
每个像素点的三基色可显示256亮度,1677216种颜色的全真色可显示,扫描频率不低于400Hz;
-
串行级联接口可以通过信号线接收和解码数据;
-
当任何两点传输距离不超过5米时,无需增加额外电路;
-
当刷新率为30帧/秒时,级联数不小于1024点;
-
数据发送速度可达8000Kbps;
-
光的颜色高度一致,性价比高。
800Kbps,相当于1.25us传输一比特数据。
引脚图
引脚功能描述:
1 | VDD | LED,Vdd 范围 3.5~ 5.3 V |
2 | DOUT | 控制信号数据输出 |
3 | VSS | 地 |
4 | DIN | 引脚控制信号数据输入 |
典型电路
串联方法
原理图
除了灯珠,只需要额外添加0.1uF电容。
硬件连接
PA15(可以选择任何一个GPIO,即使是PA15作为JTAG口也可以重用IO) | DIN |
VCC | 5V |
GND | GND |
驱动原理
数据协议采用单线归零码的通信方式,上电复位后像素点,DIN端接收从控制器传输的数据,首先发送的24bit数据从第一个像素点提取后,发送到像素点内部的数据锁器。剩余数据通过内部整形电路整形放大DOUT端口开始向下一级联的像素点转发输出,每次传输一个像素点,信号减少24bit。
采用自动整形转发技术,使像素点的级联数不受信号传输的限制,仅限于信号传输速度的要求。
由于数据被内部锁定,只要颜色值不改变(模块连续供电),颜色就不会改变,设置颜色的脉冲也不需要连续提供(单片机复位无影响),只需在修改颜色值时再次发送。
0和1的区分
Treset:复位时间
从上图可以看出,我们必须发送它 '' ,需要将GPIO引脚位高并持续0.4 us(400 ns),然后GPIO置低并持续0.85 us(850 ns),这个过程就完成了发送。如何产生这个ns水平延迟,常用的方法是使用nop空指令:
#define WS_TIMES 2 //与您的芯片主频有关,HC32F460主频168M使用 void delay_250ns(void) { u8 del_t=WS_TIMES; while(del_t--) __NOP(); }
然后用示波器观察和模块DIN引脚相连的GPIO输出脉冲信号,检查其高电平是否与我们的预定义一致,并增加或减少空指令进行调整。
经过示波器大约是这么长的时间。
////发送0和1函数 void send_0(void) { TH; delay_250ns(); TL; delay_250ns(); delay_250ns(); delay_250ns(); } void send_1(void) { TH; delay_250ns(); delay_250ns(); delay_250ns(); TL; delay_250ns(); delay_250ns(); delay_250ns(); }
实际调试的时候需要根据实际情况调整。
24 bit数据的组成
按数据传输顺序GRB顺序传输,高位在前。
voidws2812_rgb(u8ws_num,u8ws_r,u8ws_g,u8ws_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; }
数组用于记录待传输的数组RGB由于数据传输顺序按照三个字节,每个灯珠的颜色占用数据GRB顺序传输,所以赋值时要注意顺序,上面的函数是设置一个灯珠的颜色值。
设置数组中的颜色值后,将数组的数据发送到模块中,具体实现函数如下:
voidws2812_refresh(u8ws_count) { u8ws_ri=0; for(;ws_ri<ws_count*3;ws_ri ) { if((ws_data[ws_ri]&0x80)==0)send_0();elsesend_1(); if((ws_data[ws_ri]&0x40)==0)send_0();elsesend_1(); if((ws_data[ws_ri]&0x20)==0)send_0();elsesend_1(); if((ws_data[ws_ri]&0x10)==0)send_0();elsesend_1(); &bsp; 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();
}
数组中的每一个字节按位发送,因为高位在前,所以先发送每个字节的高位,获取最高位的值的方法为: 。
数据传输方法
N位的模块,一次就要发送字节的数据。
上图中D1的数据是通过单片机发送,D2,D3,D4通过像素内重塑放大传输。
主函数就是点灯逻辑了。上传参考的代码:
#ifndef ws2812_h__
#define ws2812_h__
#include "sys.h"
//用户修改处
#define WS_TIMES 6 //和你的芯片主频有关,stm32f407用6,自己多试下,或者有条件的用示波器看下
#define TH PEout(4)=1;
#define TL PEout(4)=0;
//RGB--定义了几个标准的rgb值
#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_YELLO 255,255,0
#define WS_PURPLE 255,0,255
#define WS_CYAN 0,255,255
//extern u8 ws_data[];如果你需要在其他地方用到这个数组,就不注释
void ws2812_init(void);
void ws2812_rgb(u8 ws_i,u8 ws_r,u8 ws_g,u8 ws_b);
void ws2812_rgb_all(u8 ws_i,u8 ws_r,u8 ws_g,u8 ws_b);
void ws2812_refresh(u8 ws_i);
void ws2812_reset(u8 ws_set);
void delay_250ns(void);
void send_0(void);
void send_1(void);
void send_res(void);
#endif //ws2812_h__
#include "ws2812.h"
//用法示例,先保存到数组,用 ws2812_rgb函数,然后用ws2812_refresh函数发送
// 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_YELLO);
// ws2812_rgb(7, WS_DARK);
// ws2812_rgb(8, WS_BLUE);
// ws2812_refresh(8);
void ws2812_init(void)//PE4
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_ResetBits(GPIOE,GPIO_Pin_4);
}
u8 ws_data[200]={0};
void ws2812_rgb(u8 ws_i,u8 ws_r,u8 ws_g,u8 ws_b)
{
ws_data[(ws_i-1)*3]=ws_g;
ws_data[(ws_i-1)*3+1]=ws_r;
ws_data[(ws_i-1)*3+2]=ws_b;
}
void ws2812_rgb_all(u8 ws_i,u8 ws_r,u8 ws_g,u8 ws_b)
{
static u8 rgb_wsi;
for(rgb_wsi=1;rgb_wsi<=ws_i;rgb_wsi++)
{
ws_data[(rgb_wsi-1)*3]=ws_g;
ws_data[(rgb_wsi-1)*3+1]=ws_r;
ws_data[(rgb_wsi-1)*3+2]=ws_b;
}
}
void ws2812_refresh(u8 ws_i)
{
u8 ws_ri=0;
for(;ws_ri<ws_i*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();
}
send_res();
}
void ws2812_reset(u8 ws_set)
{
for(int i=0;i<200;i++)
{
ws_data[i]=ws_set;
}
}
void delay_250ns(void)
{
u8 del_t=WS_TIMES;
while(del_t--)
__NOP();
}
void send_0(void)
{
TH;
delay_250ns();
TL;
delay_250ns();
delay_250ns();
delay_250ns();
delay_250ns();
}
void send_1(void)
{
TH;
delay_250ns();
delay_250ns();
delay_250ns();
delay_250ns();
TL;
delay_250ns();
}
void send_res(void)
{
TL;
delay_us(300);
}
最后感谢B站的视频教程,感谢网友们的分享。
收益最大的是这个:一个IO控制很多个LED,这个技能你get到了吗