单片机学习小组
直播中

张强

7年用户 1363经验值
私信 关注

怎样使用硬件定时器PWM+DMA方式实现WS2812的驱动呢

WS2812的驱动方式有哪几种?
怎样使用硬件定时器PWM+DMA方式实现WS2812的驱动呢?


回帖(1)

严岩

2022-1-25 15:42:50
一、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不变。

4.定时器参数设置,这里使用定时器TIM1的CH3通道
***注意,定时器的计数器预加载要关闭。

DMA参数设置,选择普通模式,方向从内存到外设,注意数据宽度和字长不同,以及地址是否增加。

5.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[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] = ((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] = (((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 );
3.附上输出波形

一次PWM波的波形放大

三、后记
之所以使用LL库,是因为在使用HAL库的时候,无法实现功能。封装的太狠了,导致使用的灵活性不够。
调用库函数TIM_PWM_START_DMA(…),只能正确传输一次,之后,DMA无法正确完成,无PWM产生,困了3天,选择用LL库来完成,结果简单很多。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分