一、前言
目的:对于使用STM32驱动WS2812-RGB灯,已经有很多大佬进行了分享,同时写得很好!但是对于GD32的调试WS2812确实偏少,刚好最近的项目有用到,顺便记录一下踩过的坑。开源不易,谢谢大家!
感谢:特别感谢三位大佬的的博文贡献;
1.GD32F470通过DMA输出PWM_gd32 pwm dma-CSDN博客
2.基于GD32的定时器不完全详解--定时、级联_gd32 定时器-CSDN博客
3.WS2812B彩灯 STM32HAL库开发:PWM+DMA(stm32f103c8t6)_ws2812编程实例-CSDN博客
硬件:GD32E230F6V6(72M主频)、WS2812B(RGB灯)
引脚:PA6-----复用功能_定时器2通道0
踩坑记录附到文章最后
二、WS2812B点亮原理
在DI脚通过MCU的引脚产生特定的脉冲,形成各种各样的炫彩灯光。
根据描述得知一次数据的传输的时间大概为1.25us,转换过来即800K HZ的频率,同时通过0码和1码的高电平的持续时间不同,那么我们就可以采用PWM产生对应的占空比来表示0码、1码。
通过级联的电路连接,我们就可以实现用1个MCU引脚来控制多个RGB灯。注意:每个RGB灯每次只会接收24bit的数据,接收到后就会锁存,下一个RGB灯则接收到的是第二组的24bit数据,同时接收的数据是以G、R、B的顺序。
三、话不多说,上代码
头文件:ws2812.h
#ifndef _WS2812_H_
#define _WS2812_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "gd32e230.h"
#include "systick.h"
#define WS2812_PIN_PORT GPIOA
#define WS2812_PIN_NUM GPIO_PIN_6
/*建立一个定义单个LED三原色值大小的结构体*/
typedef struct
{
uint8_t R;
uint8_t G;
uint8_t B;
}RGB_Color_TypeDef;
typedef enum{
RED_COLOR = 0,
GREEN_COLOR,
BLUE_COLOR,
SKY_COLOR,
MAGENTA_COLOR,
YELLOW_COLOR,
OEANGE_COLOR,
BLACK_COLOR,
WHITE_COLOR
}rgb_color;
void ws2812_init(void);//初始化
void ws2812_display_color(uint16_t color);//显示颜色
void ws2812_display_blink_light(uint16_t blink_num, uint16_t color,uint32_t time_out);//闪烁灯模式设置
void ws2812_display_breathe_light(uint16_t color,uint32_t time_out);//呼吸灯模式设置
#ifdef __cplusplus
}
#endif
#endif
C文件:ws2812.c
这里没有采用dma中断的形式,轮询就已够用;
通过控制PWM占空比发送0码和1码,额定周期为1.25us,则频率为800Khz
0码PWM占空比:(0码高电平时间)/(周期)---> 0.4 / 1.25 = 0.32
用占空比乘以定时器重装值加一就是0码的CCR值(代表PWM高电平计数个数)--->0.32 * (89+1) = 28.8(取28,实测不可以高于28,但23到28都可以)
1码PWM占空比:同理计算:(1码高电平时间)/ (周期)---> 0.8 / 1.25 = 0.64(占空比)*(重置值+1)= CCR ---> 0.64 * 1.25 = 57.6(取58)
#define CODE_1 (58) //1码定时器计数次数
#define CODE_0 (25) //0码定时器计数次数
通过/*Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)通过控制PWM占空比发送0码和1码,额定周期为1.25us,则频率为800Khz*/
因为使用的MCU为72M,则算出的psc = 0;arr = 89
这里主要实现了两种RGB灯的模式,一种是闪烁模式即一亮一灭
另一种为呼吸灯的渐变,我这里的处理都是全部灯珠执行相同的颜色。
#define Pixel_NUM 6 //LED数量宏定义,这里我使用6个LED,(单词pixel为像素的意思)
#include "ws2812.h"
/*这里是上文计算所得CCR的宏定义*/
#define CODE_1 (58) //1码定时器计数次数
#define CODE_0 (25) //0码定时器计数次数
#define Pixel_NUM 6 //LED数量宏定义,这里我使用一个LED,(单词pixel为像素的意思)
static uint16_t Pixel_Buf[Pixel_NUM+1][24];//RGB灯传输数据;多于的24bit是刷新用
/*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,255};
const RGB_Color_TypeDef YELLOW = {255,255,0};
const RGB_Color_TypeDef OEANGE = {255,165,0};
const RGB_Color_TypeDef BLACK = {0,0,0};
const RGB_Color_TypeDef WHITE = {255,255,255};
const RGB_Color_TypeDef TEST = {152,251,152};
static void ws2812_timer_pwm_init(void)
{
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
/* TIMER configuration */ /*Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)通过控制PWM占空比发送0码和1码,额定周期为1.25us,则频率为800Khz*/
timer_initpara.prescaler = 0;//预分频系数pcs
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 89;//计数值arr
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;//72主频不分频
timer_initpara.repetitioncounter = 0;//自动重装载系数
timer_init(TIMER2,&timer_initpara);
/* configurate CH0 in PWM mode0 */
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;//通道输出状态
timer_ocintpara.outputnstate = TIMER_CCXN_ENABLE;//互补通道输出状态
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;//通道输出极性
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;//互补通道输出极性
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_HIGH;//空闲状态通道输出(空闲状态为高电平)
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;//空闲状态互补输出状态
timer_channel_output_config(TIMER2,TIMER_CH_0,&timer_ocintpara);//外设TIMER2的通道0输出配置
timer_primary_output_config(TIMER2, ENABLE);
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,0);//配置外设TIMER2的通道0输出比较值(占空比)
timer_channel_output_mode_config(TIMER2,TIMER_CH_0,TIMER_OC_MODE_PWM0);//pwm模式设置
timer_channel_output_shadow_config(TIMER2,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);//配置TIMER2通道输出比较影子寄存器功能
/* TIMER2 CH3D DMA request enable */
timer_dma_enable(TIMER2, TIMER_DMA_UPD);//注意这里需要选择TIMER_DMA_UPD
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);//自动重装载使能
/* TIMER2 enable */
timer_enable(TIMER2);
}
static void ws2812_dma_init(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA);
/* DMA channel1 initialize */
dma_deinit(DMA_CH2);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;//内存到外设
dma_init_struct.memory_addr = (uint32_t)Pixel_Buf;//内存数据基地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;//内存地址自增
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;//存储器数据传输宽度
dma_init_struct.number = (Pixel_NUM+1)*24;//dma通道传输数据数量
dma_init_struct.periph_addr = (uint32_t)&TIMER_CH0CV(TIMER2);//外设基地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;//外设地址自增关闭
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;//外设数据传输宽度
dma_init_struct.priority = DMA_PRIORITY_HIGH;//优先级
dma_init(DMA_CH2, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA_CH2);//dma循环模式禁止
dma_memory_to_memory_disable(DMA_CH2);//存储器到存储器传输禁止
/* enable DMA channel1 */
dma_channel_enable(DMA_CH2);//使能dma通道2
}
static void ws2812_gpio_init(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
gpio_mode_set(WS2812_PIN_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, WS2812_PIN_NUM);
gpio_output_options_set(WS2812_PIN_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,WS2812_PIN_NUM);
gpio_af_set(WS2812_PIN_PORT, GPIO_AF_1, WS2812_PIN_NUM);
}
void ws2812_init(void)
{
ws2812_gpio_init();
ws2812_timer_pwm_init();
ws2812_dma_init();
}
/*
功能:设定单个RGB LED的颜色,把结构体中RGB的24BIT转换为0码和1码
参数:LedId为LED序号,Color:定义的颜色结构体
*/
static void ws2812_rgb_set_color_buf(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_Buf[LedId][i] = ( (Color.G & (1 << (7 -i)))? (CODE_1):CODE_0 );//数组某一行0~7转化存放G
for(i=8;i<16;i++) Pixel_Buf[LedId][i] = ( (Color.R & (1 << (15-i)))? (CODE_1):CODE_0 );//数组某一行8~15转化存放R
for(i=16;i<24;i++) Pixel_Buf[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)
*/
static void ws2812_reset_load(void)
{
uint8_t i;
for(i=0;i<24;i++)
{
Pixel_Buf[Pixel_NUM][i] = 0;
}
}
/*
功能:发送数组
*/
static void ws2812_rgb_send_array(void)
{
while(dma_flag_get(DMA_CH2, DMA_INTF_FTFIF)==RESET);
dma_flag_clear(DMA_CH2,DMA_INTC_FTFIFC);
dma_channel_disable(DMA_CH2);
dma_transfer_number_config(DMA_CH2,(Pixel_NUM+1)*24);
dma_channel_enable(DMA_CH2);
}
/*
功能:返回传参颜色的结构体
*/
static RGB_Color_TypeDef ws2812_return_color_type(uint16_t color)
{
RGB_Color_TypeDef color_type;
switch(color)
{
case RED_COLOR:
color_type = RED;
break;
case GREEN_COLOR:
color_type = GREEN;
break;
case BLUE_COLOR:
color_type = BLUE;
break;
case SKY_COLOR:
color_type = SKY;
break;
case MAGENTA_COLOR:
color_type = MAGENTA;
break;
case YELLOW_COLOR:
color_type = YELLOW;
break;
case OEANGE_COLOR:
color_type = OEANGE;
break;
case BLACK_COLOR:
color_type = BLACK;
break;
case WHITE_COLOR:
color_type = WHITE;
break;
default:
color_type = BLACK;
break;
}
return color_type;
}
/*
功能:设置全部灯珠颜色
参数:color:定义的颜色结构体
*/
static void ws2812_set_color(RGB_Color_TypeDef color)
{
for(uint16_t i = 0;i<Pixel_NUM;i++)//给对应个数LED写入红色
{
ws2812_rgb_set_color_buf(i,color);
}
ws2812_reset_load();
ws2812_rgb_send_array();
ws2812_reset_load();
ws2812_rgb_send_array();
}
/*
功能:显示设置的颜色
参数:color:定义的枚举颜色
*/
void ws2812_display_color(uint16_t color)
{
switch(color)
{
case RED_COLOR:
ws2812_set_color(RED);
break;
case GREEN_COLOR:
ws2812_set_color(GREEN);
break;
case BLUE_COLOR:
ws2812_set_color(BLUE);
break;
case SKY_COLOR:
ws2812_set_color(SKY);
break;
case MAGENTA_COLOR:
ws2812_set_color(MAGENTA);
break;
case YELLOW_COLOR:
ws2812_set_color(YELLOW);
break;
case OEANGE_COLOR:
ws2812_set_color(OEANGE);
break;
case BLACK_COLOR:
ws2812_set_color(BLACK);
break;
case WHITE_COLOR:
ws2812_set_color(WHITE);
break;
default:
ws2812_set_color(BLACK);
break;
}
}
/*
功能:亮度一致,灯珠同时闪烁
参数:blink_num:闪烁的次数
参数:color: 闪烁的颜色
参数:time_out:ms
*/
void ws2812_display_blink_light(uint16_t blink_num, uint16_t color,uint32_t time_out)
{
if(blink_num > 0xFFFE)
{
return;
}
for(uint16_t i = 0;i < blink_num;i++)
{
ws2812_display_color(color);
delay_1ms(time_out);
ws2812_display_color(BLACK_COLOR);
delay_1ms(time_out);
}
}
/*
功能:亮度渐变色,全部灯珠同时呼吸渐变
参数:color: 闪烁的颜色
参数:time_out:呼吸频率ms
*/
void ws2812_display_breathe_light(uint16_t color,uint32_t time_out)
{
RGB_Color_TypeDef breathe_type = {0};
RGB_Color_TypeDef color_type = ws2812_return_color_type(color);
// 亮度递增
for (int i = 0; i <= 128; i += 3) {
// 计算当前亮度下的颜色值
breathe_type.R = (color_type.R * i) / 255;
breathe_type.G = (color_type.G * i) / 255;
breathe_type.B = (color_type.B * i) / 255;
ws2812_set_color(breathe_type);
delay_1ms(time_out);
}
gpio_bit_reset(WS2812_PIN_PORT,WS2812_PIN_NUM);
}
踩坑记录
1.通过调试GD32的timer + dma 的过程中,一开始通过MCU的用户手册查询得出,PA6引脚可以复用为TIMER2 的CH0,输出PWM,输出PWM没有任何问题,但是通过内存到外设的DMA一直不能成功传输
当看到用户手册这样的描述时,理所应当的会认为使用TIME2_CH0的DMA那么就应该使能DMA的通道3,这里就是第一个坑
实际上:
当使用DMA的方向为,内存到外设的时候,同时需要将内存中的数值通过DMA传到定时器的引脚实现不同的占空比时,那么则应该使能的时DMA通道2,同时
timer_dma_enable(TIMER2, TIMER_DMA_UPD);//注意这里需要选择TIMER_DMA_UPD
2.选择DMA外设基地址时候的大坑,注意看两组代码
static void ws2812_dma_init(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA);
/* DMA channel1 initialize */
dma_deinit(DMA_CH2);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;//内存到外设
dma_init_struct.memory_addr = (uint32_t)Pixel_Buf;//内存数据基地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;//内存地址自增
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;//存储器数据传输宽度
dma_init_struct.number = (Pixel_NUM+1)*24;//dma通道传输数据数量
dma_init_struct.periph_addr = (uint32_t)TIMER_CH0CV(TIMER2);//外设基地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;//外设地址自增关闭
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;//外设数据传输宽度
dma_init_struct.priority = DMA_PRIORITY_HIGH;//优先级
dma_init(DMA_CH2, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA_CH2);//dma循环模式禁止
dma_memory_to_memory_disable(DMA_CH2);//存储器到存储器传输禁止
/* enable DMA channel1 */
dma_channel_enable(DMA_CH2);//使能dma通道2
}
TIMER_CH0CV(TIMER2);这个宏定义让我理所应当的认为,只要传TIMER2进行就是基地址了,其实这只是TIMER_CH0的值
正确的应该加一个&取值符
static void ws2812_dma_init(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA);
/* DMA channel1 initialize */
dma_deinit(DMA_CH2);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;//内存到外设
dma_init_struct.memory_addr = (uint32_t)Pixel_Buf;//内存数据基地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;//内存地址自增
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;//存储器数据传输宽度
dma_init_struct.number = (Pixel_NUM+1)*24;//dma通道传输数据数量
dma_init_struct.periph_addr = (uint32_t)&TIMER_CH0CV(TIMER2);//外设基地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;//外设地址自增关闭
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;//外设数据传输宽度
dma_init_struct.priority = DMA_PRIORITY_HIGH;//优先级
dma_init(DMA_CH2, &dma_init_struct);
/* configure DMA mode */
dma_circulation_disable(DMA_CH2);//dma循环模式禁止
dma_memory_to_memory_disable(DMA_CH2);//存储器到存储器传输禁止
/* enable DMA channel1 */
dma_channel_enable(DMA_CH2);//使能dma通道2
}
标签:DMA,struct,dma,timer,init,TIMER,GD32,调试 From: https://blog.csdn.net/weixin_57301742/article/details/140875909