7212|2

12

帖子

0

TA的资源

一粒金砂(中级)

楼主
 

第八篇 定时器+DMA驱动WS2812B [复制链接]

本帖最后由 xiaoli2018 于 2022-2-25 12:22 编辑

WS2812的驱动搞了好久才成功,有点惭愧。

WS2812B的驱动信息

从WS2812的数据手册中,可以看出其使用单总线级联的方式进行通信,一组数据包括所有灯珠的显示信息,每组数据之间通过RESET分隔。在同一组数据中,数据每过1个灯珠,减少24位,也就是说每个灯珠都读取收到的前24位数据,作为自己的显示信息,其它的数据接着往下传递,直到遇到RESET信号,然后重新读取24为的数据。

对于每位数据是0还是1,则是通过占空比进行判断的。在这里我选取1.25us为1位数据的周期,300ns的高电平作为T0H,625ns的高电平作为T0L 。

WS2812B的驱动思路

现在需要做的就是不断的改变定时器PWM的占空比,以实现数据的发送。每发送1位数据就要改变一次占空比的值,如果使用中断的方式,那么进入中断的频率就会很高,所以使用DMA的方式是比较合适的。

我这里选择的方式是,使用定时器的更新事件触发DMA的数据传输,DMA使用循环模式一直发送,这样编程比较简单,不需要关注何时发送数据,只需要关注如何修改内存中的数据就行。

对于RESET的发送,从图中可以看出RESET信号是持续280us以上的低电平,那么就可以用高电平占空比为0的PWM信号代替,也就是说224个以1.25us为周期占空比为0的PWM就可以作为RESET信号了。

对外设的设置

外设的设置包括3个部分:IO、DMA和定时器。

IO设置

我使用的是PB4,其AF1的复用功能为TIMER2_CH0 。

    rcu_periph_clock_enable(RCU_GPIOB);

    gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_4);
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);

DMA的设置

DMA和STM32F1系列不同,GD32L23x中的DMA的通道不再是和外设对应起来的,而是通过复用器去与外设连接,这样每一个通道都可以对应所有的外设,使用起来更加的灵活。

DMA的请求用的是TIMER2的更新事件DMA_REQUEST_TIMER2_UP,注意这个事件需要和TIMER设置里的事件对应起来,否则无法触发DMA。

DMA设置为循环模式dma_circulation_enable(DMA_CH0);

void dma_config(void)
{
    dma_parameter_struct dma_data_parameter;
    rcu_periph_clock_enable(RCU_DMA);
    dma_deinit(DMA_CH0);

    dma_data_parameter.periph_addr  = (uint32_t)TIMER2_CH0CV;
    dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_data_parameter.memory_addr  = (uint32_t)(&buffer);
    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       = SIZE_OF_BUFFER;
    dma_data_parameter.priority     = DMA_PRIORITY_HIGH;
    dma_data_parameter.request      = DMA_REQUEST_TIMER2_UP;
    dma_init(DMA_CH0, &dma_data_parameter);
    dma_circulation_enable(DMA_CH0);

    dma_channel_enable(DMA_CH0);
}

TIMER2的设置

定时器的设置包含三部分:

  1. 时基单元
  2. 输出模式
  3. DMA事件设置

时基单元配置为1.25us的周期。输出模式使用的是PWM0的模式,同时使能TIMER2的TIMER_DMA_UPD DMA请求。

void timer_config(void)
{
    timer_oc_parameter_struct timer_ocinitpara;
    timer_parameter_struct timer_initpara;
    timer_ic_parameter_struct timer_icinitpara;

    rcu_periph_clock_enable(RCU_TIMER2);

    timer_deinit(TIMER2);
    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;
    timer_initpara.clockdivision    = TIMER_CKDIV_DIV1;
    timer_init(TIMER2, &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(TIMER2, TIMER_CH_0, &timer_ocinitpara);
    timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_0, 0);
    timer_channel_output_mode_config(TIMER2, TIMER_CH_0, TIMER_OC_MODE_PWM0);

    timer_dma_enable(TIMER2, TIMER_DMA_UPD);

    timer_auto_reload_shadow_enable(TIMER2);
}

驱动代码

驱动代码包括了两大部分:

  1. 使能定时器
  2. 将颜色数据放入内存中

使能定时器

注意不要直接使能定时器,而是要先产生1次更新事件,然后再使能定时器。因为这样才能将内存中的第一个数据放入到定时器的中。

    timer_event_software_generate(TIMER2,TIMER_EVENT_SRC_UPG);
    timer_enable(TIMER2);

将颜色数据放入内存中

需要做的工作有2个:

  1. 将颜色的数据转化成定时器比较值寄存器中的数据
  2. 转化数据格式:常用的颜色数据是RGB数据,而WS2812中颜色的存放顺序是GRB。
#define T1H_COUNT   40		//对于WS2812输出位1时,PWM的计数值
#define T0H_COUNT   15		//对于WS2812输出位0时,PWM的计数值
void change_color_to_bit(uint32_t rgb_color,uint16_t* bit)
{
	uint32_t temp = rgb_color;
	int i;
	uint16_t temp_bit;
	for(i=0;i<24;i++){
		if(temp & 0x00000001){
			bit[23-i] = T1H_COUNT;
		}else{
			bit[23-i] = T0H_COUNT ;
		}
		temp = temp>>1;
	}
	for(i=0;i<8;i++){
		temp_bit = bit;
		bit = bit[8+i];
		bit[8+i] = temp_bit;
	}
}

上面的代码将颜色数据转化成比较值寄存器中的值。

效果展示

直接拍摄的效果不太好,用一张白纸遮着的效果要好一些。

 

ws2812b.c (4.44 KB, 下载次数: 106)

ws2812b.h (121 Bytes, 下载次数: 70)

此帖出自GD32 MCU论坛

最新回复

看到效果了, 有点疑惑,使能定时器为什么不要直接使能定时器,而是要先产生1次更新事件   详情 回复 发表于 2022-2-26 22:14
点赞(1) 关注(1)
 

回复
举报

6807

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

看到效果了,

有点疑惑,使能定时器为什么不要直接使能定时器,而是要先产生1次更新事件

此帖出自GD32 MCU论坛

点评

因为是更新事件触发的dma装载数据。如果直接使能定时器,此时没有更新事件,那么在第一个周期比较寄存器是没有值的,因为此时dma还没有向比较寄存器加载数据。当第一次周期结束,产生了更新事件之后dma才会加载数据  详情 回复 发表于 2022-2-27 10:42
 
 
 

回复

12

帖子

0

TA的资源

一粒金砂(中级)

板凳
 
Jacktang 发表于 2022-2-26 22:14 看到效果了, 有点疑惑,使能定时器为什么不要直接使能定时器,而是要先产生1次更新事件

因为是更新事件触发的dma装载数据。如果直接使能定时器,此时没有更新事件,那么在第一个周期比较寄存器是没有值的,因为此时dma还没有向比较寄存器加载数据。当第一次周期结束,产生了更新事件之后dma才会加载数据

此帖出自GD32 MCU论坛
 
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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

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

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

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