【正点原子】WS2812B和WS2811F RGB灯带驱动技术分享

1,WS2812B和WS2811F简介

RGBLED驱动芯片有两个常用的选择:WS2812B和WS2811F,它们都采用单线归零码的通讯方式,数据发送速度可达800Kbps。

芯片在上电复位以后,DIN端接受从控制器传输过来的数据,首先送过来的24bit数据被第一个芯片提取后,送到芯片内部的数据锁存器,剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的芯片,每经过一个芯片的传输,信号减少24bit。芯片采用自动整形转发技术,使得该芯片的级联个数不受信号传送的限制,仅仅受限信号传输速度要求。

当各个芯片都收到24bit的数据后,我们再发送一个大于280us的低电平复位信号,所有的芯片再同步发送对应的R、G、B信号,来控制RGBLED灯的红色、绿色和蓝色三种发光二极。我们就可以看见RGBLED灯亮不同的颜色了。

特别说明:WS2812BWS2811F通讯和级联方式等,都基本一样,只是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驱动代码

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[i][j]写入一个周期内高电平的持续时间
      (是要存放在比较值寄存器CCR的值)*/
    for(i = 0; i < led_num; i++)
    {
        for(j = 0; j < DATA_SIZE; j++)
        {
            g_pixel_buf[i][j] = 
            (((color_buf[i] << 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_InitTypeDef timx_oc_pwm_chy = {0};	/* 定时器输出句柄 */
    GPIO_InitTypeDef gpio_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_HandleTypeDef g_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[i];
            }
            
            ws2812b_display(LED_NUM, rgb_buf);  /* RGBLED灯光显示 */
            delay_ms(500);
        }
    }
}

在本实验中,我们根据连接的灯珠数量设置好LED_NUM宏定义。

mian函数中,实现多个灯珠亮相同颜色,共10种颜色。

详细的代码可以下载正点原子BEE BLOCK系列RGBLED模块的资料查看。网址如下:http://www.openedv.com/docs/boards/stm32/beeblock.html

4,实验现象

4.1,WS2812B实验现象

图9 WS2812B实验现象

4.2,WS2811F实验现象

图10 WS2811F实验现象

4.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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值