如何使用STM32F103C8T6驱动WS2812(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文件代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#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文件代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#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中代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);

完整工程: 链接

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2024 祝小炉
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信