驱动全彩LED灯带---WS2812B芯片

老小孩
原创
发布时间: 2025-08-02 10:25:09 | 阅读数 0收藏数 0评论数 0
封面
上次做了摄影灯以后,在想如果摄影灯的颜色可以调整,产生各种颜色就好了,于是上网搜索关于彩色LED灯珠以及驱动的原理,发现有一款LED灯珠,它内部自带驱动芯片---WS2812,下面咱们一起研究一下这个WS2812.
1

准备材料

市面上WS2812这种灯珠多用于LED灯带装饰,我从公司仓库的角落里翻来一节WS2812灯带,用手头现有的STM32C8T6最小系统板来驱动,再准备一下杜邦线之类的配件。

2

硬件连接

WS2812的硬件连接特别简单,只用3根线:VDD, GND,DAT。其实就用一根线传输PWM信号来控制灯带的亮暗以及各种颜色非常方便。

3

软件驱动原理

WS2812B是一种数字可编程驱动芯片,并集成了控制电路和信号处理功能。它集成到LED灯珠的内部,并且能驱动控制RGB(红、绿、蓝)三种颜色的LED灯珠,每个WS2812B LED都有一个唯一的地址,并可以通过单个数据线进行串联连接。每个LED需要24位数据控制,多出的数据会通过DOUT引脚传递给下一个LED,以此类推。

它用24位数据表示一个LED灯珠的驱动亮暗和颜色,每一种颜色用8位数据控制,也就是每一种颜色的亮度可以分成0---255个等级,也就是说3个颜色可以组成16777216种颜色。

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

4

代码部分

我用的PA8引脚驱动的,用TIM1定时器生成PWM信号,用DMA直接传输数据。

ws2812.h文件:

#ifndef __WS2812_H
#define __WS2812_H

#include "sys.h"

// 配置参数
#define WS2812B_NUM_LEDS 60 // LED数量
#define WS2812B_TIM TIM1 // 使用定时器1
#define WS2812B_TIM_CHANNEL TIM_Channel_1 // 通道1
#define WS2812B_GPIO_PORT GPIOA
#define WS2812B_GPIO_PIN GPIO_Pin_8
#define WS2812B_DMA DMA1
#define WS2812B_DMA_CHANNEL DMA1_Channel2 // TIM1_CH1对应DMA1通道2
#define WS2812B_DMA_FLAG DMA1_FLAG_TC2

// PWM参数 (基于72MHz系统时钟)
#define PWM_PERIOD 90 // PWM周期(1.25us)
#define PWM_ZERO_HIGH 28 // 0码高电平时间(0.39us)
#define PWM_ONE_HIGH 53 // 1码高电平时间(0.74us)
#define BIT_PER_LED 24 // 每个LED24位数据
#define RESET_CYCLES 80 // 复位周期(100us)

// 颜色结构 (GRB顺序)
typedef struct {
uint8_t g;
uint8_t r;
uint8_t b;
} GRB_Color;

// 函数声明
void WS2812B_Init(void);
void WS2812B_SetPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b);
void WS2812B_SetAll(uint8_t r, uint8_t g, uint8_t b);
void WS2812B_Clear(void);
void WS2812B_Update(void);
uint8_t WS2812B_IsUpdating(void);

#endif /* __WS2812B_H */



ws2812.c文件:

#include "ws2812.h"
#include <string.h>
#include "math.h"

// DMA缓冲区大小
#define DMA_BUFFER_SIZE (WS2812B_NUM_LEDS * BIT_PER_LED + RESET_CYCLES)

// 全局变量
static GRB_Color led_buffer[WS2812B_NUM_LEDS];
static uint16_t dma_buffer[DMA_BUFFER_SIZE];
static volatile uint8_t is_updating = 0;

// 初始化函数
void WS2812B_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO |
RCC_APB2Periph_TIM1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 2. 配置GPIO - PA8 (TIM1_CH1)
GPIO_InitStructure.GPIO_Pin = WS2812B_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 高速输出
GPIO_Init(WS2812B_GPIO_PORT, &GPIO_InitStructure);
// 3. 配置定时器基础
TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1; // ARR值
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // 高级定时器需要
TIM_TimeBaseInit(WS2812B_TIM, &TIM_TimeBaseStructure);
// 4. 配置PWM输出
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; // 互补输出禁用
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(WS2812B_TIM, &TIM_OCInitStructure); // 通道1
// 5. 配置DMA
DMA_DeInit(WS2812B_DMA_CHANNEL);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(WS2812B_TIM->CCR1); // 目标地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dma_buffer; // 源地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设
DMA_InitStructure.DMA_BufferSize = DMA_BUFFER_SIZE; // 传输数据量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 正常模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存到外设模式
DMA_Init(WS2812B_DMA_CHANNEL, &DMA_InitStructure);
// 6. 配置DMA中断
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_ITConfig(WS2812B_DMA_CHANNEL, DMA_IT_TC, ENABLE); // 使能传输完成中断
// 7. 配置DMA请求
TIM_DMACmd(WS2812B_TIM, TIM_DMA_CC1, ENABLE); // 使能TIM1 CH1的DMA请求
// 8. 高级定时器需要MOE主输出使能
TIM_CtrlPWMOutputs(WS2812B_TIM, ENABLE);
// 9. 启动定时器
TIM_Cmd(WS2812B_TIM, ENABLE);
// 10. 初始化缓冲区
WS2812B_Clear();
memset(dma_buffer, 0, sizeof(dma_buffer));
}

// 设置单个LED颜色
void WS2812B_SetPixel(uint16_t index, uint8_t r, uint8_t g, uint8_t b)
{
if (index < WS2812B_NUM_LEDS)
{
led_buffer[index].r = r;
led_buffer[index].g = g;
led_buffer[index].b = b;
}
}

// 设置所有LED为相同颜色
void WS2812B_SetAll(uint8_t r, uint8_t g, uint8_t b)
{
uint16_t i;

for (i = 0; i < WS2812B_NUM_LEDS; i++) {
WS2812B_SetPixel(i, r, g, b);
}
}

// 关闭所有LED
void WS2812B_Clear(void)
{
memset(led_buffer, 0, sizeof(led_buffer));
}

// 检查是否正在更新
uint8_t WS2812B_IsUpdating(void)
{
return is_updating;
}

// 更新LED显示
void WS2812B_Update(void)
{
uint16_t i;
uint32_t j, color;
int8_t bit;

// 等待上一次传输完成
if (is_updating) return;
uint32_t pos = 0;
// 1. 将LED颜色数据转换为PWM占空比序列
for (i = 0; i < WS2812B_NUM_LEDS; i++)
{
// 组合GRB数据 (WS2812B使用GRB顺序)
color = ((uint32_t)led_buffer[i].g << 16) |
((uint32_t)led_buffer[i].r << 8) |
(uint32_t)led_buffer[i].b;
// 将24位颜色数据转换为PWM占空比值
for (bit = 23; bit >= 0; bit--) {
dma_buffer[pos++] = (color & (1UL << bit)) ? PWM_ONE_HIGH : PWM_ZERO_HIGH;
}
}
// 2. 添加复位信号 (低电平)
for (j = 0; j < RESET_CYCLES; j++)
{
dma_buffer[pos++] = 0; // 占空比为0 -> 低电平
}
// 3. 清除DMA标志
DMA_ClearFlag(WS2812B_DMA_FLAG);
// 4. 重新配置DMA
DMA_Cmd(WS2812B_DMA_CHANNEL, DISABLE);
DMA_SetCurrDataCounter(WS2812B_DMA_CHANNEL, DMA_BUFFER_SIZE);
DMA_Cmd(WS2812B_DMA_CHANNEL, ENABLE);
// 5. 设置更新标志
is_updating = 1;
}

// DMA传输完成中断处理
void DMA1_Channel2_IRQHandler(void)
{
if (DMA_GetITStatus(WS2812B_DMA_FLAG))
{
DMA_ClearITPendingBit(WS2812B_DMA_FLAG);
is_updating = 0; // 清除更新标志
// 强制GPIO输出低电平,确保复位时间
GPIO_ResetBits(WS2812B_GPIO_PORT, WS2812B_GPIO_PIN);
}
}

main.c文件:

#include "sys.h"
#include "ws2812.h"
#include "math.h"

// 系统时钟配置 (72MHz)
void SystemClock_Config(void) {
// 启用外部高速晶振
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
// 等待HSE就绪
if (RCC_WaitForHSEStartUp() == SUCCESS) {
// 设置PLL
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 8MHz * 9 = 72MHz
// 设置各总线预分频
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB = 72MHz
RCC_PCLK1Config(RCC_HCLK_Div2); // APB1 = 36MHz
RCC_PCLK2Config(RCC_HCLK_Div1); // APB2 = 72MHz
// 启用PLL
RCC_PLLCmd(ENABLE);
// 等待PLL就绪
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 选择PLL作为系统时钟
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
// 等待系统时钟源切换完成
while (RCC_GetSYSCLKSource() != 0x08);
}
}



// 简单延时函数
void Delay(uint32_t n)
{
volatile uint32_t i;

for(; n > 0; n--)
{
for(i = 0; i < 1000; i++);
}
}

// HSV转RGB函数
GRB_Color HSVtoRGB(float h, float s, float v)
{
float r, g, b;
int i = (int)(h * 6);
float f = h * 6 - i;
float p = v * (1 - s);
float q = v * (1 - f * s);
float t = v * (1 - (1 - f) * s);
switch (i % 6)
{
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
GRB_Color color;
color.r = (uint8_t)(r * 255);
color.g = (uint8_t)(g * 255);
color.b = (uint8_t)(b * 255);
return color;
}

int main(void)
{
// 简单测试序列
uint8_t state = 0;
uint16_t led_index = 0;
float hue = 0.0f;
uint16_t i;
int brightness;

SystemClock_Config(); // 配置系统时钟
// LED_Init(); // 初始化板载LED(调试用)
WS2812B_Init(); // 初始化WS2812B
WS2812B_SetPixel(5, 30, 0, 0);
WS2812B_Update();
while(WS2812B_IsUpdating());

WS2812B_SetPixel(6, 0, 0, 30);
WS2812B_Update();
while(WS2812B_IsUpdating());



while (1)
{




// 板载LED闪烁指示系统运行
GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)(state ^= 1));
// 模式1: 彩虹效果
for (hue = 0; hue < 1.0f; hue += 0.005f)
{
for (i = 0; i < WS2812B_NUM_LEDS; i++)
{
// 每个LED有不同的色相偏移
float ledHue = fmod(hue + (float)i / WS2812B_NUM_LEDS, 1.0f);
GRB_Color color = HSVtoRGB(ledHue, 1.0f, 0.5f);
WS2812B_SetPixel(i, color.r, color.g, color.b);
}
WS2812B_Update();
// 等待更新完成
while(WS2812B_IsUpdating());
Delay(5);
}
// 模式2: 跑马灯效果
for (led_index = 0; led_index < WS2812B_NUM_LEDS; led_index++)
{
WS2812B_Clear();
// 创建跑马灯效果 (5个LED一组)
for (i = 0; i < 5; i++)
{
uint16_t pos = (led_index + i) % WS2812B_NUM_LEDS;
WS2812B_SetPixel(pos, 0, 50, 50); // 青色
}
WS2812B_Update();
// 等待更新完成
while(WS2812B_IsUpdating());
Delay(10);
}
// 模式3: 呼吸灯效果
for (brightness = 0; brightness < 100; brightness++)
{
for (i = 0; i < WS2812B_NUM_LEDS; i++)
{
WS2812B_SetPixel(i, brightness, brightness, brightness);
}
WS2812B_Update();
while(WS2812B_IsUpdating());
Delay(2);
}
for (brightness = 100; brightness > 0; brightness--)
{
for (i = 0; i < WS2812B_NUM_LEDS; i++)
{
WS2812B_SetPixel(i, brightness, brightness, brightness);
}
WS2812B_Update();
while(WS2812B_IsUpdating());
Delay(2);
}
}
}









5

烧录测试

实验效果还不错,不过照片种体现不出来,实际要好看很多。

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载