STM32F10XX使用定时器+DMA驱动WS2812B灯带

前言

    最近看到网上一些灯带的视频,顺手做了个驱动,水篇文章吧,床上拍的有点抽象。。。。

想要这个驱动直接滴滴我好吧,不收费,在下面评论,我看到之后秒发。

    先简单介绍一下,这个驱动和别人的有什么区别,我看到有很多人用定时器输出PWM外加DMA的这种方法做的驱动,也有人用了SPI(没有仔细了解),想了想用SPI的接口去做这个灯带确实很帅。一开始我是打算直接用GPIO来搞定的,仔细看了看手册之后发现貌似需要纳秒级的延时,最主要的是如果程序被中断掉,灯带会失真(当然这个灯珠数量不多的话传输也很快)

    这里说明一下和其他使用相同方法的驱动不同的地方,使用我这里的驱动可以单独选择每一个灯的亮灭,因为我看到很多的驱动都是写好的,比方说自己设定了一段彩色渐变流水,使用按键换一个闪烁模式等等,对于初学者想用灯带显示一些DIY的东西不太友好,使用我这里的驱动简单明了,我们可以选择每一个灯亮或不亮,想亮什么我们都可以指定。有了这些作为基础,我们当然就可以封装出来好看的流水灯了,本篇文章我打算先写一个快速入手,作为驱动的使用手册,在下面再写原理,当然,在一开始的时候我也会介绍WS2812B的参数等等。

快速使用

#define LED_num 30 //定义灯珠数量,我这里是30个灯珠
uint16_t WS2812B_DATA[LED_num*24]; //一共需要存放这么多数据
TIM_Init(); //主函数中调用这个初始化
WS2812B_off(); //给数组清零
               //注意这个函数如果报错可能是没有开C99,勾选一下就可以了
WS2812B_modify(uint32_t RGB,uint8_t num);//向第num个灯珠写入RGB的数据,这个RGB是按照GRB的顺序写
                                         //例如0xff0000是绿色
WS2812B_update();   //更新显示

第一步:需要设置灯珠的数量。

第二步:在主函数中调用TIM_Init()这个函数

第三步:给数组清零,调用WS2812B_off()函数

第四步:使用WS2812B_modify函数改变自己想要使用的灯珠的编号和颜色

第五步:使用WS2812B_update()函数来改变灯珠的状态,让其按照自己设定好的方式显示

灯带编程思路

    我们先看一下商家给的规格书上的介绍

    这样我们就能知道我们的1和0就是用一个高电平加一个低电平来表示的,主要是他们的时间不同,这也说明了为什么我们要用定时器来表示,我们现在只需要保证一个PWM波的时间在1.25纳秒就可以,我们拿1/1.25*1000000,得到了800KHz,然而我使用的单片机主频在72MHz,求一下就知道,我们应该让PSC和ARR的乘积为90,然而,我们想通过PWM波的方式就需要设置CCR和ARR的关系,ARR的取值越大,我们得到的误差越小,这边直接取值90-1。

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;	
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_Period = 90-1;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);

    这里使用的TIM2的输出比较通道一,下面还需要一个DMA,用于在更新事件产生时改变一下CCR的值,我的思路是把我们所有的CCR的值存到一个大的数组里,然后打开定时器,使用DMA不断搬运大数组里的值,注意:要根据自己的灯珠数量来编写不同的代码(数组的大小等等)。关于这个大数组,我这边有两个方法,到时候可以根据自己的应用场景选择不同的方法。

方法一:在启动定时器前,用一个函数输入RGB然后转译成CCR的值放入大数组里,这个每一次我们都需要提供一大串的数据,比方说你有30个灯珠,那么你每次都需要提供720个数据,这个效率太低了。

方法二:函数使用两个参数,一个是RGB,一个是灯珠的编号,一次只能改变一个灯珠的值(或许我研究一下不定长函数可以解决这个问题,或者使用二维数组,这里作为基础版就先不研究这些),注:这个RGB是按照GRB的顺序传递参数的,并且这个函数在使用之前要进行清零操作(不用的灯珠给全0,即25,也可以用程序中的WS2812B_off函数)

void WS2812B_off(void)
{
		for(int c=0;c<LED_num*24;c++)
	{
		WS2812B_DATA[c]=25;
	}
}
void WS2812B_modify(uint32_t RGB,uint8_t num)
{
	uint16_t i,j,n=0;

	for(j=0;j<30;j++)
	{
		if(j==num)
		{
			for(i=0;i<24;i++)
			{
				if(0x800000&((RGB << i)))
				{
					WS2812B_DATA[n]=55;
				}
				else
				{
					WS2812B_DATA[n]=25;
				}
				n++;
			}
		}
	}
}

    手动调用函数开启DMA和TIM,不断地让DMA运输这个值,改变CCR寄存器的值,从而实现翻转电平,产生不同的信号,我这里CCR分为两个值,一个25一个55,满足那个高电平和低电平的范围。

void WS2812B_update(void)
{
	DMA_SetCurrDataCounter(DMA1_Channel2, 720);
	DMA_Cmd(DMA1_Channel2, ENABLE);
	TIM_Cmd(TIM2, ENABLE);
	while(!DMA_GetFlagStatus(DMA1_FLAG_TC2));
	TIM_Cmd(TIM2, DISABLE);
	DMA_Cmd(DMA1_Channel2, DISABLE);
	DMA_ClearFlag(DMA1_FLAG_TC2);
}

最后,在主函数调用一下update函数就可以实现更改灯的颜色了,可以将更改用的函数放到按键或者一些其他事件中,这样就实现了对灯带颜色的控制,至于如何做出来流动的彩色灯带,我相信有了基础的点灯剩下的也不会很难。

如果主频不同的话是需要更改ARR的值来保证频率的相同(800khz)

驱动需要改进的地方

    这个我坦白地讲还有很多,我觉得可以做几个条件编译来表示主频和默认值(这个可以是STM32自己的库自带的,借用他的条件编译就知道选的开发板型号等信息,因此也可以设定不同的定时器引脚,从而保证无论如何,输出比较的频率都在我们想要的800K),也可以使用一个条件编译来选择我们使用几号定时器,从而大大方便了选择定时器。还有就是一次只能改变一个灯珠的RGB,或许这个可以使用一个二维数组,或者不定长参数的函数来实现,或许也可以给函数传入一个数组的地址,里面存放的是需要改掉的灯珠编号。

        期待在看这篇文章的你更新出来一个2.0。。。。

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值