找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 133|回复: 0
收起左侧

[正点原子]WS2812B和WS2811F 驱动技术分享

[复制链接]
ID:1148840 发表于 2025-4-24 11:49 | 显示全部楼层 |阅读模式
1,WS2812B和WS2811F简介
RGBLED驱动芯片有两个常用的选择:WS2812B和WS2811F,它们都采用单线归零码的通讯方式,数据发送速度可达800Kbps。
芯片在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个芯片提取后,送到芯片内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的芯片,每经过一个芯片的传输,信号减少24bit。芯片采用自动整形转发技术,使得该芯片的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。
当各个芯片都收到24bit的数据后,我们再发送一个大于280us的低电平复位信号,所有的芯片再同步发送对应的R、G、B信号,来控制RGBLED灯的红色、绿色和蓝色三种发光二极。我们就可以看见RGBLED灯亮不同的颜色了。
特别说明:WS2812B和WS2811F通讯和级联方式等,都基本一样,只是24位数据结构有先后区别。


图1 WS2812B或WS2811F级联示意图


图2 WS2812B或WS2811F数据传输方法

24位的数据结构(WS2812B):按GRB顺序,高位在前

图3 24位的数据结构(WS2812B)

24位的数据结构(WS2811F):按RGB顺序,高位在前

图4 24位的数据结构(WS2811F)
对我们的驱动来说,图3和图4是WS2812B和WS2811F的唯一区别,其他部分的代码编写是一模一样的。

WS2812B和WS2811F逻辑0、逻辑1和复位信号的时序如下:

图5 WS2812B和WS2811F逻辑0、逻辑1和复位信号的时序图


表1 WS2812B和WS2811F数据传输时间表
实际使用中,PWM的占空比为三分之一代表逻辑0,PWM的占空比为三分之二代表逻辑1,这个时间大致即可。
文章开头说了,数据发送速度可达800Kbps,即每一个数据位传输时间就是:1 / 800000s = 1.25us。每个RGB灯数据有24位,所以每一个RGB数据传输时间就是:1.25 * 24us = 30us。
假设用1000颗RGB灯组成一个矩阵屏,那么给屏幕刷一帧数据的时间为:1000*30+280us = 30280us,即矩阵屏的帧率为33帧每秒。这个帧率看个视频是够了,打游戏可能差点意思,哈哈哈。
特别注意:级联的灯珠太多,要考虑供电问题。一个LED电流大概是10~20mA,一个RGB灯有三个LED,即30~60mA。
2, 实验硬件简介
2.1,RGB灯方案
WS2811F方案的RGB灯,我们使用的是正点原子的BEE BLOCK系列的RGBLED模块。该模块设计非常美观,灯珠够大,显示效果很好,预留了灯珠级联接口。
WS2812B方案的RGB灯,我自己画了一块由20颗RGB灯级联的灯板。注意WS2812B芯片是集成到RGB灯内部了。

图6 BEE BLOCK系列RGBLED模块(左) 和 20颗RGB灯的灯板(右)

2.2,主控方案
主控制器使用STM32F103C8T6,这里直接使用正点原子的STM32入门套件BEE BLOCK系列,价格虽然贵一些,但是外观和品质是真的很好。

图7 正点原子的STM32入门套件BEE BLOCK系列

2.3,仪表工具
仪表工具使用正点原子的DL16逻辑分析仪,方便观察WS2812B和WS2811F的时序。

图8 正点原子的DL16逻辑分析仪

3,实验代码
3.1,WS2812B驱动代码
ws2812b.c代码如下:
TIM_HandleTypeDef g_tim2_handle = {0};          /* 定时器x句柄 */
DMA_HandleTypeDef g_dma_handle = {0};           /* DMA句柄 */

/* 用于存放10个RGBLED灯的颜色值 */
uint32_t g_rgb888_color[10] =
{
    GRB888_RED,         /* 红色 */
    GRB888_GREEN,       /* 绿色 */
    GRB888_BLUE,        /* 蓝色 */
    GRB888_VIOLET,      /* 紫罗兰 */
    GRB888_YELLOW,      /* 黄色 */
    GRB888_IRED,        /* 浅红色 */
    GRB888_ORANGE,      /* 橙色 */
    GRB888_PURPLE,      /* 紫色 */
    GRB888_PING,        /* 粉色 */
    GRB888_CYAN,        /* 青色 */
};

uint16_t g_pixel_buf[LED_NUM][DATA_SIZE]; /* 用于存放RGBLED灯数据对应的比较值 */

/**
* @brief       发送数据给ws2812b
* @param       无
* @retval      无
*/
void ws2812b_dats_send(void)
{
/* 使用DMA将数据发送到定时器的CCRx */
HAL_TIM_PWM_Start_DMA(&g_tim2_handle, TIM_CHANNEL_3,
(uint32_t *)g_pixel_buf, sizeof(g_pixel_buf));
}

/**
* @brief       复位ws2812b
* @note        保持低电平280us以上即可
* @param       无
* @retval      无
*/
void ws2812b_reset(void)
{
/* CCRX设置为0,直接输出低电平 */
    __HAL_TIM_SetCompare(&g_tim2_handle, TIM_CHANNEL_3, 0);
    delay_us(280);
}

/**
* @brief       将GRB888的颜色值转换成逻辑1和逻辑0对应的CCRx值并存放到
*               g_pixel_buf里,并发送给CCRx,生成PWM
* @note        可指定第几个灯
* @param       led_num : 需控制灯的数量
* @param       color  : GRB888颜色值指针
* @retval      无
*/
void ws2812b_display(uint8_t led_num, uint32_t *color_buf)
{
    uint8_t i, j;
    if(led_num > LED_NUM)return;    /* 防止写入LED数量大于LED总数 */

/* 这里是对 g_pixel_buf[j]写入一个周期内高电平的持续时间
(是要存放在比较值寄存器CCR的值)*/
    for(i = 0; i < led_num; i++)
    {
        for(j = 0; j < DATA_SIZE; j++)
        {
            g_pixel_buf[j] =
(((color_buf << j) & 0x800000) ? HIGH_LEVEL : LOW_LEVEL);
        }
    }

    ws2812b_dats_send();    /* 将g_pixel_buf发送到定时器的CCRx中,生成相应波形 */
}

/**
* @brief       ws2812b不显示颜色,复位状态
* @param       无
* @retval      无
*/
void ws2812b_show_black(void)
{
    uint32_t rgb_buf[LED_NUM];
    for(int j = 0; j < LED_NUM; j++)
    {
        rgb_buf[j] = GRB888_BLACK;
    }
    ws2812b_display(LED_NUM, rgb_buf);  /* RGBLED灯光显示 */
}

/**
* @brief       初始化ws2812b相关IO口,以及配置定时器和DMA功能 并使能时钟
* @param       无
* @retval      无
*/
void ws2812b_init(void)
{
    TIM_OC_InitTypeDeftimx_oc_pwm_chy = {0};  /* 定时器输出句柄 */
    GPIO_InitTypeDefgpio_init_struct = {0};

    __HAL_RCC_DMA1_CLK_ENABLE();                  /* DMA1时钟使能 */
    __HAL_RCC_GPIOA_CLK_ENABLE();                 /* 开启通道y的GPIO时钟 */
    __HAL_RCC_TIM2_CLK_ENABLE();                   /* 使能定时器时钟 */

    gpio_init_struct.Pin = GPIO_PIN_2;          /* 通道的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;    /* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_NOPULL;         /* 无上下拉 */
    gpio_init_struct.Speed =GPIO_SPEED_FREQ_HIGH;     /* 高速 */
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);

    g_tim2_handle.Instance = TIM2;                 /* 定时器x */
    g_tim2_handle.Init.Prescaler = (1 - 1);      /* 定时器分频 */
    g_tim2_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
    g_tim2_handle.Init.Period = (90 - 1);                   /* 自动重装载值 */
    g_tim2_handle.Init.ClockDivision =TIM_CLOCKDIVISION_DIV1;
    g_tim2_handle.Init.AutoReloadPreload =TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_PWM_Init(&g_tim2_handle);             /* 初始化PWM */

    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;   /* 模式选择PWM1 */
    timx_oc_pwm_chy.Pulse = 0;                      /* 设置比较值,此值用来确定占空比 */
    timx_oc_pwm_chy.OCPolarity =TIM_OCPOLARITY_HIGH;    /* 输出比较极性为高 */
    timx_oc_pwm_chy.OCIdleState =TIM_OCIDLESTATE_RESET;/* 空闲时输出低电平 */
HAL_TIM_PWM_ConfigChannel(&g_tim2_handle, &timx_oc_pwm_chy,TIM_CHANNEL_3);
/* 使能CCRX的影子寄存器,当修改CCRX的值时,等产生更新事件在生效 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_tim2_handle, TIM_CHANNEL_3);                                 

    g_dma_handle.Instance = DMA1_Channel1;                   /* DMA1通道1 */
    g_dma_handle.Init.Direction =DMA_MEMORY_TO_PERIPH;   /* 从内存到外设模式 */
    g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;        /* 外设非增量模式 */
    g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;    /* 存储器增量模式 */
    g_dma_handle.Init.PeriphDataAlignment= DMA_PDATAALIGN_HALFWORD;/* 16位 */
    g_dma_handle.Init.MemDataAlignment =DMA_MDATAALIGN_BYTE;         /* 16位 */
    g_dma_handle.Init.Mode = DMA_NORMAL;                   /* 普通模式 */
    g_dma_handle.Init.Priority = DMA_PRIORITY_HIGH;      /* 高优先级 */
    HAL_DMA_Init(&g_dma_handle);

/* 将DMA与timer联系起来 */
    __HAL_LINKDMA(&g_tim2_handle, hdma[TIM_DMA_ID_CC3], g_dma_handle);                          

    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 1, 1);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

    ws2812b_show_black();
}

/**
* @brief       DMA1通道1中断服务函数
* @param       无
* @retval      无
*/
void DMA1_Channel1_IRQHandler(void)
{
if(__HAL_DMA_GET_FLAG(&g_dma_handle,
__HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_handle)) != RESET)
    {
        __HAL_DMA_CLEAR_FLAG(&g_dma_handle,
__HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_handle));

        ws2812b_reset();
/* 传输完成后手动停止PWM */
        HAL_TIM_PWM_Stop_DMA(&g_tim2_handle, TIM_CHANNEL_3);  
    }
}
ws2812b.h代码如下:
/**************************************************************************/
/* RGBLED的数据高低电平逻辑定义 */
#define HIGH_LEVEL     (uint16_t)60     /* CCR3为占空比为三分之二代表逻辑1 */
#define LOW_LEVEL      (uint16_t)30     /* CCR3为占空比为三分之一代表逻辑0 */

#define LED_NUM         20               /* 灯珠的个数 */
#define DATA_SIZE       24               /* 一个RGB数据的位数,3*8 */
/**************************************************************************/
/* RGB888颜色值 */
#define GRB888_RED         0x00FF00        /* 红色 */
#define GRB888_GREEN       0xFF0000        /* 绿色 */
#define GRB888_BLUE        0x0000FF        /* 蓝色 */
#define GRB888_BLACK       0x000000        /* 黑色 */
#define GRB888_WHITE       0xFFFFFF        /* 白色 */
#define GRB888_YELLOW      0xFFFF00        /* 黄色 */
#define GRB888_IRED        0x5CCD5C        /* 浅红色 */
#define GRB888_ORANGE      0xA5FF00        /* 橙色 */
#define GRB888_PURPLE      0x008080        /* 紫色 */
#define GRB888_PING        0xB6FFC1        /* 浅粉色 */
#define GRB888_CYAN        0xFF00FF        /* 青色 */
#define GRB888_PBLUE       0x80008C        /* 孔雀蓝 */
#define GRB888_VIOLET      0x008BFF        /* 紫罗兰 */
/**************************************************************************/

extern  TIM_HandleTypeDefg_tim2_handle;                 /* 定时器x句柄 */
extern uint32_t g_rgb888_color[10];

/* 函数声明 */
void ws2812b_init(void);                                    /* 初始化W2812B */
void ws2812b_reset(void);                                   /* 复位W2812B */
/* 写入多个颜色值到W2812B(控制多个灯) */
void ws2812b_display(uint8_t led_num, uint32_t *color);  
uint32_t color_change_brigh(uint32_t rgb, float k);    /* 改变颜色的亮度值 */
void ws2812b_show_black(void);                             /* 不显示颜色,关灯 */
3.2,WS2811F驱动代码
WS2811F的代码和ws2812b几乎一样,只有RGB数据结构顺序上有区别。下面把有差异的代码贴出来。
WS2811F.c有差异部分代码如下:
/* 用于存放10个RGBLED灯的颜色值 */
uint32_t g_rgb888_color[10] =
{
    RGB888_RED,         /* 红色 */
    RGB888_GREEN,       /* 绿色 */
    RGB888_BLUE,        /* 蓝色 */
    RGB888_VIOLET,      /* 紫罗兰 */
    RGB888_YELLOW,      /* 黄色 */
    RGB888_VRED,        /* 朱红色 */
    RGB888_ORANGE,      /* 橙色 */
    RGB888_PURPLE,      /* 紫色 */
    RGB888_PGREEN,      /* 孔雀绿 */
    RGB888_CYAN,        /* 青色 */
};
WS2811F.h有差异部分代码如下:
/**************************************************************************/
/* RGB888颜色值 */
#define RGB888_RED         0xFF0000        /* 红色 */
#define RGB888_GREEN       0x00FF00        /* 绿色 */
#define RGB888_BLUE        0x0000FF        /* 蓝色 */
#define RGB888_BLACK       0x000000        /* 黑色 */
#define RGB888_WHITE       0xFFFFFF        /* 白色 */
#define RGB888_YELLOW      0xFFFF00        /* 黄色 */
#define RGB888_VRED        0xFF4D00        /* 朱红色 */
#define RGB888_ORANGE      0xFFA500        /* 橙色 */
#define RGB888_PURPLE      0x800080        /* 紫色 */
#define RGB888_PING        0xFFC0CB        /* 粉色 */
#define RGB888_CYAN        0x00FFFF        /* 青色 */
#define RGB888_PGREEN      0x00A15C        /* 孔雀绿 */
#define RGB888_VIOLET      0x8B00FF        /* 紫罗兰 */
/**************************************************************************/
3.3,main.c代码
int main(void)
{
    uint8_t i = 0, j = 0;
    uint32_t rgb_buf[LED_NUM];           /* 各灯珠RGB颜色缓冲区 */

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                        /* 延时初始化 */
    usart_init(115200);                    /* 串口初始化为115200 */
    ws2812b_init();                        /* 初始化RGBLED */

    while (1)
    {
        /* 多个灯珠亮相同颜色,10种颜色 */
        for(i = 0; i < 10; i++)
        {
            for(j = 0; j < LED_NUM; j++)
            {
                rgb_buf[j] = g_rgb888_color;
            }

            ws2812b_display(LED_NUM, rgb_buf);  /* RGBLED灯光显示 */
            delay_ms(500);
        }
    }
}
在本实验中,我们根据连接的灯珠数量设置好LED_NUM宏定义。
mian函数中,实现多个灯珠亮相同颜色,共10种颜色。
详细的代码可以下载正点原子BEE BLOCK系列RGBLED模块的资料查看。网址如下:openedv/docs/boards/stm32/beeblock.html
4,实验现象
1,WS2812B实验现象

图9 WS2812B实验现象


2,WS2811F实验现象

图10 WS2811F实验现象

3,用DL16逻辑分析仪观察PWM
WS2812B数据结构是GRB888,所以红色数据是0x00FF00。

图11 WS2812B时序

WS2811F数据结构是RGB888,所以红色数据是0xFF0000。

图12 WS2811F时序
PWM详细参数如下图:

图13 PWM时序信息
PWM周期为1.25us,逻辑1高电平时间为850ns,低电平时间为400ns。所以可以知道逻辑0高电平时间为400ns,低电平时间为850ns。

【正点原子】WS2812B和WS2811F.pdf

1.52 MB, 下载次数: 0, 下载积分: 黑币 -5

原文章

1,WS2812B.7z

597.23 KB, 下载次数: 0, 下载积分: 黑币 -5

源码

2,WS2811F.7z

597.49 KB, 下载次数: 0, 下载积分: 黑币 -5

源码

3,RGBLED模块(IO模拟时序WS2811F).7z

680.97 KB, 下载次数: 0, 下载积分: 黑币 -5

源码

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表