这里写目录标题
- 1、概述
- 2、芯片级联方法
- 3、数据传输特性
- 4、程序实例
- 4.1、硬件电路
- 4.2、定时器初始化
- 4.3、DMA初始化
- 4.4、RGB数据驱动
- 5、完整代码
- 5.1、WS2812B.c
- 5.2、WS2812B.h
- 5.3、main.c
1、概述
WS2812B是一种常见的RGB LED灯带,每个灯珠内部都有一个芯片控制,通过发送特定的时序数据来控制其亮灭。发送数据时,需要按照一定的时序发送24位RGB数据,其中高位在前低位在后,格式为GRB。发送数据时,需要注意不仅仅是发送高电平或低电平,而是要发送占空比不同的PWM波,比如给予一定的高电平和低电平时间。重置码是发送一个持续280us的低电平信号。可以先发送一组24位的数据,然后接一个重置信号表示一组结束。数据采用单总线,归零码的通讯方式, 每个像素点接收24bit数据,溢出的数据传输给下一个像素点。
WS2812B作为一种常见的RGB LED(三色LED)驱动芯片,具有以下优点和缺点:
优点:
- 集成度高:WS2812B芯片将RGB LED和控制电路集成在一个小型封装中,简化了设计和布线过程。
- 控制灵活:每个WS2812B芯片都具有独立的控制电路,可以通过单线串联多个LED,并根据需要独立控制每个LED的颜色和亮度。
- 易于编程:WS2812B芯片与微控制器通信采用简单的串行通信协议,使得编程和控制相对容易。
- 色彩丰富:每个WS2812B芯片都能够显示256级亮度的红、绿、蓝三个颜色通道,通过调整三个颜色的亮度可以实现丰富多彩的效果。
缺点:
- 电源需求高:WS2812B芯片对电源的要求较高,供电电源需要稳定而强大,以保证每个芯片的正常工作。
- 刷新频率限制:由于通信协议的限制,WS2812B的刷新频率相对较低,控制大量LED时可能会出现刷新延迟。
- 单点故障影响范围大:因为WS2812B芯片串联连接,当某个芯片发生故障时,可能会影响整个串联链路的正常工作。
2、芯片级联方法
典型应用电路:其硬件电路仅需要三根线(5V电源线、GND、DI信号线)。其仅需要一个具有定时器PWM输出功能的GPIO口,即可对多个灯进行控制。
数据传输示意图:下图中 D1 为 MCU 端发送的数据,D2、D3、D4 为级联电路自动整形转发的数据。
3、数据传输特性
如上图所示,当数据线传入灯带后,第一个灯珠截取第一个24位数据自己使用,而后会将其余的数据进行整形后发送给第二颗灯珠,第二颗灯珠会依次截取24位数据并对剩余数据进行整形发送,以此类推。直到最后一组数据被显示为止。每一组24位数根据数据的不同显示不同的颜色和亮度(每个像素点的三基色颜色可实现256级亮度显示, 完成16777216种颜色的全真色彩显示)。
4、程序实例
根据其电位控制要求,通过软件实现IO控制模拟0码和1码并进行数据传输在理论上是可以实现的,但是在实际过程中,会过于占用CPU资源,所以我们这里采用TIM1+PWM+DMA的控制方式。
通过控制PWM占空比发送0码和1码,额定周期为1.25us,则频率为800Khz
0码PWM占空比:
(0码高电平时间)/(周期)—> 0.4 / 1.25 = 0.32
用占空比乘以定时器重装值加一就是0码的CCR值(代表PWM高电平计数个数)—>
0.32 * (209+1) = 67.2(我实际使用的60,左右相差不太大都可正常使用)
1码PWM占空比:
(1码高电平时间)/(周期)—> 0.8 / 1.25 = 0.64
用占空比乘以定时器重装值加一就是1码的CCR值(代表PWM高电平计数个数)—>
0.64 * (209+1) = 134.4(我实际使用的130,左右相差不太大都可正常使用)
4.1、硬件电路
我在电路设计中,是使用的PE13作为WS2812B的控制引脚,
根据芯片手册可知,PE13是TIM1的CH3通道
4.2、定时器初始化
下面是定时器1的CH3(PE13)输出PWM的初始化代码
我这里使用的是STM32F407VET6,配置的系统时钟为168MHZ,可根据时钟更改TIM_TimeBaseStructure.TIM_Period的值来修改定时器周期(168MHZ / 210 = 800KHZ)
/*
*函数名称:Timer1_Init
*功能描述:配置TIM1_CH3输出PWM
*传入参数:无
*返回值 :无
*/
void Timer1_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); /*使能GPIOE时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); /*使能TIM1时钟*/
GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_TIM1); /*将GPIOE13重映射到TIM1的输出通道上*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; /*将引脚功能配置为复用*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; /*推挽输出*/
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; /*上拉*/
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* 定时器周期 : T =(arr + 1) * (PSC + 1) / Tck. arr:周期值 PSC:预分频值 Tck: 系统时钟频率 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 210 - 1; /* T = (TIM_Period + 1)*(0+1)/168M,此时800kHz*/
TIM_TimeBaseStructure.TIM_Prescaler = 0; /* 0:不预分频 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; /* 0:时钟分频因子设为1,不分频*/
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; /*向上计数*/
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; /*重复计数值为0,不使用重复计数*/
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; /*PWM1模式,计数值小于比较值输出高电平,大于输出低电平*/
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; /*使能输出通道*/
TIM_OCInitStructure.TIM_Pulse = 0; /*设置占空比为0, 1 ~ TIM_TimeBaseStructure.TIM_Period */
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /*设置输出极性高电平有效*/
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; /*禁用互补输出通道*/
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; /*设置互补输出通道极性高电平有效*/
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCNIdleState_Set; /*设置空闲时输出高电平*/
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; /*设置互补通道空闲时输出低电平*/
TIM_OC3Init(TIM1, &TIM_OCInitStructure); /*将上述配置应用到定时器一的通道三上*/
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); /*使能通道三的输出比较寄存器的预装载功能*/
TIM_Cmd(TIM1, ENABLE); /*使能TIM1定时器*/
TIM_CtrlPWMOutputs(TIM1,ENABLE); /*使能TIM1的PWM输出*/
}
4.3、DMA初始化
下面是DMA初始化代码:
/*
*函数名称:dma2_Init
*功能描述:配置从源地址到TIM1_CCR3的DMA数据传输
*传入参数:无
*返回值 :无
*/
uint16_t g_ledDataBuffer[24];
void dma2_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); /*使能DMA2时钟*/
DMA_DeInit(DMA2_Stream6); /*对DMA2_Stream6进行默认值初始化*/
/* DMA2 Stream6 Config for PWM1 by TIM1_CH1*/
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0; /*配置DMA通道,通道0*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM1->CCR3); /*配置外设基地址:TIM1的CCR3寄存器*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)g_ledDataBuffer; /*配置源数据的基地址*/
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; /*设置搬运方向:存储器到外设*/
DMA_InitStructure.DMA_BufferSize = 42; /*设置传输数据的大小*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*外设基地址不自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /*源数据基地址自增*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /*设置外设数据大小为半字(16bit)*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /*设置存储器数据大小为半字(16bit)*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /*设置DMA为普通模式*/
DMA_InitStructure.DMA_Priority = DMA_Priority_High; /*DMA优先级为高*/
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /*禁用DMA的FIFO模式*/
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; /*设置DMA的FIFO阈值为满*/
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /*设置内存突发传输模式为单次传输*/
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /*设置外设突发传输模式为单次传输*/
DMA_Init(DMA2_Stream6, &DMA_InitStructure); /*将上述配置应用到 DMA2_Stream6*/
TIM_DMACmd(TIM1, TIM_DMA_CC3, ENABLE); /*将TIM1的DMA请求映射到通道三并使能*/
}
4.4、RGB数据驱动
#define TIMING_ONE (130)
#define TIMING_ZERO (60)
/*
*函数名称:RGB_Show
*功能描述:通过DMA控制器从内存中读取数据,然后将数据传输到TIM1的CC3通道,以驱动RGB灯。
*传入参数:
*返回值 :无
*/
void RGB_Show(uint8_t (*color)[3], uint16_t len)
{
uint8_t i = 0;
uint16_t memaddr = 0;
uint16_t buffersize = 0;
buffersize = (len * 24) + 1; // 缓冲区字节数 = len(RGB灯个数)*24+1
while (len)
{
for(i = 0; i < 8; i++)/* green data */
{
g_ledDataBuffer[memaddr] = ((color[0][1] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
for(i = 0; i < 8; i++)/* red data */
{
g_ledDataBuffer[memaddr] = ((color[0][0] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
for(i = 0; i < 8; i++)/* blue data */
{
g_ledDataBuffer[memaddr] = ((color[0][2] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
len--;
}
DMA_SetCurrDataCounter(DMA2_Stream6, buffersize); /*指定要传输的数据量*/
TIM_Cmd(TIM1, ENABLE); /*启用TIM1定时器*/
TIM_DMACmd(TIM1, TIM_DMA_CC3, ENABLE); /*将TIM1的DMA请求映射到通道三并使能*/
DMA_Cmd(DMA2_Stream6, ENABLE); /*启用DMA2_Stream6,开始数据传输*/
while(!DMA_GetFlagStatus(DMA2_Stream6, DMA_FLAG_TCIF6)); /*等待DMA传输完成,使用循环检查DMA传输完成标志*/
DMA_Cmd(DMA2_Stream6, DISABLE); /*禁用DMA2流6,停止数据传输*/
DMA_ClearFlag(DMA2_Stream6, DMA_FLAG_TCIF6); /*清除DMA传输完成标志*/
TIM_Cmd(TIM1, DISABLE); /*禁用TIM1定时器,停止计时*/
}
5、完整代码
5.1、WS2812B.c
#include "WS2812B.h"
#include "DWT_Delay.h"
#include "string.h"
#define TIMING_ONE (130)
#define TIMING_ZERO (60)
/*
*函数名称:Timer1_Init
*功能描述:配置TIM1_CH3输出PWM
*传入参数:无
*返回值 :无
*/
void Timer1_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); /*使能GPIOE时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); /*使能TIM1时钟*/
GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_TIM1); /*将GPIOE13重映射到TIM1的输出通道上*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; /*将引脚功能配置为复用*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; /*推挽输出*/
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; /*上拉*/
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* 定时器周期 : T =(arr + 1) * (PSC + 1) / Tck. arr:周期值 PSC:预分频值 Tck: 系统时钟频率 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 210 - 1; /* T = (TIM_Period + 1)*(0+1)/168M = 800kHz*/
TIM_TimeBaseStructure.TIM_Prescaler = 0; /* 0:不预分频 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; /* 0:时钟分频因子设为1,不分频*/
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; /*向上计数*/
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; /*重复计数值为0,不使用重复计数*/
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; /*PWM1模式,计数值小于比较值输出高电平,大于输出低电平*/
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; /*使能输出通道*/
TIM_OCInitStructure.TIM_Pulse = 0; /*设置占空比为0, 1 ~ TIM_TimeBaseStructure.TIM_Period */
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /*设置输出极性高电平有效*/
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; /*禁用互补输出通道*/
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; /*设置互补输出通道极性高电平有效*/
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCNIdleState_Set; /*设置空闲时输出高电平*/
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset; /*设置互补通道空闲时输出低电平*/
TIM_OC3Init(TIM1, &TIM_OCInitStructure); /*将上述配置应用到定时器一的通道三上*/
TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); /*使能通道三的输出比较寄存器的预装载功能*/
TIM_Cmd(TIM1, ENABLE); /*使能TIM1定时器*/
TIM_CtrlPWMOutputs(TIM1,ENABLE); /*使能TIM1的PWM输出*/
}
uint16_t g_ledDataBuffer[24];
/*
*函数名称:dma2_Init
*功能描述:配置从源地址到TIM1_CCR3的DMA数据传输
*传入参数:无
*返回值 :无
*/
void dma2_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); /*使能DMA2时钟*/
DMA_DeInit(DMA2_Stream6); /*对DMA2_Stream6进行默认值初始化*/
/* DMA2 Stream6 Config for PWM1 by TIM1_CH1*/
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0; /*配置DMA通道,通道0*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM1->CCR3); /*配置外设基地址:TIM1的CCR3寄存器*/
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)g_ledDataBuffer; /*配置源数据的基地址*/
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; /*设置搬运方向:存储器到外设*/
DMA_InitStructure.DMA_BufferSize = 42; /*设置传输数据的大小*/
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*外设基地址不自增*/
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /*源数据基地址自增*/
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /*设置外设数据大小为半字(16bit)*/
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /*设置存储器数据大小为半字(16bit)*/
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /*设置DMA为普通模式*/
DMA_InitStructure.DMA_Priority = DMA_Priority_High; /*DMA优先级为高*/
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /*禁用DMA的FIFO模式*/
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; /*设置DMA的FIFO阈值为满*/
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; /*设置内存突发传输模式为单次传输*/
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /*设置外设突发传输模式为单次传输*/
DMA_Init(DMA2_Stream6, &DMA_InitStructure); /*将上述配置应用到 DMA2_Stream6*/
TIM_DMACmd(TIM1, TIM_DMA_CC3, ENABLE); /*将TIM1的DMA请求映射到通道三并使能*/
}
/*
*函数名称:RGB_Show
*功能描述:通过DMA控制器从内存中读取数据,然后将数据传输到TIM1的CC3通道,以驱动RGB灯。
*传入参数:
*返回值 :无
*/
void RGB_Show(uint8_t (*color)[3], uint16_t len)
{
uint8_t i = 0;
uint16_t memaddr = 0;
uint16_t buffersize = 0;
buffersize = (len * 24) + 1; // 缓冲区字节数 = len(RGB灯个数)*24+1
while (len)
{
for(i = 0; i < 8; i++)/* green data */
{
g_ledDataBuffer[memaddr] = ((color[0][1] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
for(i = 0; i < 8; i++)/* red data */
{
g_ledDataBuffer[memaddr] = ((color[0][0] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
for(i = 0; i < 8; i++)/* blue data */
{
g_ledDataBuffer[memaddr] = ((color[0][2] << i) & 0x0080) ? TIMING_ONE : TIMING_ZERO;
memaddr++;
}
len--;
}
DMA_SetCurrDataCounter(DMA2_Stream6, buffersize); /*指定要传输的数据量*/
TIM_Cmd(TIM1, ENABLE); /*启用TIM1定时器*/
TIM_DMACmd(TIM1, TIM_DMA_CC3, ENABLE); /*将TIM1的DMA请求映射到通道三并使能*/
DMA_Cmd(DMA2_Stream6, ENABLE); /*启用DMA2_Stream6,开始数据传输*/
while(!DMA_GetFlagStatus(DMA2_Stream6, DMA_FLAG_TCIF6)); /*等待DMA传输完成,使用循环检查DMA传输完成标志*/
DMA_Cmd(DMA2_Stream6, DISABLE); /*禁用DMA2流6,停止数据传输*/
DMA_ClearFlag(DMA2_Stream6, DMA_FLAG_TCIF6); /*清除DMA传输完成标志*/
TIM_Cmd(TIM1, DISABLE); /*禁用TIM1定时器,停止计时*/
}
5.2、WS2812B.h
#ifndef __WS2818B_H
#define __WS2812B_H
#include "stm32f4xx.h" // Device header
void Timer1_Init(void);
void dma2_Init(void);
void RGB_Show(uint8_t (*color)[3], uint16_t len);
#endif
5.3、main.c
#include "stm32f4xx.h" // Device header
#include "DWT_Delay.h"
#include "WS2812B.h"
uint8_t rgbRed[][3] = {{0xff, 0xff, 0x99}};
uint8_t rgbRed1[][3] = {{0x20, 0x20, 0x20}};
uint8_t rgbRed2[][3] = {{0x33, 0x00, 0x33}};
uint8_t rgbRed3[][3] = {{0xFF, 0x33, 0xff}};
int main(void)
{
DWT_Init();
Timer1_Init();
dma2_Init();
while(1)
{
RGB_Show(rgbRed,1);DWT_DelayMS(1000);
RGB_Show(rgbRed1,1);DWT_DelayMS(1000);
RGB_Show(rgbRed2,1);DWT_DelayMS(1000);
RGB_Show(rgbRed3,1);DWT_DelayMS(1000);
RGB_Show(rgbRed,1);DWT_DelayMS(1000);
}
}