STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)

一、摘要

  1. 本文主要目的是为了制作RoboMaster比赛能量机关的灯条控制,模拟比赛场中大符,搭建出类似场地道具,以供视觉测试
  2. 使用的是RoboMaster开发板A型,芯片为STM32F427IIH6,使用的灯条为WS2812B。
  3. 采用PWM+DMA控制,使用CUBEMX配置底层,keil5编辑烧录代码
  4. 实现:一共五片扇叶,每片扇叶均实现流水灯+常亮颜色
  5. 参考文章在后面会标记出来

二、选型分析

下面的分析仅从我个人角度出发,比较菜,勿喷

芯片选型

能量机关分为两部分:一部分为流水灯,一部分为常亮;可都采用常亮模式,运用到的接口会少些,但是为了方便视觉识别,所以两部分都要实现

使用PWM+DMA控制灯条实现亮灭

网络上有关于控制灯条的多种方法,使用GPIO、PWM+DMA、SPI+DMA等各种方法,还是选择了PWM+DMA的方式去控制,因为开源资料太多了。

优点:DMA转运,硬件自动数据搬运(由内存到外设),减少CPU资源占用

选择A板理由:

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第1张图片

一块扇叶由两部分组成,一部分为绿色区域,一部分为红色区域,需要两个PWM通道同时控制一片扇叶,以达到相同时间实现两种不同效果;所以所需PWM通道=5*2=10

对比之下,C板有8个PWM,A板有16个PWM,虽然STM32F103的板子开源很多,但是用的太多太麻烦了,不如一块板子解决问题。

WS2812B彩灯:

可编程彩灯,型号不一样,所以对应的时序波形图也不一样,这里仅作简单的说明:

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第2张图片STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第3张图片

对于代码参数,详细请看下面参考文献文章,内有计算关系,具体计算占空比参数,0码与1码

三、执行流程

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第4张图片四、CUBEMX配置

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第5张图片

按照A板手册开启定时器通道,我这里方便省事就都开了。

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第6张图片

时钟数的配置,前面Input frequency给的25,这里给的数值可能不太对,以前都给12,但是反应过来的时候是已经弄完了,可能后面遇到的bug与这里有关。HCLK给168

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第7张图片

PWM频率:

Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)

数据传送频率为800Khz,Tclk为168Mhz,设置psc = 0,arr= 210-1。

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第8张图片

 这里开启DMA通道,选择Normal模式,这里Data Width决定的是代码程序的字长,后面我遇到的问题也是与这里有些关系,导致TIM8与其他TIM的配置不同。

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第9张图片

 TIM8这里的配置与TIM2配置相同

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第10张图片

这里我Date Width选择的是Half Word16字节的。

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第11张图片

 这里我给的是105-1,这里其实是已经有一些模糊的,因为给210-1时TIM4输出信号不对,这里我猜测可能与所挂timer clock有关。

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第12张图片

 TIM5和TIM4的配置相同。上面针对定时器的配置总体来看是有些混乱的,截图出来的是我测试出有效的结果。

五、代码

下面代码大部分采用其他博主给出结果

RGB.h

#ifndef __RGB_H__
#define __RGB_H__

#include "main.h"
/*这里是计算所得CCR的宏定义,没有按照预期的计算结果,但是数据是有效的*/
#define CODE8_1       (58)       //1码定时器计数次数tim8  
#define CODE8_0       (25)       //0码定时器计数次数tim8 
/*将定时器8与其他定时器分开来,使用不同的数据,测试出来的结果*/
#define CODE_1       (30)       //1码定时器计数次数
#define CODE_0       (13)       //0码定时器计数次数

#define Pixel_NUM 100  //LED数量宏定义
 

/*建立一个定义单个LED三原色值大小的结构体*/
typedef struct
{
	uint8_t R;
	uint8_t G;
	uint8_t B;
}RGB_Color_TypeDef;
 
void RGB_SetColor8(uint8_t LedId,RGB_Color_TypeDef Color);//给一个LED装载24个颜色数据码(0码和1码)
void RGB_SetColor245(uint8_t LedId,RGB_Color_TypeDef Color);//给一个LED装载24个颜色数据码(0码和1码)
void RGB_SetColorliushui245(uint8_t LedId,RGB_Color_TypeDef Color);
void RGB_SetColorliushui8(uint8_t LedId,RGB_Color_TypeDef Color);
void Reset_Load(void); //该函数用于将数组最后24个数据变为0,代表RESET_code


void RGB_RED(uint16_t pin);  //显示红灯
void RGB_BLUE(uint16_t pin); //显示蓝灯
void RGB_BLACK(uint16_t pin);
void RGB_GREEN();
	
void MultiArrowFlowingLED(uint16_t ArrowPositions[], uint16_t ArrowCount, uint16_t Pixel_Len,uint16_t pin) ;//流水灯
void MultiArrowFlowingLEDAnimation(uint16_t pin) ;
void Sendall(uint16_t pin);//发送函数
void sunshine (uint16_t pin);//闪烁函数

#endif

 RGB.c

#include "RGB.h"
#include "tim.h"
 #include "control.h"
/*Some Static Colors------------------------------*/
const RGB_Color_TypeDef RED      = {255,0,0};   //显示红色RGB数据
const RGB_Color_TypeDef GREEN    = {0,255,0};
const RGB_Color_TypeDef BLUE     = {0,0,255};
const RGB_Color_TypeDef SKY      = {0,255,255};
const RGB_Color_TypeDef MAGENTA  = {255,0,220};
const RGB_Color_TypeDef YELLOW   = {127,216,0};
const RGB_Color_TypeDef OEANGE   = {127,106,0};
const RGB_Color_TypeDef BLACK    = {0,0,0};
const RGB_Color_TypeDef WHITE    = {255,255,255};
 
/*二维数组存放最终PWM输出数组,每一行24个
数据代表一个LED,最后一行24个0代表RESET码*/
/*为了防止存储数据错乱,使用4个数组用于存放数据*/
uint32_t Pixel_Buf32[Pixel_NUM+1][24];  //tim2 4 5

uint16_t Pixel_Buf16[Pixel_NUM+1][24];  //tim8

uint32_t Pixel_Buf328[Pixel_NUM+1][24];  //tim2 4 5

uint16_t Pixel_Buf168[Pixel_NUM+1][24];  //tim8
uint8_t currentLed = 0;
/*
功能:设定单个RGB LED的颜色,把结构体中RGB的24BIT转换为0码和1码
参数:LedId为LED序号,Color:定义的颜色结构体
*/
void RGB_SetColor8(uint8_t LedId,RGB_Color_TypeDef Color)
{
	uint8_t i; 
	if(LedId > Pixel_NUM)return; //avoid overflow 防止写入ID大于LED总数
	
	for(i=0;i<8;i++) Pixel_Buf16[LedId][i]   = ( (Color.G & (1 << (7 -i)))? (CODE8_1):CODE8_0 );//数组某一行0~7转化存放G
	for(i=8;i<16;i++) Pixel_Buf16[LedId][i]  = ( (Color.R & (1 << (15-i)))? (CODE8_1):CODE8_0 );//数组某一行8~15转化存放R
	for(i=16;i<24;i++) Pixel_Buf16[LedId][i] = ( (Color.B & (1 << (23-i)))? (CODE8_1):CODE8_0 );//数组某一行16~23转化存放B
}
 
void RGB_SetColor245(uint8_t LedId,RGB_Color_TypeDef Color)
{
	uint8_t i; 
	if(LedId > Pixel_NUM)return; //avoid overflow 防止写入ID大于LED总数
	
	for(i=0;i<8;i++) Pixel_Buf32[LedId][i]   = ( (Color.G & (1 << (7 -i)))? (CODE_1):CODE_0 );//数组某一行0~7转化存放G
	for(i=8;i<16;i++) Pixel_Buf32[LedId][i]  = ( (Color.R & (1 << (15-i)))? (CODE_1):CODE_0 );//数组某一行8~15转化存放R
	for(i=16;i<24;i++) Pixel_Buf32[LedId][i] = ( (Color.B & (1 << (23-i)))? (CODE_1):CODE_0 );//数组某一行16~23转化存放B
}

void RGB_SetColorliushui8(uint8_t LedId,RGB_Color_TypeDef Color)
{
	uint8_t i; 
	if(LedId > Pixel_NUM)return; //avoid overflow 防止写入ID大于LED总数
	
	for(i=0;i<8;i++) Pixel_Buf168[LedId][i]   = ( (Color.G & (1 << (7 -i)))? (CODE8_1):CODE8_0 );//数组某一行0~7转化存放G
	for(i=8;i<16;i++) Pixel_Buf168[LedId][i]  = ( (Color.R & (1 << (15-i)))? (CODE8_1):CODE8_0 );//数组某一行8~15转化存放R
	for(i=16;i<24;i++) Pixel_Buf168[LedId][i] = ( (Color.B & (1 << (23-i)))? (CODE8_1):CODE8_0 );//数组某一行16~23转化存放B
}
 
void RGB_SetColorliushui245(uint8_t LedId,RGB_Color_TypeDef Color)
{
	uint8_t i; 
	if(LedId > Pixel_NUM)return; //avoid overflow 防止写入ID大于LED总数
	
	for(i=0;i<8;i++) Pixel_Buf328[LedId][i]   = ( (Color.G & (1 << (7 -i)))? (CODE_1):CODE_0 );//数组某一行0~7转化存放G
	for(i=8;i<16;i++) Pixel_Buf328[LedId][i]  = ( (Color.R & (1 << (15-i)))? (CODE_1):CODE_0 );//数组某一行8~15转化存放R
	for(i=16;i<24;i++) Pixel_Buf328[LedId][i] = ( (Color.B & (1 << (23-i)))? (CODE_1):CODE_0 );//数组某一行16~23转化存放B
}

/*
功能:最后一行装在24个0,输出24个周期占空比为0的PWM波,作为最后reset延时,这里总时长为24*1.2=30us > 24us(要求大于24us)
*/
void Reset_Load(void)
{
	uint8_t i;
	for(i=0;i<24;i++)
	{
		Pixel_Buf16[Pixel_NUM][i] = 0;
		Pixel_Buf32[Pixel_NUM][i] = 0;
		
		Pixel_Buf168[Pixel_NUM][i] = 0;
		Pixel_Buf328[Pixel_NUM][i] = 0;
	}
}
 
void RGB_BLACK(uint16_t cont)
{
	uint16_t i;
	for(i=0;i<200;i++)//给对应个数LED写入红色
	{
		RGB_SetColor8(i,BLACK);
		RGB_SetColor245(i,BLACK);
	}
	Reset_Load();
	Sendall(cont);
}
/*
功能:显示红色
参数:Pixel_Len为显示LED个数
*/
void RGB_RED(uint16_t cont)
{
	uint16_t i;
	for(i=0;i<200;i++)//给对应个数LED写入红色
	{
		RGB_SetColor8(i,RED);
		RGB_SetColor245(i,RED);
	}
	Reset_Load();
	Sendall(cont);
}
 
/*
功能:显示蓝色
参数:Pixel_Len为显示LED个数
*/
void RGB_BLUE(uint16_t cont)
{
	uint16_t i;
	for(i=0;i<50;i++)//给对应个数LED写入蓝色
	{
		RGB_SetColor8(i,BLUE);
		RGB_SetColor245(i,BLUE);
	}
	Reset_Load();
	Sendall(cont);

}

/*
功能:显示绿色
参数:Pixel_Len为显示LED个数
*/
void RGB_GREEN()
{
	uint16_t i;
	for(i=0;i<50;i++)//给对应个数LED写入蓝色
	{
		RGB_SetColor8(i,GREEN);
		RGB_SetColor245(i,GREEN);
	}
	Reset_Load();
	HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//D13 G
}
/**
  * @brief  风车标杆流水灯
  * @param  ArrowPositions:存储每个箭头位置  
						ArrowCount:箭头数量
						Pixel_Len:灯条长度
  * @retval 
  */
void MultiArrowFlowingLED(uint16_t ArrowPositions[], uint16_t ArrowCount, uint16_t Pixel_Len,uint16_t pin) {
    uint16_t i;
	  Reset_Load();
    for (i = 0; i < Pixel_Len; i++) {
        uint8_t isArrow = 0; // 标志以检查当前LED是否为箭头位置
        for (uint16_t j = 0; j < ArrowCount; j++) {
            if (i == ArrowPositions[j] || i == ArrowPositions[j] + 5 || i == ArrowPositions[j] + 10) {
                isArrow = 1;
                break; // 如果是箭头,无需检查其他箭头位置
            }
        }
        if (isArrow) {
            RGB_SetColorliushui8(i, BLUE);// 设置箭头位置为红色
						RGB_SetColorliushui245(i,BLUE);
        } else {
            RGB_SetColorliushui8(i, BLACK); // BLACK表示没有颜色(LED关闭)
						RGB_SetColorliushui245(i,BLACK);
        }
    }

		Sendall(pin);
}
/**
  * @brief  风车标杆流水灯
  * @param  ArrowPositions:存储每个箭头位置  
						ArrowCount:箭头数量
						Pixel_Len:灯条长度
  * @retval 
  */
uint16_t loopCounter = 0; // 计数器,用于限制循环次数
uint16_t maxLoops = 40;
void MultiArrowFlowingLEDAnimation(uint16_t pin) {
    uint16_t frameDelay = 1; // 调整延迟时间以控制动画速度
    uint16_t Pixel_Len = 30;
    uint16_t totalFrames = Pixel_Len; // 循环遍历LED的总帧数(个数)
    uint16_t ArrowPositions[4] = {6, 12, 18, 24};
    uint16_t ArrowCount = 10;

    // 设置希望执行的总循环次数,这里假设为 40 次
    // 使用计数器来代替 while(1)
    while (loopCounter < maxLoops) {
        for (uint16_t frame = 0; frame < totalFrames; frame++) {
            MultiArrowFlowingLED(ArrowPositions, ArrowCount, Pixel_Len, pin);
            HAL_Delay(frameDelay);
        }

        // 移动箭头位置
        for (uint16_t j = 0; j < ArrowCount; j++) {
            ArrowPositions[j] = (ArrowPositions[j] + 1) % Pixel_Len;
        }
        loopCounter++; // 增加计数器
    }
}

void Sendall(uint16_t pin)
{
	switch (pin)
	{
		case 1:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//W
					break;
		case 2:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I6 X
					break;
		case 3:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I7 Y				
					break;
		case 4:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_4, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I2 Z				
					break;
		case 5:
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//A0 S
					break;
		case 6:
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf328,(Pixel_NUM+1)*24);//A1 T			
					break;
		case 7:
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf328,(Pixel_NUM+1)*24);//A2 U
					break;
		case 8:
					HAL_TIM_PWM_Start_DMA(&htim5, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf328,(Pixel_NUM+1)*24);//H10 D			
					break;
		case 9:
					HAL_TIM_PWM_Start_DMA(&htim5, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf328,(Pixel_NUM+1)*24);//H11 C
					break;
		case 10:
					HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf328,(Pixel_NUM+1)*24);//D13 h
					break;
		
		case 11:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//W
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//A1 T
					break;
		case 12:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I6 X
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//A2 U
					break;
		case 13:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I7 Y			
					HAL_TIM_PWM_Start_DMA(&htim5, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//H10 D		
					break;
		case 14:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_4, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I2 Z		
					HAL_TIM_PWM_Start_DMA(&htim5, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//H11 C		
					break;
		case 15:
					HAL_TIM_PWM_Start_DMA(&htim4, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//D13 h
//					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//A0 S
					break;
		case 16:
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//W
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_2, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I6 X
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_3, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I7 Y		
					HAL_TIM_PWM_Start_DMA(&htim8, TIM_CHANNEL_4, (uint32_t *)Pixel_Buf16,(Pixel_NUM+1)*24);//I2 Z		
					HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t *)Pixel_Buf32,(Pixel_NUM+1)*24);//A0 S
					break;
		default :break;
	}
}
void sunshine (uint16_t pin)
{

		for(int i=0;i<=5;i++)
				{RGB_BLUE(pin);
				HAL_Delay (100);
				RGB_BLACK (pin);
				HAL_Delay (100);
				}
			RGB_BLUE(pin);

}

control.h 

#ifndef __CONTROL_H__
#define __CONTROL_H__

void wind_control();
void wind_overall();
void wind_one();

void wind1();
void wind2();
void wind3();
void wind4();
void wind5();
void delay(int milliseconds) ;
void wind_1();
void wind_2();
void wind_3();
void wind_4();
void wind_5();
void wind_stop();
#endif

 control.c

#include "control.h"
#include "Send.h"
#include "rgb.h"
#include "tim.h"

extern uint16_t loopCounter;

void delay(int milliseconds) {
    // 计算循环次数
    int count = 0;
    // 根据 CPU 频率和循环次数计算延时时间
    // 这里的延时时间是一个简单的近似值,实际延时时间可能会有误差
    int loops = milliseconds * 10000; 
    for (count = 0; count < loops; count++) {
        // 空循环
    }
}

void wind_overall()
{
			wind1();
			wind2();
			wind3();
			wind4();
			wind5();
			wind_stop();
}
void wind_one()
{
	wind_1 ();
	RGB_BLACK(11);
	wind_2();
	RGB_BLACK(12);
	wind_4 ();
	RGB_BLACK(14);
	wind_3();
	RGB_BLACK(13);
	wind_5 ();
	RGB_BLACK(15);
}
void wind1()
{
        wind_1();
        sunshine(11);
		RGB_BLUE(11);
}

void wind2()
{
        wind_2();
        sunshine(12);
        RGB_BLUE(12);
}
void wind3()
{
        wind_3();
        sunshine(13);
        RGB_BLUE(13);
}
void wind4()
{
        wind_4();
        sunshine(14);
        RGB_BLUE(14);
}
void wind5()
{
		wind_5 ();
		RGB_BLUE(15);	
        sunshine(16);
}

void wind_1()
{
	RGB_BLUE (1);
	MultiArrowFlowingLEDAnimation(6);
	loopCounter=0;
}
void wind_2()
{
	RGB_BLUE (2);
	MultiArrowFlowingLEDAnimation(7);
	loopCounter=0;
}
void wind_3()
{
	RGB_BLUE (3);
	MultiArrowFlowingLEDAnimation(8);
	loopCounter=0;
}
void wind_4()
{
	RGB_BLUE (4);
	MultiArrowFlowingLEDAnimation(9);
	loopCounter=0;
}
void wind_5()
{
	RGB_BLUE (5);
	MultiArrowFlowingLEDAnimation(10);
	loopCounter=0;
}
void wind_stop()
{
	RGB_BLACK(11);
	RGB_BLACK(12);
	RGB_BLACK(13);
	RGB_BLACK(14);
	RGB_BLACK(15);
	HAL_Delay(3000);
}
	

STM32HAL库开发大疆A板WS2812B彩灯(PWM+DMA)_第13张图片

上面的计算0码与1码处是最大的问题,按照其他博主给的1码与0码计算方式去算结果,程序执行不起来,所以上面两处结果属于试出来的,不知道其他人能不能找到问题所在。

六、成果展示

能量机关

七、其他

参考链接:https://blog.csdn.net/qq_49519820/article/details/127930074

有其他问题大家可以私信我或者评论,本人也是个菜鸡,所以勿喷~

你可能感兴趣的:(STM32,stm32,单片机,嵌入式硬件)