|
| 1 | +--- |
| 2 | +title: 彩色灯珠 WS2812 |
| 3 | +order: 11 |
| 4 | +icon: braille |
| 5 | +--- |
| 6 | + |
| 7 | +## 简介 |
| 8 | + |
| 9 | +本章介绍使用Air001开发板驱动 WS2812 灯。 |
| 10 | + |
| 11 | +::: tip |
| 12 | + |
| 13 | +[WS2812 是一个三色 LED 驱动芯片](http://world-semi.com/ws2812-family/),一般集成在 LED 灯珠中。 |
| 14 | +它仅需一个 IO 传输数据,可通过多个 LED 串联传递信号,实现批量控制。 |
| 15 | + |
| 16 | +::: |
| 17 | + |
| 18 | +## 硬件准备 |
| 19 | + |
| 20 | +- 按[☁️ Air001开发板入门](/tutorial-advanced/Air001_start.html),将`Air001`和`DAPLink调试器`使用排针排母连接。 |
| 21 | + |
| 22 | +- 将`Air001开发板`的 USB 插入一根数据线,用向灯板提供 5V 电压。 |
| 23 | + |
| 24 | +- 将`WS2812`LED 模块与`Air001开发板`,按如下表格进行相连: |
| 25 | + |
| 26 | +| WS2812 模块 | Air001 | |
| 27 | +| :---------: | :------: | |
| 28 | +| GND | GND | |
| 29 | +| VCC | VBUS(5V) | |
| 30 | +| DIN | PA_7 | |
| 31 | + |
| 32 | +::: tip |
| 33 | + |
| 34 | +由于 WS2812 的时序要求相对严格,我们将使用 SPI 的 MOSI 引脚(PA_7)对其进行驱动。 |
| 35 | + |
| 36 | +::: |
| 37 | + |
| 38 | +## 软件部分 |
| 39 | + |
| 40 | +查阅资料,我们可以得知`WS2812`需要严格的时序,我们将参考[此处](https://github.com/452/arduino-ws2812-direct-spi-control)的代码,使用 SPI 进行驱动。 |
| 41 | + |
| 42 | +:::details WS2812 的控制时序,此处不作深入讲解 |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +::: |
| 47 | + |
| 48 | +首先,**为了保证 SPI 频率在 8MHz,我们需要将芯片的主频设置为 16M**,这样只要设置 SPI 二分频即可实现输出为 8MHz。 |
| 49 | + |
| 50 | +:::details 在Arduino中设置芯片主频 |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | +::: |
| 55 | + |
| 56 | +我们在代码开头,引用库与定义需要使用的全局量: |
| 57 | + |
| 58 | +```cpp |
| 59 | +#include<SPI.h> |
| 60 | +//LED灯的个数 |
| 61 | +#define LED_N 64 |
| 62 | +//用于存储当前LED灯的状态,默认全为(0,0,0)不亮 |
| 63 | +unsigned char LED_T[LED_N][3] = {0}; |
| 64 | +``` |
| 65 | +
|
| 66 | +这里的`LED_T`用于存储当前的LED灯颜色数据,可以在刷新时一次性将所有状态全部发给`WS2812`。 |
| 67 | +
|
| 68 | +在`setup`初始化函数中,我们初始化一下`SPI`,并将灯的状态初始化(全部熄灭): |
| 69 | +
|
| 70 | +```cpp |
| 71 | +void setup (void) { |
| 72 | + //初始化SPI |
| 73 | + SPI.begin(); |
| 74 | + SPI.setBitOrder(MSBFIRST); |
| 75 | + SPI.setDataMode(SPI_MODE1); |
| 76 | + // 这里需要让SPI处于8MHz |
| 77 | + // 所以芯片要设置16M,SPI配置为2分频: |
| 78 | + // 16/2=8MHz |
| 79 | + SPI.setClockDivider(SPI_CLOCK_DIV2); |
| 80 | + delay(10); |
| 81 | + //刷新一下所有灯的状态,函数见文档接下来的内容 |
| 82 | + WS2812_refresh(); |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +这里的重点是`SPI.setClockDivider`函数,它设置了 SPI 频率在 8MHz。 |
| 87 | +`SPI.setBitOrder`和`SPI.setDataMode`的配置,也保证了后续模拟时序的正确性。 |
| 88 | + |
| 89 | +接下来是真正模拟`WS2812`时序的函数,这个函数传入 RGB 值,转换为信号进行发送: |
| 90 | + |
| 91 | +```cpp |
| 92 | +void WS2812_send(unsigned char r, unsigned char g, unsigned char b) { |
| 93 | + unsigned char bits = 24; |
| 94 | + unsigned long value = 0x00000000; |
| 95 | + value = (((unsigned long)g << 16) | ((unsigned long)r << 8) | ((unsigned long)b)); |
| 96 | + while (bits > 0) { |
| 97 | + if ((value & 0x800000) != LOW) { |
| 98 | + SPI.transfer(0xF8);//1 |
| 99 | + asm("nop"); |
| 100 | + asm("nop"); |
| 101 | + } else { |
| 102 | + SPI.transfer(0xC0);//0 |
| 103 | + } |
| 104 | + value <<= 1; |
| 105 | + bits--; |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | +
|
| 110 | +由于`WS2812`灯珠是串在一起进行通信的,当需要控制所有灯珠时,只要将颜色数据一个个发送出去就可以了。 |
| 111 | +所以`WS2812_refresh`刷新函数就像下面这样,依次发送颜色数据: |
| 112 | +
|
| 113 | +```cpp |
| 114 | +void WS2812_refresh() { |
| 115 | + unsigned int n = 0; |
| 116 | + for (n = 0; n < LED_N; n++) { |
| 117 | + WS2812_send(LED_T[n][0], LED_T[n][1], LED_T[n][2]); |
| 118 | + } |
| 119 | + delayMicroseconds(60); |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +最后我们在`loop`函数中,简单写一个按顺序亮灯的小功能: |
| 124 | + |
| 125 | +```cpp |
| 126 | +int count = 0; |
| 127 | +void loop(void) { |
| 128 | + count++; |
| 129 | + //把上一次亮的灯灭了 |
| 130 | + LED_T[(count-1)%LED_N][0] = 0; |
| 131 | + LED_T[(count-1)%LED_N][1] = 0; |
| 132 | + LED_T[(count-1)%LED_N][2] = 0; |
| 133 | + //电亮这次的这个灯 |
| 134 | + //来个浅蓝色吧 |
| 135 | + //亮度低一点,不然刺眼 |
| 136 | + LED_T[count%LED_N][0] = 5; //R |
| 137 | + LED_T[count%LED_N][1] = 5; //G |
| 138 | + LED_T[count%LED_N][2] = 20;//B |
| 139 | + //刷新所有灯的状态 |
| 140 | + WS2812_refresh(); |
| 141 | + //延时一小会 |
| 142 | + delay(10); |
| 143 | +} |
| 144 | +``` |
| 145 | +
|
| 146 | +## 输出结果 |
| 147 | +
|
| 148 | +可以看到,灯会按顺序闪烁: |
| 149 | +
|
| 150 | + |
0 commit comments