硬件连接
WS2812B是一款全彩LED控制IC,单总线控制,可以级联。
单个灯珠连接将DIN脚连接至某一个IO口并接上电源即可
多个灯珠串联将第一个灯珠的DOUT脚连接至下一个灯珠的DIN脚
驱动原理
1.时序图
如图,由高低电平持续的不同时间代表0或者1,用一段长时间拉低表示reset,下图为对应的不同时长。
2.数据传输方式
如图,MCU将串联的GRB码传输进第一个灯珠,每个灯接收的是24bit数据(GRB颜色值),D1灯在收到24bit数据后,会把数据保存,如果还收到数据,会通过DO脚传给D2。
重点:高低电平的时间一定要控制在规格书的要求范围内
驱动代码
代码部分参考_祥子@这位老哥的文章
WS2812.h
typedef struct color{ //结构体定义
uint8_t R;
uint8_t G;
uint8_t B;
}Color_TypeDef;
#define LED_NUM 12 //定义灯珠数量,便于修改
#define WS2812B_High() HAL_GPIO_WritePin(RGB_GPIO_Port,RGB_Pin,GPIO_PIN_SET); //定义拉高拉低
#define WS2812B_Low() HAL_GPIO_WritePin(RGB_GPIO_Port,RGB_Pin,GPIO_PIN_RESET);
/*******************************************函数*********************************************/
void delay_320ns(void);
void delay_1000ns(void);
void MY_delay_us(uint16_t num);
void WS2812B_Reset(void);
void WS2812B_WriteByte(uint8_t dat);
void WS2812B_WriteColor(Color_TypeDef *pColor);
void Copy_Color(Color_TypeDef *pDst,Color_TypeDef *pScr);
void WS2812B_Refresh(void);
void WS2812B_Fillcolor(uint16_t start,uint16_t end,Color_TypeDef *pColor);
void WS2812B_Move(uint8_t dir);
WS2812.c
1.先写出发送0码1码的函数,使用了__nop();函数,一个__nop()对应一个机器周期,具体时长需要测量
void delay_320ns(void) //T0H & T1L 延时320ns
{
uint8_t i;
for(i=0;i<5;i++)
{
__nop();
}
}
void delay_1000ns(void) //T0L & T1H //延时1000ns
{
uint8_t i;
for(i=0;i<25;i++)
{
__nop();
}
}
void MY_delay_us(uint16_t num) //us级延时
{
uint8_t i;
while(num)
{
for(i=0;i<25;i++)
{
__nop();
}
num--;
}
}
测量延时时长的方法&函数
在main函数中调用这个函数,然后开启debug,轮流测试两个函数延时时长,不断更改循环次数直到时长满足时序要求。
void WS2812B_Test2(void)
{
WS2812B_Hi();
delay_320ns();
//delay_1000ns();
WS2812B_Low();
}
debug测试方法
在测试函数前后加上断点放在while内,进入debug后看右下角
如果是t0就右击选择t2或者t1。
在测试函数前的断点停下时再右键把数值清零,到结束的断点停下时显示的值就是延时函数实际的时间
2.写数据发送函数
void WS2812B_Reset(void) //reset函数
{
WS2812B_Low();
int i=60;
while(i--)
{
delay_1000ns();
}
}
void WS2812B_WriteByte(uint8_t dat) //写一个字节
{
uint8_t i;
for (i=0;i<8;i++)
{
//先发送高位
if (dat & 0x80) //1 //数据 &0x80 ,若高位为1则结果为1,否则为0
{
WS2812B_High();
delay_1000ns(); //T1H
WS2812B_Low();
delay_320ns(); //T1L
}
else //0
{
WS2812B_High();
delay_320ns(); //T0H
WS2812B_Low();
delay_1000ns(); //T0L
}
dat<<=1;
}
}
void WS2812B_WriteColor(Color_TypeDef *pColor) //发送颜色,通过分别发送三个颜色的字节完成
{
WS2812B_WriteByte(pColor->G);
WS2812B_WriteByte(pColor->R);
WS2812B_WriteByte(pColor->B);
}
void WS2812B_Refresh(void) //把最新的结构体数据刷新到灯上显示
{
uint8_t i;
for(i=0;i<LED_NUM;i++)
{
WS2812B_WriteColor(&WS2812B_Buf[i]);
}
}
3.灯珠颜色处理
(实现跑马灯/彩虹渐变)
//1.复制颜色
void Copy_Color(Color_TypeDef *pDst,Color_TypeDef *pScr) //把后一个灯的颜色给前一个灯珠
{
pDst->R = pScr->R;
pDst->G = pScr->G;
pDst->B = pScr->B;
}
//2.填充颜色(用于给一长串灯珠同一个颜色值)
void WS2812B_Fillcolor(uint16_t start,uint16_t end,Color_TypeDef *pColor) //始于,终于,颜色值
{
if(start>end)
{
uint16_t temp;
temp=start;
start=end;
end=temp;
}
if (start>=LED_NUM)return;
if(end>=LED_NUM)end=LED_NUM-1;
while(start<=end)
{
Copy_Color(&WS2812B_Buf[start],pColor);
start++;
}
}
//3.移动灯珠颜色
void WS2812B_Move(uint8_t dir) //dir为移动的个数,越大移动越多
{
Color_TypeDef temp;
uint8_t i;
if(dir)
{
Copy_Color(&temp,&WS2812B_Buf[LED_NUM-1]);
i = LED_NUM-1;
while(i)
{
Copy_Color(&WS2812B_Buf[i],&WS2812B_Buf[i-1]);
i--;
}
Copy_Color(&WS2812B_Buf[0],&temp);
}
else
{
Copy_Color(&temp,&WS2812B_Buf[0]);
i = 0;
while(i<(LED_NUM-1))
{
Copy_Color(&WS2812B_Buf[i],&WS2812B_Buf[i+1]);
i++;
}
Copy_Color(&WS2812B_Buf[LED_NUM-1],&temp);
}
}
main.c
定义
Color_TypeDef WS2812B_Buf[LED_NUM];
初始化
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
uint8_t i;
for(i=0;i<8;i++) //给前12个灯珠不同的GRB值,这里给的是R-G-B渐变
{
WS2812B_Buf[i].R=63-7*i;
WS2812B_Buf[i].G=7*i;
WS2812B_Buf[i].B=0;
HAL_Delay(1);
}
for(i=8;i<12;i++)
{
WS2812B_Buf[i].R=0;
WS2812B_Buf[i].G=127-7*i;
WS2812B_Buf[i].B=7*i-56;
HAL_Delay(1);
}
WS2812B_Refresh(); //由于上一步只更新了颜色值,并没有显示,所以加入refresh刷新显示
循环
while(1)
{
HAL_Delay(40); //延时用于调整速度
WS2812B_Move(5); //move函数控制移动个数
WS2812B_Refresh(); //刷新显示
}
效果展示
初始化完的灯效 (加上一块均光板或者喷过漆的亚克力板可以让光更均匀)
(随便捡了一块废板喷的漆)