如何使用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、选择下载方式
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);