【GD32】从0开始学GD32单片机(12)—— TIMER高级定时器详解+DMA修改PWM波占空比例程

简介

上两篇介绍了基本定时器和通用定时器,下面是文章的链接。
TIMER基本定时器详解+1毫秒延时例程
TIMER通用定时器详解+PWM波形输出捕获例程

这一篇来介绍GD32中最后一类定时器——高级定时器。
下面按惯例附上GD32中3类定时器的差异表。

在这里插入图片描述

高级定时器比最高级的L0通用定时器,多出了可编程的计数器重复功能、互补和死区功能和终止输入的功能

下面是高级定时器的基本架构框图。

在这里插入图片描述

重复计数器

重复计数器是用来在N+1个计数周期之后产生更新事件,更新定时器的寄存器。N为重复计数器中的起始值,为用户所确定。重复计数器是一个向下计数的计数器,每次定时器产生上溢或下溢时,重复计数器的值自减一,达到零时产生更新事件,复位定时器。

下面是定时器向上计数模式下重复计数器N为0、1和2时的时序图。

在这里插入图片描述

对于初学者有一个误区就是,以为定时器只要达到了重装载值,产生溢出,就会产生更新事件;而事实上即使计数器溢出,但只要重复计数器的值不为0,定时器就不会硬件发出更新事件
但也有特例,比如由用户软件产生或通过硬件从模式控制器产生,那么重复计数器为任何值,更新事件都会产生
对于基本定时器和通用定时器,因为它们并不支持重复计数器特性,因此不需要考虑这一点。

互补模式和死区插入

互补模式和死区插入通常是在一起使用的。互补模式十分好理解,就是定时器可以输出两路互补的信号,但死区时间就比较难理解,下面通过一张时序图来帮助理解。

在这里插入图片描述

从上图可以看到,OCx和OCxN两路互补信号并不是完全对齐的,带有一定的延时,这个延时就是死区时间。
下面是一个更加完整的时序图。

在这里插入图片描述

我们先看CHx_ON信号,当计数器的值达到输出比较值的时候,它并没有立即切换到高电平,而是等待一段时间后再切换;对于CHx_O信号也是如此,当计数器的值小于输出比较值时,它也没有立即切换至高电平,而是等待一段时间后再切换。

那么这种死区时间有什么用呢?下面引用一段解释。

通常,大功率电机、变频器等,末端都是由大功率管、IGBT等元件组成的H桥或3相桥。每个桥的上半桥和下半桥是是绝对不能同时导通的,但高速的PWM驱动信号在达到功率元件的控制极时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。
死区就是在上半桥关断后,延迟一段时间再打开下半桥或在下半桥关断后,延迟一段时间再打开上半桥,从而避免功率元件烧毁,这段延迟时间就是死区(就是上、下半桥的元件都是关断的)。死区时间控制在通常的低端单片机所配备的PWM中是没有的。

中止模式

中止模式就是当定时器接收到了中止源的有效信号时,使定时器的输出处于某个特定状态
中止源可以设置为高电平有效或低电平有效,在终止模式下定时器输出的电平值由TIMERx_CTL1寄存器的ISOx和ISOxN位决定。

下面是一个通道响应中止信号的时序图。

在这里插入图片描述

霍尔传感器接口功能

霍尔传感器接口的作用是用来控制无刷直流(BLDC)电机
BLDC电机中有3个霍尔传感器,它们能监测磁场的变化,当电机旋转时,霍尔传感器的值也会相应变化。通过读取这3个信号,我们就能计算出转子的速度和位置,进而调整对电机的控制。

下面是一个BLDC电机连接的示意图。

在这里插入图片描述

要完整地控制一个BLDC电机,需要两个定时器,一个定时器负责捕获霍尔传感器的数据,一个定时器负责控制电机的旋转。

其中,负责捕获霍尔传感器数据的定时器需要有异或功能,这个高级定时器和L0通用定时器都满足;而负责控制电机的定时器需要有输出互补信号和死区插入的能力,这个只有高级定时器能满足

下面展示一张使用2个定时器控制BLDC电机的时序图。

在这里插入图片描述

鉴于我对BLDC的控制还不是很了解,因此这里先略过了。等以后接触了BLDC电机的控制项目再回来补这一个坑。

DMA模式

定时器的DMA模式稍微有点特别,因为定时器的内部有多个寄存器可以接收DMA的数据传输;因此,当定时器内部产生了中断事件,定时器就会给DMA发送请求,DMA响应请求就会把数据传输至TIMERx_DMATB寄存器,但这个寄存器只是作为一个缓冲区,之后定时器根据用户的映射,才会把里面的数据传输进最终的内部寄存器中。

定时器的DMA功能在某些应用中是相当有用的;例如,它能够在定时器工作的中途对输出比较寄存器的值进行修改,这样我们可以连续输出不同占空比的PWM信号;又例如,在定时器工作的中途修改重装载值,这样我们可以实现不同周期的连续计时。
通过DMA进行修改的好处是我们不需要停止定时器,修改定时器寄存器,再开启定时器;DMA能实现连贯的修改操作,不用中断定时器的工作。

例程

在这个例程中我们使用两个定时器,一个定时器负责输出PWM信号,并且使用DMA功能使它的PWM占空比随时变化,另一个定时器负责捕获PWM信号,计算其频率和占空比,并通过串口输出结果。

负责输出的定时器使用定时器0,输出到通道0,对应PA8管脚。PWM的输出频率为100Hz,为了降低占空比的更新频率,我把重复计数器的值设置为99,这样定时器每1秒才会发出更新事件,并请求DMA传输,DMA要使能循环模式,这样它就能一直重复进行传输。负责输入捕获的定时器和上一篇文章是一样的,所以这里就不再赘述了。

timer.c文件

#include "timer.h"

static uint16_t buffer[3] = {2499, 4999, 7499};

void TIM_DmaInit(void)
{
    /* 初始化GPIO */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_AF);

    /* PWM输出管脚为复用推挽模式 */
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    /* PWM输入管脚为浮空输入模式 */
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);

    
    /* 初始化DMA */
    dma_parameter_struct dma_init_struct = {0};

    rcu_periph_clock_enable(RCU_DMA0);

    dma_deinit(DMA0, DMA_CH4);

    dma_init_struct.direction    = DMA_MEMORY_TO_PERIPHERAL;  // 内存到外设模式
    dma_init_struct.memory_addr  = (uint32_t)buffer;  // 内存基地址
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;  // 开启内存地址自增
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;  // 内存宽度16位
    dma_init_struct.number       = 3;  // 3个数据包
    dma_init_struct.periph_addr  = (uint32_t)(TIMER0 + 0x34);  // 外设基地址
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;  // 关闭外设基地址自增
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;  // 外设宽度16位
    dma_init_struct.priority     = DMA_PRIORITY_ULTRA_HIGH;  // 优先级高
    dma_init(DMA0, DMA_CH4, &dma_init_struct);

    dma_circulation_enable(DMA0, DMA_CH4);  // 开启循环模式
    dma_memory_to_memory_disable(DMA0, DMA_CH4);  // 关闭内存到内存模式
    dma_channel_enable(DMA0, DMA_CH4);  // 使能DMA
    
    /* 初始化TIMER0 */
    timer_oc_parameter_struct timer_ocintpara = {0};
    timer_parameter_struct timer_initpara = {0};

    rcu_periph_clock_enable(RCU_TIMER0);

    timer_deinit(TIMER0);

    timer_initpara.prescaler         = (108 - 1);  // 预分频:108MHz / 108 = 1MHz
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;  // 边沿对齐计数
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;  // 向上计数
    timer_initpara.period            = (10000 - 1);  // 周期:1MHz / 10000 = 100Hz
    timer_initpara.repetitioncounter = (100 - 1);  // 重复计数器,每100个周期更新一次,即每1秒更新一次
    timer_init(TIMER0, &timer_initpara);

    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;  // 使能输出比较
    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;  // 输出极性为高
    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_HIGH;  // 空闲电平高
    timer_channel_output_config(TIMER0, TIMER_CH_0, &timer_ocintpara);

    timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, buffer[0]);  // 设置输出比较值
    timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0);  // 设置为PWM模式0
    timer_channel_output_shadow_config(TIMER0, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE);  // 关闭输出比较影子
    timer_primary_output_config(TIMER0, ENABLE);  // 使能所有通道
    timer_dma_enable(TIMER0, TIMER_DMA_UPD);  // 使能更新事件DMA请求
    timer_auto_reload_shadow_enable(TIMER0);  // 使能自动重装载影子
    timer_enable(TIMER0);  // 使能定时器
    
    /* 初始化TIMER2 */
    timer_ic_parameter_struct timer_icinitpara = {0};
    
    rcu_periph_clock_enable(RCU_TIMER2);

    timer_deinit(TIMER2);

    timer_initpara.prescaler         = (108 - 1);  // 预分频:108MHz / 108 = 1MHz
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;  // 边沿对齐计数
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;  // 向上计数模式
    timer_initpara.period            = (65536 - 1);  // 周期最好设置为最高,以免计数器溢出
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;  // 输入时钟1分频
    timer_init(TIMER2,&timer_initpara);

    timer_icinitpara.icpolarity  = TIMER_IC_POLARITY_RISING;  // 输入极性为上升沿,即上升沿有效
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;  // 输入捕获通道连接至CIx
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;  // 时钟1分频
    timer_icinitpara.icfilter    = 0x0;
    timer_input_pwm_capture_config(TIMER2, TIMER_CH_0, &timer_icinitpara);

    timer_input_trigger_source_select(TIMER2, TIMER_SMCFG_TRGSEL_CI0FE0);  // 输入触发源为通道0
    timer_slave_mode_select(TIMER2, TIMER_SLAVE_MODE_RESTART);  // 从模式选择为复位模式
    timer_master_slave_mode_config(TIMER2, TIMER_MASTER_SLAVE_MODE_ENABLE);  // 使能从模式

    timer_auto_reload_shadow_enable(TIMER2);  // 使能重装载影子
    
    nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);  // 抢占优先级4位,响应优先级0位
    nvic_irq_enable(TIMER2_IRQn, 1, 0);  // 使能中断服务,抢占优先级为1
    
    timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH0);  // 清除通道0中断标志位
    timer_interrupt_enable(TIMER2, TIMER_INT_CH0);  // 使能通道0中断

    timer_enable(TIMER2);  // 使能定时器2
}

gd32f10x_it.c文件

void TIMER2_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER2, TIMER_INT_CH0))
    {
        timer_interrupt_flag_clear(TIMER2, TIMER_INT_CH0);  // 清除中断标志位
        
        ic1value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_0) + 1;

        if(0 != ic1value)
        {
            ic2value = timer_channel_capture_value_register_read(TIMER2, TIMER_CH_1) + 1;

            dutycycle = (ic2value * 100) / ic1value;  // 计算占空比
            frequency = (float)1000000 / ic1value;  // 计算频率
        }
        else
        {
            dutycycle = 0;
            frequency = 0;
        }
    }
}

main.c文件

#include "gd32f10x.h"
#include "main.h"
#include "systick.h"
#include "usart.h"
#include "timer.h"
#include <stdio.h>
#include <string.h>

extern __IO uint16_t dutycycle;
extern __IO uint16_t frequency;

int main(void)
{
    systick_config();
    USART_Config();
    TIM_DmaInit();

    while(1)
    {
        printf("dutycycle: %d%%, frequency: %dHz\n", dutycycle, frequency);
        delay_ms(500);
    }
}

在这里插入图片描述

  • 13
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
以下是使用定时器9和DMA输出PWM脉冲的示例代码: ```c #include "gd32f4xx.h" #define TIMER9_PERIOD 999 // PWM周期,单位为计数值 #define PWM_DUTY_CYCLE 500 // PWM占空比,单位为计数值 uint16_t pwm_data = PWM_DUTY_CYCLE; // PWM脉冲数据 void timer9_init(void) { rcu_periph_clock_enable(RCU_TIMER9); // 使能定时器9时钟 timer_deinit(TIMER9); // 复位定时器9 timer_oc_parameter_struct timer_ocinitpara; timer_oc_struct_para_init(&timer_ocinitpara); timer_ocinitpara.oc_mode = TIMER_OC_MODE_PWM0; // PWM模式0 timer_ocinitpara.oc_polarity = TIMER_OC_POLARITY_HIGH; // 输出极性为高 timer_ocinitpara.oc_pulse = PWM_DUTY_CYCLE; // PWM脉冲宽度 timer_output_channel_config(TIMER9, TIMER_CH_0, &timer_ocinitpara); // 配置TIM9_CH0为PWM输出 timer_auto_reload_value_config(TIMER9, TIMER9_PERIOD); // 配置PWM周期 timer_enable(TIMER9); // 启动定时器9 } void dma_init(void) { rcu_periph_clock_enable(RCU_DMA0); // 使能DMA时钟 dma_deinit(DMA0, DMA_CH3); // 复位DMA0_CH3 dma_parameter_struct dma_init_struct; dma_struct_para_init(&dma_init_struct); dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; // 存储器到外设 dma_init_struct.memory_addr = (uint32_t)&pwm_data; // 存储器地址为pwm_data dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 存储器地址自增 dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; // 存储器数据宽度为16位 dma_init_struct.number = 1; // DMA传输次数为1 dma_init_struct.periph_addr = (uint32_t)&TIMER_CAR(TIMER9); // 外设地址为定时器9的自动重载寄存器地址 dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 外设地址不自增 dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; // 外设数据宽度为16位 dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; // DMA优先级为最高 dma_single_data_parameter_config(DMA0, DMA_CH3, &dma_init_struct); // 配置DMA0_CH3 dma_channel_enable(DMA0, DMA_CH3); // 启动DMA0_CH3 } int main(void) { timer9_init(); // 初始化定时器9 dma_init(); // 初始化DMA while(1); } ``` 在以上代码中,我们使用了定时器9和DMA实现了输出PWM脉冲。具体实现步骤如下: 1. 初始化定时器9:启用定时器9时钟,复位定时器9,配置TIM9_CH0为PWM输出,配置PWM周期为TIMER9_PERIOD,启动定时器9。 2. 初始化DMA:启用DMA时钟,复位DMA0_CH3,配置DMA传输方向为存储器到外设,存储器地址为pwm_data,存储器地址自增,存储器数据宽度为16位,DMA传输次数为1,外设地址为定时器9的自动重载寄存器地址,外设地址不自增,外设数据宽度为16位,DMA优先级为最高,配置DMA0_CH3,启动DMA0_CH3。 在实际应用中,我们可以通过修改pwm_data的值来改变PWM脉冲的占空比,从而实现PWM输出的调节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马浩同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值