首先,让我向各位介绍一下fastNP,这是一款强大的STM32库,它可以用于并行刷新多个可寻址 LED 链,最多支持16个LED链并行刷新,无需超频CPU。该库适用于WS2812B LED以及其克隆版本,例如SK6812等,我们通常称之为 NeoPixels。我已经在STM32F103微控制器(在所谓的 “Blue Pill” 板上)上进行了开发和测试,但理论上它也可以移植到其他的STM32微控制器上。

实战项目下载

fastNP库的使用介绍

fastNP库使用起来非常直接明了,但在使用之前,你需要对STM32的基本编程以及WS2812B LED的原理有所了解。此外,你还需要一个STM32F103微控制器和一些可寻址LED(例如WS2812B或SK6812)。

在你准备好这些之后,首先需要下载fastNP库,然后将其添加到你的STM32项目中。这里,我假设你已经使用STM32CubeMX或类似的工具配置了你的STM32F103微控制器,并且设置了一个合适的时钟源。你需要确保CPU的频率足够高,以便在刷新LED链时有足够的处理能力。

fastNP库的核心代码是一个称为 fastNP_refresh() 的函数。这个函数接受三个参数:第一个参数是LED链的数目,第二个参数是指向LED链数组的指针,第三个参数是每个LED链的长度。

下面是一个简单的例子:

#include "fastNP.h"

#define NUM_LEDS 16
#define NUM_STRIPS 1

uint32_t leds[NUM_STRIPS][NUM_LEDS];

int main(void) {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[0][i] = fastNP_rgb(0, 0, i * 16);  // Blue color gradient
  }
  
  while (1) {
    fastNP_refresh(NUM_STRIPS, leds, NUM_LEDS);
  }
}

这个例子首先定义了一个二维数组 leds,其大小为LED链的数目乘以每个链中的LED数目。在这个例子中,我们只有一个LED链,其中有16个LED。然后我们使用 fastNP_rgb() 函数为每个LED赋予一个蓝色渐变色,然后我们在一个无限循环中不断地调用 fastNP_refresh() 函数来刷新这些LED。

在这个例子中,我们只是简单地为所有的LED赋予了同样的颜色。但实际上,你可以为每个LED赋予任意的颜色,你只需要调用 fastNP_rgb() 函数并传递三个参数:红色、绿色和蓝色的强度(每个参数的范围都是0-255)。


以上就是 fastNP 库使用的基本介绍和一个简单的示例。下面,我将深入地解析 fastNP 的原理,并讨论如何使用它来实现更复杂的效果。

这一部分的内容就到这里,下一部分我将开始解析fastNP的实现原理,包括硬件定时器和DMA等概念的介绍以及示例代码的详细解释。

fastNP库的原理解析

在深入探讨fastNP库的工作原理之前,我们需要先了解一下WS2812B LED的工作原理。

WS2812B LED使用一种特殊的通信协议,它接受一个24位的数据,分别表示红色、绿色和蓝色的强度(每种颜色8位)。这些数据通过一个单线串行接口发送,使用PWM(脉宽调制)编码。具体的编码规则是:数据位为0时,PWM脉冲的高电平时间短于低电平时间;数据位为1时,PWM脉冲的高电平时间长于低电平时间。

然而,WS2812B LED的通信协议有一个挑战,那就是它需要非常精确的时序。为了达到这样的精度,我们通常需要使用硬件定时器和DMA(直接内存访问)。这就是fastNP库的核心。

fastNP的实现原理

fastNP库的核心是一个硬件定时器和一个DMA通道。硬件定时器被配置为PWM模式,并且与DMA通道关联,以实现WS2812B LED的PWM编码。

fastNP库的 fastNP_refresh() 函数会将RGB数据转换为PWM编码,并将编码后的数据存储在一个缓冲区中。然后,DMA通道会被配置为从这个缓冲区中读取数据,并将数据送到硬件定时器的比较寄存器。最后,硬件定时器会根据比较寄存器的值生成PWM脉冲,从而控制LED。

这里有一个关键的技巧,那就是使用DMA的循环模式。在这种模式下,当DMA传输完一次数据后,它会自动回到缓冲区的开始位置,并开始下一次传输。通过这种方式,我们可以实现对LED链的持续刷新。

下面是 fastNP_refresh() 函数的一个简化版实现:

void fastNP_refresh(uint8_t num_strips, uint32_t *leds, uint16_t num_leds) {
  // Convert RGB data to PWM codes
  for (int i = 0; i < num_strips * num_leds; i++) {
    // Convert each bit of RGB data to a PWM code
    for (int j = 0; j < 24; j++) {
      pwm_buffer[i * 24 + j] = ((leds[i] >> j) & 1) ? PWM_CODE_ONE : PWM_CODE_ZERO;
    }
  }

  // Configure DMA to transfer PWM codes to timer's compare register
  DMA1_Channel1->CNDTR = num_strips * num_leds * 24;  // Number of data to transfer
  DMA1_Channel1->CMAR = (uint32_t)pwm_buffer;  // Memory address
  DMA1_Channel1->CCR |= DMA_CCR_EN;  // Enable DMA

  // Start timer
  TIM2->CR1 |= TIM_CR1_CEN;
}

注意这个函数需要一个名为 pwm_buffer 的缓冲区,其大小至少为 num_strips * num_leds * 24。这个缓冲区用于存储PWM编码。

在这个函数中,首先将RGB数据转换为PWM编码,并存储在 pwm_buffer 中。然后配置DMA通道,设置传输的数据量和内存地址,最后启动DMA并启动定时器。

fastNP 的实现细节

实际上,fastNP库的实现要比上面的简化版本复杂很多。例如,它需要考虑DMA的并行传输、数据的对齐、时钟的设置、中断的处理等等。这些细节我将在下一部分进行详细的解释。

上述内容已经包含了fastNP库的基本使用方法以及工作原理的一部分,下一部分我将继续讲解fastNP库的更多细节,包括如何处理中断、如何设置时钟、如何优化性能等等。

fastNP 库的实现细节

在上一部分,我们了解了fastNP库的基本工作原理。现在,我们将深入了解一些更详细的实现细节。

DMA 并行传输

为了实现多个 LED 链的并行刷新,fastNP 库使用了 DMA 的并行传输功能。具体来说,它使用了一个叫做 DMA 重映射(DMA remapping)的特性,使得多个 DMA 通道可以同时传输数据到同一个硬件定时器。

在这种设置下,每个 LED 链需要一个单独的 DMA 通道,并且所有的 DMA 通道都需要配置为从同一个 PWM 缓冲区中读取数据。这样,当硬件定时器开始计数时,所有的 DMA 通道将同时开始传输数据,从而实现了 LED 链的并行刷新。

数据对齐和缓冲区管理

由于 DMA 传输的数据需要与硬件定时器的比较寄存器对齐,因此 PWM 缓冲区中的数据也需要按照相应的规则对齐。在 fastNP 库中,这是通过一个名为 fastNP_align() 的函数来实现的。这个函数接受一个 32 位的 RGB 值,然后将其转换为一个 24 位的 PWM 编码,同时确保数据的对齐。

此外,由于 PWM 缓冲区的大小是固定的,因此需要一种方式来管理这个缓冲区。在 fastNP 库中,这是通过一个简单的循环缓冲区来实现的。每当 fastNP_refresh() 函数被调用时,它会检查缓冲区是否有足够的空间来存储新的 PWM 编码。如果没有,它会等待当前的 DMA 传输完成,然后再开始新的转换和传输。

时钟设置和性能优化

为了确保 PWM 编码的精度,需要对硬件定时器的时钟进行精确的设置。在 fastNP 库中,这是通过 STM32 的时钟树和预分频器来实现的。

此外,为了提高性能,fastNP 库采取了一些优化措施。例如,它使用了一个快速的位操作算法来生成 PWM 编码,而不是使用传统的 for 循环。它还使用了 DMA 的 burst 模式来提高数据传输的效率。


以上就是 fastNP 库的详细实现。虽然这个库可能看起来有些复杂,但实际上它只是使用了 STM32 的一些基本功能,如硬件定时器、DMA、时钟设置等。通过理解这些基本功能,你应该能够理解 fastNP 库的工作原理,以及如何使用它来实现你的项目。

希望这篇文章能帮助你理解和使用 fastNP 库,如果你有任何问题,欢迎留言。我将尽我最大的努力回答你的问题。

06-30 10:06