嵌入式开发笔记 - 写一个WS2812驱动

一、引言

在嵌入式开发中,WS2812是一种广泛使用的LED灯珠。它具有内置的驱动芯片,可以方便地控制颜色和亮度,因此在许多应用中都得到了广泛的应用。本文将介绍如何为WS2812编写一个驱动程序,以实现对其颜色和亮度的控制。

二、硬件连接

WS2812引脚定义

引脚图

WS2812典型应用电路

典型应用电路

在开始编写驱动程序之前,需要将WS2812连接到开发板上。通常,WS2812使用SPI接口进行通信,因此需要将WS2812的DIN引脚连接到开发板的SPI接口上。此外,还需要将WS2812的VCC和GND引脚连接到开发板的电源和地线。

特别注意WS2812电源电源为3.5v~5.3v,最好不要用单片机的3.3v

三、驱动程序编写

WS2812协议

级联和编码方法

级联和编码方法

WS2812协议编码

WS2812协议编码

由于控制WS2812的脉冲高低电平在0.85us,0.4us,时间间隔,为了产生这样的脉冲,使用普通的软件控制IO口是无法完成的,一般使用SPI或者PWM硬件控制,这里采用SPI控制。

当发送波形高电平(T0H)在220ns~380ns且低电平(TOL)在580ns~1us时表示编码 0,同理 1码和复位码也是如此。总结:一个编码的时间1.25us±300ns之间都能识别到,主要是识别高电平持续的时间;时间段固定,高电平短为0高电平时间长为1,280ns时间无高电平就表示这一个信号结束。

一颗ws2812的RGB灯里面有三颗灯珠,有红、绿、蓝三个颜色的LED灯。每一个灯使用PWM驱动,发送的数据即为PWM的宽度,一颗LED灯的数据宽度为 8 bits(1 byte) ,所以一颗 ws2812 RGB灯共需要24 bits(3 bytes)的数据。

一颗RGB灯发送数据的时序如下,高位先行:

G7 … G0 R7 … R0 B7 … B0

借助 SPI 来控制 WS2812,我们用 SPI 的 MOSI 接口的一个 Byte(8位)模拟 WS2812 的一个位。比如 SPI 设置的 7M 速率,使用SPI发送一个字节(byte)来模拟WS2812的一个编码(0或1)。比如用SPI发送的 0xF8(1111 1000)表示WS2812编码的1,发送的数据是 0xC0 (1100 0000)时这代表编码 0;这是因为在SPI发送一个字节的时间符合WS2812的一个编码的时间,发送0XF8时高电平持续时间在580ns~1us内,发送0xC0时高电平持续220ns~380ns内。即

SPI发送 0xF8 = 1 ,SPI发送 0xC0 = 0。

SPI发送24个字节可以控制一颗RGB灯,打成一个包,控制n个灯则需要:n x 包 +RET码。

SPI的速度在5.52——9.41MHz范围内,SPI发送一个字节时间长度等于一个编码的时间长度。

在编写WS2812驱动程序时,需要考虑以下几个方面:

  1. 初始化WS2812
    初始化WS2812最重要的就是初始化控制的灯珠数量和SPI的发送函数。初始化时创建一个大小为控制灯珠数量*24的缓冲区用来操作WS2812。

  2. 控制WS2812的颜色和亮度
    在控制WS2812前首先调用cleanBuffer()清空缓冲区,然后再对缓冲区进行修改,然后调用sendBuffer(),将缓冲区的内容发送至WS2812完成显示。控制函数如下:

    1
    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
    /// @brief 设置一颗灯珠的颜色
    /// @param LedId 该灯珠的id,第一个灯珠id为0,下一个id加一,以此类推
    /// @param rgb_t 设置的rgb值,如RGB(255,255,255)
    void setColor(uint8_t LedId, const RGBColor_TypeDef *rgb_t)
    {
    if (LedId < num)
    {
    uint8_t dat_r = rgb_t->R;
    uint8_t dat_g = rgb_t->G;
    uint8_t dat_b = rgb_t->B;
    for (int j = 0; j < 8; j++)
    {
    *(pbuf+LedId*24+7-j) = code[dat_g & 0x01];
    *(pbuf+LedId*24+15 - j) = code[dat_r & 0x01];
    *(pbuf+LedId*24+23 - j) = code[dat_b & 0x01];
    dat_g >>= 1;
    dat_r >>= 1;
    dat_b >>= 1;
    }
    }
    }

    /// @brief 设置一定数目的灯珠颜色
    /// @param LedId 起始灯珠的id,第一个灯珠id为0,下一个id加一,以此类推
    /// @param rgb_t 设置的rgb值,如RGB(255,255,255)
    /// @param num 灯珠数量
    void setColor(uint8_t LedId, const RGBColor_TypeDef *rgb_t, uint8_t num)
    {
    while (LedId < this->num && LedId < num)
    {
    setColor(LedId, rgb_t);
    LedId++;
    }
    }

    /// @brief 把全部灯珠设置为某个颜色
    /// @param rgb_t 设置的rgb值,如RGB(255,255,255)
    void setColor(const RGBColor_TypeDef *rgb_t)
    {
    uint8_t LedId = 0;
    while (LedId < this->num)
    {
    setColor(LedId, rgb_t);
    LedId++;
    }
    }

四、测试与调试

完成驱动程序编写后,需要进行测试和调试。可以使用串口输出调试信息,以便快速定位问题。此外,还可以使用示波器、逻辑分析仪等工具测试WS2812的GPIO引脚信号,以确保驱动程序正确控制了WS2812的颜色和亮度。

五、总结

本文介绍了如何为WS2812编写一个驱动程序,以实现对其颜色和亮度的控制。通过掌握WS2812的硬件连接、驱动程序编写、测试与调试等方面的知识,可以更好地应用WS2812,并为其应用提供有力的支持。

六、示例工程

https://github.com/Retuze/WS2812