[STM32]驱动WS2812b(TIM+DMA)

WS2812B简介:

● 智能反接保护,电源反接不会损坏IC。
● IC控制电路与LED点光源公用一个电源。
● 控制电路与RGB芯片集成在一个5050封装的元器件中,构成一个完整的外控像素点。
● 内置信号整形电路,任何一个像素点收到信号后经过波形整形再输出,保证线路波形畸变不会累加。
● 内置上电复位和掉电复位电路。
● 每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示,扫描频率不低于
400Hz/s。
● 串行级联接口,能通过一根信号线完成数据的接收与解码。
● 任意两点传传输距离在不超过5米时无需增加任何电路。
● 当刷新速率30帧/秒时,级联数不小于1024点。
● 数据发送速度可达800Kbps。

WS2812b协议

在这里插入图片描述

帧单位:

这个信号是一帧数据传输完成的标志。当你完成对灯珠的写入,需要给灯珠一个帧单位信号,灯珠才会按写入的数据亮灯,否则,灯珠只会按上一回写入的数据亮灯。(可以理解为输出更新)。

占空比:

在WS2812b中,零码的占空比为30%,一码的占空比为60%

stm32驱动WS2812b(TIMPWM+DMA)

测试环境:

使用TIM2输出比较通道1,GPIOA_Pin0

基本步骤:

● 初始化DMA,TIM,GPIO外设
● 将PWM输出GPIO设为复用推挽输出
● 将TIM时基单元的时钟来源设为内部时钟(72M),预分频器(PSC)设为1-1,自动重装器(ARR)设为90-1。

800k=72M/90

● 开启输出比较寄存器(CRR)的影子寄存器,开启DMA请求。

示意图:

请添加图片描述

第一个灯初始化后是绿色的:

解决办法:把第一个灯单独再灭一次(给第一个灯写入:0x000000)

代码:

支持

#define Led_Num 30//灯珠数量
uint16_t WS2812_Value[24*Led_Num];//WS2812b数据存储区

WS2812b发送帧单位/输出更新信号函数:

void WS2812_rest()
{
	TIM_Cmd(TIM2,DISABLE);//关闭PWM输出
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);//拉低DIN
	Delay_ms(1);//等待一毫秒
}

WS2812b数据清零函数:

void WS2812_Clear()
{
	uint16_t i=0;
	for(i=0;i<(24*Led_Num);i++)//Led_Num是灯珠数量
	{
		WS2812_Value[i]=30;//把占空比数据设为零码
	}
}

WS2812b发送数据

Num:要传输的灯珠数据的数量
void WS2812_Show(uint8_t Num)
{
	WS2812_rest();//WS2812b发送帧单位信号
	DMA_SetCurrDataCounter(DMA1_Channel5, 24*(Num+1));//(设置待转移数据个数为(24*Num+1)
	DMA_Cmd(DMA1_Channel5, ENABLE);//开启DMA转运
	TIM_Cmd(TIM2, ENABLE);//开启TIM2
	while (DMA_GetFlagStatus(DMA1_FLAG_TC5) != SET);//等待ws2812b数据传输完成
	DMA_ClearFlag(DMA1_FLAG_TC5);//清除DMA转运完成标志位
	DMA_Cmd(DMA1_Channel5, DISABLE);//关闭DMA转运
	TIM_Cmd(TIM2, DISABLE);//关闭TIM2
    WS2812_rest();//WS2812b发送帧单位信号(更新显示)
}

WS2812b初始化函数:

void WS2812_Init()
{
	/*开启RCC时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	/*配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);//将GPIOA_Pin0配置为复用推挽输出
	/*TIM2配置时基单元*/
	TIM_InternalClockConfig(TIM2);//TIM2时钟来源设为内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频,不分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式,向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period = 90 - 1;		//设为自动重装寄存器(ARR)的值为90
	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;		//设为预分频器(PSC)的值为1
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//没用
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	/*配置TIM2输出比较通道一*/
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);//先把不需要的参数填入缺省值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式,PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//有效电平为高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能,使能
	TIM_OCInitStructure.TIM_Pulse = 60;		//这里设置输出比较寄存器(CRR)的值无所谓
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);//配置输出比较通道一
	TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);//输出比较通道一DMA请求开启
	TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);//使用输出比较寄存器(CRR)的影子寄存器
	TIM_CtrlPWMOutputs(TIM2, ENABLE);//高级定时器才有用
	/*配置DMA1*/
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&(TIM2->CCR1);//设置&(TIM2->CCR1)为外设基地址
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设站点的数据宽度为16bits
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设站点的地址不自增
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)WS2812_Value;//设置WS2812_Value为存储器基地址
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//存储器站点的数据宽度为16bits
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//存储器站点的地址自增
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//将外设站点设为传输目的地
	DMA_InitStructure.DMA_BufferSize=24;//没用,填什么无所谓
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//不使用自动重装
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//使用硬件触发
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级中等
	DMA_Init(DMA1_Channel5,&DMA_InitStructure);//初始化DMA1通道5使用DMA_Init初始化。

	DMA_Cmd(DMA1_Channel5,DISABLE);//先不使能DMA,需要时再使能
	
	TIM_Cmd(TIM2, DISABLE);//先不使能TIM2,需要时再使能
	/*WS2812灯带清零*/
	DMA_SetCurrDataCounter(DMA1_Channel5, (24*Led_Num));//设置待转移数据个数为(24*灯珠个数)
	WS2812_Clear();//WS2812b数据清零
	WS2812_rest();//WS2812b发送帧单位信号
	DMA_Cmd(DMA1_Channel5,ENABLE);//开启DMA转运
	TIM_Cmd(TIM2, ENABLE);//开启TIM2
	while(DMA_GetFlagStatus(DMA1_FLAG_TC5)!= SET);//等待ws2812b数据传输完成
	DMA_Cmd(DMA1_Channel5,DISABLE);//关闭DMA转运
	DMA_ClearFlag(DMA1_FLAG_TC5);//清除DMA转运完成标志位
	TIM_Cmd(TIM2, DISABLE);//关闭TIM2
		WS2812_rest();//WS2812b发送帧单位信号
	/*解决绿灯问题*/
	DMA_SetCurrDataCounter(DMA1_Channel5, 24);/设置待转移数据个数为(24*1WS2812_Clear();//WS2812b数据清零
	WS2812_rest();//WS2812b发送帧单位信号
	DMA_Cmd(DMA1_Channel5,ENABLE);//开启DMA转运
	TIM_Cmd(TIM2, ENABLE);//开启TIM2
	while(DMA_GetFlagStatus(DMA1_FLAG_TC5)!= SET);//等待ws2812b数据传输完成
	DMA_Cmd(DMA1_Channel5,DISABLE);//关闭DMA转运
	DMA_ClearFlag(DMA1_FLAG_TC5);//清除DMA转运完成标志位
	TIM_Cmd(TIM2, DISABLE);//关闭TIM2
	WS2812_rest();//WS2812b发送帧单位信号
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值