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
暂无评论