跳至主要內容

彩色灯珠 WS2812

大约 3 分钟

简介

本章介绍使用Air001开发板驱动 WS2812 灯。

提示

WS2812 是一个三色 LED 驱动芯片open in new window,一般集成在 LED 灯珠中。
它仅需一个 IO 传输数据,可通过多个 LED 串联传递信号,实现批量控制。

硬件准备

  • ☁️ Air001开发板入门,将Air001DAPLink调试器使用排针排母连接。

  • Air001开发板的 USB 插入一根数据线,用向灯板提供 5V 电压。

  • WS2812LED 模块与Air001开发板,按如下表格进行相连:

WS2812 模块Air001
GNDGND
VCCVBUS(5V)
DINPA_7

提示

由于 WS2812 的时序要求相对严格,我们将使用 SPI 的 MOSI 引脚(PA_7)对其进行驱动。

软件部分

查阅资料,我们可以得知WS2812需要严格的时序,我们将参考此处open in new window的代码,使用 SPI 进行驱动。

WS2812 的控制时序,此处不作深入讲解
文档时序内容
文档时序内容

首先,为了保证 SPI 频率在 8MHz,我们需要将芯片的主频设置为 16M,这样只要设置 SPI 二分频即可实现输出为 8MHz。

在Arduino中设置芯片主频
设置芯片主频
设置芯片主频

我们在代码开头,引用库与定义需要使用的全局量:

#include<SPI.h>
//LED灯的个数
#define LED_N   64
//用于存储当前LED灯的状态,默认全为(0,0,0)不亮
unsigned char LED_T[LED_N][3] = {0};

这里的LED_T用于存储当前的LED灯颜色数据,可以在刷新时一次性将所有状态全部发给WS2812

setup初始化函数中,我们初始化一下SPI,并将灯的状态初始化(全部熄灭):

void setup (void) {
  //初始化SPI
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE1);
  // 这里需要让SPI处于8MHz
  // 所以芯片要设置16M,SPI配置为2分频:
  // 16/2=8MHz
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  delay(10);
  //刷新一下所有灯的状态,函数见文档接下来的内容
  WS2812_refresh();
}

这里的重点是SPI.setClockDivider函数,它设置了 SPI 频率在 8MHz。
SPI.setBitOrderSPI.setDataMode的配置,也保证了后续模拟时序的正确性。

接下来是真正模拟WS2812时序的函数,这个函数传入 RGB 值,转换为信号进行发送:

void WS2812_send(unsigned char r, unsigned char g, unsigned char b) {
  unsigned char bits = 24;
  unsigned long value = 0x00000000;
  value = (((unsigned long)g << 16) | ((unsigned long)r << 8) | ((unsigned long)b));
  while (bits > 0) {
    if ((value & 0x800000) != LOW) {
      SPI.transfer(0xF8);//1
      asm("nop");
      asm("nop");
    } else {
      SPI.transfer(0xC0);//0
    }
    value <<= 1;
    bits--;
  }
}

由于WS2812灯珠是串在一起进行通信的,当需要控制所有灯珠时,只要将颜色数据一个个发送出去就可以了。
所以WS2812_refresh刷新函数就像下面这样,依次发送颜色数据:

void WS2812_refresh() {
  unsigned int n = 0;
  for (n = 0; n < LED_N; n++) {
    WS2812_send(LED_T[n][0], LED_T[n][1], LED_T[n][2]);
  }
  delayMicroseconds(60);
}

最后我们在loop函数中,简单写一个按顺序亮灯的小功能:

int count = 0;
void loop(void) {
  count++;
  //把上一次亮的灯灭了
  LED_T[(count-1)%LED_N][0] = 0;
  LED_T[(count-1)%LED_N][1] = 0;
  LED_T[(count-1)%LED_N][2] = 0;
  //点亮这次的这个灯
  //来个浅蓝色吧
  //亮度低一点,不然刺眼
  LED_T[count%LED_N][0] = 5; //R
  LED_T[count%LED_N][1] = 5; //G
  LED_T[count%LED_N][2] = 20;//B
  //刷新所有灯的状态
  WS2812_refresh();
  //延时一小会
  delay(10);
}

输出结果

可以看到,灯会按顺序闪烁:

灯按顺序亮起
灯按顺序亮起