PY32F030驱动WS2812灯

一.WS2812驱动原理

WS2812灯有+5v,GND,DO三个引脚,采用的驱动方式为高低电平驱动,具体高低电平的时序要求如下图所示,采用RGB三元色的搭配来组合颜色,为24bit的数据,对应顺序为GRB。对应颜色的GRB的网上查阅。

由于时序的范围区间比较小,且为纳秒级别,所以一般采用PWM+DMA方式控制,也可采用延时函数的方法,但是操作比较困难。

本人将实现两个WS2812灯珠的点亮。下面是具体的实现步骤。

 二.实现步骤

2.1 TIM1初始化

定时器的初始化包括定时器基地址,预分频值,计数模式,自动重装值,分频系数,和重复次数,以及预装载功能的设置。

然后需要先清理PWM更新事件标注位。

接下来配置PWM,PWM模式一般为PWM1,设置自动重装值为0(由于还没有开传输,所以给0即可),低电平有效,关闭快速模式。最后选择通道,使能PWM。DMA使能传输可在传输时再开启。具体代码如下。

void TIM1_Init(uint16_t arr, uint16_t psc, uint8_t rep)
{
	//初始化定时器
	TIM1_Handle.Instance = TIM1;                                  
	TIM1_Handle.Init.Prescaler = psc;                                         //分频值
	TIM1_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;                        //计数模式
	TIM1_Handle.Init.Period = arr;                                            //自动重装值
	TIM1_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;                  //分频系数
	TIM1_Handle.Init.RepetitionCounter = rep;                                 //重复次数
	TIM1_Handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;      //设置自动重装值 预装载使能位
	HAL_TIM_PWM_Init(&TIM1_Handle);

	__HAL_TIM_CLEAR_FLAG(&TIM1_Handle, TIM_FLAG_UPDATE);                      //人为清理 HAL_TIM_PWM_Init 产生的 更新事件的标志位
	
	
	TIM_OC_Handle.OCMode = TIM_OCMODE_PWM1;                                   //PWM模式1
	TIM_OC_Handle.Pulse = 0;                                                //比较值
	TIM_OC_Handle.OCPolarity = TIM_OCPOLARITY_HIGH;                            //低电平有效
	TIM_OC_Handle.OCFastMode = TIM_OCFAST_DISABLE;                            //关闭快速模式
	HAL_TIM_PWM_ConfigChannel(&TIM1_Handle, &TIM_OC_Handle, TIM_CHANNEL_1);   //设置PWM通道
	
//	HAL_TIM_PWM_Start_DMA(&TIM1_Handle, TIM_CHANNEL_1, (uint32_t *)DMA_BUFF, 4);   //DMA方式打开通道1 4次传输
}

 

2.2 TIM_PWM回调函数

由于使用的是HAL方式,所以要在HAL_TIM_PWM_MspInit函数中进行IO口和DMA的相关初始化工作。

回调函数中首先是打开IO口、TIM1、DMA时钟,然后需要使用HAL_SYSCFG_DMA_Req(0x0b);        /* DMA1_MAP 选择TIM1_CH1 */,这也是PY系列和ST系列DMA使用的区别,需重点注意,具体的参数需要在芯片参考手册中查找,这里使用的是TIM1-CH1,所以是写0x0b。然后使能需要使用的IO口。

接着配置DMA,DMA的配置一般包括基地址,使用的是DMA1_Channel1;传输方向,方向为存储器到外设;起始地址是否自增,起始地址为数组地址,自增;终点地址是否自增,终点地址为TIM1,不自增;起始地址和终点地址的数据宽度都为半字,也就是16bit;由于采用轮询的方式,所以工作模式为单次模式。优先级给中等即可。然后链接DMA,这是非常重要的步骤,__HAL_LINKDMA(htim, hdma[TIM_DMA_ID_CC1], DMA_Handle);     /* DMA1关联TIM_UP事件 */。然后初始化DMA。

最后设置DMA的中断优先级和使能DMA中断。

具体代码如下图所示。

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	if(htim -> Instance == TIM1){
		__HAL_RCC_GPIOA_CLK_ENABLE();
		__HAL_RCC_TIM1_CLK_ENABLE();
		__HAL_RCC_DMA_CLK_ENABLE();
		__HAL_RCC_SYSCFG_CLK_ENABLE();
		
		HAL_SYSCFG_DMA_Req(0x0b);                                   /* DMA1_MAP 选择TIM1_CH1 */
//		 
		GPIO_InitStruct.Pin = GPIO_PIN_8;                                     //配置pa8
		GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                               //复用功能 推挽输出
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;                          //输出速度
		GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;		
		HAL_GPIO_Init(GPIOA,  &GPIO_InitStruct);
		
//		HAL_NVIC_SetPriority(TIM1_CC_IRQn, 1, 1);
//		HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
		
		//DMA配置
		DMA_Handle.Instance = DMA1_Channel1;
		DMA_Handle.Init.Direction = DMA_MEMORY_TO_PERIPH;                     //传输方向存储器到外设
		DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE;                         //终点地址不递增
		DMA_Handle.Init.MemInc = DMA_MINC_ENABLE;                             //起点地址递增
		DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;        //外设数据宽度
		DMA_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;           //存储区数据宽度
		DMA_Handle.Init.Mode = DMA_NORMAL;                                  //工作模式 单次模式
		DMA_Handle.Init.Priority = DMA_PRIORITY_MEDIUM;                       //优先级 中等

		__HAL_LINKDMA(htim, hdma[TIM_DMA_ID_CC1], DMA_Handle);     /* DMA1关联TIM_UP事件 */
		HAL_DMA_Init(&DMA_Handle);
		
		HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 1, 1);
		HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
	}
}

2.3 转化数据函数

这也是驱动WS2812最关键的部分,最重要的是要给定合适的预分配值,自动重装值和比较值,这样才能成功驱动WS2812。使用的时钟为24M,TIM1时钟不分频,所以TIM1时钟也是24M 。

对于发1的协议来说,给定arr为30,那么一个定时器周期为1/(24M/30)=1250ns。给定比较值为15。那么高低电平的时间各为625ns。满足发1的时序要求。

对于发0的协议来说,给定arr为30,那么一个定时器周期为1/(24M/30)=1250ns。给定比较值为8。那么高电平的时间为333.33ns,低电平的时间为916.66。满足发0的时序要求。

需要控制两个灯珠,每一个为24位,所以采用两个for循环依次取出对应的值,然后将其保存在数组中,最后通过DMA搬运到TIM,即可完成对WS2812的控制。由于WS2812的默认电平为低电平,所以最后要给有效值为0,使其一直输出低电平,以方便下次的控制。所以对于两个灯珠来说,数组大小为24+24+1=49。

最后通过HAL_TIM_PWM_Start_DMA开始传输。

具体实现代码如下。

void WS2818_Data(uint32_t led1, uint32_t led2)
{
	uint8_t i;
	for(i=0; i<24; i++)
	{
		if(0x00800000 & (led1<<i))
		{
			tim1_GRB[i] = 15;
		}
		else
		{
			tim1_GRB[i] = 8;
		}
	}
	
	
	for(i=24; i<48; i++)
	{
		if(0x00800000 & (led2<< (i - 24)))
		{
			tim1_GRB[i] = 15;
		}
		else
		{
			tim1_GRB[i] = 8;
		}
	}
	
	tim1_GRB[48] = 0;
	
	HAL_TIM_PWM_Start_DMA(&TIM1_Handle, TIM_CHANNEL_1, (uint32_t *)tim1_GRB, 49);//DMA方式 打开通道1 49次传输
}

2.4 DMA中断函数

在DMA通道1中断处理函数中调用DMA中断处理函数,清除相关标志位,方便下次的传输。

程序中并没有对TIM的关闭操作,再传输完成后,有效值给0,IO口一直保持低电平,所以不必要关闭定时器,下次传输时直接开启DMA即可。

//DMA通道1中断函数
void DMA1_Channel1_IRQHandler(void)
{
	HAL_DMA_IRQHandler(TIM1_Handle.hdma[TIM_DMA_ID_CC1]);
}

2.5 main.c操作

    给定事先计算好的定时器参数初始化定时器。

	TIM1_Init(30 - 1, 1 - 1, 0);

然后在while循环中循环调用转化数据函数。

	while(1)
	{
		WS2818_Data(0x0000FF, 0x0000FF);
		HAL_Delay(1000);
		WS2818_Data(0x00FF00, 0x00FF00);
		HAL_Delay(1000);
		WS2818_Data(0xFF0000, 0xFF0000);
		HAL_Delay(1000);
	
	}

结语:该文件为自己没事瞎写,记录自己的一点点收获,有不对的地方欢迎各位大神指正,实在看不顺眼也请大哥轻点喷,谢谢谢谢大家!

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值