基本原理
根据WS2812B的datasheet,这个灯组的控制方式是单IO控制,以高电平的时间不同来判定当前的数据是“1”还是“0”。如下图所示:
从图中可以看出0 code对应一个高电平400ns低电平850ns的脉冲,而1 code对应一个高电平800ns,低电平450ns的脉冲,无论0 code还是1 code的脉冲周期都是1250ns。还有一个reset电平是一个大于50us的低电平表示reset。
显示1个LED灯珠的数据需要24位,GRB3种颜色分别占8位,假设现在有3个灯珠,每个灯需要显示红色也就是R=0xFF需要发送如下的数据:
RESET电平 -> LED1(G(00),R(FF),B(00)) -> LED2(G(00),R(FF),B(00)) -> LED3(G(00),R(FF),B(00))
基本思路
第一步是实现1 code跟0 code的脉冲,这里用IO口反转的方式实现,使用nop命令达到ns的延时,通过写BSRR跟BRR来实现输出高低电平,具体实现如下:
1 code:
#define WS_WRITE_ONE GPIOB->BSRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();\
GPIOB->BRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop()
逻辑分析仪抓波形:
1 code的波形:高电平750ns,低电平417ns,根据数据手册高电平800ns±150ns,低电平450ns±150ns在范围之内
0 code:
#define WS_WRITE_ZERO GPIOB->BSRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop(); \
GPIOB->BRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop()
逻辑分析仪抓波形:
0 code的波形:高电平417ns,低电平792ns,根据数据手册高电平400ns±150ns,低电平850ns±150ns在范围之内
对于RESET信号,直接通过for循环完成:
void RGB_LED_Reset(void)
{
GPIOB->BRR = 4;
for(int i=0;i<480;i++)
{
__nop();__nop();__nop();__nop();
}
}
代码实现
以一个灯为例,一个灯需要24个0 code或者1 code的脉冲加上reset信号的低电平组成,首先定义一个24*LED_NUM大小的数组用来存放灯的脉冲数据:
#define LED_NUM 1
uint8_t io_data[24*LED_NUM]={0};
其次需要将传入的color值分解以下,传入的color是RGB格式的,由于ws2812b传输格式是GRB所以先处理G数据:
for(i=15;i>=8;i--)//G
{
(((color>>i)&0x01)==1)?(io_data[index*24+(15-i)]=1):(io_data[index*24+(15-i)]=0);
}
G的数据是从color的第15bit开始到8bit结束,每次获取到bit需要判断是不是1,如果是1就需要在io_data的相应位置填入1,否则填入0.
用相同的方式处理R,B的数据:
for(i=23;i>=16;i--)//R
{
(((color>>i)&0x01)==1)?(io_data[index*24+(31-i)]=1):(io_data[index*24+(31-i)]=0);
}
for(i=7;i>=0;i--)//B
{
(((color>>i)&0x01)==1)?(io_data[index*24+(23-i)]=1):(io_data[index*24+(23-i)]=0);
}
使用一个函数循环发送出去:
void led_show(void)
{
RGB_LED_Reset();
for(int i=0;i<LED_NUM*24;i++)
{
if(io_data[i])
{
WS_WRITE_ONE;
}
else
{
WS_WRITE_ZERO;
}
}
}
总结
优点:
- 使用单IO口,不用选择外设。
- 程序简单,只需要填充所有显示的数据之后发送出去就行
缺点: - 有一定的硬延时会对系统造成影响。
针对这个缺点,用逻辑分析仪抓取波形观察:
这个抓的波形是显示256个灯的时候,程序中的延时给的是10ms,但实际抓出来的是17.9ms,通过计算256241.25=7680us差不多是,跟抓到的波形符合。所以这种方式不能用在需要时间很准的地方。如果显示的灯数量不多可以忽略这个问题。
关键源码
#define WS_WRITE_ONE GPIOB->BSRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();\
GPIOB->BRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop()
#define WS_WRITE_ZERO GPIOB->BSRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop(); \
GPIOB->BRR = 4; \
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();\
__nop();__nop()
#define LED_NUM 1
uint8_t io_data[24*LED_NUM]={0};
void RGB_LED_Reset(void)
{
GPIOB->BRR = 4;
for(int i=0;i<480;i++)
{
__nop();__nop();__nop();__nop();
}
}
void led_show(void)
{
HAL_SuspendTick();
RGB_LED_Reset();
for(int i=0;i<LED_NUM*24;i++)
{
if(io_data[i])
{
WS_WRITE_ONE;
}
else
{
WS_WRITE_ZERO;
}
}
HAL_ResumeTick();
}
void show_dot(unsigned int index,unsigned int color)
{
int i=0;
for(i=15;i>=8;i--)//G
{
(((color>>i)&0x01)==1)?(io_data[index*24+(15-i)]=1):(io_data[index*24+(15-i)]=0);
}
for(i=23;i>=16;i--)//R
{
(((color>>i)&0x01)==1)?(io_data[index*24+(31-i)]=1):(io_data[index*24+(31-i)]=0);
}
for(i=7;i>=0;i--)//B
{
(((color>>i)&0x01)==1)?(io_data[index*24+(23-i)]=1):(io_data[index*24+(23-i)]=0);
}
led_show();
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
show_dot(0,0x00000f);
HAL_Delay(500);
}
}