STM32使用HAL_DMA_PWM方式驱动WS2812全彩灯珠

一、WS2812全彩灯珠原理与驱动方式

关于WS2812系列灯珠的介绍这里就不讲了,网上资源一大堆,还没找到教程的可以参考这篇文章:STM32驱动WS2812D全彩LED
这里还是把时序波形图附上,方便后面讲解。

在这里插入图片描述
驱动WS2812的关键就在于一是要输出800KHz的方波,二是要精确控制每个周期内高低电平时间从而输出能够被WS2812识别的逻辑1和0。之后只需要定义一个输出缓冲数组,往数组里边填数字就行了。关键就在于选择何种方式输出方波。总结一下:

(1)最简单最笨的方法,软件延时实现高低电平翻转,STM32F1系列都有72MHz的主频,F7系列达到216MHz,输出800KHz的方波还是绰绰有余,使用的时候需要用示波器或逻辑分析仪去监视,毕竟软件延时不准。

(2)使用STM32的PWM连续传送模式,也就是DMA输出PWM,可以输出高精度的方波,且脉冲数量、占空比可调。

(3)使用STM32的硬件SPI传送数据,同样可以达到高精度。

显然是不推荐方法一的,精度无法保证不说,还极大的浪费CPU资源,如果程序就只是单独驱动WS2812还好说,如果有其他任务的话CPU不能啥也不干光在那延时吧。复杂系统中WS2812必然只是作为指示灯或者附加的炫彩效果,这样独占CPU资源?项目可以黄了。

所以推荐使用方法二和方法三。本文采用HAL库DMA输出PWM方式控制WS2812。

关于DMA输出PWM的原理与工程配置请参考在下的上一篇文章:STM32F1/F7使用HAL库DMA方式输出PWM详解(输出精确数量且可调周期与占空比)

二、STM32使用HAL库配置DMA方式输出PWM控制WS2812

在上一篇STM32F1/F7使用HAL库DMA方式输出PWM详解(输出精确数量且可调周期与占空比)工程的基础上,添加WS2812.c与WS2812.h文件到工程中。

首先是PWM的配置,本例程使用的STM32F767芯片,主频216MHz,配置T4_CH2来驱动WS2812。分频为0即不分频,周期为 1/108M = 9.26ns,自动重装载值为135,则周期为1.25us,频率800KHz。刚好为WS2812的控制频率。

WS2812.h

#ifndef _WS2812_H
#define _WS2812_H
#endif

#include "main.h"
#include "delay.h"

#define PIXEL_NUM  64
#define NUM (24*PIXEL_NUM + 300)        // Reset 280us / 1.25us = 224
#define WS1 100
#define WS0  35

extern uint16_t send_Buf[NUM];

void WS_Load(void);
void WS_WriteAll_RGB(uint8_t n_R, uint8_t n_G, uint8_t n_B);
void WS_CloseAll(void);

uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue);
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor);
void WS281x_SetPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue);

uint32_t Wheel(uint8_t WheelPos);
void rainbow(uint8_t wait);
void rainbowCycle(uint8_t wait);

在WS2812.h中定义一个发送缓冲区uint16_t send_Buf[NUM],NUM的值为单个灯珠的位宽(24)* 灯珠数量(PIXEL_NUM)+ 复位脉冲数。这里我给的300, 300*9.26ns > 280us, 只要满足复位时间就行了。

宏WS1和WS0分别表示输出1和0所需要设置的PWM比较值,设置为100和35可以满足时序要求。

在WS2812.c就可以写WS2812的驱动函数了,首先定义一个启动传输函数:

void WS_Load(void)
{
    
    
	HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_2, (uint32_t *)send_Buf, NUM);
}

功能就是启动DMA-PWM传输。

关闭所有灯:

void WS_CloseAll(void)
{
    
    
	uint16_t i;
	for (i = 0; i < PIXEL_NUM * 24; i++)
		send_Buf[i] = WS0; // 写入逻辑0的占空比
	for (i = PIXEL_NUM * 24; i < NUM; i++)
		send_Buf[i] = 0; // 占空比比为0,全为低电平
	WS_Load();
}

设置所有灯为同一颜色:

void WS_WriteAll_RGB(uint8_t n_R, uint8_t n_G, uint8_t n_B)
{
    
    
	uint16_t i, j;
	uint8_t dat[24];
	// 将RGB数据进行转换
	for (i = 0; i < 8; i++)
	{
    
    
		dat[i] = ((n_G & 0x80) ? WS1 : WS0);
		n_G <<= 1;
	}
	for (i = 0; i < 8; i++)
	{
    
    
		dat[i + 8] = ((n_R & 0x80) ? WS1 : WS0);
		n_R <<= 1;
	}
	for (i = 0; i < 8; i++)
	{
    
    
		dat[i + 16] = ((n_B & 0x80) ? WS1 : WS0);
		n_B <<= 1;
	}
	for (i = 0; i < PIXEL_NUM; i++)
	{
    
    
		for (j = 0; j < 24; j++)
		{
    
    
			send_Buf[i * 24 + j] = dat[j];
		}
	}
	for (i = PIXEL_NUM * 24; i < NUM; i++)
		send_Buf[i] = 0; // 占空比比为0,全为低电平
	WS_Load();
}

在main函数中调用WS_WriteAll_RGB()就可以点亮所有灯珠了,例如WS_WriteAll_RGB(0xFF,0,0)将所有灯设置为绿色。

下面可以实现一些酷炫的灯光效果。

设置单个灯的颜色(两个函数分别以不同格式设置颜色):

uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue)
{
    
    
	return green << 16 | red << 8 | blue;
}

void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor)
{
    
    
	uint8_t i;
	if (n < PIXEL_NUM)
	{
    
    
		for (i = 0; i < 24; ++i)
			send_Buf[24 * n + i] = (((GRBColor << i) & 0X800000) ? WS1 : WS0);
	}
}

void WS281x_SetPixelRGB(uint16_t n, uint8_t red, uint8_t green, uint8_t blue)
{
    
    
	uint8_t i;

	if (n < PIXEL_NUM)
	{
    
    
		for (i = 0; i < 24; ++i)
			send_Buf[24 * n + i] = (((WS281x_Color(red, green, blue) << i) & 0X800000) ? WS1 : WS0);
	}
}

这里我只移植了两个函数,实现彩虹灯效果:

uint32_t Wheel(uint8_t WheelPos)
{
    
    
	WheelPos = 255 - WheelPos;
	if (WheelPos < 85)
	{
    
    
		return WS281x_Color(255 - WheelPos * 3, 0, WheelPos * 3);
	}
	if (WheelPos < 170)
	{
    
    
		WheelPos -= 85;
		return WS281x_Color(0, WheelPos * 3, 255 - WheelPos * 3);
	}
	WheelPos -= 170;
	return WS281x_Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void rainbow(uint8_t wait)
{
    
    
	uint32_t timestamp = HAL_GetTick();
	uint16_t i;
	static uint8_t j;
	static uint32_t next_time = 0;

	uint32_t flag = 0;
	if (next_time < wait)
	{
    
    
		if ((uint64_t)timestamp + wait - next_time > 0)
			flag = 1;
	}
	else if (timestamp > next_time)
	{
    
    
		flag = 1;
	}
	if (flag) // && (timestamp - next_time < wait*5))
	{
    
    
		j++;
		next_time = timestamp + wait;
		for (i = 0; i < PIXEL_NUM; i++)
		{
    
    
			WS281x_SetPixelColor(i, Wheel((i + j) & 255));
		}
	}
	WS_Load();
}

void rainbowCycle(uint8_t wait)
{
    
    
	uint32_t timestamp = HAL_GetTick();
	uint16_t i;
	static uint8_t j;
	static uint32_t next_time = 0;

	static uint8_t loop = 0;
	if (loop == 0)
		next_time = timestamp;
	loop = 1; //首次调用初始化

	if ((timestamp > next_time)) // && (timestamp - next_time < wait*5))
	{
    
    
		j++;
		next_time = timestamp + wait;
		for (i = 0; i < PIXEL_NUM; i++)
		{
    
    
			WS281x_SetPixelColor(i, Wheel(((i * 256 / (PIXEL_NUM)) + j) & 255));
		}
	}
	WS_Load();
}

还有其他好玩的灯光效果大家自己开发叭。

代码资源上传到了CSDN,可以免费下载,别忘了点个赞哦!!

传送门:STM32F767使用HAL_DMA_PWM方式驱动WS2812灯珠

猜你喜欢

转载自blog.csdn.net/qq_30267617/article/details/109600880