stm32【RGB_LED_WS2812灯珠】

RGB_LED 内置驱动IC:WS2812

测试平台:stm32f103c8t6
库版本:官方标准库3.5.0版本

LED规格:RGB-5050
内置IC:ws2812
驱动方式:归零码

相比普通的LED灯珠,内置驱动芯片的灯珠在实用性,整体体积,电路设计,程序设计上都有很大的优势,本文所写内置驱动IC的WS2812的灯珠是比较常见的一种彩色LED灯珠,通过级联方式连接每颗LED,电路设计简单,采用归零码输入彩色数据。
万能的某宝有很多这类内置驱动的灯珠,购买前记得询问是否由数据手册或者规格书等,这对电路设计和程序设计有很大帮助。

注: 文中首次出现的代码块会标注[xxx.c]或[xxx.h],表明该代码是属于对应的文件,未标注的即为重复出现的

1、电路设计

由于是级联方式连接相邻的灯珠,因此电路很简单,
灯珠管脚▼

规格书描述▼

只要将相邻灯珠的DOUT和DIN连接在一起即可,这里的104电容并不是必须的

电路原理图▼

LED1~LED10一路
LED11~LED20一路
这里是设计需求,两路并联在一起,同步控制

PCB图▼

这是一个灯板的PCB,注意左右两边接口表明的顺序,左边的DI对应右边的DO,可以通过左右接口将多个灯板并联起来控制。

2、程序设计

归零码通讯是单线通讯,对时序要求比较严格,通过单周期内的高低电平持续时间来判断数据bit为0或1
规格书描述▼




信息1: 单颗灯珠可接收3Byte(24bit)数据
信息2: 单颗灯珠数据结构G-R-B,高位先发
信息3: 码元周期,上边两张图都是这款灯珠规格书里的截图,码元周期T有两种描述,分别是2us(500kHz)和1.25us(800kHz),1.25us是网上关于WS2812教程里出现比较多的典型码元周期,2us似乎是该灯珠特有的,在实际的测试中,两种周期均可以实现驱动,因此程序以1.25us进行编写
信息4: 0码和1码占空比

T0码高电平占比1码高电平占比
2us14%45%
1.25us25.6%51.2%

2.1 驱动方式

【程序以T = 1.25us为准】
要想驱动该灯珠,实际上是要实现0码和1码的发送时序,能够产生这样的时序信号的方式大致有三种:
第一种是使用延时函数在特定延时时间内对输出管脚进行翻转操作,这种方式非常占用单片机资源,而且实现1.25us延时的准确度不高

第二种是使用定时器进行PWM输出,输出频率设置为800kHz即可实现1.25us周期循环,通过改变周期内占空比来实现0码和1码输出

第三种是使用SPI,这种方式比较巧妙,使用SPI的clk线和mosi线,通过8分频可以设置clk时钟线的输出频率(主频72Mhz),然后采用16byte数据模拟0码和1码,这样输出频率为562.5kHz,理论上也是可以驱动的,本人没有实践过,该up主的文章【SPI驱动ws2812】有介绍这种方式,感兴趣的可以尝试下。

第二种使用定时器的方式,精度高,只有在更新中断服务程序才占用系统资源,是比较理想的驱动方式,考虑到STM32单片机的定时器较多,硬件SPI有限(其他拓展功能有可能会使用到SPI),因此最终方案选择使用定时器产生PWM来驱动灯珠

2.2 定时器配置

避开有特殊外设功能的IO口,这里选择PB0管脚进行PWM输出,其对应的定时器是TIM3,输出通道是CH3
IO口配置为复用推挽输出,启动复用时钟▼

[ws2812.c]
/********************************************************************************
  * @brief  TIM对应的GPIO配置
  * @param  none
  * @retval	none
  * @retval	none
  *******************************************************************************/
static void TIM_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

TIM3基础以及PWM模式配置▼

[ws2812.c]
/********************************************************************************
  * @brief  TIM基础配置以及PWM配置
  * @param  none
  * @retval	none
  * @retval	none
  *******************************************************************************/
static void TIM_PWM_Config(void)
{
	TIM_TimeBaseInitTypeDef	TIM_TimeBaseStructure;
	TIM_OCInitTypeDef		TIM_OCInitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = 90-1; // (144 500KHz 2us) (90 800KHz 1.25us)
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

	/* PWM1 Mode configuration */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC3Init(TIM3, &TIM_OCInitStructure);
	
	TIM_ARRPreloadConfig(TIM3, ENABLE);								//使能TIM_ARR重载,即周期重载
	TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);				//使能TIM_CRR重载,即占空比重载
	TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);						//使能TIM更新中断
//	TIM_Cmd(TIM3, ENABLE);                   						//使能TIM

}

注意:周期是从0开始算的,预分频为0,即不分频,频率为72MHz,周期为72MHz / 90 = 800kHz
使能定时器更新中断,这样可以在中断服务函数里更改占空比
配置完成先不使能TIM3,等需要发送数据时再使能定时器

NVIC配置,这个随意▼

[ws2812.c]
/********************************************************************************
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  none
  * @retval none
  *******************************************************************************/
static void NVIC_TIM3_Config(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置TIM3_IRQ中断为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

到此定时器配置完成

2.3 数据处理与发送

一颗灯珠接收3byte(24bit)数据,当接收完3byte(24bit)数据后,自动整型转发接下来的数据,高位先发
采用PWM来产生驱动时序,一个周期代表1bit,这1bit代表0或者1则是由周期内的高电平占空比来决定

在定时器配置中,周期设置TIM_ARR = 90
那么0码占空比设置为20(22%),1码占空比设置为50(55%)即可
实际测试0码小于30,1码大于35都是可以驱动的

首先点亮一个灯珠,使其发出绿光,按数据格式G-R-B,需要发送3byte数据【0xFF0000】,总共24bit的数据,数据处理就是根据24bit数据转换成占空比数组,然后在中断服务函数里更新TIM_CCR的值

数据处理和发送函数▼

[ws2812.c]

#define DATA_SIZE	0x800				//2k
#define DATA_BUF_Address	0x20004800
u8 DATA_BUF[DATA_SIZE]		__attribute__ ((at(DATA_BUF_Address)));

#define DATA_BIT_0 20	//20 
#define DATA_BIT_1 50	//75 

/********************************************************************************
  * @brief  发送数据
  * @param  *buff	缓存基地址
  * @retval	len		发送数据量(n byte)
  * @retval	none
  *******************************************************************************/
void DATA_Send(u8 *buff, u32 len)
{
	u8 i = 0;
	u32 j = 0;
	u32 counter = 0;
	u8 data;

	for(j = 0; j < len; j++){
		data = buff[j];
		//1byte数据分解成8byte
		for(i = 0; i < 8; i++){
			DATA_BUF[counter] = ((data & 0x80) ? DATA_BIT_1 : DATA_BIT_0);
			data = data << 1;
			counter++;
		}
	}
	
	//初始化结构体
	ws2812.data_cnt = len*8;
	ws2812.send_cnt = 0;
	ws2812.flag = 0;
	
	//使能TIM,开始发送数据
    TIM3->CR1 |= TIM_CR1_CEN;
}

【DATA_BUF[DATA_SIZE]】数组是一个足够大的数组,最终的TIM_CCR值缓存数组

直接看发送函数有点难解释,先看这个函数是如何调用的▼

[ws2812.c]
u8 data[3];
/********************************************************************************
  * @brief  打开文件并读取文件基础信息
  * @param  name	文件名称
  *
  * @retval	1		模式1
  * @retval	2		模式2
  *******************************************************************************/
void test(void)
{
	data[0] = 0xff;
	data[1] = 0x00;
	data[2] = 0x00;

	DATA_Send(data,3);
}

使用一个u8类型的数组【data[3]】存储绿灯数据【0xFF0000】
然后把数组地址和数据个数【3】传递到发送函数【DATA_Send】

回到【DATA_Send】函数,for(i = 0; i < 8; i++)循环将1byte数据转换成8byte数据,是占空比的值,存储在【DATA_BUF[ ]】数组里边,那么经过三次循环,将【data[3]】分解成【DATA_BUF[24]】个数据
此时【DATA_BUF[24]】各值为

DATA_BUF[0]DATA_BUF[1]DATA_BUF[2]DATA_BUF[3]DATA_BUF[4]DATA_BUF[5]DATA_BUF[6]DATA_BUF[7]
5050505050505050
DATA_BUF[8]DATA_BUF[9]DATA_BUF[10]DATA_BUF[11]DATA_BUF[12]DATA_BUF[13]DATA_BUF[14]DATA_BUF[15]
2020202020202020
DATA_BUF[16]DATA_BUF[17]DATA_BUF[18]DATA_BUF[19]DATA_BUF[20]DATA_BUF[21]DATA_BUF[22]DATA_BUF[23]
2020202020202020

转换完数据后先初始化一个结构体,然后启动定时器TIM3
结构体定义▼

[ws2812.h]
typedef struct
{
	u32 data_cnt;		//需发送的数据个数
	u32 send_cnt;		//已发送的数据个数
	u8 flag;			//发送完成标志
}WS2812_TypeDef;

2.4 定时器中断服务函数

在数据发送函数最后的操作是启动定时器TIM3,具体的占空比更改是在中断服务程序里实现的▼

[ws2812.c]
/********************************************************************************
  * @brief  TIM中断服务程序
  * @param  none
  * @retval none
  *******************************************************************************/
void TIM3_IRQHandler(void)
{
	TIM3->SR = (uint16_t)~TIM_IT_Update;		//清除中断
	TIM3->CCR3 = DATA_BUF[ws2812.send_cnt];		//更新占空比
	ws2812.send_cnt++;
	if(ws2812.send_cnt > ws2812.data_cnt){		//当发送完所需数据之后关闭TIM
		TIM3->CR1 &= ~((uint16_t)TIM_CR1_CEN);	//关闭TIM
	}
}

定时器每产生一次中断,可以理解为发送了1byte数据,因此在中断函数里更新下一个TIM_CCR值,当发送完24byte数据后,就关闭定时器TIM3,

3、总结

在主函数里调用TIM配置函数后,直接调用测试函数【void test(void)】即可驱动一颗灯珠发绿光▼

(由于左右两路是并联的,因此两个灯珠同步点亮)
改下函数即可控制其他灯珠▼

void test(void)
{

	data[0] = 0x0f;
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;
	data[4] = 0x0f;
	data[5] = 0x00;
	data[6] = 0x00;
	data[7] = 0x00;
	data[8] = 0x0f;

	DATA_Send(data,9);

}

RGB效果(拍照色差比较严重)▼

内置ws2812的彩色LED灯珠的驱动就这么些,具体怎么使用还得看应用场景

  • 13
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
基于STM32CubeMX配置的DMA_PWM驱动SW2812b全彩RGB灯是一种使用DMA和PWM功能来驱动WS2812灯珠的方式。在配置过程中,需要注意时钟和预分频值的设置,以及芯片时钟频率的配置。此外,还需要配置DMA外设地址通道等相关参数。 WS2812灯珠的驱动方式有几种,其中一种是使用延时函数直接翻转IO口产生时序,这是一种简单易用的方式,只需要控制延时的时间即可。另一种方式是使用SPI进行数据传输来产生时序,只需控制适当的波特率即可。还有一种方式是使用DMA Timer来产生时序,其中一个通道固定产生一个周期为1.25us的PWM信号,并在周期的1/3处将数据搬运到IO口。最后一种方式是使用Timer PWM DMA来产生时序,它有两种驱动方式,一种是直接建立一个大的数组来存放所有灯珠的数据,然后启动DMA传输;另一种是建立两个灯组数据大小的数组,通过不断改变数组的方式来节约内存。 总的来说,STM32 DMA_PWM SW2812是一种使用DMA和PWM来驱动WS2812灯珠的方案,可以通过配置STM32CubeMX来实现相应的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【基于 STM32CubeMX + PWM + DMA驱动SW2812b全彩RGB灯】](https://blog.csdn.net/weixin_45936798/article/details/127930491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [STM32使用PWM+DMA方式驱动WS2812灯珠](https://blog.csdn.net/William_Zhang_CSDN/article/details/126699601)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值