要想驱动WS2812灯带,必须弄明白其工作原理。这点我们从厂家提供的规格书中很容易整明白。
需要注意的是,不同的厂家,这些参数会有较大的差异,很可能导致无法点亮灯珠,所以我们需要根据不同厂家的产品,调整相应的参数。但数据传输方式是相同的,也就是说我们的代码应该是不用修改的,只是有必要的时候,需要调整相应的参数,比如0码、1码,RES时间等。
WS2812灯珠对时序要求还是比较严格的,虽然原则上可以通过直接写IO口,并用冗余代码的方式来控制时间从而达到控制WS2812灯带的目的,但这种方式几乎没有任何实用性,不同的主频需要修改代码,甚至只是简单的代码优化都需要修改代码,否则就没办法点亮灯珠。
正是因为上述原因,我们采用PWM+DMA的方式来驱动WS2812灯珠,先上代码。
ws2812.h
#ifndef _WS2812_H
#define _WS2812_H
#include "stm32f10x.h"
#ifndef _USE_TIMER_CLK
#define _USE_TIMER_CLK
#endif
#define WS2812_GPIO_PORT GPIOB
#define WS2812_GPIO_CLK RCC_APB2Periph_GPIOB
#define WS2812_GPIO_PIN GPIO_Pin_0
#define WS2812_TIM TIM3
#define WS2812_Freq 800
#define WS2812_RESET_TIME 240
#define LED_NUMS 60
#define WS2812_LED_BIT 24
#define WS2812_LED_DATA_0 30
#define WS2812_LED_DATA_1 45
#define WS2812_RST_NUM 50
#define WS2812_LED_DATA_LEN (WS2812_LED_BIT * LED_NUMS + WS2812_RST_NUM )
#define WS2812_LED_HIGH() GPIO_SetBits(WS2812_GPIO_PORT,WS2812_GPIO_PIN)
#define WS2812_LED_LOW() GPIO_ResetBits(WS2812_GPIO_PORT,WS2812_GPIO_PIN)
enum {
POWER_OFF = 0,
POWER_ON
};
enum {
GREEN_DATA_POS = 0,
RED_DATA_POS,
BLUE_DATA_POS
};
enum {
WS2812_LED_LEVEL_0 = 0,
WS2812_LED_LEVEL_1,
WS2812_LED_LEVEL_2,
WS2812_LED_LEVEL_3,
WS2812_LED_LEVEL_4,
WS2812_LED_LEVEL_5,
WS2812_LED_LEVEL_6,
WS2812_LED_LEVEL_7,
WS2812_LED_LEVEL_8,
WS2812_LED_LEVEL_9,
WS2812_LED_LEVEL_10,
WS2812_LED_LEVEL_11,
WS2812_LED_LEVEL_12,
WS2812_LED_LEVEL_13,
WS2812_LED_LEVEL_14,
WS2812_LED_LEVEL_15,
WS2812_LED_LEVEL
};
typedef struct
{
uint8_t G;
uint8_t R;
uint8_t B;
uint8_t phase;
}WS2812_LED_CTRL;
void Change_WS2812LED_Status(void);
#endif
ws2812.c
#include "stm32f10x.h"
#include "ws2812.h"
#include "string.h"
uint16_t LED_Data[WS2812_LED_DATA_LEN] = {0};
WS2812_LED_CTRL ws2812_led = {0xff,0,0,0};
void ws2812_Init(void)
{
#ifdef _USE_TIMER_CLK
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef WS2812_TIM_TimeBaseStructure;
TIM_OCInitTypeDef WS2812_TIM_OCInitStructure;
DMA_InitTypeDef WS2812_DMA_InitStructure;
RCC_APB2PeriphClockCmd(WS2812_GPIO_CLK, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
GPIO_InitStructure.GPIO_Pin = WS2812_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(WS2812_GPIO_PORT, &GPIO_InitStructure);
WS2812_TIM_TimeBaseStructure.TIM_Prescaler = 0;
WS2812_TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
WS2812_TIM_TimeBaseStructure.TIM_Period = (SystemCoreClock/(WS2812_Freq*1000))-1; //800K
WS2812_TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(WS2812_TIM,&WS2812_TIM_TimeBaseStructure);
WS2812_TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
WS2812_TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
WS2812_TIM_OCInitStructure.TIM_Pulse = 0;
WS2812_TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //????:TIM???????
TIM_OC3Init(WS2812_TIM,&WS2812_TIM_OCInitStructure); //?????
TIM_OC3PreloadConfig(WS2812_TIM,TIM_OCPreload_Enable); //??TIM3?CCR3????????
TIM_Cmd(WS2812_TIM,DISABLE); //??TIM3 ??????????
TIM_DMACmd(WS2812_TIM,TIM_DMA_CC3,ENABLE); //??TIM2_CH3?DMA??(CC2????3)
WS2812_DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM3->CCR3); //??DMA????
WS2812_DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LED_Data;
WS2812_DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //??:???SendBuff???ReceiveBuff
WS2812_DMA_InitStructure.DMA_BufferSize = WS2812_LED_DATA_LEN; //??????DMA_BufferSize=SendBuffSize
WS2812_DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //ReceiveBuff????
WS2812_DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //SendBuff????
WS2812_DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//ReceiveBuff????,16bit
WS2812_DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //SENDBUFF_SIZE????,16bit
WS2812_DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA??:????(????)
WS2812_DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //???:?
WS2812_DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //????????
DMA_Init(DMA1_Channel2,&WS2812_DMA_InitStructure); //??DMA1?2??(???????????)
DMA_Cmd(DMA1_Channel2,DISABLE); //???DMA??
#else
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(WS2812_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = WS2812_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(WS2812_GPIO_PORT, &GPIO_InitStructure);
WS2812_LED_HIGH ();
#endif
for (uint8_t idx = 0; idx < LED_NUMS ;idx ++)
LED_Data[idx] = WS2812_LED_DATA_0;
// memset(LED_Data,WS2812_LED_DATA_0,WS2812_LED_DATA_LEN - WS2812_RST_NUM);
Change_WS2812LED_Status();
}
void Fill_WS2812_LED_Data(uint8_t G,uint8_t R,uint8_t B,uint16_t Pos)
{
// uint16_t temp[WS2812_LED_BIT] = {0};
// uint8_t idx;
uint32_t indexx=(Pos*WS2812_LED_BIT);
if (Pos > LED_NUMS)
{
for (uint8_t idx = 0; idx < LED_NUMS ;idx ++)
LED_Data[idx] = WS2812_LED_DATA_0;
}
else
{
for (uint8_t i = 0;i < 8;i++)
{
LED_Data[indexx+i] = (G << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
LED_Data[indexx+i + 8] = (R << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
LED_Data[indexx+i + 16] = (B << i) & (0x80)?WS2812_LED_DATA_1:WS2812_LED_DATA_0;
}
}
}
void WS2812_LED_PowerOff(void)
{
for (uint8_t idx = 0; idx < LED_NUMS ;idx ++)
LED_Data[idx] = WS2812_LED_DATA_0;
Change_WS2812LED_Status();
}
void WS2812_LED_Circle(void)
{
uint16_t idx,i;
// uint8_t level;
for (idx = LED_NUMS - 1; idx > 0; idx --)
{
for (i = 0; i < WS2812_LED_BIT; i ++)
LED_Data[idx* WS2812_LED_BIT + i] = LED_Data[(idx-1) * WS2812_LED_BIT + i];
}
if (ws2812_led.phase < WS2812_LED_LEVEL)
{
ws2812_led.G -= (0xff / WS2812_LED_LEVEL);
ws2812_led.R += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL)
{
ws2812_led.G = 0;
ws2812_led.R = 0xff;
}
}
else if (ws2812_led.phase < WS2812_LED_LEVEL * 2)
{
ws2812_led.R -= (0xff / WS2812_LED_LEVEL);
ws2812_led.B += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL * 2)
{
ws2812_led.R = 0;
ws2812_led.B = 0xff;
}
}
else if (ws2812_led.phase < WS2812_LED_LEVEL * 3)
{
ws2812_led.B -= (0xff / WS2812_LED_LEVEL);
ws2812_led.G += (0xff / WS2812_LED_LEVEL);
ws2812_led.phase ++;
if (ws2812_led.phase == WS2812_LED_LEVEL * 3)
{
ws2812_led.B = 0;
ws2812_led.G = 0xff;
ws2812_led.phase = 0;
}
}
Fill_WS2812_LED_Data(ws2812_led.G,ws2812_led.R,ws2812_led.B,0);
Change_WS2812LED_Status();
}
void WS2812_LED_RST(void)
{
#ifdef _USE_TIMER_CLK
TIM_Cmd(WS2812_TIM,DISABLE);
#endif
WS2812_LED_LOW();
DelayUs(WS2812_RESET_TIME);
}
void Change_WS2812LED_Status(void)
{
#ifdef _USE_TIMER_CLK
DMA_SetCurrDataCounter(DMA1_Channel2,(WS2812_LED_DATA_LEN));
DMA_Cmd(DMA1_Channel2,ENABLE);
TIM_Cmd(WS2812_TIM,ENABLE);
while(DMA_GetFlagStatus(DMA1_FLAG_TC2) != SET);
DMA_Cmd(DMA1_Channel2,DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC2);
TIM_Cmd(WS2812_TIM,DISABLE);
// WS2812_LED_RST();
#else
int i;
for(i = 0;i < LED_NUMS;i++)
{
Write_One_RGBLED(LED_Data[3 * i],LED_Data[3 * i + 1],LED_Data[3 * i + 2]);
}
WS2812_LED_RST();
#endif
}
从规格书中,我们知道TH+TL大概1.1us,这样我们设置PWM的周期为1.25us,频率800K。
两次数据传输必须发送RESET CODE,实际上就是保持数据线为低电平300us左右,所以我们DMA的数据buff中,需要加上这段时间的传输数据,#define WS2812_RESET_TIME 240,240 * 1.25 = 300。这样我们可以设置DMA方式为循环方式,这样设置后,我们不再需要代码控制WS2812灯珠了。如果需要修改不同的显示效果,只需要修改DMA的数据buff就可以。
灯珠的显示效果大家可以根据自己的需求重写代码,我们这里只是实现了一种跑马灯的效果。
另外有一点需要注意的是,虽然WS2812灯珠的数据是8位,但因为TIM3->CCR3是16位的,所以DMA的DataSize不能设置为byte,而要设置为半字,所以DMA的数据buff的类型设置为uint16_t 。
最后需要完整工程文件的可以参考:stm32F103C8驱动ws2812灯带PWM+DMA驱动资源-CSDN文库