如何使用STM32F103C8T6控制WS2812 LED:利用PWM和DMA

一、WS2812概述:

WS2812 内部集成了处理芯片和3颗不同颜色的led灯(红,绿,蓝),通过单总线协议分别控制三个灯的亮度强弱,达到全彩的效果。

数据传输时间:

T0H 0码,高电平时间 220ns~380ns
T1H 1码,高电平时间 580ns~1us
T0L 0码,低电平时间 580ns~1us
T1L 1码,低电平时间 220ns~420ns
RES 帧单位,低电平时间 280us以上

时序波形图:

24 bit数据结构:

每一个灯需要 8 bits(1 byte) 的数据 (8个1时最亮、8个0时不亮),所以一颗 ws2812 共需要24 bits(3 bytes) **(24个1时最亮、24个0时不亮)**的数据。

注:高位先发,按照 GRB 的顺序发送数据

G7 G6 G5 G4 G3 G2 G1 G0 R7 R6 R5 R4 R3 R2 R1 R0 B7 B6 B5 B4 B3 B2 B1 B0

绿色

1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

红色

0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0

蓝色

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1

二、WS2812驱动的几种方式

1、使用 延时函数

直接翻转IO口产生时序,这种方式最为简单易用,只需要控制延时的时间,就可以从产生0和1码,它需要占用系统资源。
使用 SPI 数据传输产生时序
2、通过SPI控制

只需要控制在合适的波特率,在传输不同数据的时候,可以产生符合要求的0和1码,这种方式需要等同于使用了一个SPI设备
3、使用 DMA+Timer 产生时序

这种方式需要使用一个定时器,其中一个通道固定产生一个周期1.25us的PWM,占空比2/3,接着需要另一个通道,在周期的1/3处搬运数据到IO口,若为1,PWM不变,若为0,PWM则为0码,这种方式有更大的局限性,由于DMA只能搬运至少一个字节,所以每次会同时改变8个IO口的高低电平,或许使用位带操作可以解决这问题
4、使用 Timer+PWM+DMA 产生时序

本文讨论的实现方案,这种方案有2种驱动的方式,一种是直接建立一个大的数组,存放所有灯珠的数据,然后启动DMA传输,第二种是建立2个灯组数据大小的数组,当DMA传输一个灯珠数据时,改变另一个灯组数据,通过不断改变数组的方式,节约内存,相比较而言,第一种方式较为直观,第二种方式则可以解决灯珠较多的情况,本文讨论第一种的原理和程序的实现。

三、TIM+PWM+DMA驱动WS2812

定时器 TIM 用以产生一个固定周期的PWM,DMA用以改变PWM 的占空比:

如图,DMA通过不断的搬运数据到定时器调节占空比的CCR寄存器,实现ws2812时序的产生,在STM32中,通过配置外设可实现:定时器每产生一次溢出事件(即计数完成),就请求一次DMA搬运一个数据(长度:字节/半字/字可选),所以用户只需要将数据排列在数组里,就可以产生所需要的时序。

四、STM32CubeMx配置

1、选择芯片 STM32F103C8T6

2、选择下载方式

ws2812_3-1

3、配置系统时钟

4、时钟树配置

5、生成工程配置

6、生成工程配置

7、定时器配置

8、DMA配置

9、选择TIM3_CH1/TRIG

10、选择Normal Byte Byte

11、GENERATE CODE生产代码

12、打开工程

13、编译成功

五、程序设计

新建一个.c和.h文件

c文件代码如下

#include "ws2812.h"

WS28xx_TypeStruct WS28xx;
void __show()
{
	HAL_TIM_PWM_Start_DMA(&WS28xx_PWM_hTIMER,WS28xx_PWM_Chaneel,(uint32_t *)(&WS28xx.WS28xx_Data),sizeof(WS28xx.WS28xx_Data));
}
	
//设置index的颜色
void __SetPixelColor_RGB(unsigned short int index,unsigned char r,unsigned char g,unsigned char b)
{
	unsigned char j;
	if(index > WS28xx.Pixel_size)
		return;
	for(j = 0; j < 8; j++)
	{		
		WS28xx.WS28xx_Data.ColorRealData [24 * index + j]        = (g & (0x80 >> j)) ? BIT_1 : BIT_0;  //G 将高位先发
		WS28xx.WS28xx_Data.ColorRealData [24 * index + j + 8]    = (r & (0x80 >> j)) ? BIT_1 : BIT_0;  //R将高位先发
		WS28xx.WS28xx_Data.ColorRealData [24 * index + j + 16]   = (b & (0x80 >> j)) ? BIT_1 : BIT_0;  //B将高位先发
	}
}
//获取某个位置的RGB
void __GetPixelColor_RGB(unsigned short int index,unsigned char *r,unsigned char *g,unsigned char *b)
{
	unsigned char j;
	*r=0;
	*g=0;
	*b=0;
	if(index > WS28xx.Pixel_size)
		return;
	for(j = 0; j <8; j++)
	{
		(*g)|=((WS28xx.WS28xx_Data.ColorRealData [24 * index + j]     >=BIT_1)? 0x80>>j:0); 	//G
		(*r)|=((WS28xx.WS28xx_Data.ColorRealData [24 * index + j + 8] >=BIT_1)? 0x80>>j:0);  	//R
		(*b)|=((WS28xx.WS28xx_Data.ColorRealData [24 * index + j + 16]>=BIT_1)? 0x80>>j:0);   //B
	}
}

void __SetPixelColor_From_RGB_Buffer( unsigned short int pixelIndex,unsigned char pRGB_Buffer[][3],unsigned short int DataCount)
{
	unsigned short int Index,j=0;
	for(Index=pixelIndex;Index < WS28xx.Pixel_size; Index++)
	{
			WS28xx.SetPixelColor_RGB(Index,pRGB_Buffer[j][0],pRGB_Buffer[j][1],pRGB_Buffer[j][2]);
			j++;
			if(j>DataCount)
				return;
	}
}

//设置所有颜色
void __SetALLColor_RGB(unsigned char r,unsigned char g,unsigned char b)
{
	unsigned short int Index;
	for(Index=0;Index < WS28xx.Pixel_size; Index++)
	{
		WS28xx.SetPixelColor_RGB(Index,r,g,b);
	}
}

void	WS28xx_TypeStructInit()
{
	WS28xx.Pixel_size=PIXEL_SIZE;
	WS28xx.GetPixelColor_RGB=__GetPixelColor_RGB;
	WS28xx.SetPixelColor_From_RGB_Buffer=__SetPixelColor_From_RGB_Buffer;
	WS28xx.SetPixelColor_RGB=__SetPixelColor_RGB;
	WS28xx.SetALLColor_RGB=__SetALLColor_RGB;
	WS28xx.show=__show;
}

h文件代码如下

#ifndef MYLIB_WS28XX
#define MYLIB_WS28XX

#include "tim.h"			
/****************************************
*Config
****************************************/
#define BIT_1               61            //1码比较值为61-->850us
#define BIT_0               28            //0码比较值为28-->400us

#define PIXEL_SIZE			    8             //灯的数量
#define WS28xx_PWM_hTIMER	  htim3         //定时器3
#define WS28xx_PWM_Chaneel  TIM_CHANNEL_1	//通道1	

//整个WS28xx_DataTypeStruct结构体将被以PWM方式发送
typedef struct
{						
	unsigned char ColorStartData[3];           //3个0等待PWM稳定			
	unsigned char ColorRealData[24*PIXEL_SIZE];//实际GRB数据(已经转换为PWM对应的值)
	unsigned char ColorEndData;             	 //结束位为0
}WS28xx_DataTypeStruct;

/****************************************
*对象化编程
****************************************/
typedef struct 
{
	//实际发送的数据
	WS28xx_DataTypeStruct WS28xx_Data;
	//灯数量
	unsigned short int Pixel_size;
	
    //单独设置index的RGB颜色
    void (*SetPixelColor_RGB)(unsigned short int index,unsigned char r,unsigned char g,unsigned char b);
    //从RGB数据读出:设置index的RGB颜色
    void (*SetPixelColor_From_RGB_Buffer)( unsigned short int pixelIndex,unsigned char pRGB_Buffer[][3],unsigned short int DataCount);
    //设置所有为RGB颜色
    void (*SetALLColor_RGB)(unsigned char r,unsigned char g,unsigned char b);
    //获取某个位置的RGB
    void (*GetPixelColor_RGB)(unsigned short int index,unsigned char *r,unsigned char *g,unsigned char *b);
    //显示(发出数据)
    void (*show)(void);
}WS28xx_TypeStruct;

extern WS28xx_TypeStruct WS28xx;

void   WS28xx_TypeStructInit(void);

#endif


:记得在main.c中加上初始化函数 WS28xx_TypeStructInit

while中代码如下

	WS28xx.SetALLColor_RGB (255,0,0);//整体红色
    WS28xx.show ();
    HAL_Delay (200);
    WS28xx.SetALLColor_RGB (0,255,0);//整体绿色
    WS28xx.show ();
    HAL_Delay (200);
    WS28xx.SetALLColor_RGB (0,0,255);//整体蓝色
    WS28xx.show ();
    HAL_Delay (200);	
    WS28xx.SetALLColor_RGB (0,0,0);  //集体熄灭
    WS28xx.show ();
	WS28xx.SetPixelColor_RGB(0,0,0,255);//第一个亮蓝灯
	WS28xx.show ();
	HAL_Delay (200);		
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(1,0,0,255);//第二个亮绿灯 	
	WS28xx.show ();
	HAL_Delay (200);	
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(2,0,0,255);//第三个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(2,0,0,255);//第四个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(4,0,0,255);//第五个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);			
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(5,0,0,255);//第六个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);	
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(6,0,0,255);//第七个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);	
	WS28xx.SetALLColor_RGB (0,0,0);
	WS28xx.SetPixelColor_RGB(7,0,0,255);//第八个亮蓝灯 	
	WS28xx.show ();
	HAL_Delay (200);		

完整工程: 链接

物联沃分享整理
物联沃-IOTWORD物联网 » 如何使用STM32F103C8T6控制WS2812 LED:利用PWM和DMA

发表评论