esp32 文件读取_esp32 文件读取


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


esp32 文件读取_数据_02


作者:Tkwer

公众号:Tkwer望远镜

WS2812B彩灯介绍


esp32 文件读取_esp32 文件读取_03


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


esp32 文件读取_esp32 文件读取_04


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


esp32 文件读取_数据_05


esp32 文件读取_stm32计时器代码_06


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


esp32 文件读取_缓冲器_07


数据线低电平保持时间大于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生成工程模板


esp32 文件读取_数据_08


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


esp32 文件读取_stm32计时器代码_09


首先选中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 这个模式节省很多内存资源,稍后再介绍。


esp32 文件读取_数据_10


esp32 文件读取_缓冲器_11


或许有的同学在第一步就有这个困惑,为啥我配置的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毫秒的时间。


esp32 文件读取_ci_12


esp32 文件读取_数据_13


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

  1. 加载2 * 24字节的复位信号并开始循环DMA传输。
  2. 半传输触发器-将另外24个字节加载到前半缓冲区中。
  3. 完全传输的触发器-第一个LED的数据到缓冲器的后半部分。
  4. 半传输触发-第二个(偶数)LED的数据传输到缓冲器的前半部分。
  5. 完全传输的触发器-第三个(奇数)二LED的数据到缓冲器的后半部分。
  6. 重复4和5,直到所有LED均已发送。
  7. 完成。


esp32 文件读取_数据_14


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彩灯