您尚未登录。

楼主 # 2022-01-22 16:28:15

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

[记录]使用SPI驱动WS2811

市面上常见的2款串行可编程控制LED方案,分别是WS2811和WS2812。
    其中,

    [1]WS2811是芯片,常规供电为12V,按理说,驱动距离会远一些。

    [2]WS2812是灯珠,把可编程芯片内嵌在灯珠里面,常规供电5V,按理说,驱动距离相对短。

   

    相信很多朋友都在51单片机上面驱动过这个芯片,使用方法是:IO模拟单总线时序,达到传输RGB数据的效果。

    因为WS2811/2812驱动时序,是us级的,IO模拟的方法,过度依赖CPU,这种做法实在不合理。

    正确做法应该是借助片上的通讯外设,如UART/SPI等,兼容WS2811/2812的时序,腾出CPU资源,并且提高灯带驱动的稳定性。



    本文章阐述,使用SPI驱动WS2811的思路。

    先看手册:   

    ![image-20220122160847782](image-20220122160847782.png)
        image-20220122160847782.png

    ![image-20220122160959434](image-20220122160959434.png)
        image-20220122160959434.png

    从WS2811的手册,可以得知( 高速模式下 ):

    [1]这款芯片支持2种驱动速度:分别为高速模式和低速模式。

    [2]对单总线送'0'码和'1'码,需要使用T0H / T1H / T0L /T1L搭配。

    [3]搭配出来的'0'码和'1'码所占用时长皆为 2.5us。

    [4]T0H = T1L = 0.5us

    [5]T1H = T0L =2.0us = 4 * T0H = 4 * T1L

   

    因此,SPI总线的驱动时钟,一个时钟脉冲的周期不能大于 T0H / T1L ,也就是不能大于0.5us。

    我们试着就让SPI总线一个时钟脉冲的周期就等于0.5us,Tspiclk = 0.5us。

    那么,频率Fspiclk = 1 / Tspiclk  = 2MHz。

    这种情况下,如果想要送RGB888数据中的 1 位 数据 到WS2811,SPI总线需要发出5个位的数据:

○  1 : 00001 

○  0 : 11110

    如果要发1字节数据(8个位),如R = 0xF0,对应二进制为:

	1111 0000

    那么SPI应该发出这样的数据:

 	00001 00001 00001 00001 11110 11110 11110 11110

    对其进行组合,可以得到5个字节:

    0000 1000 0100 0010 0001 1111 0111 1011 1101 1110

    所以,SPI应该发送的数据为:

	0X08 		0X42		0X1F		0X7B		0XDE

    同理,对于R 、G、 B都可以这样操作。

    那么,也就是说,想要给一颗WS2811输送RGB888数据,需要发送5*3 = 15个字节

最近编辑记录 LinjieGuo (2022-01-22 16:30:37)

离线

楼主 #1 2022-03-07 14:13:06

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

时隔多日,回来续帖。
    前面知道了原理,接下来,我们得生成一个color表。
    color表的结构应该是这样:
   

uint8_t ws2811_color[256][5]={......} 

    其中,256对应一中颜色分量的颜色深浅,因为RGB各占8bit。
    以下是我针对1楼的时序,生成的color表:
    color_map_ws2811.c
    color_map_ws2811.txt
    下一步是借助color_map_ws2811.c文件,编写CH579M的驱动代码!

最近编辑记录 LinjieGuo (2022-03-07 14:15:59)

离线

#2 2022-03-09 23:16:09

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

离线

楼主 #3 2022-03-10 08:39:29

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

xboot 说:

哈哈,大哥果然神速。
我昨天调试了半天ws2811。
怎么搞都不行,后面才发现。12V没接......

离线

楼主 #4 2022-03-10 09:02:39

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

xboot 说:

另外,可以把dma怼上去。
我是这样搞:

#define CHIP_NUM  100
typedef strut{
  uint8_t rst[200];    //0.5us *200 = 100us 
  uint8_t frame[ CHIP_NUM ][3][5];
  //3 代表颜色分量,R / G / B
  //5 颜色数据,5字节SPI数据描述一个分量的颜色深浅
}ws28xx_t,ws28xx_pt;

然后定时使用DMA将帧内存通过SPI发送出去就可以了。

最近编辑记录 LinjieGuo (2022-03-10 09:04:32)

离线

#5 2022-03-10 09:06:17

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

WS2812,WS2812B这两灯珠之间有啥区别,看TB卖的基本都是带B的。

离线

#6 2022-03-10 09:09:58

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

ws2812x.png
搜了一个总结,用2M做SPI CLK,这个能算通用时序吗?

离线

#7 2022-03-10 09:20:59

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

按这个总结的表格,最优的兼容性最好的,应该是T0H = 0.25us,T1H = 1us,T0L = 1us,T1L = 0.25us,两者间4倍关系,也就是说,上面的5bit表格无需改变,仅需将SPI的clk由原先的2M,换乘4M,就可以更通用了,当然这仅仅是分析,没有实际测试过。

离线

楼主 #8 2022-03-10 09:25:55

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

对于上面我使用的WS2811的情况,2M SPI CLK,
情况如下:
(1)T0H = T1L = 0.5us = 500ns
(2)T1H = T0L =2.0us = 4 * T0H = 4 * T1L
看了你的WS2812手册里面给了个范围,
我们以WS2812B为准,将SPI CLK提升到4M。
那么,周期变成一半。
(1)T0H = T1L = 0.25us = 250ns
(2)T1H = T0L =1.0us = 1000ns =4 * T0H = 4 * T1L

观察了一下,完全符合WS2812B的时序要求。
(WS2812的数据传输速度是800KHz,WS2811(400KHz)的2倍)
所以,你至少使用4M的SPI CLK,才能驱动WS2812。

最近编辑记录 LinjieGuo (2022-03-10 09:26:48)

离线

楼主 #9 2022-03-10 09:47:48

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

@xboot
分析的没错。
还有个地方需要注意。
那就是复位时序的时间长短,
有很大区别。
WS2812B需要280us以上
WS2811 反而只需50us以上。
(可能是WS2812B的传输数据速度快,为了确保稳定性,把复位时长提高?)

最近编辑记录 LinjieGuo (2022-03-10 09:49:24)

离线

#10 2022-03-10 10:07:47

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

是的,有这个可能,更长时间的复位,可以提升传输可靠性。

离线

#11 2022-03-10 20:22:11

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

更新了下WS2812的驱动,添加缓存支持,提升刷新效率。

26a11c1820cdb4e202a99876c37971684c4634e5
 src/driver/led/ledstrip-ws2812.c | 37 ++++++++++++++++++++-----------------
 1 file changed, 20 insertions(+), 17 deletions(-)

diff --git a/src/driver/led/ledstrip-ws2812.c b/src/driver/led/ledstrip-ws2812.c
index 6f598bb3e..bc0736ef4 100644
--- a/src/driver/led/ledstrip-ws2812.c
+++ b/src/driver/led/ledstrip-ws2812.c
@@ -47,9 +47,11 @@ struct ledstrip_ws2812_pdata_t {
 	struct spi_device_t * spidev;
 	int count;
 	struct color_t * color;
+	int buflen;
+	unsigned char * buffer;
 };
 
-static const unsigned char ws2812_table[256][5]={
+static const unsigned char ws2812_map[256][5]={
 	{ 0x84, 0x21, 0x08, 0x42, 0x10, },
 	{ 0x84, 0x21, 0x08, 0x42, 0x1e, },
 	{ 0x84, 0x21, 0x08, 0x43, 0xd0, },
@@ -308,12 +310,6 @@ static const unsigned char ws2812_table[256][5]={
 	{ 0xf7, 0xbd, 0xef, 0x7b, 0xde, },
 };
 
-static void ledstrip_ws2812_send(struct ledstrip_t * strip, unsigned char byte)
-{
-	struct ledstrip_ws2812_pdata_t * pdat = (struct ledstrip_ws2812_pdata_t *)strip->priv;
-	spi_device_write_then_read(pdat->spidev, (void *)&ws2812_table[byte][0], 5, NULL, 0);
-}
-
 static void ledstrip_ws2812_set_count(struct ledstrip_t * strip, int n)
 {
 	struct ledstrip_ws2812_pdata_t * pdat = (struct ledstrip_ws2812_pdata_t *)strip->priv;
@@ -322,9 +318,20 @@ static void ledstrip_ws2812_set_count(struct ledstrip_t * strip, int n)
 	{
 		if(pdat->color)
 			free(pdat->color);
+		if(pdat->buffer)
+			free(pdat->buffer);
 		pdat->count = n;
 		pdat->color = malloc(pdat->count * sizeof(struct color_t));
 		memset(pdat->color, 0, pdat->count * sizeof(struct color_t));
+		pdat->buflen = n * 3 * 5 + 150;
+		pdat->buffer = malloc(pdat->buflen);
+		for(int i = 0; i < n; i++)
+		{
+			memcpy(&pdat->buffer[(i * 3 + 0) * 5], (void *)&ws2812_map[0][0], 5);
+			memcpy(&pdat->buffer[(i * 3 + 1) * 5], (void *)&ws2812_map[0][0], 5);
+			memcpy(&pdat->buffer[(i * 3 + 2) * 5], (void *)&ws2812_map[0][0], 5);
+		}
+		memset(&pdat->buffer[n * 3 * 5], 0, 150);
 	}
 }
 
@@ -338,6 +345,9 @@ static void ledstrip_ws2812_set_color(struct ledstrip_t * strip, int i, struct c
 {
 	struct ledstrip_ws2812_pdata_t * pdat = (struct ledstrip_ws2812_pdata_t *)strip->priv;
 	memcpy(&pdat->color[i], c, sizeof(struct color_t));
+	memcpy(&pdat->buffer[(i * 3 + 0) * 5], (void *)&ws2812_map[pdat->color[i].g][0], 5);
+	memcpy(&pdat->buffer[(i * 3 + 1) * 5], (void *)&ws2812_map[pdat->color[i].r][0], 5);
+	memcpy(&pdat->buffer[(i * 3 + 2) * 5], (void *)&ws2812_map[pdat->color[i].b][0], 5);
 }
 
 static void ledstrip_ws2812_get_color(struct ledstrip_t * strip, int i, struct color_t * c)
@@ -349,16 +359,7 @@ static void ledstrip_ws2812_get_color(struct ledstrip_t * strip, int i, struct c
 static void ledstrip_ws2812_refresh(struct ledstrip_t * strip)
 {
 	struct ledstrip_ws2812_pdata_t * pdat = (struct ledstrip_ws2812_pdata_t *)strip->priv;
-	int i;
-
-	for(i = 0; i < 150; i++)
-		ledstrip_ws2812_send(strip, 0x00);
-	for(i = 0; i < pdat->count; i++)
-	{
-		ledstrip_ws2812_send(strip, pdat->color[i].g);
-		ledstrip_ws2812_send(strip, pdat->color[i].r);
-		ledstrip_ws2812_send(strip, pdat->color[i].b);
-	}
+	spi_device_write_then_read(pdat->spidev, pdat->buffer, pdat->buflen, NULL, 0);
 }
 
 static struct device_t * ledstrip_ws2812_probe(struct driver_t * drv, struct dtnode_t * n)
@@ -386,6 +387,8 @@ static struct device_t * ledstrip_ws2812_probe(struct driver_t * drv, struct dtn
 	pdat->spidev = spidev;
 	pdat->count = 0;
 	pdat->color = NULL;
+	pdat->buflen = 0;
+	pdat->buffer = NULL;
 
 	strip->name = alloc_device_name(dt_read_name(n), dt_read_id(n));
 	strip->set_count = ledstrip_ws2812_set_count;

离线

#12 2022-03-12 08:33:34

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

使用5bit,测试下来,发现可靠性偏低,感觉有必要搞成8bit,或者16bit,反正SPI时钟速度不是瓶颈。还有感觉想要搞通用时序,估计可靠性都好不到哪里去,最优的方式,还是用可配置的8bit或者16bit方案。

离线

楼主 #13 2022-03-12 09:52:01

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

xboot 说:

使用5bit,测试下来,发现可靠性偏低,感觉有必要搞成8bit,或者16bit,反正SPI时钟速度不是瓶颈。还有感觉想要搞通用时序,估计可靠性都好不到哪里去,最优的方式,还是用可配置的8bit或者16bit方案。

2812没有测试过,
值得一提的是,我这里使用WS2811芯片,
测试情况是,没有问题。
随时热拔插,瞬间就点亮并且呈现目标颜色。不存在乱闪的问题。
我现在看看搞一个8bit/16bit的表格出来。

离线

楼主 #14 2022-03-12 10:03:11

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

这是我测试到的波形,看起来数据宽度并没有绝对0.5us/2.0us,
是由于我的晶振使用内部32MHz晶振,时钟不准,但是点亮WS2811没有什么问题。
我现在看看WS2812,搞一个表出来。

(1)whycan下载
(2)站外免积分下载

最近编辑记录 LinjieGuo (2022-03-12 10:05:58)

离线

#15 2022-03-12 10:20:05

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

我看有的工程才有6.4M频率,8bit模式,1.25us / 8 = 0.15625us,然后8比特是3比5和5比3的方式

离线

楼主 #16 2022-03-12 10:21:18

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

我从别的网站帖子上面看到了如下信息,我根据这些信息列出理论依据。
WS2812原理与实现
裁剪帖子的部分信息,可以看到,对于WS2812,其时序要求如下:
ws2812_clk.png
(1)T0H = T1L = 0.4us    (±150ns,建立color表时,尽量不考虑误差范围,可作为芯片SPI时钟误差)
(2)T1H = T0L = 0.85us  (±150ns,建立color表时,尽量不考虑误差范围,可作为芯片SPI时钟误差)

(3)与1楼的理论同理,得:
令SPI的一个时钟周期t = 0.05us,即SPI 的f = 20MHz。

T1H = T0L = 17个SPI数据
T0H = T1L =   8个SPI数据位

但是,芯片支持误差容忍(0.15us/150ns),
所以,可以尝试:

T1H = T0L = 16个SPI数据(少1个时钟节拍,0.05us/50ns)
T0H = T1L =   8个SPI数据位

最近编辑记录 LinjieGuo (2022-03-12 10:26:12)

离线

#17 2022-03-12 10:26:30

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

按WS2812B规格书的完美参数
0.4 / 1.25 = 0.32
0.45 / 1.25 = 0.36

8bit模式
3 / 8 = 0.375

16bit模式
5 / 16 = 0.3125
6 / 16 = 0.375

看来16bit不是最优的,要找个介于0.32 和0.36之间的比值,会相对更好点。

比如12bit的话,感觉比例比较合适
4 / 12 = 0.333333333

当然12bit的话,频率就要变成9.6MHZ

离线

#18 2022-03-12 10:31:51

xboot
会员
注册时间: 2019-10-15
已发帖子: 692
积分: 434

Re: [记录]使用SPI驱动WS2811

综合判定下来,应该选择8bit模式,比较合理,资源也相对少一点,8bit的话,基本就可以不要表格了,直接运算就行了。

离线

楼主 #19 2022-03-12 11:22:15

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

xboot 说:

综合判定下来,应该选择8bit模式,比较合理,资源也相对少一点,8bit的话,基本就可以不要表格了,直接运算就行了。

按我上面所描述的8bit处理,那么,描述一个RGB,使用24字节。
如果存在表格,按字取数,取6次就可以了。
表格不需要运行时参与,存在代码区。
我随便撸几行代码,搞一个表格出来。哈哈。

最近编辑记录 LinjieGuo (2022-03-12 11:22:44)

离线

楼主 #20 2022-03-12 12:24:28

LinjieGuo
Moderator
注册时间: 2019-07-24
已发帖子: 583
积分: 587
个人网站

Re: [记录]使用SPI驱动WS2811

大哥,ws2812你看看这个color表行不行。

SPI CLK 20MHz

T1H = T0L = 16个SPI数据     = 0.8us
T0H = T1L =   8个SPI数据位  = 0.4us


color_map_ws2812.c

最近编辑记录 LinjieGuo (2022-03-12 12:26:45)

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn