【GD32】从0开始学GD32单片机(11)—— TIMER通用定时器详解+PWM波形输出捕获例程

简介

在我的上一篇文章讲了基本定时器的用法和内部结构,点击下面的链接可以回顾一下。
文章链接:TIMER基本定时器详解+1毫秒延时例程

而这里将继续深入定时器,讲一讲通用定时器。
下面是GD32各个定时器的差异表。

在这里插入图片描述

通用定时器比较特别,它们之间还分了3个不同的版本——L0、L1和L2。大致的区别在于捕获\比较通道数、单脉冲模式支持、正交译码器支持、从设备控制器支持、内部连接支持、DMA支持
下面就以功能最全面的L0通用定时器为例,详细讲一讲。

在这里插入图片描述

计数模式

通用定时器支持向上计数、向下计数和中央对齐,3种计数模式。
向上计数和向下计数比较简单就不多赘述了,下面详细讲中央对齐模式。

在这里插入图片描述

定时器开始向上计数,当数值达到重装载值时定时器产生上溢,这时发出更新事件\中断(若用户使能了相关的寄存器);但这时定时器不再从0开始计数,而是开始向下计数,直到数值减到0时,定时器产生下溢,同时也发出更新事件\中断;接着定时器又重复一开始的步骤,开始从0开始向上计数。

捕获和比较通道

输入捕获模式

下面是捕获通道的系统结构框图。

在这里插入图片描述
捕获模式允许我们测量一个波形时序,频率,周期,占空比等。输入级包括一个数字滤波器,一个通道极性选择,边沿检测和一个通道预分频器。如果在输入引脚上出现被选择的边沿,定时器会捕获计数器当前的值,同时对应的标志位位被置1,如果使能了相关的中断寄存器,则产生通道中断。

以下是配置定时器输入捕获通道的简要步骤:

  1. 配置滤波器,根据输入信号和请求信号的质量,配置相应的寄存器;
  2. 配置通道极性,选择上升沿或者下降沿;
  3. 配置捕获源,一旦通过配置选择输入捕获源,必确保通道配置在输入模式;
  4. 根据需要使能相应中断,可以获得中断和DMA请求;
  5. 使能捕获;
  6. 当期望的输入信号发生时,捕获\比较值寄存器被设置成当前计数器的值,CHxIF置1;如果CHxIF位已经为1,则CHxOF位置1;若配置了相应的寄存器,相应的中断和DMA请求会被提出。

CHxIF,全称“Channel x Interrupt Flag”,为某一通道的中断标志位。
CHxOF,全称“Channel x Overflow Flag”,为某一通道的溢出标志位。

输出比较模式

下面是输出比较模式的系统框图。

在这里插入图片描述

在输出比较模式,定时器可以产生时控脉冲,其位置,极性,持续时间和频率都是可编程的。当一个输出通道的比较值寄存器与计数器的值匹配时,根据用户的配置,这个通道的
输出可以被置高电平、被置低电平或者反转。当计数器的值与比较值寄存器的值匹配时,通道中断标志位被置1,如果使能了中断则会产生中断,如果使能了DMA则会产生DMA请求。

以下是配置定时器输出比较通道的简要步骤:

  1. 配置定时器时钟源,预分频器等;
  2. 配置比较模式,配置输出比较影子寄存器、输出模式(置高电平/置低电平/反转)和有效电平的极性
  3. 使能比较通道输出;
  4. 根据需要配置中断和DMA请求使能;
  5. 通过配置重装载寄存器和输出比较寄存器配置输出比较时基(用户可以在运行时修改输出比较寄存器的值,来改变输出波形);
  6. 使能定时器。

关于3个输出输出模式,下面的时序图可以使大家比较容易理解。

在这里插入图片描述
由上图可以看到,输出比较寄存器配置为2,当定时器计数到2时,触发输出。对于置高和置低模式,输出电平分别由低置高和由高置低,但当定时器继续运行,即使计数器再达到比较寄存器的值也不会产生电平变换;但对于翻转模式来说,输出通道的电平可以一直改变,每次计时器达到比较寄存器的值,管脚电平翻转一次。

PWM模式

一般来说,我们会将定时器的输出模式配合PWM模式来使用,来帮助我们输出不同的PWM波形。
在PWM模式下分有PWM模式0和PWM模式1,具体的区别只是在于输出的波形极性不同。
用户对定时器计数模式的不同选择也会导致输出PWM波形的不同,可以分为EAPWM(边沿对齐PWM)和CAPWM(中央对齐PWM)

边沿对齐PWM

下面是EAPWM的时序图。

在这里插入图片描述
以PWM模式0、向上计数为例,定时器输出通道开始时为高电平,当计数器的值大于输出比较寄存器的值时,输出通道变为低电平,直到计数器达到重装载寄存器的值,产生溢出并复位。PWM模式1的电平和PWM模式0是互补的,向上计数和向下计数的区别在于输出比较寄存器和重装载寄存器的比较方式是相反的。

中央对齐PWM

下面是CAPWM的时序图。

在这里插入图片描述

可以理解为中央对齐PWM是边沿对齐PWM的两种计数方式的结合体。当计数器的值小于输出比较寄存器的值时,通道输出为空闲电平;当计数器的值大于输出比较寄存器的值时,通道输出的是有效电平。

正交译码器

正交译码器主要应用在电机的控制上,电机有两个输出脉冲——A信号和B信号,通过正交编码器识别这两个信号的变化,通过计算我们可以算出电机的旋转位置、速度等物理参数。

但对于正交编码器的工作我并不是很了解,也没有接触相应的项目,所以下面只是简单介绍而已。等以后上手电机控制的相关项目,再单独出一篇文章来详细讲一讲正交编码器的工作原理,包括如何计算得出电机的实时位置、速度等物理量。

通俗来说,定时器中的正交编码器是通过识别CI0、CI1正交输入捕获信号的变化来改变定时器的计数方向
下面给出了一张表来表示计数方向与编码器信号之间的关系。

在这里插入图片描述

下面的时序图表示了CI0和CI1两个正交信号捕获通道中脉冲值的变化是如何改变定时器的计数方向的。

在这里插入图片描述

从控制器

定时器的从控制器模式下,定时器允许接收外部的触发,当外部触发来临时执行相关的操作
通用定时器的从控制器支持3种模式——复位模式、暂停模式和事件模式
用户可以调整触发信号的极性(上升沿触发或下降沿触发),但每个触发源之间会有轻微差别,具体看用户手册的介绍。

下面是复位模式下的时序图。

在这里插入图片描述

复位模式下,定时器外部触发(ITI0)有效,计数器的值将会复位至0。

由上面可以看到,当触发源(ITI0)发出有效信号时,定时器并不会立即响应(TRGIF),这一延迟为“内部同步延迟”。
下面的例子也是存在这种延迟。

下面是暂停模式的时序图。

在这里插入图片描述

暂停模式下,定时器外部触发(CI0FE0)有效,定时器将暂停计数,相当于关闭了定时器。

下面是事件模式的时序图。

在这里插入图片描述

事件模式下,定时器外部触发(ETIFP)有效,定时器将继续计数,相当于使能了定时器。

例程

在这个例程中,我们使能定时器1,配置其为PWM输出,使能定时器2,配置为输入捕获,使用串口每秒输出PWM波的周期和占空比。

这个例程还是比较复杂的,所以详细解释一下是如何配置的。
首先是负责输出PWM波的定时器1,设置为边沿PWM模式0,周期根据自己需求设置就好,使能定时器通道1输出。输出PWM波比较简单,难的是如何捕获并计算它的周期和占空比。
接着是负责捕获PWM波的定时器2,周期最好设置为最高值(65535),这样是为了避免计数时计数器溢出而导致数据不正确。配置输入捕获为定时器通道0,通道极性为上升沿,即在遇到上升沿时,定时器会捕获计数器的值,同时我们设置了中断,所以定时器一旦捕获,我们就可以在中断服务函数中开始计算对应的周期和频率。接着配置从模式,设置为复位模式,这样一旦发生捕获,计数器的值就会归零,重新开始计数

timer.c文件

void TIM_PwmInit(void)
{
    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_1);
    
    /* PWM输入管脚为浮空输入模式 */
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
    
    /* TIMER1初始化 */
    timer_oc_parameter_struct timer_ocintpara = {0};
    timer_parameter_struct timer_initpara = {0};

    rcu_periph_clock_enable(RCU_TIMER1);

    timer_deinit(TIMER1);

    timer_initpara.prescaler         = (108 - 1);  // 预分频:108MHz / 108 = 1MHz
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;  // 边沿对齐计数
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;  // 向上计数
    timer_initpara.period            = (16000 - 1);  // 周期:1MHz / 16000 = 62.5Hz
    timer_init(TIMER1, &timer_initpara);

    /* 配置所有通道为PWM模式0 */
    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;  // 通道输出极性高,即高电平有效
    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;  // 使能输出通道
    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;  // 输出通道空闲低电平

    timer_channel_output_config(TIMER1,TIMER_CH_1, &timer_ocintpara);

    /* 配置通道1为50%占空比 */
    timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, (8000 - 1));  // 8000 / 16000 = 50%
    timer_channel_output_mode_config(TIMER1,TIMER_CH_1, TIMER_OC_MODE_PWM0);  // 配置为PWM模式0
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);  // 关闭输出影子

    timer_auto_reload_shadow_enable(TIMER1);  // 使能重装载影子
    timer_enable(TIMER1);  // 使能定时器1

    /* 初始化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文件

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

uint32_t ic1value = 0, ic2value = 0;
__IO uint16_t dutycycle = 0;
__IO uint16_t frequency = 0;

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_PwmInit();

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

将定时器1的输出通道1管脚(PA1)与定时器2的输入通道0管脚(PA6)相连,并下载程序,打开串口就能看到下面的结果了。

在这里插入图片描述

  • 8
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
要配置GD32E230的PWM定时器0和GPIOF输出,可以按照以下步骤进行操作: 1. 首先,确保你已经熟悉GD32E230的寄存器和引脚功能定义。 2. 配置GPIOF引脚为输出模式。你可以使用GPIO_Init函数来完成这个任务。下面是一个示例代码片段: ```c rcu_periph_clock_enable(RCU_GPIOF); gpio_init(GPIOF, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0); ``` 3. 配置PWM定时器0。你需要使用以下寄存器来配置定时器: - TIMx_PSC:设置预分频值,决定定时器时钟频率。 - TIMx_ARR:设置自动重载值,决定定时器的周期。 - TIMx_CCRx:设置通道x的占空比。 下面是一个示例代码片段,演示如何配置PWM定时器0: ```c rcu_periph_clock_enable(RCU_TIMER0); timer_deinit(TIMER0); timer_prescaler_config(TIMER0, 7199); // 设置预分频值,时钟频率为72MHz/(7199+1) = 10kHz timer_autoreload_value_config(TIMER0, 999); // 设置自动重载值,周期为(999+1)/10kHz = 100ms timer_channel_output_pulse_value_config(TIMER0, TIMER_CH_0, 500); // 设置通道0的占空比为(500+1)/1000 = 50% timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0); // 设置通道0的PWM模式 timer_channel_output_shadow_config(TIMER0, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE); // 禁用通道0的PWM输出影子寄存器 timer_primary_output_config(TIMER0, ENABLE); // 启用定时器的主输出 timer_auto_reload_shadow_enable(TIMER0); // 启用自动重载值的影子寄存器 timer_enable(TIMER0); // 启动定时器 ``` 注意:以上代码片段仅供参考,实际使用时需要根据你的具体需求进行修改。另外,还需要根据实际情况配置其他GPIO引脚和定时器通道。 希望以上信息能对你有所帮助!如有更多问题,请继续提问。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

马浩同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值