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、主要实现代码
……
/*******************************************************************************
* @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、工程源码
6、需要解答
如附件程序中的配置,一共有60个灯珠,一个灯珠需要24个PWM周期的时序控制,60个灯珠是串行的,所以点亮60个灯珠需要DMA搬运60次数据;在DMA搬运数据时,发现第一次的PWM高电平占用的时间是不正确的(Delta_A为100ns、Delta_B为310ns),如下图所示;在程序做了修正后(强制在延长第一个PWM高电平占比,使Delta_A与Delta_B的时间相等),才调节到正常状态,如最后一张图所示;有哪个小伙伴或者原厂FAE知道原因的,可以解答一下吗?
60个灯珠循环显示是重复在第一个灯珠的第一个bit时发生这种宽度与实际不符的情况,还是无论显示多长时间,只发会生一次?
试试写一个简单的程序,只发送几个脉冲看一下结果。
我看你的代码每一个灯珠的显示都要配置一次定时器,从波形上看2个灯珠的显示会有一定间隔,使用PWM的方式如果让DMA一次搬运全部灯珠的数据理论上能实现吗?
引用: littleshrimp 发表于 2021-12-15 10:24 60个灯珠循环显示是重复在第一个灯珠的第一个bit时发生这种宽度与实际不符的情况,还是无论显示多长时间, ...
可能表述没有让你很直观的明白问题点,如果有兴趣,可以找一串灯珠运行、调试一下程序就明白了;在发帖子之前我也做了多种情况的实验和验证
引用: wkingxi 发表于 2022-1-26 11:04 楼主有试过用 HC32F460的 PWM+DMA驱动2812吗? 可以做一个驱动例程共享吗?
HC32F460 使用的TIMER 好像没有对应 DMA硬件中断,我调试不通。希望楼主能做个HC32F460做个例程,谢谢!
引用: wkingxi 发表于 2022-1-26 11:11 HC32F460 使用的TIMER 好像没有对应 DMA硬件中断,我调试不通。希望楼主能做个HC32F460做个例程,谢谢! ...
我手上暂时没有这个硬件环境,你可以联系华大的代理或者是原厂寻求技术支持
引用: xld0932 发表于 2022-1-27 10:47 我手上暂时没有这个硬件环境,你可以联系华大的代理或者是原厂寻求技术支持
找了些办法,没得到支持