WS2812是一种基于数字信号传输的RGB LED灯珠,包含了三种颜色的LED和一个控制电路。电路构成中分为两个部分:数据输入和数据输出。
1. 数据输入:将控制器输出的数据信号通过串行通讯方式输入到WS2812内部的控制电路中。数据格式为24位二进制数,其中每8位表示一种颜色(红、绿、蓝),可以调节每种颜色的亮度和颜色值。
2.数据输出:根据接收到的数据信号,WS2812内部的控制电路会自动解码并控制三种颜色的LED发光,从而实现不同颜色和亮度的光效显示。具体来说,控制电路会将接收到的信号转换为PWM(脉冲宽度调制)信号,根据不同的PWM占空比来控制LED的亮度和颜色变化。
其构成的三色LED参数:
驱动时,每个LED占用3个字节,数据形式为:
每个 bit 都是通过(一个高电平 + 一个低电平)表示。
根据手册
1、一个短的(0.35 µs)高电平加一个长的(0.90 µs)低电平表示为“0”
2、一个长的(0.90 µs)高电平加一个短的(0.35 µs)低电平表示为“1”
3、单个 bit 信号周期, 高低电平时长合计为 1.25 µs
4、串接场合,发送超过 24 bit 信号后, 之前输入的信号会依次传递给串行的下一个 WS2812 LED
5、控制器发送数据前需要保持低电平超过 50 µs(又称为 RESET), 用于通知 WS2812 开始接收数据.
根据上面的信息, 对单颗LED发送数据, 需要的时间为
24×1.25µs+50µs=80µs24×1.25µs+50µs=80µs
对于8颗LED, 需要的时间为
8×24×1.25µs+50µs=290µs
事实上,这写时间不要求严格遵守,允许有一定的偏差。在此粗略地视为占空比信号。0码是1/4的。1码是3/4的。
当传输信号时, 高低电平时间间隔如果不符合手册要求, 差距较大时LED会不工作(不亮), 在间隔接近但是不完全满足时, LED会出现显示错乱, 色彩乱跳等。
WS2812的工作电压范围比较宽,3.5V ~ 5.3V。WS2812的数据传输速率较慢,在使用单片机驱动WS2812时要注意传输速率。
在实际应用中经常使用级联方式,将多个WS2812串接起来,制作成灯条、灯带等方式。
在级联方式下的数据传输:
如果以SPI方式驱动WS2812,根据计算,每个Bit位按照1.2微秒,把每个位量化为4个脉冲对应的基本单位数据,每个基本单位数据占用0.3微秒。
按照每个位占用4个基本单位方式处理,一个LED需要用3个字节,24位,也就是说一个LED要是用24 x 4 = 96个基本单位。这96个基本单位是8和16的整倍数,因此可以换算为SPI传输的字节数据。也就是说,一个LED用的控制数据3字节(24位),可以转变为SPI传输用的码数据12字节(96位)。
比如发绿色光时,按照亮度为16,则一个LED的数据:
码数据就是通过SPI实际发送的数据。这样控制好SPI发送的速度,保证码数据的时序与WS2812的时序一致,就可以保证LED按照我们的要求显示颜色。
为了配合WS2812的码位时序要求,码率为1/(2.4μS/Byte/2) = 833KHz。
根据STC32G12K128的用户手册,SPI的工作频率由SPI 控制寄存器(SPCTL)的B1和B0决定,
系统时钟的设置:
如果设置码率为800KHz的话,0码(1/4占空比): H=0.3125us L=0.9375us; 1码(3/4占空比): H=0.9375us L=0.3125us,刚好是可以用的。这样的话,SPI的速度 = 800KHz * 4 = 3.2MHz,那么根据系统设置,有以下选择:
后面就以25.6MHz主频的方式编程测试WS2812。
电路构成:
代码:
#define MAIN_Fosc25600000UL//定义主时钟 #include"STC32G.h" //sfr SPCTL = 0x85;SPI控制寄存器 // 7 6 5 4 3 2 1 0 Reset Value //SSIGSPENDORDMSTRCPOLCPHASPR1SPR00x00 #defineSSIG1//1: 忽略SS脚,由MSTR位决定主机还是从机0: SS脚用于决定主机还是从机。 #defineSPEN1//1: 允许SPI,0:禁止SPI,所有SPI管脚均为普通IO #defineDORD0//1:LSB先发,0:MSB先发 #defineMSTR1//1:设为主机0:设为从机 #defineCPOL0//1: 空闲时SCLK为高电平,0:空闲时SCLK为低电平 #defineCPHA1//1: 数据在SCLK前沿驱动,后沿采样.0: 数据在SCLK前沿采样,后沿驱动. #defineSPR10//SPR1,SPR0 00: fosc/4, 01: fosc/8 #defineSPR00// 10: fosc/16, 11: fosc/2 //DMA_SPI_CR SPI_DMA控制寄存器 #defineDMA_ENSPI1// SPI DMA功能使能控制位, bit7, 0:禁止SPI DMA功能, 1:允许SPI DMA功能。 #defineSPI_TRIG_M1// SPI DMA主机模式触发控制位,bit6, 0:写0无效, 1:写1开始SPI DMA主机模式操作。 #defineSPI_TRIG_S0// SPI DMA从机模式触发控制位,bit5, 0:写0无效, 1:写1开始SPI DMA从机模式操作。 #defineSPI_CLRFIFO1// 清除SPI DMA接收FIFO控制位,bit0, 0:写0无效, 1:写1复位FIFO指针。 //DMA_SPI_CFG SPI_DMA配置寄存器 #defineDMA_SPIIE0// SPI DMA中断使能控制位,bit7, 0:禁止SPI DMA中断, 1:允许中断。 #defineSPI_ACT_TX1// SPI DMA发送数据控制位,bit6, 0:禁止SPI DMA发送数据,主机只发时钟不发数据,从机也不发. 1:允许发送。 #defineSPI_ACT_RX0// SPI DMA接收数据控制位,bit5, 0:禁止SPI DMA接收数据,主机只发时钟不收数据,从机也不收. 1:允许接收。 #defineDMA_SPIIP0// SPI DMA中断优先级控制位,bit3~bit2, (最低)0~3(最高). #defineDMA_SPIBAP0// SPI DMA数据总线访问优先级控制位,bit1~bit0, (最低)0~3(最高). //DMA_SPI_CFG2 SPI_DMA配置寄存器2 #defineSPI_WRPSS0// SPI DMA过程中使能SS脚控制位,bit2, 0: SPI DMA传输过程不自动控制SS脚。 1:自动拉低SS脚。 #defineSPI_SSS 0// SPI DMA过程中自动控制SS脚选择位,bit1~bit0, 0: P1.2, 1:P2.2, 2: P7.4, 3:P3.5。 //DMA_SPI_STA SPI_DMA状态寄存器 #defineDMA_SPIIF0// SPI DMA中断请求标志位,bit0, 软件清0. #defineSPI_RXLOSS0// SPI DMA接收数据丢弃标志位,bit1, 软件清0. #defineSPI_TXOVW0// SPI DMA数据覆盖标志位,bit2, 软件清0. #defineLED_LD10//亮度,最大255 #defineLED_NUM6//LED灯个数 #defineSPI_NUM(LED_NUM*12)//LED灯对应SPI字节数 u8xdata led_RGB[LED_NUM][3];//LED对应的RGB,led_buff[i][0]-->绿,led_buff[i][1]-->红,led_buff[i][0]-->蓝. u8xdata led_SPI[SPI_NUM];//LED灯对应SPI字节数 u16SPI_TxIndex;//SPI DMA发送索引 /************* 外部函数和变量声明 *****************/ voidLoadSPI(void); void DMA_SPI_config(void); void delay_ms(u16 ms) { u16 i; do { i = MAIN_Fosc / 6000; while(--i); }while(--ms); } /*************** 主函数 *******************************/ void main(void) { u8xdata *px; WTST = 0; CKCON = 0; EAXFR = 1;//允许访问扩展寄存器 // SPI初始化函数,参数0=0: SPI切换到P1.2/P1.3/P1.4/P1.5 // 参数1=1: SPI速度=fosc/8 (24MHz主频,SPI速度为3MHz) HSCLKDIV = 0;//高速时钟不分频 SPCTL = (SSIG << 7) + (SPEN << 6) + (DORD << 5) + (MSTR << 4) + (CPOL << 3) + (CPHA << 2) + 0x01;//配置SPI P_SW1 = (P_SW1 & ~0x0c);//切换IO P1n_standard(0x38);//切换到 P5.4(SS) P1.3(MOSI) P1.4(MISO) P1.5(SCLK), 设置为准双向口 P5n_standard(0x10);//P5.4(SS) 设置为准双向口 P14 = 0;//MISO = 0, MOSI输出完毕保持低电平 P1n_push_pull(Pin3);//驱动WS2812将MOSI设置为推挽输出 delay_ms(10); px = &led_RGB[0][0];//亮度(颜色)首地址 // 第1个LED:红色 led_RGB[0][0] = 0; led_RGB[0][1] = LED_LD;//红色的显示亮度 led_RGB[0][2] = 0; // 第2个LED:绿色 led_RGB[1][0] = LED_LD;//绿色的显示亮度 led_RGB[1][1] = 0; led_RGB[1][2] = 0; // 第3个LED:蓝色 led_RGB[2][0] = 0; led_RGB[2][1] = 0; led_RGB[2][2] = LED_LD;//蓝色的显示亮度 // 第4个LED:蓝色 led_RGB[3][0] = 0; led_RGB[3][1] = 0; led_RGB[3][2] = LED_LD;//蓝色的显示亮度 // 第5个LED:绿色 led_RGB[4][0] = LED_LD;//绿色的显示亮度 led_RGB[4][1] = 0; led_RGB[4][2] = 0; // 第6个LED:红色 led_RGB[5][0] = 0; led_RGB[5][1] = LED_LD;//红色的显示亮度 led_RGB[5][2] = 0; //将颜色装载到SPI数据 LoadSPI(); //启动SPI DMA DMA_SPI_config(); while (1) { } } // 将颜色装载到SPI voidLoadSPI(void) { u8xdata *px; u16i,j; u8k; u8dat; for(i=0; i<SPI_NUM; i++) led_SPI[i] = 0; px = &led_RGB[0][0];//首地址 for(i=0, j=0; i<(LED_NUM*3); i++) { dat = *px; px++; for(k=0; k<4; k++) { if(dat & 0x80) led_SPI[j] = 0xE0;//数据1 : 111 00000 else led_SPI[j] = 0x80;//数据0 : 1000 0000 if(dat & 0x40) led_SPI[j] |= 0x0E;//数据1 else led_SPI[j] |= 0x08;//数据0 dat <<= 2; j++; } } } // 以DMA方式传输数据 void DMA_SPI_config(void) { u16 j; j = (u16)&led_SPI[0];//取首地址 DMA_SPI_TXAH = (u8)(j >> 8);//发送地址寄存器高字节 DMA_SPI_TXAL = (u8)j;//发送地址寄存器低字节 DMA_SPI_AMTH = (u8)((SPI_NUM-1)/256);//设置传输总字节数 = n+1 DMA_SPI_AMT = (u8)((SPI_NUM-1)%256);//设置传输总字节数 = n+1 DMA_SPI_STA = 0x00; DMA_SPI_CFG = (DMA_SPIIE << 7) + (SPI_ACT_TX << 6) + (SPI_ACT_RX << 5) + (DMA_SPIIP << 2) + DMA_SPIBAP; DMA_SPI_CFG2 = (SPI_WRPSS << 2) + SPI_SSS; DMA_SPI_CR = (DMA_ENSPI << 7) + (SPI_TRIG_M << 6) + (SPI_TRIG_S << 5) + SPI_CLRFIFO; }
测试效果:
实际测试时,发现主频=24MHz也没有问题,但改成12MHz时,颜色就不对了。证明了时序不对造成的颜色偏离。