【STM32】WS2812B灯珠的PWM+DMA控制(库函数)

【STM32】WS2812B灯珠的PWM+DMA控制(库函数),第1张

【STM32】WS2812B灯珠的PWM+DMA控制(库函数)

文章目录

一、WS2812B资料

1.硬件参数2.硬件连接 二、驱动方式选择

1.数据传输与组成2.数据编码 三、软件驱动编写四、测试效果总结


一、WS2812B资料

  很多博客都有中文的DATASHEET,自行搜索,本文为英文DATASHEET截图。

1.硬件参数

绝对最大额定值与电气特性

  根据参数,使用5V电压供电即可;输入的电流1uA,IO口可以直接驱动,但若想驱动多个灯珠,最好还是独立供电。高电平最小值2.7V,低电平最大值0.7V,使用IO口直接做控制输入可以满足。

2.硬件连接

下图为DataSheet中的应用电路

  请将“DIN”信号连接到单片机上有定时器通道的引脚。前一个WS2812B的“DOUT”连接到下一个WS2812B的“DIN”。

二、驱动方式选择 1.数据传输与组成

首先看一眼数据传输方式,D1、D2、D3、D4代表按顺序排列的WS2812B灯珠。
可以看到,第三个WS2812B灯珠接收到了第三个24bit的数据,
故推断第一个WS2812B灯珠先收到第一个24bit的数据后,做出响应(发光);
再收到第二个24bit的数据后,直接转发给第二个WS2812B灯珠,由第二个WS2812B灯珠做出响应;
依次类推。
做不同颜色的灯光显示时,注意数据传输的先后。

  那么这24bit的数据内容是什么呢?下图为该24bit数据的组成。

  根据描述,数据发送的顺序是GRB,高位先发。
  每8bit代表了WS2812B灯珠对此种颜色光发光的强度,故耀眼的绿、红、蓝纯色光对应十六进制的编码分别为:0xff0000(绿)、0x00ff00(红)、0x0000ff(蓝)。

2.数据编码

  有了编码信息,再看看WS2812B灯珠对0/1码的接收方式是什么。下图为WS2812B的编码时序图。

  其中的数据传输时间如下图所示。

  可以看到,WS2812B编码所需的时间精度为ns级别,定时器中断不合适了;stm32的时钟周期在72mhz的系统时钟下为13.89ns,使用systemtick做delay也可以实现,但时间不会很精确,且需要考虑不同的指令周期对编码的影响,粗糙又繁琐。
  WS2812B的0/1编码很像一个周期内不同占空比的波形,可以考虑PWM控制,但此时又出现一个问题:
  如何在不改变频率的情况下在几个周期内连续改变占空比?
  通常我们是用库函数中TIM_SetCompare Y(TIM_TypeDef* TIM X, uint16_t Compare Z)来改变,但由于该函数执行时间很长,影响了编码时序,故不能采用此方法。
  另一种方式是直接改变定时器CCRx寄存器的值,此时又出现一个问题:
  如何在不到1us的时间内改变CCRx寄存器值?
  知识点来了:STM32中,有影子寄存器的预装载寄存器有三个,分别是:分频寄存器PSC,自动重装载ARR,自动捕获CCRx。我们读写访问的是这几个寄存器的值,往CCRx寄存器内写入值时,会写在影子寄存器的下一半字(uint16_t)中。当我们使能了预装载寄存器后(TIMx_CCMR1寄存器的OC1PE位为0 ),当PWM执行完一个周期后,将影子寄存器中下一半字(uint16_t)中的值赋给CCRx预装载寄存器。参考:STM32xx参考手册的"捕获/比较模式寄存器 1(TIMx_CCMR1)"章节。
  知道了这点后,我们可以在确定24bit的颜色编码后,得到对应位的0/1编码比较值,用数组传输给CCRx。然后启动PWM,即可得到占空比不断变化的PWM波。
  注意到STM32在定时器部分有DMA功能后,便可以尝试将数据传输部分交给DMA了。

三、软件驱动编写

  我测试使用的MCU为STM32f103c8t6,定时器使用是TIM2_CH2,移植时需要注意函数及实参使用相对应的。附图通道对应的DMA,作为移植参考。

  其他废话不多说,直接上代码,注释我写的很清楚。

头文件:HDL_WS2812B.h

 
#ifndef _HDL_WS2812B_H
#define _HDL_WS2812B_H

#include "stm32f10x.h"

#define WS2812B_PIN    GPIO_Pin_1
#define WS2812B_PORT   GPIOA
#define WS2812B_HIGH   GPIO_SetBits(WS2812B_PORT,WS2812B_PIN)
#define WS2812B_LOW	   GPIO_ResetBits(WS2812B_PORT,WS2812B_PIN)

#define WS2812B_ARR 90		//TIM2的自动重装载值
#define T0H 30						//0编码高电平时间占1/3
#define T1H 60						//1编码高电平时间占2/3


#define LED_NUM  100		//底层驱动未用,为应用方便,先加上
#define DATA_SIZE  24   //WS2812B一个编码的bit数,3*8

void PWM_WS2812B_Init(uint16_t arr);
void WS2812B_Reset(void);
void PWM_WS2812B_Write_24Bits(uint32_t GRB_Data);
void PWM_WS2812B_Show(void);
void PWM_WS2812B_Red(void);
void PWM_WS2812B_Green(void);
void PWM_WS2812B_Blue(void);

#endif

c文件:HDL_WS2812B.c

 
#include "HDL_WS2812B.h"
#include "delay.h"	//延时函数加自己对应的头文件,在复位处使用


uint16_t Single_LED_Buffer[DATA_SIZE];


void PWM_WS2812B_Init(uint16_t arr)
{
	//结构体变量
	GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_TimebaseInitTypeDef  TIM_TimebaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	
	//使能RCC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//使能PORTA时钟	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);			//使能TIM2时钟    
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);				//使能DMA1时钟    
	
	//初始化GPIO口
	GPIO_InitStructure.GPIO_Pin = WS2812B_PIN;					//GPIO口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//速度50MHz
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;			//复用推挽输出                               
	GPIO_Init(WS2812B_PORT,&GPIO_InitStructure);				//根据指定的参数初始化GPIO口
	
	//初始化TIM2
	TIM_TimebaseStructure.TIM_Prescaler=0;											//定时器分频:(0+1)=1,不分频
	TIM_TimebaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		//向上计数模式
	TIM_TimebaseStructure.TIM_Period=arr;												//自动重装载值
	TIM_TimebaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;				//时钟分割
	TIM_TimebaseInit(TIM2,&TIM_TimebaseStructure);							//根据指定的参数初始化TIM2
	
	//初始化TIM2 Channel2 PWM模式
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 						//选择定时器模式:TIM脉冲宽度调制模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	//TIM_OCInitStructure.TIM_Pulse = 0;													//待装入捕获比较寄存器的脉冲值(此程序不用加初值)
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 		//输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);  										//根据指定的参数初始化外设TIM2 Channel2
	
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);  						//使能TIM2在CCR2上的预装载寄存器
	
	TIM_Cmd(TIM2, ENABLE);  									//使能TIM2
	
	TIM_DMACmd(TIM2, TIM_DMA_CC2, ENABLE);		//使能TIM2_CH2的DMA功能(CC2对应通道2)
	
	DMA_InitStructure.DMA_PeripheralbaseAddr = (uint32_t)&(TIM2->CCR2); 				//设置DMA目的地址
  	DMA_InitStructure.DMA_MemorybaseAddr = (uint32_t)Single_LED_Buffer;					//设置DMA源地址
 	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;													//方向:从内存SendBuff到内存ReceiveBuff  
 	DMA_InitStructure.DMA_BufferSize = DATA_SIZE; 															//一次传输大小DMA_BufferSize=SendBuffSize   
  	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 						//ReceiveBuff地址不增
  	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;											//SendBuff地址自增
  	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//ReceiveBuff数据单位,16bit
  	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;					//SENDBUFF_SIZE数据单位,16bit
  	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;																//DMA模式:正常模式(传输一次)
  	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;												//优先级:中 
  	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 																//内存到内存的传输
  	DMA_Init(DMA1_Channel7, &DMA_InitStructure);																//配置DMA1的7通道(不同定时器的通道不一样)
    
  	DMA_Cmd(DMA1_Channel7, DISABLE);					//失能DMA1的7通道,因为一旦使能就开始传输
}
//复位灯带
void WS2812B_Reset(void)
{
	TIM_Cmd(TIM2, DISABLE);
	WS2812B_LOW;
	delay_ms(1);	//此处延时时间最小值大于50us即可
}
//写数据编码
void PWM_WS2812B_Write_24Bits(uint32_t GRB_Data)
{
  uint8_t i;
  for(i = 0; i < DATA_SIZE; i++)
  {
		
		Single_LED_Buffer[i] = ((GRB_Data << i) & 0x800000) ? T1H : T0H;
  }
}

//点亮灯珠
void PWM_WS2812B_Show(void)
{
	
  	DMA_SetCurrDataCounter(DMA1_Channel7, DATA_SIZE);
  	DMA_Cmd(DMA1_Channel7, ENABLE);
  	TIM_Cmd(TIM2, ENABLE);
  	while(DMA_GetFlagStatus(DMA1_FLAG_TC7) != SET);
  	DMA_Cmd(DMA1_Channel7, DISABLE);
  	DMA_ClearFlag(DMA1_FLAG_TC7);
  	TIM_Cmd(TIM2, DISABLE);
}

//单个灯珠发红光
void PWM_WS2812B_Red(void)
{
	PWM_WS2812B_Write_24Bits(0x00ff00);
	PWM_WS2812B_Show();
}
//单个灯珠发绿光
void PWM_WS2812B_Green(void)
{
	PWM_WS2812B_Write_24Bits(0xff0000);
	PWM_WS2812B_Show();
}
//单个灯珠发蓝光
void PWM_WS2812B_Blue(void)
{
	PWM_WS2812B_Write_24Bits(0x0000ff);
	PWM_WS2812B_Show();
}

测试主函数

#include "stm32f10x.h"
#include "delay.h"
#include "HDL_WS2812B.h"
int main(void)
{
	SystemInit();
	delay_init(72);
	PWM_WS2812B_Init(WS2812B_ARR);
	while(1)
	{
		WS2812B_Reset();
		PWM_WS2812B_Red();
		PWM_WS2812B_Green();
		PWM_WS2812B_Blue();
	}
}
四、测试效果

总结

  至此,WS2812B灯珠的简单驱动就编写完成了,其他颜色以及亮灯效果可以在此基础上任意发挥。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5712067.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存