一、简介
板上集成了8颗RGB彩灯,但只需用到一个GPIO口,这是如何做到的呢?这节我们就点亮WS2812B展开话题。WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同,每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路。
二、驱动原理
由以上原理图可知,各个RGB灯是通过级联连接在一起的,因此可以延伸到12颗、16颗。 通信协议:
数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。也就是说,第一颗灯珠将读取从MCU传输过来的所有数据,取其中24位作为自身数据,并将其余数据整形后向后传递,以此类推,直到数据传送完成。之后,MCU发送一个>=50us的低电平,ws281x芯片复位,重新开始第二次数据传输。
综上:这种通信方式大大节省了所需MCU的引脚,但是缺点也很明显。数据的0位和1位通过高电平和低电平的时间来控制,精确度在纳秒级别(0位和1位各占1.25us),对低速MCU极其不友好。 引脚定义:
VDD:电源正,范围+3.5V~+5.3V
VSS :电源地
DIN:输入输入端,电压范围-0.5V ~ VDD+0.5V
DOUT:数据输出端,电压范围-0.5V ~ VDD+0.5V 注意点:
如果使用的是3.3V的单片机,是不能直接驱动WS2812,需要接5V的上拉电阻,然后设置IO口为开漏输出模式,这样才能满足WS2812的电平协议。 时序分析
该通讯方式为单总线通信,即利用高低电平的持续时间来确定0和1,与我们之前学的DS18B20温度传感器类似。
根据上图,小结一下:在一个周期内,高电平和低电平的比例,确定了数据帧为0、还是为1。
下面的是数据帧的组成部分:
数据帧为1:高电平持续T1H,低电平持续T1L
数据帧为0:高电平持续T0H,低电平持续T0L
复位帧:电平持续时间大于280us
复位帧可以理解为一个结束符号,让灯带知道我们需要操作LED灯的个数,例如板上只需要操作8个灯珠,但是我们外接了12个灯珠,这时候我们在第8个指令后面加个复位帧,就可以只控制前面8个灯,而不会控制后面的灯,当然前提是灯珠首位相连。
通常情况下,我们设置一个通信周期为800khz,即1250ns。短的持续时间为300ns、长的持续时间为850ns。 数据格式
注意:数据传输顺序为GRB,先传输高位数据。
由此可举例如下:
假如有三颗WS2812B灯珠,
设置第0个灯珠的RGB颜色为[15,25,128]
设置第1个灯珠的RGB颜色为[10,0,255]
设置第2个灯珠的RGB颜色为[0,25,12]
则第0个灯珠的数据表示 (GRB):
G[0001 1001]R[0000 1111]B[1000 0000]
则第1个灯珠的数据表示 (GRB):
G[0000 0000]R[0000 1010]B[1111 1111]
则第2个灯珠的数据表示 (GRB):
G[0001 1001]R[0000 0000]B[0000 1100]
结合上述分析,每隔灯珠数据的发送顺序是:第0个灯珠数据,第1个灯珠数据,第2个灯珠数据,依次往下。控制引脚的电平变化 (忽略信号保持时间,0为高电平,1为低电平) 应该是:
[第0个灯珠数据GRB][第1个灯珠数据GRB][第2个灯珠数据GRB]
引脚状态从下面数据的左边第0位开始
[0001 1001 0000 1111 1000 0000 0000 0000 0000 1010 1111 1111 0001 1001 0000 0000 0000 1100] 驱动方式
目前关于WS2812B大概有三种驱动方法:
1)GPIO配合机器空周期直接驱动
2)PWM+DMA驱动
3)SPI+DMA驱动
参考例程中有使用到第二种驱动方式,这里简单介绍一下第二种驱动方式。我们知道每一位的特征周期为1.25us。这个间隔对应于800kHz的频率。此外,通过协议可得知,我们可以注意到唯一改变的是每个周期的高部分。它类似于一个脉冲宽度调制信号,可以做一个800Khz的PWM波去驱动。理论上说到这里灯就已经可以亮起来了,但如果引入DMA,数据传输交给DMA控制器来完成。数据传输不占用CPU,会使程序运行更高效。由于这里的灯珠不是很多,因此参考程序中没采用DMA进行数据传输。如若用到多节WS2812B灯带,采用DMA进行数据传输是非常有必要的。
三、工程源码
#include "me32g070.h"
#include "me32g070_ioconfig.h"
#include "me32g070_gpio.h"
#include "me32g070_sys.h"
#include "me32g070_timer.h"
#define MAX_LIGHT_NUMBER 12
__align(4) uint8_t org_rgb[3]={0xFF,0x0,0x0};
__align(4) uint8_t rgb[MAX_LIGHT_NUMBER][3];
uint32_t * rgbptr;
uint32_t * rgbendptr;
uint8_t start_color=0;
uint32_t data_index=0;
int main(void)
{
SystemInit ();
PB15_INIT(PB15_BTIM0_MAT0);
PC12_INIT(PC12_BTIM0_MAT0);
BTIM_Init(BTIM0, 16000000);
BTIM_ConfigPWMDataSift(BTIM0, 20, 5, 20, 15);
NVIC_EnableIRQ(BTIM0_IRQn);
BTIM_Init(BTIM1, 10000);
BTIM_ConfigMatch1(BTIM1, 1000, TIM_MATCH_RESET_COUNTER|TIM_MATCH_TRIGGER_INT);
NVIC_EnableIRQ(BTIM1_IRQn);
TIM_START(BTIM1);
PB->DIR_b.DIR10 =0x1;
PC->DIR_b.DIR4 =0x1;
while(1)
{
SYS_Delay(0x4FFFF);
}
}
void BTIM0_IRQHandler(void)
{
rgbptr=BTIM_DataSiftInt(BTIM0, rgbptr, rgbendptr);
BTIM_ClearIntFlag(BTIM0);
}
void BTIM1_IRQHandler(void)
{
uint32_t i;
switch (start_color)
{
case 0:
org_rgb[0]=org_rgb[0]-51;
org_rgb[1]=org_rgb[1]+51;
if (org_rgb[1]==0xFF)
start_color=1;
break;
case 1:
org_rgb[1]=org_rgb[1]-51;
org_rgb[2]=org_rgb[2]+51;
if (org_rgb[2]==0xFF)
start_color=2;
break;
case 2:
org_rgb[2]=org_rgb[2]-51;
org_rgb[0]=org_rgb[0]+51;
if (org_rgb[0]==0xFF)
start_color=0;
break;
}
for (i=MAX_LIGHT_NUMBER-1;i>0;i--)
{
rgb[i][0]=rgb[i-1][0];
rgb[i][1]=rgb[i-1][1];
rgb[i][2]=rgb[i-1][2];
}
rgb[0][0]=org_rgb[0];
rgb[0][1]=org_rgb[1];
rgb[0][2]=org_rgb[2];
rgbptr=(uint32_t *)rgb;
rgbendptr=rgbptr+((MAX_LIGHT_NUMBER*3)>>2);
if ((MAX_LIGHT_NUMBER*3)&0x3)
rgbendptr++;
rgbptr=BTIM_StartDataSift(BTIM0, rgbptr, rgbendptr);
BTIM_ClearIntFlag(BTIM1);
}
四、驱动效果
正如下面视频
驱动效果
|