2445|6

151

帖子

0

资源

一粒金砂(高级)

03.WS2812B驱动实现之PWM+DMA [复制链接]

1、简介

上一篇我们介绍了通过GPIO引脚来模拟WS2812B的时序逻辑,来实现对彩色灯带的控制和显示;但通过GPIO实现的方式是独占式的,在写入时序数据时,是不能够被打断的;程序的移植性很差,相同的MCU如果运行的时钟频率变化了,对应控制WS2812B的逻辑时序也需要跟着做相应的调整,更不要说在不同MCU之间进行移植了;当然MCU可使用的资源远不止一个GPIO,我们还可以通过其它方式巧妙的来实现,本节主要就是介绍了使用MCU定时器的PWM功能,结合DMA来实现对WS2812B的时序逻辑控制。

 

2、定时器的选择

GD32L233x系列的MCU带有6种类型的定时器,如下图所示;我们可以看到通用L1类型的定时器8和定时器11是不支持DMA功能的;所以我们就可以在其它几个定时器中任选一个;下面的程序中我们选择的是定时器1,使用定时器1的通道1,对应的GPIO端口是PA1,对应的复用功能是AF1;

3.png 4.png

 

3、主要实现代码


……

/*******************************************************************************
 * @brief * @param       
 * @retval      
 * @attention *******************************************************************************/
static void WS2812B_Write24Bits(uint8_t R, uint8_t G, uint8_t B)
{
    for(uint8_t i = 0; i < 8; i++)
    {
        if((G << i) & 0x80)
        {
            RGB_BitBuffer[i + 0x00] = WS2812B_PWM_BIT_H;
        }
        else
        {
            RGB_BitBuffer[i + 0x00] = WS2812B_PWM_BIT_L;
        }
    }

    for(uint8_t i = 0; i < 8; i++)
    {
        if((R << i) & 0x80)
        {
            RGB_BitBuffer[i + 0x08] = WS2812B_PWM_BIT_H;
        }
        else
        {
            RGB_BitBuffer[i + 0x08] = WS2812B_PWM_BIT_L;
        }
    }

    for(uint8_t i = 0; i < 8; i++)
    {
        if((B << i) & 0x80)
        {
            RGB_BitBuffer[i + 0x10] = WS2812B_PWM_BIT_H;
        }
        else
        {
            RGB_BitBuffer[i + 0x10] = WS2812B_PWM_BIT_L;
        }
    }

    RGB_BitBuffer[0] += RGB_StartByte;
    RGB_StartByte     = 0;

    __disable_irq();

    dma_transfer_number_config(DMA_CH0, 24);
    dma_channel_enable(DMA_CH0);
    timer_enable(TIMER1);
    while(!dma_flag_get(DMA_CH0, DMA_FLAG_FTF));
    timer_disable(TIMER1);
    dma_channel_disable(DMA_CH0);
    dma_flag_clear(DMA_CH0, DMA_FLAG_FTF);

    __enable_irq();
}

……

/*******************************************************************************
 * @brief       
 * @param       
 * @retval      
 * @attention   
*******************************************************************************/
void WS2812B_Init(void)
{
    dma_parameter_struct        dma_data_parameter;
    timer_parameter_struct      timer_initpara;
    timer_oc_parameter_struct   timer_ocinitpara;

    rcu_periph_clock_enable(WS2812B_GPIO_CLK);
    gpio_mode_set(WS2812B_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, WS2812B_GPIO_PIN);
    gpio_output_options_set(WS2812B_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, WS2812B_GPIO_PIN);
    gpio_af_set(WS2812B_GPIO_PORT, GPIO_AF_1, WS2812B_GPIO_PIN);

    rcu_periph_clock_enable(RCU_DMA);

    dma_deinit(DMA_CH0);

    dma_data_parameter.periph_addr  = (uint32_t)TIMER1_DMATB_ADDR;
    dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_data_parameter.memory_addr  = (uint32_t)RGB_BitBuffer;
    dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;
    dma_data_parameter.direction    = DMA_MEMORY_TO_PERIPHERAL;
    dma_data_parameter.number       = 24;
    dma_data_parameter.priority     = DMA_PRIORITY_HIGH;
    dma_data_parameter.request      = DMA_REQUEST_TIMER1_UP;
    dma_init(DMA_CH0, &dma_data_parameter);

    dma_circulation_disable(DMA_CH0);

//  dma_channel_enable(DMA_CH0);

    rcu_periph_clock_enable(RCU_TIMER1);

    timer_deinit(TIMER1);

    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler        = 0;
    timer_initpara.alignedmode      = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection = TIMER_COUNTER_UP;
    timer_initpara.period           = 79;               //---800kHz
    timer_initpara.clockdivision    = TIMER_CKDIV_DIV1;
    timer_init(TIMER1, &timer_initpara);

    timer_channel_output_struct_para_init(&timer_ocinitpara);
    timer_ocinitpara.outputstate    = TIMER_CCX_ENABLE;
    timer_ocinitpara.ocpolarity     = TIMER_OC_POLARITY_HIGH;
    timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocinitpara);

    timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 0);
    timer_channel_output_mode_config(TIMER1, TIMER_CH_1, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER1, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);

    timer_dma_transfer_config(TIMER1, TIMER_DMACFG_DMATA_CH1CV, TIMER_DMACFG_DMATC_1TRANSFER);
    timer_dma_enable(TIMER1, TIMER_DMA_UPD);

    timer_auto_reload_shadow_enable(TIMER1);

//  timer_enable(TIMER1);

    WS2812B_DisplayAllRed();    SysTick_DelayMS(500);
    WS2812B_DisplayAllGreen();  SysTick_DelayMS(500);
    WS2812B_DisplayAllBlue();   SysTick_DelayMS(500);

    RGB_StartByte = 13;
    TASK_Append(TASK_ID_WS2812B, WS2812B_DisplayFullColor, 50);
}

……

 

4、运行视频效果


 

 

5、工程源码

Template_WS2812B_PWM.zip (391.41 KB, 下载次数: 44)

个人签名We are a team and we work as a team !

回复

9691

帖子

21

资源

版主

60个灯珠循环显示是重复在第一个灯珠的第一个bit时发生这种宽度与实际不符的情况,还是无论显示多长时间,只发会生一次?

试试写一个简单的程序,只发送几个脉冲看一下结果。

 

我看你的代码每一个灯珠的显示都要配置一次定时器,从波形上看2个灯珠的显示会有一定间隔,使用PWM的方式如果让DMA一次搬运全部灯珠的数据理论上能实现吗?

点评

可能表述没有让你很直观的明白问题点,如果有兴趣,可以找一串灯珠运行、调试一下程序就明白了;在发帖子之前我也做了多种情况的实验和验证  详情 回复 发表于 2021-12-15 21:26
个人签名虾扯蛋,蛋扯虾,虾扯蛋扯虾

回复

151

帖子

0

资源

一粒金砂(高级)

littleshrimp 发表于 2021-12-15 10:24 60个灯珠循环显示是重复在第一个灯珠的第一个bit时发生这种宽度与实际不符的情况,还是无论显示多长时间, ...

可能表述没有让你很直观的明白问题点,如果有兴趣,可以找一串灯珠运行、调试一下程序就明白了;在发帖子之前我也做了多种情况的实验和验证

个人签名We are a team and we work as a team !

回复

3

帖子

0

资源

一粒金砂(初级)

楼主有试过用 HC32F460的 PWM+DMA驱动2812吗? 可以做一个驱动例程共享吗?


回复

3

帖子

0

资源

一粒金砂(初级)

wkingxi 发表于 2022-1-26 11:04 楼主有试过用 HC32F460的 PWM+DMA驱动2812吗? 可以做一个驱动例程共享吗?

HC32F460 使用的TIMER 好像没有对应 DMA硬件中断,我调试不通。希望楼主能做个HC32F460做个例程,谢谢!

点评

我手上暂时没有这个硬件环境,你可以联系华大的代理或者是原厂寻求技术支持  详情 回复 发表于 2022-1-27 10:47

回复

151

帖子

0

资源

一粒金砂(高级)

wkingxi 发表于 2022-1-26 11:11 HC32F460 使用的TIMER 好像没有对应 DMA硬件中断,我调试不通。希望楼主能做个HC32F460做个例程,谢谢! ...

我手上暂时没有这个硬件环境,你可以联系华大的代理或者是原厂寻求技术支持

个人签名We are a team and we work as a team !

回复

3

帖子

0

资源

一粒金砂(初级)

xld0932 发表于 2022-1-27 10:47 我手上暂时没有这个硬件环境,你可以联系华大的代理或者是原厂寻求技术支持

找了些办法,没得到支持


回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

最新文章 更多>>
    推荐帖子
    Qt学习之路第15篇 标准对话框 QMessageBox

    所谓标准对话框,是 Qt 内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置 ...

    Ubuntu下MSP430开发环境搭建

    msp430G2553程序烧写方法: #include int main (void) { volatile int i; /* Stop watchdog ti ...

    DSP28335 外设时钟

    TMS320F28335通过外部时钟信号、OSC和PLL产生倍频时钟信号CLKIN后,CLKIN经过CPU后产生时钟SYSCLKOUT(CLKIN和SYSCLKOUT频率是 ...

    下一代电池监控器:如何在提高精度和延长运行时间的同时提高电池的安全性

    近年来,诸如吸尘器、电动工具(如钻头、锯子和螺丝刀)和园艺工具(如割草机、修边机和草坪拖拉机)等消费品已从依靠绳索和墙壁 ...

    TPS61040升压异常问题

    本帖最后由 chenzhouyu 于 2020-11-21 15:07 编辑 大家好! 请教个问题,我用TPS61040做了一个给LCD供电的升压电路。 L ...

    【已发奖】EEWorld月月有奖——9月

    活动详情:EEWorld月月有奖重磅开启!你有料,我有奖! 感谢大家对EEWorld的活动支持!在过去的9月,大家在论坛分享,答疑, ...

    关闭
    站长推荐上一条 1/9 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2022 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表