STM32 SPI+DMA实现WS2812灯的驱动

WS2812的时序频率比较高,STM32芯片使用软件生成WS2812的时序是没有问题的,但是要屏蔽所有中断,防止时序被干扰,但是这样当刷新频率很高的时候会导致CPU的占用率增高很多,STM32也没有控制WS2812的专门外设,所以为了解决这个棘手的问题,我提出了一个使用SPI+DMA的方式进行WS2812的控制。WS2812的介绍参考之前转载的文章:https://blog.csdn.net/tq384998430/article/details/53391575

首先说明一下,WS2812的时序中一个位大约需要1.25us的时间,这里面当高电平的时间占用时间位0.85us,低电平占用时间为0.4us时表示传输位为1,反过来则表示传输位为0。

我们使用SPI不是用他的SCLK引脚的信号,而是MOSI引脚的信号,因为SCK引脚的脉冲宽度时固定的,但是我们可以控制MOSI输出信号。例如我们输出 b1111 1000 时,(MOSI原本是低电平状态)产生了一个高电平脉冲,高电平的宽度与地电平的比值为 5:3,然后我们可以再输出 b1110 0000,这时候输出脉冲的高低电平的比值为3:5,好巧哦,我们可以控制输出信号的脉宽比了,如果觉得这个比值还是太粗糙可以使用多个字节进行脉冲高低电平时间的比例分配。

恰巧前面说过了,WS2812的位1是由0.85us的高电平加上0.4us的低电平组成的,高低电平的占例大约为2多一点,其实和上面说的输出 b1111 1000 数据时生成的脉冲高低电平比例差不多,反过来WS2812的位0也和 b1110 0000 数据的输出效果差不多。我们通过控制SPI的时钟频率可以实现SPI传输8个位的数据使用的时间大致等于1.25us,例如STM32的SPI1挂载的APB2总线频率为72Mhz,8分频之后得到的SPI1的SCK的频率为9Mhz,周期为0.11us,传输8个位的时间为0.89微妙,没法正好等于1.25us,所以取一个大致相近的值即可。还有要注意设置SPI的工作模式为SCK在第一个上升沿采样数据,SCK的极性无所谓。

基于上面的论证,假设我们需要控制的WS2812灯一共有60颗,我们可以建立一个 60 * 24 个字节的数组A,这个数组存放的都是 b1111 1000 或者 b1110 0000,也就是每个字节存放的其实是一个WS2812的单个位的数据,我们将这个数组都写成 b1111 1000 的时候,灯光效果就都是白色,数组全部写 b1110 0000 的时候,灯光效果就都是黑色的。但是好像控制灯光的时候有点不方便啊!我们可以再建立一个数组B,用于存放每个灯(像素)的颜色信息,这个数组是32位类型的,存放RGB888的数据,我们修改灯光效果的时候可以先修改数组B的内容,然后通过运算将数组B中的数据映射到数组A中去,然后在将数组A通过SPI发送出去。

到这里基本上就可以实现SPI控制WS2812灯了,但是每次调用SPI发送数据是有间隔时间的,并且会被中断打断,这时候DMA的作用就体现出来了。将DMA的内存地址设置为上面的数组A,长度设置为数组A的大小,开启DMA发送,你只需要喝杯咖啡,等待DMA发送完成标志位置位即可。

还有一个需要注意的点是WS2812需要有复位操作,也就是发送50us以上的低电平进行复位,每次传输数据的时候都需要先复位,要实现复位功能,我们可以发送 b0000 0000 即可,由于一个位的时间为0.89us,要实现50us以上,需要至少发送57次b0000 0000 ,我们可以创建一个数组C,C的内容全是 b0000 0000,C的长度设置为100保证有效复位,然后开启DMA,传输数组C的内容即可完成复位。

Surprise,没想到SPI+DMA可以这样用,其实同样的技巧可以用在很多其他的应用场景中去,例如控制步进电机需要特定个数的脉冲,我们也可以使用SPI的MOSI来实现(SCK可能频率太高,步进电机控制器无法接受),例如发送 b0011 1100 这个数据就可以在MOSI上产生一个高脉冲,通过DMA可以实现输出特定个数脉冲的功能。

 

上一篇:STM32 HAL库 us延时


下一篇:postgresql认证和常见命令