STM32使用LL库PWM的DMA模式驱动ws2812

文末有源代码链接。

一、WS2812简介

给个链接吧,ws2812时序简介

WS2812使用“单总线”驱动,可以级联驱动n个,当然n是有限制的。时序超级简单,也有一定难度。类似的帖子网上有很多,在此不再赘述。

WS2812驱动方式大概有以下几种:
1.使用GPIO模拟,中间加延时实现“0”、“1”的时序,延时需要借助示波器、逻辑分析仪来调试。
2.使用硬件定时器+PWM实现,当然要控制发送PWM脉冲个数需要严格控制,PWM+DMA的组合程序设计更加简单、更加便捷,但是调试起来要麻烦一些。
3.使用SPI来模拟ws2812的时序,这也是一个很讨巧的办法,当然需要计算SPI的速率、位数、“0”“1”时序要发送的对应的数值。当然SPI+DMA的话,更省cpu。
本帖主要使用硬件定时器PWM+DMA方式实现。

二、CUBEMX初始化代码配置

1.打开cubemx,选择STM32G070RBT6,这是我使用的cpu的型号。
2.时钟树配置,使用内部时钟,sysclk最后为64MHZ,
时钟树配置
3.SYS配置,为了调试方便,勾选SW调试接口,tick时钟选择默认systick不变。
sys配置
4.定时器参数设置,这里使用定时器TIM1的CH3通道
***注意,定时器的计数器预加载要关闭。
定时器参数设置
DMA参数设置,选择普通模式,方向从内存到外设,注意数据宽度和字长不同,以及地址是否增加。
定时器DMA参数设置
5.project manager工程设置
project manager工程设置
6. 代码生成器设置
代码生成器设置
7.高级设置 (这里选择LL库)
高级设置
8.代码生成,打开工程。这里使用keil mdk开发环境。生成的工程的代码结构
工程结构
9.编辑tim.c
在文件开头包含头文件

	#include "bsp_ws2812.h"

在MX_TIM1_Init()函数最后添加如下几行代码

  	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7,LEN);//设置dma数据传输个数/长度
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_7,(uint32_t)pixelBuffer);//设置内存地址,也就是设置buffer地址
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_7,(uint32_t)&(TIM1->CCR3));//设置外设地址
	LL_DMA_ClearFlag_GI7(DMA1);//清除中断标志
	LL_DMA_ClearFlag_TC7(DMA1);//清除中断标志
	LL_DMA_EnableIT_TC(DMA1,LL_DMA_CHANNEL_7);//使能传送完成中断
	LL_TIM_EnableDMAReq_CC3(TIM1);//使能TIM1的CC3 DMA请求
	LL_TIM_EnableAllOutputs(TIM1);//使能TIM的输出
	LL_TIM_CC_SetDMAReqTrigger(TIM1,LL_TIM_CCDMAREQUEST_CC);//设置TIM1 DMA请求触发器	
	LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH3);//使能TIM1 的cc通道ch3	

这样,每次使用dma的时候,只需要重新设置数据传送长度、使能定时器和DMA通道就可以进行数据传输了。如下:

    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7, LEN);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_7);
    LL_TIM_EnableCounter(TIM1);

10.编辑stm32g0xx_it.c文件
主要是dma tc中断后,失能dma和tim,并清除中断标志。

	void DMA1_Ch4_7_DMAMUX1_OVR_IRQHandler(void)
	{
	  /* USER CODE BEGIN DMA1_Ch4_7_DMAMUX1_OVR_IRQn 0 */
	  if(LL_DMA_IsActiveFlag_TC7(DMA1))
		{
		  	 ws2812_xfer_flag=0;
			 LL_DMA_ClearFlag_GI7(DMA1);
			 LL_DMA_ClearFlag_TC7(DMA1);
			 LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_7);
			 LL_TIM_DisableCounter(TIM1);	  
		}
	  /* USER CODE END DMA1_Ch4_7_DMAMUX1_OVR_IRQn 0 */
	  
	  /* USER CODE BEGIN DMA1_Ch4_7_DMAMUX1_OVR_IRQn 1 */
	
	  /* USER CODE END DMA1_Ch4_7_DMAMUX1_OVR_IRQn 1 */
	}

11.添加bsp_ws2812.c文件和bsp_ws2812.h文件,此ws2812特效显示代码主要参考一位网友。
链接如下:参考代码网址,在此表示感谢。
11.1)bsp_ws2812.h

#ifndef __BSP_WS2812_H_
#define __BSP_WS2812_H_
#include "main.h"

#define PIXEL_NUM  13
#define GRB  24   //3*8
#define  LEN        (PIXEL_NUM*GRB)
#define WS_LOW  31
#define WS_HIGH 60

extern volatile uint8_t  ws2812_xfer_flag;
extern           uint8_t  pixelBuffer[PIXEL_NUM][GRB];

void WS281x_Init(void);
void WS281x_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);
void WS281x_Show(void);

void WS281x_RainbowCycle(uint8_t wait);
void WS281x_TheaterChase(uint32_t c, uint8_t wait);
void WS281x_ColorWipe(uint32_t c, uint8_t wait);
void WS281x_Rainbow(uint8_t wait);
void WS281x_TheaterChaseRainbow(uint8_t wait);

#endif

/********************************End of File************************************/

11.2)bsp_ws2812.c文件

#include "bsp_ws2812.h"

volatile uint8_t  ws2812_xfer_flag = 0;
uint8_t pixelBuffer[PIXEL_NUM][GRB] = {0};

/*******************************************************************************
** 函数名称: WS281x_CloseAll
** 功能描述:
********************************************************************************/
void WS281x_CloseAll(void)
{
    uint8_t i;
    uint8_t j;

    for(i = 0; i < PIXEL_NUM; ++i)
    {
        for(j = 0; j < 24; ++j)
        {
            pixelBuffer[i][j] = WS_LOW;
        }
    }
    WS281x_Show();
}

/*******************************************************************************
** 函数名称: WS281x_Color
** 功能描述:
********************************************************************************/
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue)
{
    return green << 16 | red << 8 | blue;
}

/*******************************************************************************
** 函数名称: WS281x_SetPixelColor
** 功能描述: 
********************************************************************************/
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor)
{
    uint8_t i;
    if(n < PIXEL_NUM)
    {
        for(i = 0; i < GRB; i++)
        {
            pixelBuffer[n][i] = ((GRBColor << i) & 0x800000) ? WS_HIGH : WS_LOW;
        }
    }
}

/*******************************************************************************
** 函数名称: WS281x_SetPixelRGB
** 功能描述:
********************************************************************************/
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 < GRB; ++i)
        {
            pixelBuffer[n][i] = (((WS281x_Color(red, green, blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
        }
    }
}


/*******************************************************************************
** 函数名称: WS281x_Show
** 功能描述:
********************************************************************************/
void WS281x_Show(void)
{
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7, LEN);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_7);
    LL_TIM_EnableCounter(TIM1);
    while(ws2812_xfer_flag); //dma发送完成后,会清零此flag。
}

/*******************************************************************************
** 函数名称: WS281x_Wheel
** 功能描述: Input a value 0 to 255 to get a color value. The colours are a transition r - g - b - back to r.
********************************************************************************/
uint32_t WS281x_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);
}


/*******************************************************************************
** 函数名称: WS281x_ColorWipe
** 功能描述: Fill the dots one after the other with a color
********************************************************************************/
void WS281x_ColorWipe(uint32_t c, uint8_t wait)
{
    for(uint16_t i = 0; i < PIXEL_NUM; i++)
    {
        WS281x_SetPixelColor(i, c);
        WS281x_Show();
        LL_mDelay(wait);
    }
}

/*******************************************************************************
** 函数名称: WS281x_Rainbow
** 功能描述:
********************************************************************************/
void WS281x_Rainbow(uint8_t wait)
{
    uint16_t i, j;

    for(j = 0; j < 256; j++)
    {
        for(i = 0; i < PIXEL_NUM; i++)
        {
            WS281x_SetPixelColor(i, WS281x_Wheel((i + j) & 255));
        }
        WS281x_Show();
        LL_mDelay(wait);
    }
}


/*******************************************************************************
** 函数名称: WS281x_RainbowCycle
** 功能描述: Slightly different, this makes the rainbow equally distributed throughout
********************************************************************************/
void WS281x_RainbowCycle(uint8_t wait)
{
    uint16_t i, j;

    for(j = 0; j < 256 * 5; j++) // 5 cycles of all colors on wheel
    {
        for(i = 0; i < PIXEL_NUM; i++)
        {
            WS281x_SetPixelColor(i, WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
        }
        WS281x_Show();
        LL_mDelay(wait);
    }
}


/*******************************************************************************
** 函数名称: WS281x_TheaterChase
** 功能描述: Theatre-style crawling lights.
********************************************************************************/
void WS281x_TheaterChase(uint32_t c, uint8_t wait)
{
    for (int j = 0; j < 10; j++) //do 10 cycles of chasing
    {
        for (int q = 0; q < 3; q++)
        {
            for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3)
            {
                WS281x_SetPixelColor(i + q, c);  //turn every third pixel on
            }
            WS281x_Show();

            LL_mDelay(wait);

            for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3)
            {
                WS281x_SetPixelColor(i + q, 0);      //turn every third pixel off
            }
        }
    }
}

/*******************************************************************************
** 函数名称: WS281x_TheaterChaseRainbow
** 功能描述: Theatre-style crawling lights with rainbow effect
********************************************************************************/
void WS281x_TheaterChaseRainbow(uint8_t wait)
{
    for (int j = 0; j < 256; j++)     // cycle all 256 colors in the wheel
    {
        for (int q = 0; q < 3; q++)
        {
            for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3)
            {
                WS281x_SetPixelColor(i + q, WS281x_Wheel( (i + j) % 255)); //turn every third pixel on
            }
            WS281x_Show();

            LL_mDelay(wait);

            for (uint16_t i = 0; i < PIXEL_NUM; i = i + 3)
            {
                WS281x_SetPixelColor(i + q, 0);      //turn every third pixel off
            }
        }
    }
}

/********************************End of File************************************/

12.main.c中的使用
12.1)包含头文件

	#include "bsp_ws2812.h"

12.2)显示
只需要在需要显示的地方调用如下的函数就可以了,当然还有别的特效函数没有在此显示出来。

        WS281x_ColorWipe(30, 100);
        WS281x_ColorWipe(30 << 8, 100);
        WS281x_ColorWipe(30 << 16, 100);
        WS281x_TheaterChaseRainbow ( 20 );

13.附上输出波形
输出波形
一次PWM波的波形放大
一次PWM波形的测量结果
三、后记
之所以使用LL库,是因为在使用HAL库的时候,无法实现功能。封装的太狠了,导致使用的灵活性不够。
调用库函数TIM_PWM_START_DMA(…),只能正确传输一次,之后,DMA无法正确完成,无PWM产生,困了3天,选择用LL库来完成,结果简单很多。
github源代码仓库地址:gitee
视频链接:

stm32 LL库使用定时器pwm dma驱动ws2812

  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值