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