在我的上一篇文章讲了基本定时器的用法和内部结构,点击下面的链接可以回顾一下。
文章链接:TIMER基本定时器详解+1毫秒延时例程
而这里将继续深入定时器,讲一讲通用定时器。
下面是GD32各个定时器的差异表。
通用定时器比较特别,它们之间还分了3个不同的版本——L0、L1和L2。大致的区别在于捕获\比较通道数、单脉冲模式支持、正交译码器支持、从设备控制器支持、内部连接支持、DMA支持。
下面就以功能最全面的L0通用定时器为例,详细讲一讲。
通用定时器支持向上计数、向下计数和中央对齐,3种计数模式。
向上计数和向下计数比较简单就不多赘述了,下面详细讲中央对齐模式。
定时器开始向上计数,当数值达到重装载值时定时器产生上溢,这时发出更新事件\中断(若用户使能了相关的寄存器);但这时定时器不再从0开始计数,而是开始向下计数,直到数值减到0时,定时器产生下溢,同时也发出更新事件\中断;接着定时器又重复一开始的步骤,开始从0开始向上计数。
下面是捕获通道的系统结构框图。
捕获模式允许我们测量一个波形时序,频率,周期,占空比等。输入级包括一个数字滤波器,一个通道极性选择,边沿检测和一个通道预分频器。如果在输入引脚上出现被选择的边沿,定时器会捕获计数器当前的值,同时对应的标志位位被置1,如果使能了相关的中断寄存器,则产生通道中断。
以下是配置定时器输入捕获通道的简要步骤:
CHxIF,全称“Channel x Interrupt Flag”,为某一通道的中断标志位。
CHxOF,全称“Channel x Overflow Flag”,为某一通道的溢出标志位。
下面是输出比较模式的系统框图。
在输出比较模式,定时器可以产生时控脉冲,其位置,极性,持续时间和频率都是可编程的。当一个输出通道的比较值寄存器与计数器的值匹配时,根据用户的配置,这个通道的
输出可以被置高电平、被置低电平或者反转。当计数器的值与比较值寄存器的值匹配时,通道中断标志位被置1,如果使能了中断则会产生中断,如果使能了DMA则会产生DMA请求。
以下是配置定时器输出比较通道的简要步骤:
关于3个输出输出模式,下面的时序图可以使大家比较容易理解。
由上图可以看到,输出比较寄存器配置为2,当定时器计数到2时,触发输出。对于置高和置低模式,输出电平分别由低置高和由高置低,但当定时器继续运行,即使计数器再达到比较寄存器的值也不会产生电平变换;但对于翻转模式来说,输出通道的电平可以一直改变,每次计时器达到比较寄存器的值,管脚电平翻转一次。
一般来说,我们会将定时器的输出模式配合PWM模式来使用,来帮助我们输出不同的PWM波形。
在PWM模式下分有PWM模式0和PWM模式1,具体的区别只是在于输出的波形极性不同。
用户对定时器计数模式的不同选择也会导致输出PWM波形的不同,可以分为EAPWM(边沿对齐PWM)和CAPWM(中央对齐PWM)。
下面是EAPWM的时序图。
以PWM模式0、向上计数为例,定时器输出通道开始时为高电平,当计数器的值大于输出比较寄存器的值时,输出通道变为低电平,直到计数器达到重装载寄存器的值,产生溢出并复位。PWM模式1的电平和PWM模式0是互补的,向上计数和向下计数的区别在于输出比较寄存器和重装载寄存器的比较方式是相反的。
下面是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
#include
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
#include
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)相连,并下载程序,打开串口就能看到下面的结果了。