一、WS2812B资料
1.硬件参数2.硬件连接 二、驱动方式选择
1.数据传输与组成2.数据编码 三、软件驱动编写四、测试效果总结
一、WS2812B资料
很多博客都有中文的DATASHEET,自行搜索,本文为英文DATASHEET截图。
1.硬件参数绝对最大额定值与电气特性
根据参数,使用5V电压供电即可;输入的电流1uA,IO口可以直接驱动,但若想驱动多个灯珠,最好还是独立供电。高电平最小值2.7V,低电平最大值0.7V,使用IO口直接做控制输入可以满足。
下图为DataSheet中的应用电路
请将“DIN”信号连接到单片机上有定时器通道的引脚。前一个WS2812B的“DOUT”连接到下一个WS2812B的“DIN”。
首先看一眼数据传输方式,D1、D2、D3、D4代表按顺序排列的WS2812B灯珠。
可以看到,第三个WS2812B灯珠接收到了第三个24bit的数据,
故推断第一个WS2812B灯珠先收到第一个24bit的数据后,做出响应(发光);
再收到第二个24bit的数据后,直接转发给第二个WS2812B灯珠,由第二个WS2812B灯珠做出响应;
依次类推。
做不同颜色的灯光显示时,注意数据传输的先后。
那么这24bit的数据内容是什么呢?下图为该24bit数据的组成。
根据描述,数据发送的顺序是GRB,高位先发。
每8bit代表了WS2812B灯珠对此种颜色光发光的强度,故耀眼的绿、红、蓝纯色光对应十六进制的编码分别为:0xff0000(绿)、0x00ff00(红)、0x0000ff(蓝)。
有了编码信息,再看看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灯珠的简单驱动就编写完成了,其他颜色以及亮灯效果可以在此基础上任意发挥。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)