【TOLIN】第六章|移植WS2812FX库(上)

【TOLIN】第六章|移植WS2812FX库(上)

        ↑ 点击上方,关注“Tkwer望远镜”

作者:Tkwer

公众号:Tkwer望远镜

 

WS2812B彩灯介绍

    在上个推送中我们使用PWM+DMA驱动WS2812B。并且成功移植了Adafruit_NeoPixel库。最近在github上关注了WS2812FX库,这个库的实现函数要比Adafruit_NeoPixel库丰富的多。

    很可惜,这个库是基于Arduino和ESP8266、ESP32的,是用C++写的,移植到我们的TOIN核心板上,需要很大的工作量。不过有这个想法,肯定不止我一个!所以:

    Thank lamik !这个是他基于STM32F103写的,我们只需要配置底层代码,即配置SPI+DMA,就能在我们的TOIN核心板跑啦。由于WS2812B发送一位有时钟要求,所以我们先回顾一下它的时序图吧。

    数据线低电平保持时间大于50us时,为复位信号。复位后,每个LED读取“DIN”线上开始的24bit(绿:红:蓝为8:8:8)数据到驱动芯片内部缓存。除了开始的24bit数据,后面的数据都通过“DOUT”脚传递到下一个LED,即每经过一个像素点的传输,信号减少24bit。内部缓存数据在下一个复位脉冲后被写入PWM控制器。一个bit为1.25us±0.15us,一个LED有3*8bits=24bits,传输完大概需要24*1.25us=30us。(注:在spi中,我们用spi一个字节(8位)来表示这里的一"bit",0 code :11000000 1 code :11111000)

所以在ws2812_spi.c文件中做以下定义了:

1//  ___         
2// |   |_____|   11000000  low level
3
4//  _____   
5// |     |___|   11111000  high level
6#define zero 192U  
7#define one 248U

 

 

使用STM32CubeMX生成工程模板

   

    本例程我们需要用到SPI和DMA,所以我们在CuBeMX中添加配置一下。

    首先选中SPI--->Mode 选择Transmit only master--->配置 Parameter Settings 

    注意这里的圈出来的时钟范围一定要满足WS2812时钟要求,如果不满足我们需要进行时钟树配置,SPI1是挂在APB2 Peripheral clocks(MHz)时钟上的.(ps:1/1.25us/8 = 6.4MHz,输入误差公式,时钟范围可以是6.4±0.66MHz。6.4MHz/8=800khz,用SPI一个字节传输代表LED的一个“bit”)

   DMA配置,一定是Memory to Peripheral 且Mode是circular。作者是通过circular 这个模式节省很多内存资源,稍后再介绍。

 

    或许有的同学在第一步就有这个困惑,为啥我配置的SPI1口在的是PB3,PB5,而自己配置图显示的是PA5,PA7,这个不是很大问题。cubeMX默认是SPI1映射到PA5,PA7,但是我们可以通过选pin脚让它映射到PB3,PB5。

以上就算配置好需要的SPI驱动了,下面我来回答几个问题:

  • 为什么要用DMA?

    WS2812灯用作一些动态刷新的时候需要传输大量数据,如果不使用DMA,可能我们在用中断的时候破坏了传输数据。这个库也确切用到了SysTick计时器的中断。

 

  • 为什么DMA Mode是Circular 而不是Normal?

    从内存方面思考,每个LED消耗24字节,100颗就消耗2.4KB内存(这很不利于我们做Bad apple!),但是我们利用DMA的HAL_SPI_TxHalfCpltCallback回调函数,可以先传送一半存储区的内容,一半用于装载数据,注意装载数据所需时间应该是小于传送出去的时间的。circular就可以实现两个半缓存区交替。缓存区大小不受灯数量影响!

    从时间层面,通过DMA发送一半缓冲区(24字节)所需的时间为31 µs。 接下来的24个字节的数据准备仅花费MCU 7 µs。一个LED可以为CPU节省26 µs的时间,这可以做其他事情。如果有100个LED,则为2.6毫秒,而有1000个LED,则CPU需要26毫秒的时间。

 

数据缓冲区将缩短为48个字节,即它将适合两个二极管的数据。它将很好地包装。操作图如下:

  1. 加载2 * 24字节的复位信号并开始循环DMA传输。

  2. 半传输触发器-将另外24个字节加载到前半缓冲区中。

  3. 完全传输的触发器-第一个LED的数据到缓冲器的后半部分。

  4. 半传输触发-第二个(偶数)LED的数据传输到缓冲器的前半部分。

  5. 完全传输的触发器-第三个(奇数)二LED的数据到缓冲器的后半部分。

  6. 重复4和5,直到所有LED均已发送。

  7. 完成。

 

 

 1void WS2812B_Refresh()

 2{
 3  CurrentLed = 0;
 4  ResetSignal = 0;
 5
 6  for(uint8_t i = 0; i < 48; i++)
 7    buffer[i] = 0x00;
 8
 9  HAL_SPI_Transmit_DMA(hspi_ws2812b, buffer, 48); // Additional 3 for reset signal
10  while(HAL_DMA_STATE_READY != HAL_DMA_GetState(hspi_ws2812b->hdmatx));
11}
12
13void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi)
14{
15  if(hspi == hspi_ws2812b)
16  {
17    if(!ResetSignal)
18    {
19      for(uint8_t k = 0; k < 24; k++) // To 72 impulses of reset
20      {
21        buffer[k] = 0x00;
22      }
23      ResetSignal = 1; // End reset signal
24    }
25    else // LEDs Odd 1,3,5,7...
26    {
27      if(CurrentLed > WS2812B_LEDS)
28      {
29        HAL_SPI_DMAStop(hspi_ws2812b);
30      }
31      else
32      {
33        uint8_t j = 0;
34        //GREEN
35        for(int8_t k=7; k>=0; k--)
36        {
37          if((ws2812b_array[CurrentLed].green & (1<<k)) == 0)
38            buffer[j] = zero;
39          else
40            buffer[j] = one;
41          j++;
42        }
43
44        //RED
45        for(int8_t k=7; k>=0; k--)
46        {
47          if((ws2812b_array[CurrentLed].red & (1<<k)) == 0)
48            buffer[j] = zero;
49          else
50            buffer[j] = one;
51          j++;
52        }
53
54        //BLUE
55        for(int8_t k=7; k>=0; k--)
56        {
57          if((ws2812b_array[CurrentLed].blue & (1<<k)) == 0)
58            buffer[j] = zero;
59          else
60            buffer[j] = one;
61          j++;
62        }
63        CurrentLed++;
64      }
65    }
66  }
67}
68
69void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
70{
71  if(hspi == hspi_ws2812b)
72  {
73    if(CurrentLed > WS2812B_LEDS)
74    {
75      HAL_SPI_DMAStop(hspi_ws2812b);
76    }
77    else
78    {
79      // Even LEDs 0,2,0
80      uint8_t j = 24;
81      //GREEN
82      for(int8_t k=7; k>=0; k--)
83      {
84        if((ws2812b_array[CurrentLed].green & (1<<k)) == 0)
85          buffer[j] = zero;
86        else
87          buffer[j] = one;
88        j++;
89      }
90
91      //RED
92      for(int8_t k=7; k>=0; k--)
93      {
94        if((ws2812b_array[CurrentLed].red & (1<<k)) == 0)
95          buffer[j] = zero;
96        else
97          buffer[j] = one;
98        j++;
99      }
100
101      //BLUE
102      for(int8_t k=7; k>=0; k--)
103      {
104        if((ws2812b_array[CurrentLed].blue & (1<<k)) == 0)
105          buffer[j] = zero;
106        else
107          buffer[j] = one;
108        j++;
109      }
110      CurrentLed++;
111    }
112  }
113}

 

传送门回顾:【TOLIN】第四章|驱动WS2812B彩灯

 

注:由于我们修改了时钟参数,之前的PWM+DMA移植的代码在这个工程可能不起作用,需要重新计算数值。

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值