GD32使用PWM+DMA调试WS2812-RGB灯调试记录(附GD32中的TIMER定时器和DMA的踩坑记录)

一、前言

目的:对于使用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

}

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值