在日常生活中,我们不免会接触到各种各样的光源。众所周知,任何颜色的光源都可以通过红、绿、蓝三种颜色的光源混合而成,这样我们最多只需要三颗灯珠就可以合成任意颜色的光源了。但是如果要单独控制每一颗灯珠的颜色的话需要很多的 IO,这时候 WS2812 之类的灯珠就派上用场了。WS2812 是一种智能控制的 LED 灯珠,它可以通过单线串行通信来控制每颗灯珠的颜色和亮度,并且可以级联多个灯珠。
WS2812 通讯协议
WS2812 使用一种叫做单极归零码的协议。查询手册我们可以看到它的时序定义:
可以看出来,和常用的 UART 等协议不同,它的每一个 bit 总是从高电平开始,到低电平结束,每一个 bit 周期内都会有一次下降沿发生,因此称之为单极归零码。其中的高低电平的不同持续时间也就决定了是逻辑 1 还是逻辑 0。并且每个 bit 的持续时间总是差不多的,整体来说通信的速率差不多是 800Kbps。
另外需要注意的就是 RESET 信号,它是一个低电平信号,持续时间大于 80us 时会重置通信。这个信号在实际应用中通常是由主控芯片在发送完数据后主动拉低的。
WS2812 显示格式
我们知道,任何颜色的光都可以通过 RGB 三种颜色混合而成。幸运的是 WS2812 给我们提供了一个方便的接口,可以直接设置每颗灯珠的 RGB 值。每颗灯珠的 RGB 值由 3 个字节共 24 个 bit 组成,分别表示红色、绿色和蓝色的亮度值。每个字节的取值范围是 0-255。
另外在图中也可以看出,如果发完一个灯珠的数据后继续发送下一个灯珠的数据,那么第一颗灯珠将会自动转发数据给第二颗灯珠。因此我们可以通过级联多个 WS2812 灯珠来实现更复杂的显示效果。
从硬件连接中可以看出,WS2812 连接到了PA32上,并且电源使用的是芯片片上 LDO 输出。因此在使用之前需要先打开芯片的 LDO 电源输出。
图形化配置
RGBLED 依赖 pwm 设备,因此需要在 menuconfig 中配置 RGBLED 的使能以及 pwm 设备名称和通道。需要注意的是修改了 pwm 设备名称和通道之后,初始化的 PINMUX 同样需要修改。
程序讲解
初始化定时器
在 SiFli-SDK 中,对 WS2812 之类的灯珠进行了封装,可以直接使用rgbled设备进行操作。需要注意的是,WS2812 的通信模拟我们使用定时器+DMA 的形式进行模拟。因此在使用之前我们需要初始化定时器的 IO,以及打开灯珠供电的电源。
static void rgb_led_init()
{
HAL_PMU_ConfigPeriLdo(PMU_PERI_LDO3_3V3, true, true);
HAL_PIN_Set(PAD_PA32, GPTIM2_CH1, PIN_NOPULL, 1);
rgbled_device = rt_device_find(RGBLED_NAME); // find rgb
if (!rgbled_device)
{
RT_ASSERT(0);
}
}
2
3
4
5
6
7
8
9
10
控制灯珠颜色
控制灯珠颜色,我们使用以下函数来进行:
static void rgb_led_set_color(uint32_t color)
{
struct rt_rgbled_configuration configuration;
configuration.color_rgb = color;
rt_device_control(rgbled_device, PWM_CMD_SET_COLOR, &configuration);
}
2
3
4
5
6
程序编写
#include "rtthread.h"
#include "bf0_hal.h"
#include "drv_io.h"
#include "stdio.h"
#include "string.h"
#include "drivers/rt_drv_pwm.h"
#define RGB_COLOR (0x00ff00)
#define RGBLED_NAME "rgbled"
struct rt_device *rgbled_device;
struct rt_color
{
char *color_name;
uint32_t color;
};
static struct rt_color rgb_color_arry[] = {
{"black", 0x000000}, {"blue", 0x00001f}, {"green", 0x001f00},
{"cyan", 0x001f1f}, {"red", 0x1f0000}, {"purple", 0x1f001f},
{"yellow", 0x1f1f00}, {"white", 0x1f1f1f}};
void rgb_led_init()
{
HAL_PMU_ConfigPeriLdo(PMU_PERI_LDO3_3V3, true, true);
HAL_PIN_Set(PAD_PA32, GPTIM2_CH1, PIN_NOPULL, 1);
rgbled_device = rt_device_find(RGBLED_NAME);//find rgb
if (!rgbled_device)
{
RT_ASSERT(0);
}
}
void rgb_led_set_color(uint32_t color)
{
struct rt_rgbled_configuration configuration;
configuration.color_rgb = color;
rt_device_control(rgbled_device, PWM_CMD_SET_COLOR, &configuration);
}
void rgb_color_array_display()
{
uint16_t i = 0;
rt_kprintf("start display color!\n");
while (1)
{
if (i < sizeof(rgb_color_arry) / sizeof(struct rt_color))
{
rt_kprintf("-> %s\n", rgb_color_arry[i].color_name);
rgb_led_set_color(rgb_color_arry[i].color);
rt_thread_mdelay(1000);
}
i++;
if (i >= 8)
i = 0;
}
}
/**
* @brief Main program
* @param None
* @retval 0 if success, otherwise failure number
*/
int main(void)
{
/* Output a message on console using printf function */
rt_kprintf("Hello world!\n");
rgb_led_init();
//rgb_color_auto();
rgb_color_array_display();
/* Infinite loop */
while (1)
{
rt_thread_mdelay(1);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79