STM32驱动WS2812B-2020 RGB彩灯(二)

STM32驱动WS2812B-2020 RGB彩灯(二)

上一节我们简单分析了WS2812B-2020彩灯的一些硬件方面的驱动原理,进行了驱动思路的整理。
具体参考上一篇文章:STM32驱动WS2812B-2020 RGB彩灯(一)

接下来我们一步步分析具体的如何用代码来实现点亮彩灯,更进一步的实现彩虹灯效。

上回我们已经把灯如何点亮的条件都分析好了,就是需要在一个周期内,给一个固定占空比的信号。这个信号过来就是一个bit位,表示一个0或者一个1,连续给24个信号来,就组成了一个RGB灯的颜色的数值。

在这里插入图片描述
如上图所示:我们仍然使用的WS2812B的时序图来做例子。图中标注的t1指的是,当传输bit位为0时,这个0一共占用多长时间,就是需要用到的一个周期;t2指的是,传输0的这个周期里,高电平需要持续的时间;同理,t3指的是,传输0的这个周期里,低电平需要持续的时间。

具体这里的理解,上一篇已经说过了。这个周期取多少合适呢?还是结合datasheet,有一个800K的传输速率,也就是传输一个bit位的速率,换算成时间就是1.25us,那么这里的周期就先用一个1.25us试试看,t1=1.25us。注意这里1.25us这个周期就是PWM的脉冲周期!!需要和我们后面分析的时钟周期区别!

下面具体分析下时钟周期和脉冲周期的概念:我们用的主控的主频是72MHz,计算一下周期是1/72us≈13.89ns。我们把这个时间记为T,这个时间就是时钟周期。是整个系统运行需要参考的时钟周期,包括需要设置的PWM里面的脉冲周期,也需要参考这个时钟周期。如果想了解时钟,具体可参考STM32的时钟树体系:

在这里插入图片描述

图中箭头所指的地方,是我们使用到的定时器,它的时钟来源是从AHB处过来的,本例中,APB1不分频,所以TIMXCLK时钟就等于SYSCLK,就是72MHz,当然这个不是我们讲解的重点。

接下来重点要研究的是PWM。具体原理自己理解。我们需要用到两个参数:一个是count,另一个是dummy。这两个参数,count是计数值,STM32的DATASHEET中这个值对应的的ARR寄存器(重装载寄存器)这个值就是用来决定我们的脉冲周期的,即t1的值 ( 需要关注的是,count是一个累加值,每增加1,用的时间是我们上面说的时钟周期T);另一个值,dummy就是所谓的占空比,也就是在一个PWM的脉冲周期内,高电平的持续时间,即t2的值,对应的是STM32手册中的CCR(捕获/比较寄存器)。可参考下图

在这里插入图片描述

那么,这个count的值怎么获取呢?

刚刚我们分析到,时钟周期13.89ns,脉冲周期1.25us。就是说,我经过N个时钟周期后,能达到一个脉冲周期。这里的N,就是我们需要的额count值。计算一下,得出count的值为90。

这里需要参考datasheet中列表中列出的具体的高低电平需要的时间。再次给出该表:

在这里插入图片描述
还是以“0”码为例。高电平需要的时间是220ns-380ns,需要M个时钟周期能达到这个时间长度,显然是一个范围。那就是M*13.89=220~380。这里大概给一个值25,计算得到的高电平时间是347.25ns,在范围之内,然后确定一下低电平的时间:1250-347.25=902.75ns,对比一下范围(580ns~1us),很明显这个值也是符合要求的。那么这里确定下来的25,就是表示“0”码时,需要的高电平的持续时间的计数值,也就是占空比。这个值就是最后要通过DMA写到定时器的CCR中的。

同理,“1”码的占空比计算了是45。这个值,都是可以在一个范围内取的,只要保证计算下来的高低电平的时间满足上表列出的时间即可。不能理解的可以留言探讨。

接下来我们需要做的就是配置PWM了。整个配置都是在CUBEMX里完成的。
例程中我们用的TIM4的通道1.
在这里插入图片描述
首先配置时钟源,然后通道,然后不分频,counter period为89,因为在计算的时候需要加1。

然后说说DMA这个。DMA(Direct Memory Access),就是数据传输的时候,不经过CPU,可以在内存和外设之间直接传输数据。我们这里设置的DMA输出传输方向就是内存到外设,内存里面是需要我们自己定义一个存储空间,比如一个数组,然后将24bit的颜色数据存到这个数组中,在配置DMA的时候这里使用定义的该数组的地址。外设就是TIM产生PWM的捕获/比较寄存器CCR。然后看一下DMA的配置:

在这里插入图片描述
基本配置好了,可以生成代码看下了:

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */

  /* USER CODE END TIM2_MspInit 0 */
    /* TIM2 clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
  /* USER CODE BEGIN TIM2_MspInit 1 */

  /* USER CODE END TIM2_MspInit 1 */
  }
  else if(tim_baseHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspInit 0 */

  /* USER CODE END TIM3_MspInit 0 */
    /* TIM3 clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();
  /* USER CODE BEGIN TIM3_MspInit 1 */

  /* USER CODE END TIM3_MspInit 1 */
  }
  else if(tim_baseHandle->Instance==TIM4)
  {
  /* USER CODE BEGIN TIM4_MspInit 0 */

  /* USER CODE END TIM4_MspInit 0 */
    /* TIM4 clock enable */
    __HAL_RCC_TIM4_CLK_ENABLE();

    /* TIM4 DMA Init */
    /* TIM4_CH1 Init */
    hdma_tim4_ch1.Instance = DMA1_Channel1;
    hdma_tim4_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_tim4_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim4_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim4_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_tim4_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_tim4_ch1.Init.Mode = DMA_NORMAL;
    hdma_tim4_ch1.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_tim4_ch1) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim4_ch1);

  /* USER CODE BEGIN TIM4_MspInit 1 */

  /* USER CODE END TIM4_MspInit 1 */
  }
}

这里是DMA部分的配置,然后准备准备,看能不能点灯。

#define WS2812B_LED_QUANTITY	8

uint32_t WS2812B_Buf[WS2812B_LED_QUANTITY];	//0xGGRRBB
uint32_t WS2812B_Bit[24*WS2812B_LED_QUANTITY+1];//这个是将上面的buffer拆解成bit
uint8_t WS2812B_Flag;

void WS2812B_IRQHandler(void);



void WS2812B_ClearBuf(void)//所有数据清零,关灯
{
	uint8_t i;
	for(i=0;i<WS2812B_LED_QUANTITY;i++)
	{
		WS2812B_Buf[i]=0x000000;
	}
}

void WS2812B_SetBuf(uint32_t Color)//设置灯的颜色
{
	uint8_t i;
	for(i=0;i<WS2812B_LED_QUANTITY;i++)
	{
		WS2812B_Buf[i]=Color;
	}
}

void WS2812B_UpdateBuf(void)//将设置好的颜色写入到灯里面,让它亮
{
	uint8_t i,j;
	for(j=0;j<WS2812B_LED_QUANTITY;j++)
	{
		for(i=0;i<24;i++)
		{
			if(WS2812B_Buf[j]&(0x800000>>i)){WS2812B_Bit[j*24+i+1]=45;}  //BIT位为1 设置相应的占空比
			else{WS2812B_Bit[j*24+i+1]=25;}
		}
	}
	HAL_TIM_PWM_Start_DMA(&htim4,TIM_CHANNEL_1,&WS2812B_Bit[0],24*WS2812B_LED_QUANTITY+1);
	//DMA1_Start(24*WS2812B_LED_QUANTITY+1);
	//TIM2_Cmd(ENABLE);
	//while(WS2812B_Flag==0);
	//WS2812B_Flag=0;
}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)//PWM信号传输完成回调函数,该函数非常之重要
{
	__HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,0);
   HAL_TIM_PWM_Stop_DMA(&htim4, TIM_CHANNEL_1);
	//WS2812B_Flag=1;
}

这一部分的代码,就是对我们以上所有内容的验证,如果真正读懂了上面分析的内容,那这边代码研究研究还是可以理解的。说一下最后那个回调函数,在上一篇中说到了一个reset=280us的,就是当写入数据到RGB灯里面后,需要给一个不低于280us的低电平,让数据生效。就是在这个里面实现的。一开始调的时候灯怎么都不会亮,搞了很久才搞定的,具体实现的代码是这一句:__HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,0);

上面这些基本可以让你的灯点亮了,需要什么颜色,就设置colour的值,如果你需要更酷炫的效果,可以参考下面的部分:

void ColorLight_Mode0(void)
{
	static uint8_t i,Color;
	//ColorLight_Time=6;

	if(i==0)WS2812B_SetBuf((Color));
	if(i==1)WS2812B_SetBuf((255-Color));
	if(i==2)WS2812B_SetBuf((Color)<<8);
	if(i==3)WS2812B_SetBuf((255-Color)<<8);
	if(i==4)WS2812B_SetBuf((Color)<<16);
	if(i==5)WS2812B_SetBuf((255-Color)<<16);
	if(i==6)WS2812B_SetBuf((Color)|((Color)<<8));
	if(i==7)WS2812B_SetBuf((255-Color)|((255-Color)<<8));
	if(i==8)WS2812B_SetBuf((Color)|((Color)<<16));
	if(i==9)WS2812B_SetBuf((255-Color)|((255-Color)<<16));
	if(i==10)WS2812B_SetBuf(((Color)<<8)|((Color)<<16));
	if(i==11)WS2812B_SetBuf(((255-Color)<<8)|((255-Color)<<16));
	if(i==12)WS2812B_SetBuf(((Color))|((Color)<<8)|((Color)<<16));
	if(i==13)WS2812B_SetBuf(((255-Color))|((255-Color)<<8)|((255-Color)<<16));

	Color++;
	if(Color==0)
	{
		i++;
		i%=14;
	}
}

void ColorLight_Mode1(void)
{
	uint8_t i,RandNum;
	uint32_t G,R,B;
	static uint8_t j;
	ColorLight_Time=20;
	for(i=31;i>0;i--)
	{
		WS2812B_Buf[i]=WS2812B_Buf[i-1];
	}
	
	if(j==0)
	{
		RandNum=rand()%7;
		if(RandNum==0)WS2812B_Buf[0]=0x0000FF;
		if(RandNum==1)WS2812B_Buf[0]=0x00FF00;
		if(RandNum==2)WS2812B_Buf[0]=0xFF0000;
		if(RandNum==3)WS2812B_Buf[0]=0x00FFFF;
		if(RandNum==4)WS2812B_Buf[0]=0xFF00FF;
		if(RandNum==5)WS2812B_Buf[0]=0xFFFF00;
		if(RandNum==6)WS2812B_Buf[0]=0xFFFFFF;
	}
	else if(j<15)
	{
		G=WS2812B_Buf[1]/0x10000%0x100;
		R=WS2812B_Buf[1]/0x100%0x100;
		B=WS2812B_Buf[1]%0x100;
		if(G>20)G-=20;
		if(R>20)R-=20;
		if(B>20)B-=20;
		WS2812B_Buf[0]=(G<<16)|(R<<8)|B;
	}
	else
	{
		WS2812B_Buf[0]=0;
	}
	
	j++;
	j%=50;
}


void ColorLight_Mode2(void)
{
	uint8_t i;
	static uint8_t j;
	ColorLight_Time=20;
	for(i=31;i>0;i--)
	{
		WS2812B_Buf[i]=WS2812B_Buf[i-1];
	}
	if(j==0)WS2812B_Buf[0]=rand()%0x1000000;
	else WS2812B_Buf[0]=WS2812B_Buf[1];
	j++;
	j%=10;
}

void ColorLight_Mode3(void)
{
	uint8_t i;
	ColorLight_Time=500;
	for(i=0;i<32;i++)
	{
		WS2812B_Buf[i]=rand()%0x1000000;
	}
}

这些效果都可以很容易找到,希望可以帮助你更快的点亮你的灯。墨迹了两天总算把文章堆好了,好多东西时间长了都忘了,有不正确的可以提出指正。
月明星稀 乌鹊南飞

版权声明:本文为CSDN博主「南山维拉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Anitawen/article/details/118487050

生成海报
点赞 0

南山维拉

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

RT-Thread Studio联合STM32CubeMX进行开发

RT-Thread Studio联合STM32CubeMX进行开发 一、准备内容 1.1硬件平台 使用正点原子STM32F4探索者 使用到板载LED灯,原理图如下: 1.2软件环境 STM32CubeMX软件

STM32G474_FDCAN的普通CAN模式使用

由于鄙人比较懒,因此本文章只是对 FDCAN 的 经典模式 的简单使用介绍。对于我不需要使用的功能 我就没有深入研究,因此本文只是 CAN 的常用方式的笔记,深入研究的话可以详细阅读手册,

OV7670摄像头模块资料

OV7670摄像头模块资料 一、实物图和原理图 二、模块简介 OV7670 是 OV( OmniVision)公司生产的 CMOS VGA 图像传感器。该传感器体积小、工作电压低,提供单片 VGA