一、硬件介绍
WS2812B控制IC下常见的相关模块(方形 / 圆形LED灯)
主要特点:
● IC控制电路与LED点光源共用一个电源。
● 控制电路与RGB芯片集成在一个5050封装的元器件中, 构成一个完整的外控像素点。
● 内置信号整形电路, 任何一个像素点收到信号后经过波形整形再输出, 保证线路波形畸变不会累加。
● 内置上电复位和掉电复位电路。
● 每个像素点的三基色颜色可实现256级亮度显示, 完成16777216种颜色的全真色彩显示。
● 端口扫描频率2KHz/s。
● 串行级联接口, 能通过一根信号线完成数据的接收与解码。
● 任意两点传输距离在不超过5米时无需增加任何电路。
● 当刷新速率30帧/秒时, 级联数不小于1024点。
● 数据发送速度可达800Kbps。
● 光的颜色高度一致, 性价比高
产品概述:
WS2812B是一个集控制电路与发光电路于一体的智能外控LED光源其外型与一个5050LED灯珠相同, 每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路, 还包含有高精度的内部振荡器和可编程定电流控制部分, 有效保证了像素点光的颜色高度一致。
数据协议采用单线归零码的通讯方式, 像素点在上电复位以后, DIN端接受从控制器传输过来的数据。
首先送过来的24bit数据被第一个像素点提取后, 送到像素点内部的数据锁存器, 剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点, 每经过一个像素点的传输, 信号减少24bit。
像素点采用自动整形转发技术, 使得该像素点的级联个数不受信号传送的限制, 仅受限信号传输速度要求。高达 2KHz 的端口扫描频率, 在高清摄像头的捕捉下都不会出现闪烁现象, 非常适合高速移动产品的使用。280μs以上的RESET时间, 出现中断也不会引起误复位, 可以支持更低频率、 价格便宜的MCU。LED具有低电压驱动、 环保节能、 亮度高、 散射角度大、 一致性好超、 低功率及超长寿命等优点。 将控制电路集成于LED上面, 电路变得更加简单, 体积小, 安装更加简便。
二、硬件引脚连接
硬件引脚连接:
VCC -> 5V
GND -> GND
IN -> PA15 ( 定时器2 PWM_CH1 )
OUT -> IN ( 若要构成级联,连接到下一个模块的IN端 )
每个灯珠全亮的功率:0.3W
每个灯珠全亮的电流:0.6mA
三、程序代码
以STM32F103C8T6_HAL库代码为例,驱动WS2812B_RGB模块。
1. WS2812B的通信协议要求
WS2812B使用一种特殊的单线通信协议,通过高低电平的持续时间来区分数据位(0和1)。
具体时序要求如下:
- 逻辑0:高电平持续时间约为0.25µs,低电平持续时间约为1µs。
- 逻辑1:高电平持续时间约为1µs,低电平持续时间约为0.25µs。
- 复位信号:低电平持续时间至少为280µs。
这些时序要求非常严格,误差不能超过±150ns,否则WS2812B无法正确解析数据。
2.使用PWM控制电平时间
PWM(脉宽调制)是一种通过调节信号的占空比(高电平时间与周期的比例)来模拟特定波形的方法。
对于WS2812B:
- PWM可以精确控制高电平和低电平的持续时间。
- 通过调整PWM的占空比,可以生成符合WS2812B要求的逻辑0和逻辑1信号。
例如:
- 使用800kHz的PWM频率(周期为1.25µs):
- 逻辑0:高电平占空比为20%(0.25µs高电平,1µs低电平)。
- 逻辑1:高电平占空比为80%(1µs高电平,0.25µs低电平)。
通过PWM,可以轻松生成符合WS2812B要求的信号。
3.使用DMA传输数据
DMA(直接内存访问)是一种硬件功能,允许外设直接访问内存,而不需要CPU的干预。
使用DMA的好处包括:
- 减少CPU负担:CPU不需要参与每个PWM周期的数据传输,可以执行其他任务。
- 高精度:DMA可以确保数据传输的精确性和实时性,避免因CPU中断或任务切换导致的时序误差。
- 高效传输:DMA可以一次性传输大量数据(如多个LED的颜色数据),而不需要CPU逐字节处理。
在驱动WS2812B时:
-
每个LED需要24个PWM周期(每个颜色位一个周期)。
-
多个LED的数据需要连续传输,且不能中断。
-
DMA可以将所有LED的数据一次性传输到定时器的PWM寄存器中,确保信号的连续性和精确性。
4、HAL库相关配置
时钟树配置:主频为72Mhz,不分频
1、TIM2定时器配置
需要配置定时器生成800kHz的PWM信号。(WS2812B的通信频率为800kHz,详见硬件介绍章节)
- 定时器预分频器(Prescaler)设置为0(不分频)。
- 定时器周期(Period)设置为89 (90 - 1),以生成800kHz的PWM信号。
PWM频率 = 系统时钟 / (Prescaler + 1) / (Period + 1)
800kHz = 72MHz / 1 / (89 + 1)
2、DMA配置
TIM的CCR为16位(Half Word)
5、相关代码
WS2812B.c:
WS2812B.c:
#include "ws2812b.h"
extern TIM_HandleTypeDef htim2;
extern DMA_HandleTypeDef hdma_tim2_ch1;
//每一行24个bit代表一个RGB数据
volatile uint16_t RGB_Buf[ LED_COUNT + 1 ][ RGB_COUNT ]; // 最后一组数据为低电平,复位数据。
/*
功能:GRB -> RGB颜色转换
WS2812B的发送顺序为 GRB顺序(高位在前),需转换将要发送的 RGB(常用) [ 255,255,255 ] -> GRB
Brightness:亮度设置(0~1f)
*/
static void RGB_Color(uint8_t LED_Num,RGB_Color_TypeDef Color,float Brightness){
Brightness = (Brightness > 1.0f) ? 1.0f : Brightness;
Brightness = (Brightness < 0.0f) ? 0.0f : Brightness;
// Gamma校正 + 亮度调整
float gamma = 2.2f;
uint8_t g_out = (uint8_t)(powf(Color.G / 255.0f, gamma) * Brightness * 255.0f + 0.5f);
uint8_t r_out = (uint8_t)(powf(Color.R / 255.0f, gamma) * Brightness * 255.0f + 0.5f);
uint8_t b_out = (uint8_t)(powf(Color.B / 255.0f, gamma) * Brightness * 255.0f + 0.5f);
//颜色转换
for(uint8_t i=0;i<24;i++){
if(i<8) RGB_Buf[LED_Num][i] = ( g_out ) & (1 << (7 -i)) ? HIGH : LOW;
else if(i<16) RGB_Buf[LED_Num][i] = ( r_out ) & (1 << (15 -i)) ? HIGH : LOW;
else
RGB_Buf[LED_Num][i] = ( b_out ) & (1 << (23 -i)) ? HIGH : LOW;
}
}
/*
功能:所有LED关闭(清屏)
*/
static void RGB_BLACK_All(void){
for(uint8_t Led =0;Led<LED_COUNT;Led++)
RGB_Color(Led,BLACK,0);
}
/*
功能:设置多个LED (固定颜色)
Led_Arr:要设置的LED (每行所在的位置)
Color:颜色RGB {255,255,255}
Brightness:亮度设置(0~1f)
*/
void Led_Single_Color(uint8_t Led_Arr[],RGB_Color_TypeDef Color,float Brightness)
{
for (uint8_t y = 0; y < LED_HEIGHT; y++) { // 8 * 8
for (uint8_t x = 0; x < LED_WIDTH; x++) {
uint8_t led_index = y * LED_WIDTH + x; // 0~63
if (Led_Arr[y] & (1 << (7-x))) {
RGB_Color(led_index, Color,Brightness);
}else{
RGB_Color(led_index, BLACK,0);
}
}
}
/* 传输设置好的颜色数据
功能:发送数组RGB_Buf内的所有数据,包含Reset数据
发送8位数据,需要1.25us;发送LED_COUNT个数据需要:LED_COUNT * 24 * 1.25us + Reset
Reset: 24 * 1.25us = 30us;
30us足够。
*/
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)RGB_Buf,(LED_COUNT + 1)*24);
}
/*
功能:设置多个LED (自定义颜色)
Led_Arr:要设置的LED (每行所在的位置)
Color[]:颜色RGB数组 {255,255,255}...
Brightness:亮度设置(0~1f)
*/
void Led_Multiple_Colors(uint8_t Led_Arr[],RGB_Color_TypeDef Color[],float Brightness)
{
uint8_t RGB_Num = 0;
for (uint8_t y = 0; y < LED_HEIGHT; y++) {
for (uint8_t x = 0; x < LED_WIDTH; x++) {
uint8_t led_index = y * LED_WIDTH + x;
if (Led_Arr[y] & (1 << (7-x))) {
RGB_Color(led_index, Color[RGB_Num++],Brightness);
}else{
RGB_Color(led_index, BLACK,0);
}
}
}
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)RGB_Buf,(LED_COUNT + 1)*24);
}
//清除所有LED颜色(清屏)
void RGB_Off(void){
RGB_BLACK_All();
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)RGB_Buf,(LED_COUNT + 1)*24);
}
// PWM DMA 完成回调函数 停止
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim2)
{
HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_1);
}
}
WS2812B.h:
WS2812B.h:
#ifndef __WS2812B_H__
#define __WS2812B_H__
#include "main.h"
#include "Stdbool.h"
#include <string.h>
#include <math.h>
#define LED_WIDTH 8
#define LED_HEIGHT 8
#define LED_COUNT (LED_WIDTH * LED_HEIGHT) // 总的LED的数量
#define RGB_COUNT 24 //每个LED所需的RGB 24位
#define HIGH 72// PWM占空比表示高电平,逻辑1: [ 高电平时间 = x / 90 × 1.25μs = 1μs ] => x = 72
#define LOW 18 // PWM占空比表示低电平,逻辑0: [ 高电平时间 = x / 90 × 1.25μs = 0.25μs ] => x = 18
#define WHITE (RGB_Color_TypeDef){255, 255, 255} //全白 #255
#define BLACK (RGB_Color_TypeDef){0, 0, 0} //全黑 #0
#define RED (RGB_Color_TypeDef){255, 0, 0}
#define GREEN (RGB_Color_TypeDef){0, 255, 0}
#define BLUE (RGB_Color_TypeDef){0, 0, 255}
//单个LED的颜色控制结构体
typedef struct
{
uint8_t R;
uint8_t G;
uint8_t B;
}RGB_Color_TypeDef;
void Led_Single_Color(uint8_t Led_Arr[],RGB_Color_TypeDef Color,float Brightness);
void Led_Multiple_Colors(uint8_t Led_Arr[],RGB_Color_TypeDef Color[],float Brightness);
void RGB_Off(void);
#endif
main.c
...
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ws2812b.h"
/* USER CODE END Includes */
int main(void){
const uint8_t Num_0[] = {0x00,0x1C,0x22, 0x22,0x22,0x1C,0x00,0x00}; // 0
const uint8_t Num_1[] = {0x00,0x10,0x10,0x10,0x1C,0x10,0x00,0x00}; // 1
const uint8_t Num_2[] = {0x00,0x3E,0x0C,0x10,0x22,0x3C,0x00,0x00}; // 2
const uint8_t Num_3[] = {0x00,0x1E,0x20,0x1C,0x20,0x3E,0x00,0x00}; // 3
const uint8_t Num_4[] = {0x00,0x20,0x7E,0x24,0x28,0x30,0x00,0x00}; // 4
const uint8_t Num_5[] = {0x00,0x1E,0x20,0x1E,0x02,0x3E,0x00,0x00}; // 5
const uint8_t Num_6[] = {0x00,0x1C,0x22,0x3E, 0x06,0x1C,0x00,0x00}; // 6
const uint8_t Num_7[] = {0x00,0x08,0x08,0x10,0x20,0x7E,0x00,0x00};// 7
const uint8_t Num_8[] = {0x00,0x1C,0x22,0x1C,0x22,0x3E,0x00,0x00};// 8
const uint8_t Num_9[] = {0x00,0x1E,0x30,0x3C,0x22,0x1C,0x00,0x00};// 9
uint8_t *ptr[] = {Num_0, Num_1, Num_2, Num_3, Num_4, Num_5, Num_6, Num_7, Num_8, Num_9};
uint8_t i = 0;
float light = 0.01;
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE BEGIN 3 */
if (i >= 10) i = 0;
if (light > 1.0) light = 0.01;
Led_Single_Color(ptr[i++], BLUE, light += 0.02);
HAL_Delay(500);
}
/* USER CODE END 3 */
}
四、演示效果
0~9数字(500ms / 亮度逐渐增加)