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不变。
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[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波的波形放大
三、后记
之所以使用LL库,是因为在使用HAL库的时候,无法实现功能。封装的太狠了,导致使用的灵活性不够。
调用库函数TIM_PWM_START_DMA(…),只能正确传输一次,之后,DMA无法正确完成,无PWM产生,困了3天,选择用LL库来完成,结果简单很多。
github源代码仓库地址:gitee
视频链接:
stm32 LL库使用定时器pwm dma驱动ws2812