STM32F 驱动WS2812B (2) PWM+DMA


一、基本思路

原理说明在《STM32F 驱动WS2812B (1) IO口》已经说明

STEP1:

实现逻辑1跟逻辑0的脉冲,这里用PWM修改占空比方式实现。

STEP2:

将数据通过DMA发送到TIM外设,发送前面80us的低电平RESET信号以及每个灯珠的24个0 code或者1 code的脉冲。

二、实现

1.配置CUBEMX

使用的芯片是STM32F412,选用TIM2的第2个通道输出PWM信号,因为是100M的时钟,所以分频系数为0即为不分频,然后125的counter period就是125个时钟周期为一个脉冲周期,所以一个脉冲周期为(1/100M)*125=0.00000125s=0.00125ms=1.25us。

PWM的Pulse先设置成0,这个值用来控制占空比,刚才计算出来125个时钟周期就是1250ns,那么一个时钟周期就是1个时钟周期就是10ns,所以根据ws2812b的手册说明的0code的高电平时400ns,低电平850ns,对应这个PULSE应该设置成40,表示40个时钟周期即为400ns,同理1code的高电平为800ns所以这个值应该设置成80即可。

在这里插入图片描述

2.配置DMA

单击Add添加一个DMA通道,这里因为时TIM2的2通道,所以把这个通道打开,然后在Direction的地方选择memory to peripheral表示从内存到外设,这里的内存就是定义的数据数组,外设就是TIM产生PWM的比较寄存器。其他的如下图所示,这边推荐Data Width都选择Word即可。
在这里插入图片描述

3.配置GPIO口

把GPIO的上下拉改成下拉,这样在不输出PWM的时候时低电平
在这里插入图片描述

4.修改工程

点击生成代码,生成MDK的代码。每个灯珠需要24个脉冲,每个脉冲的占空比根据0,1变化,在这之前需要一个reset信号,是大于50us的低电平。我们使用80us的低电平,之前通过cubemx配置的输出PWM的周期是1.25us,所以这个80us的低电平需要使用64个占空比为0的脉冲形成,所以先定义一个数组用来存放灯的数据,这边以1个灯为例:

uint32_t pwm_data[RESET_WORD+24*LED_NUM+DUMMY_WORD]={0};

这里的RESET_WORD定义成64,LED_NUM定义成1,DUMMY_WORD定义是1,这个DUMMY_WORD如果不加波形会出现问题,这个稍后再说,即1个灯需要64+24+1个脉冲。初始化为0,这样我们修改64个数据之后的数据不会影响前面的reset的数据。

假设需要亮蓝色,蓝色的RGB为0x0000FF,WS2812B的传输顺序是GRB,并且是先传输高bit,所以要先把G的颜色值放入数组,需要一个for循环,循环7次从高到低将bit对应的占空比填入数组,0对应的应该是40,1对应的是80:

for(j=15;j>=8;j--)//G
{	
	(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(15-j))]=80):(pwm_data[RESET_WORD+(i*24+(15-j))]=40);
}

因为第一个传输的是G数据,所以从RBG_COLOR的第15位bit15开始判断是否为1如果是1就用占空比数值80表示,一直判断到bit8。这里从RGB_COLOR的bit15到bit8应该存放到pwm_data的对应灯的bit0-bit7。

之后是R传输,同理:

for(j=23;j>=16;j--)//R
	(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(31-j))]=80):(pwm_data[RESET_WORD+(i*24+(31-j))]=40);

最后是B传输,同理:

for(j=7;j>=0;j--)//B			(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(23-j))]=80):(pwm_data[RESET_WORD+(i*24+(23-j))]=40);

把这个数组填好之后就可以开启DMA传输了:

HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,&pwm_data[0], RESET_WORD+24*LED_NUM+DUMMY_WORD);

这里的传输数据的个数就是整个数组的个数。

传输已经开始了,在传输结束之后会进入到回调函数,我们引用这个回调函数来处理结束的时候停止DMA传输,否则会一直传输。

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
  HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_2);
}

这样一轮发送点亮等LED灯的DMA就完成了。

三、验证

1.抓取0 code波形

将LED_NUM设置成1,并且将全部的占空比的参数设置成40 也就是0 code的脉冲
在这里插入图片描述

2.抓取1 code波形

将LED_NUM设置成1,并且将全部的占空比的参数设置成80 也就是1 code的脉冲
在这里插入图片描述

3.验证发送脉冲个数以及时间

正常情况下如果将LED_NUM设置成1,并且将全部的占空比的参数设置成80 也就是1 code的脉冲,应该会发送24个脉冲,如下是抓到的波形:
在这里插入图片描述
如果在发送的数据中不加入dummy word就会出现如下的波形,通过计算脉冲个数可以看出脉冲个数不对,而且波形变得很奇怪:
在这里插入图片描述
这个原因还没搞清楚,如果有知道原因的可以在下面评论下,互相探讨下

一些想法

1:如果不设置GPIO的上下拉方式,在抓取的波形观察,会发现在没有传输的时候一直为高电平。
2:在设置DMA传输的时候一定需要注意传输的方向是Memory To Peripheral
3:DMA的Data Width要跟程序中定义的数组的类型一致。

部分关键代码

Main关键源码如下:

#define DUMMY_WORD 1
#define RESET_WORD 64
#define LED_NUM 2
uint32_t pwm_data[RESET_WORD+24*LED_NUM+DUMMY_WORD]={0};
void ws2812b_show_dot(uint8_t dot_index,uint32_t color)
{
	int i=0,j=0;
	for(i=0;i<LED_NUM;i++)
	{
		if(i==dot_index)
		{
			for(j=15;j>=8;j--)//G
				(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(15-j))]=80):(pwm_data[RESET_WORD+(i*24+(15-j))]=40);
			for(j=23;j>=16;j--)//R
				(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(31-j))]=80):(pwm_data[RESET_WORD+(i*24+(31-j))]=40);
			for(j=7;j>=0;j--)//B
				(((color>>j)&0x01)==1)?(pwm_data[RESET_WORD+(i*24+(23-j))]=80):(pwm_data[RESET_WORD+(i*24+(23-j))]=40);			
		}
		else
		{
			for(j=0;j<24;j++)
			{
				pwm_data[RESET_WORD+i*24+j]=40;
			}			
		}
	}
	HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,&pwm_data[0],RESET_WORD+24*LED_NUM+DUMMY_WORD);
}
int main(void)
{
  HAL_Init();
  uint32_t RGB_COLOR=0x0000ff;
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM2_Init();
  while (1)
  {
	  ws2812b_show_dot(0,RGB_COLOR);
	  HAL_Delay(500);
	  ws2812b_show_dot(1,RGB_COLOR);
	  HAL_Delay(500);
  }
}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
  HAL_TIM_PWM_Stop_DMA(&htim2, TIM_CHANNEL_2);
}
  • 2
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值