STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法

一种高效的WS2812B控制算法——基于STM32G431CBU6的SPI+DMA

1.WS2812B介绍

ws2812b是一款集控制电路与发光电路于一体的智能外控LED光源,采用单线归0码协议,每个像素点的三基色颜色可实现256级亮度显示。速率能达到1024pixel × 30fps / s,故被广泛用于各种需要大量使用RGB灯的场合。

不同厂商生产的ws2812存在不同的时序要求,下图是一款最常见的ws2812b通信协议。由此可以看出,我们一个0码1码的周期在800ns~1400ns左右。

在这里插入图片描述
在这里插入图片描述

如果使用GPIO直接控制该总线,不但时序不好保证(编译器优化不同、不同库的汇编编译结果不同),而且十分占用CPU资源。如果采用外设去模拟IO口的翻转,则可以极大的减少CPU的资源占用。目前常见的通用外设控制方案为IICSPIPWM等。考虑IIC可能使用得较多,故采用相对较少使用得SPI总线模拟该归0协议。若使用IIC模拟的思路相同。

2.SPI介绍

SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI01的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。

SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]

  • CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-msNpNf3K-1684475130171)(./assets/205973_fig_03.png)]

  • CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出
    在这里插入图片描述

由于需要控制某个电平持续一定时间,那么数据采样需要在时钟周期的结束,而不是开始,即CPHA = 1

3.设计思路

由于控制高低电平占比(ws2812 bit)的时间需要多个SPI bit,故SPI发送的长度肯定大于通信的内容,即用多个SPI bit去模拟一个ws2812 bit。常见的SPI传输是8bit,但也可配置为12、16等比特。但为了DMA搬运数据时方便对齐,故配置为8bit

SPI 8bit的数据长度下,我们采用2,4,8,12,16的SPI bit去模拟一个ws2812 bit。比如可以得出一下组合(实际上的ws2812时序并没有数据手册上那么严格,且不同厂家的配置不同):

  • 用4个SPI bit去模拟,则用SPI bit表示ws2812 bit0为:1000bit1可表示为1110。且一个SPI bit在250~420ns均可。
  • 用8个SPI bit去模拟,则用SPI bit表示ws2812 bit0为:1100 0000bit1可表示为1111 1100。且一个SPI bit在120~210ns均可。

当然不同比例的SPI bit可以调整出不同的SPI时间,具体根据SPI的配置时间。8个SPI bit能将时间调整得更精细,而4个SPI bit更省RAM。具体怎么配置还要和SPI的速度匹配。故我采用4个SPI bit模式。那么SPI的通信速率为 1 250 n s ≤ f r e q ≤ 1 420 n s \frac{1}{250ns}≤freq≤\frac{1}{420ns} 250ns1freq420ns1。即2.4Mbps~4Mbps。

4.实现

用4个SPI bit去模拟,则用SPI bit表示ws2812 bit0为:1000bit1可表示为1110。即LOW=0x08 HIGH=0x0E

首先将SPI调整发送模式数据长度大小端分频系数CPHA

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbqydMj4-1684475130172)(./assets/image-20230519121023359.png)]

然后配置GPIO,把发送脚配置为下拉

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WPNgWMtC-1684475130172)(./assets/image-20230519121138265.png)]

最后配置DMA管脚方向位宽

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jr7f2hVU-1684475130173)(./assets/image-20230519121211447.png)]

代码:

#define  WSLEDNUM    8												// 定义灯的数量
uint8_t  wsFillMap[4] = {0x88, 0x8E, 0xE8, 0xEE};       			// 这是一个哈希表 00=0x88 01=0x8E 10=0xE8 11=0xEE
uint8_t  ws2812Buffer[WSLEDNUM*12+2] = {0};							// 数组缓冲区

void setOnePixRGB(uint8_t R, uint8_t G, uint8_t B, uint16_t index)
{
	uint8_t i;
	uint8_t *bufHead = ws2812Buffer + (12 * index);					// 通过bufHead确定该像素的首地址,减少后面计算
	
	for (i = 0; i < 4; ++i)											// 每次位移两位,通过哈希表来填充缓存区(当然你可以写一个4位的哈希表,就不用位移了)。并同时为GRB赋值,减少循环次数。
	{
			bufHead[0+i] = wsFillMap[(G >> (6 - 2 * i)) & 0x03];
			bufHead[4+i] = wsFillMap[(R >> (6- 2 * i)) & 0x03];
			bufHead[8+i] = wsFillMap[(B >> (6 - 2 * i)) & 0x03];
	}
}

void flushWs2812(void)												// 刷新函数,多发两个低电平稳定电平(心里安慰,其实没用)
{
	HAL_SPI_Transmit_DMA(&hspi1, ws2812Buffer, WSLEDNUM*12+2);
}

这段代码实现了刷新一个灯的缓冲区,并通过哈希表减少运算量。最后通过DMA发送缓冲区数据即更新所有灯的状态。

注意,同时因为下拉的存在,所以不需要发送很多的0来发送RESET,但必须需保证两帧间隔大于文档中的RESET码时间。

5.总结与展望

实际上,这次买的ws2812b系列的灯,并不是该厂家的。通过调整系统主频发现可以稳定“超频”到5Mbps的SPI速率,由于STM32G431CBU6可以达到150M甚至170M,所以最终固定为:150MHz的主频,4.6875MbpsSPI

150M的主频下,执行setOnePixRGB()这个函数1000次仅用时800us,故在不考虑DMA效率下,连续设置250个灯的时间也小于RESET200us的时间,可以放心用,不用考虑双Buffer,更省RAM

最后,这段代码仅提供思路,哈希表可以更加优化,对长灯带的缓存区数据填充有更合适的方法。

6.参考文献

1.Piyu Dhaker. SPI接口简介[EB/OL]. 亚德诺半导体, 2017-06-19. [2023-05-19]. https://www.analog.com/cn/analog-dialogue/articles/introduction-to-spi-interface.html.

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康娜喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值