[单片机芯片] 基于ch32v003之spi dma实现的ws2812b炫彩灯光效果

[复制链接]
4890|34
 楼主| lilijin1995 发表于 2023-3-4 09:42 | 显示全部楼层 |阅读模式
本帖最后由 lilijin1995 于 2023-3-4 10:36 编辑

背景:项目进度已经基于CH32V003 PWM DMA模式驱动了WS2812B 灯光效果。但是ch32V003有两个定时器,TIM1已经配置为Encoder模式,TIM2要改成PWM输出,所以不能用以驱动WS2812灯了,故而希望以SPI DMA的模式去驱动实现炫彩灯光效果。
软件设计:
因为不确定CH32V003的SPI DMA能否驱动WS2812B,所以这里我创建了独立的工程去验证一下,整个项目的代码这里不方便贴出。
其实WS2812B这类灯光效果的实现国外开源一大堆,比如开源大户Arduino,但是它是8位的AVR芯片,对于我们32位机,优先考虑STM32的,因为方便移植。
如下图:这是我找到的GitHub上开源WS2812驱动:https://github.com/Apex-yuan/STM32f103_WS2812
475926402a59e16c13.png
作者是Apex-yuan,在此表示感谢!!!正式因为大佬的开源,节约了我们大量重复造轮子的时间。轮子有了,接下来是建立自己的工程代码。虽然MRS创建工程很简单,但是有官方例程,创建好的Example干嘛不用呢。

178086402a71bafae0.png

如下图,我们将使用SPI_DMA工程,选择这个工程的原因大家应该已经意会了。熟悉CH32的朋友都知道STM32代码移植到CH32是非常方便的。前提是基于标准库的例程。这次选用的开源例程正式基于标准库的。所以我们直接复制大佬的WS2812驱动到我们自个的工程;最后移植后的代码如下:WS2812.c:
  1. #include "ws2812.h"
  2. #include "debug.h"
  3. uint8_t pixelBuffer[PIXEL_NUM][24] ;
  4. void ws281x_init(void)
  5. {
  6.     GPIO_InitTypeDef GPIO_InitStructure;
  7.     SPI_InitTypeDef  SPI_InitStructure;
  8.     DMA_InitTypeDef DMA_InitStructure;
  9.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //PORTA时钟使能
  10.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //SPI1时钟使能
  11.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        //使能DMA传输


  12.   /* PC6  SPI1_MOSI */
  13.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  14.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  15.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  16.     GPIO_Init(GPIOC, &GPIO_InitStructure);

  17.         SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
  18.         SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                //设置SPI工作模式:设置为主SPI
  19.         SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                //设置SPI的数据大小:SPI发送接收8位帧结构
  20.         SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                //串行同步时钟的空闲状态为低电平
  21.         SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        //串行同步时钟的第2个跳变沿(上升或下降)数据被采样
  22.         SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  23.         SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;                //定义波特率预分频的值:波特率预分频值为16
  24.         SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;        //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  25.         SPI_InitStructure.SPI_CRCPolynomial = 7;        //CRC值计算的多项式
  26.         SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

  27.         SPI_Cmd(SPI1, ENABLE); //使能SPI外设
  28.     SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
  29.   
  30.     DMA_DeInit(DMA1_Channel3);   //将DMA的通道1寄存器重设为缺省值
  31.         DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DATAR); //cpar;  //DMA外设ADC基地址
  32.         DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar;  //DMA内存基地址
  33.         DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
  34.         DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr;  //DMA通道的DMA缓存的大小
  35.         DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
  36.         DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
  37.         DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
  38.         DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
  39.         DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
  40.         DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
  41.         DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
  42.         DMA_Init(DMA1_Channel3, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
  43.   
  44.         ws281x_closeAll();  //关闭全部的灯
  45.         Delay_Ms(100); //关闭全部的灯需要一定的时间
  46. }

  47. void ws281x_closeAll(void)
  48. {
  49.   uint16_t i;
  50.   uint8_t j;
  51.   
  52.   for(i = 0; i < PIXEL_NUM; ++i)
  53.   {
  54.     for(j = 0; j < 24; ++j)
  55.     {
  56.       pixelBuffer[i][j] = WS_LOW;
  57.     }
  58.   }
  59.   ws281x_show();
  60. }

  61. uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)
  62. {
  63.   return green << 16 | red << 8 | blue;
  64. }

  65. void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor)
  66. {
  67.   uint8_t i;
  68.   if(n < PIXEL_NUM)
  69.   {
  70.     for(i = 0; i < 24; ++i)
  71.     {
  72.       pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
  73.     }
  74.   }
  75. }

  76. void ws281x_setPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
  77. {
  78.   uint8_t i;
  79.   
  80.   if(n < PIXEL_NUM)
  81.   {
  82.     for(i = 0; i < 24; ++i)
  83.     {
  84.       pixelBuffer[n][i] = (((ws281x_color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
  85.     }
  86.   }
  87. }

  88. void ws281x_show(void)
  89. {
  90.     DMA_Cmd(DMA1_Channel3, DISABLE );  //关闭USART1 TX DMA1 所指示的通道
  91.     DMA_ClearFlag(DMA1_FLAG_TC3);   
  92.            DMA_SetCurrDataCounter(DMA1_Channel3,24 * PIXEL_NUM );//DMA通道的DMA缓存的大小
  93.            DMA_Cmd(DMA1_Channel3, ENABLE);  //使能USART1 TX DMA1 所指示的通道
  94. }



  95. // Input a value 0 to 255 to get a color value.
  96. // The colours are a transition r - g - b - back to r.
  97. uint32_t ws281x_wheel(uint8_t wheelPos) {
  98.   wheelPos = 255 - wheelPos;
  99.   if(wheelPos < 85) {
  100.     return ws281x_color(255 - wheelPos * 3, 0, wheelPos * 3);
  101.   }
  102.   if(wheelPos < 170) {
  103.     wheelPos -= 85;
  104.     return ws281x_color(0, wheelPos * 3, 255 - wheelPos * 3);
  105.   }
  106.   wheelPos -= 170;
  107.   return ws281x_color(wheelPos * 3, 255 - wheelPos * 3, 0);
  108. }

  109. // Fill the dots one after the other with a color
  110. void ws281x_colorWipe(uint32_t c, uint8_t wait) {
  111.   for(uint16_t i=0; i<PIXEL_NUM; i++) {
  112.     ws281x_setPixelColor(i, c);
  113.     ws281x_show();
  114.     Delay_Ms(wait);
  115.   }
  116. }

  117. void ws281x_rainbow(uint8_t wait) {
  118.   uint16_t i, j;

  119.   for(j=0; j<256; j++) {
  120.     for(i=0; i<PIXEL_NUM; i++) {
  121.       ws281x_setPixelColor(i, ws281x_wheel((i+j) & 255));
  122.     }
  123.     ws281x_show();
  124.     Delay_Ms(wait);
  125.   }
  126. }

  127. // Slightly different, this makes the rainbow equally distributed throughout
  128. void ws281x_rainbowCycle(uint8_t wait) {
  129.   uint16_t i, j;

  130.   for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
  131.     for(i=0; i< PIXEL_NUM; i++) {
  132.       ws281x_setPixelColor(i,ws281x_wheel(((i * 256 / PIXEL_NUM) + j) & 255));
  133.     }
  134.     ws281x_show();
  135.     Delay_Ms(wait);
  136.   }
  137. }

  138. //Theatre-style crawling lights.
  139. void ws281x_theaterChase(uint32_t c, uint8_t wait) {
  140.   for (int j=0; j<10; j++) {  //do 10 cycles of chasing
  141.     for (int q=0; q < 3; q++) {
  142.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  143.         ws281x_setPixelColor(i+q, c);    //turn every third pixel on
  144.       }
  145.       ws281x_show();

  146.       Delay_Ms(wait);

  147.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  148.         ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
  149.       }
  150.     }
  151.   }
  152. }

  153. //Theatre-style crawling lights with rainbow effect
  154. void ws281x_theaterChaseRainbow(uint8_t wait) {
  155.   for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
  156.     for (int q=0; q < 3; q++) {
  157.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  158.         ws281x_setPixelColor(i+q, ws281x_wheel( (i+j) % 255));    //turn every third pixel on
  159.       }
  160.       ws281x_show();

  161.       Delay_Ms(wait);

  162.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  163.         ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
  164.       }
  165.     }
  166.   }
  167. }
其中ws281x_init初始化代码要注意的是要是能RCC_APB2Periph_GPIOC时钟,并初始化PC6作为GPIO_Mode_AF_PP,因为CH32V003是的PC6是MOSI;
另外DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DATAR); 跟STM32的SPI1 -> DR不太一样;然后再贴出WS2812.h代码
  1. #ifndef __WS2812_H
  2. #define __WS2812_H

  3. #include <ch32v00x.h>

  4. #define PIXEL_NUM 9

  5. //硬件spi模拟ws2811时序(用spi的8位数据模拟ws281x的一位数据)
  6. //  _____   
  7. // |     |___|   11110000  high level
  8. //  ___         
  9. // |   |_____|   11000000  low level

  10. #define WS_HIGH 0XF0
  11. #define WS_LOW  0XC0

  12. void ws281x_init(void);
  13. void ws281x_closeAll(void);
  14. void ws281x_rainbowCycle(uint8_t wait);
  15. uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue);
  16. void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor);
  17. void ws281x_show(void);

  18. void ws281x_theaterChase(uint32_t c, uint8_t wait);
  19. void ws281x_colorWipe(uint32_t c, uint8_t wait);
  20. void ws281x_rainbow(uint8_t wait);
  21. void ws281x_theaterChaseRainbow(uint8_t wait);

  22. #endif /* __WS2812_H */

大佬用的是56Mhz的系统时钟,8分频算下来是7Mhz,但我们用的是48Mhz的HSI,所以8分频后只有6Mhz,但是这并不影响我们驱动WS2812B,其中:
// | |___| 11110000 high level
// ___
// | |_____| 11000000 low level

#define WS_HIGH 0XF0
#define WS_LOW 0XC0


1码是这样算得:T=1/f=1/6Mhz=0.1666666666666667us,  4*T=0.6666666666666667us;
0码是这样算得:T=1/f=1/6Mhz=0.1666666666666667us,  2*T=0.3333333333333334us;
再来看WS2812的时序


508686402ab48b79c3.png
都是满足的。
最后在main函数调用灯光效果API:
  1. int main(void)
  2. {

  3.     u8 i=0;
  4.     Delay_Init();
  5.     USART_Printf_Init(460800);
  6.     printf("SystemClk:%d\r\n", SystemCoreClock);
  7.     ws281x_init();
  8.     while(1)
  9.     {
  10.         // Some example procedures showing how to display to the pixels:
  11.      ws281x_colorWipe(ws281x_color(255, 0, 0), 50); // Red
  12.      ws281x_colorWipe(ws281x_color(0, 255, 0), 50); // Green
  13.      ws281x_colorWipe(ws281x_color(0, 0, 255), 50); // Blue
  14.    //colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
  15.      // Send a theater pixel chase in...
  16.      ws281x_theaterChase(ws281x_color(127, 127, 127), 50); // White
  17.      ws281x_theaterChase(ws281x_color(127, 0, 0), 50); // Red
  18.      ws281x_theaterChase(ws281x_color(0, 0, 127), 50); // Blue

  19.      ws281x_rainbow(20);
  20.      ws281x_rainbowCycle(10);
  21.      ws281x_theaterChaseRainbow(50);

  22.        for(i = 0; i < PIXEL_NUM; ++i)
  23.      {
  24.        ws281x_setPixelColor(i, ws281x_color(0,250,0));
  25.        ws281x_show();
  26.        Delay_Ms(500);
  27.      }
  28.     }
  29. }
是不是就可以实现了呢?直接编译通过下载的话,我估计是白灯,因为我们的工程用的是48Mhz的HSI,但是SPI_DMA用的是HSE,我们在system_ch32v00x.c中
直接修改一下即可:

408026402ac15a348a.png

下载验证:
最后我们一起欣赏一下实验效果:



ap0405209 发表于 2023-3-12 16:46 | 显示全部楼层
嗯嗯,不错,留着备用。谢谢楼主。
LED13686863148 发表于 2023-3-12 19:44 | 显示全部楼层
tpgf 发表于 2023-4-6 15:21 | 显示全部楼层
我们使用dma是为了提高工作效率还是为了节省资源呢
qcliu 发表于 2023-4-6 15:45 | 显示全部楼层
这个灯光效果的细腻程度是不是已经由ws2812b限制住了呢
drer 发表于 2023-4-6 16:12 | 显示全部楼层
请问这种芯片调光的原理大概是什么呢
coshi 发表于 2023-4-6 16:41 | 显示全部楼层
看代码的话 如果想要更改灯光  就需要凭借经验慢慢更改配比吗
kxsi 发表于 2023-4-6 16:51 | 显示全部楼层
芯片是成熟的芯片了  可控制的数量也是很多的
wiba 发表于 2023-4-6 17:09 | 显示全部楼层
请问一片ws2812b可以控制多少条灯带啊
houjiakai 发表于 2023-5-5 23:01 | 显示全部楼层
这个可以通过延时实现的驱动吗              
dspmana 发表于 2023-5-5 23:39 | 显示全部楼层
这个是不是需要一个数组才能实现呢
chenci2013 发表于 2023-5-5 23:47 | 显示全部楼层
需要建立相关的库吗              
qiufengsd 发表于 2023-5-6 00:15 | 显示全部楼层
最大的驱动速度是多少              
cemaj 发表于 2023-5-6 00:29 | 显示全部楼层
WS2812B 是一款基于串行通信接口的 RGB LED 灯珠,它可以通过使用 DMA 实现 SPI 通信来控制 LED 的颜色和亮度。
pentruman 发表于 2023-5-6 00:36 | 显示全部楼层
SK6812和WS2812B的区别大吗?
mikewalpole 发表于 2023-5-7 10:36 | 显示全部楼层
这个怎么控制时序的呢              
cashrwood 发表于 2023-5-7 10:45 | 显示全部楼层
ws2812和ws2812b能混用吗
bestwell 发表于 2023-5-7 13:52 | 显示全部楼层
如何实现ws2812b led灯点阵的驱动呢
1988020566 发表于 2023-5-7 13:55 | 显示全部楼层
arduino开发ws2812最简答了。
pl202 发表于 2023-5-7 14:02 | 显示全部楼层
SPI 使用DMA方式如何来做
您需要登录后才可以回帖 登录 | 注册

本版积分规则

56

主题

165

帖子

8

粉丝
快速回复 在线客服 返回列表 返回顶部