stm32F103 用PWM+DMA方式驱动WS2812灯带

要想驱动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文库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永远的元子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值