Timer13是一个L2级别定时器,它的功能简单,非常适合定时器入门学习。其特性如下:
Timer13的时钟源只能是来自RCU模块的内部时钟。结合下图GD32F130的时钟树和GD32F130的固件库时钟设置所知,CK_AHB为48MHz,且APB1预分频器系数为1,则Timer13的外设时钟为48MHz。由于gd32f130可以跑到72MHz,因此后文都以72MHz主频讲解。
//文件:system_gd32f1x0.c
//固件版本V3.3.3
static void system_clock_48m_hxtal(void)
{
//......
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV1; //APB1分频系数为1
//......
}
Timer13的计数器是一个16位的向上计数型计数器,在计数时钟(PSC_CLK)的驱动下,从0开始向上计数。TIMERx_CNT存储着计数器的当前计数值,此寄存器低16位有效,可被用户读写。当TIMERx_CNT的值与TIMERx_CAR自动重载寄存器相等时,就会产生上溢出事件,此时计数器TIMERx_CNT的值自动归零,并开始一个新的计数周期。
通过配置CTL0寄存器的ARSE来选择是否启用TIMERx_CAR寄存器的影子寄存器:
An update event is issued when the counter underflows or overflows and a new period starts. It triggers an
interrupt or DMA request that is used for adjusting timer
parameters synchronously with its period, which is useful
for real-time control. This update event triggers the
transfer from preload to active registers for multiple
parameters, and in particular for the clock prescaler, auto-
reload value, compare registers and PWM mode.——《stm32L4 Timers手册》更新事件是当计数器上溢或者下溢而开始新的计数周期时触发的。更新事件可以触发DMA请求,以用于在下一个计数周期开始时及时更新定时器的运行参数,特别适用于实时控制。更新事件可以导致预装载寄存器的值更新转移到它对应的活动寄存器中去,例如预分频寄存器,自动重载寄存器等
TIMERx_CTL0寄存器的UPS位用来选择更新事件触发源,即哪些情况可以产生更新事件。
通过设置寄存器TIMERx_CTL0的UPDIS位,来选择是否允许产生更新事件。一般我们都会允许更新事件产生。
下面代码使用Timer13的时基单元,每隔50ms产生一次更新事件中断,在中断中翻转LED,实现LED闪烁。
#include "gd32f1x0.h"
void NVIC_config(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
nvic_irq_enable(TIMER13_IRQn,1,0); //使能Timer13的中断
}
void RCU_config(void)
{
rcu_periph_clock_enable(RCU_GPIOC); //打开GPIOC外设时钟
rcu_periph_clock_enable(RCU_TIMER13); //打开TIMER13外设时钟
}
void GPIO_config(void)
{
//配置PC13推挽输出
gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_13);
}
/*
TIMER13外设时钟=72MHz
配置Timer13每计数50000次,即计数周期为50ms产生一次溢出事件中断
*/
void TIMER13_config(void)
{
//===复位TIMER13===
timer_deinit(TIMER13);
//===时基单元配置===
timer_parameter_struct tps; //定义时基单元初始化结构体
tps.prescaler = 72-1; //设置预分频系数,TIMER_PSC寄存器
tps.period = 50000-1; //设置自动重载寄存器,TIMER_CAR寄存器
tps.repetitioncounter = 0; //重复计数寄存器,TIMER_CREP寄存器 #####Timer13无此设置
tps.alignedmode = TIMER_COUNTER_EDGE; //CNT计数对齐模式,TIMERx_CTL0.CAM #####Timer13无此设置
tps.counterdirection = TIMER_COUNTER_UP; //CNT寄存器计数方向,TIMERx_CTL0.DIR #####Timer13无此设置
tps.clockdivision = TIMER_CKDIV_DIV1; //设置CTL0.CKDIV位段,TIMERx_CTL0.CKDIV
timer_init(TIMER13,&tps); //初始化定时器
timer_counter_value_config(TIMER13,0); //TIMER_CNT寄存器
timer_auto_reload_shadow_enable(TIMER13); //TIMERx_CTL0.ARSE=1,使能自动重装寄存器的影子寄存器
timer_update_source_config(TIMER13,TIMER_UPDATE_SRC_GLOBAL); //TIMERx_CTL0.UPS=0,选择产生更新事件的触发源
timer_update_event_enable(TIMER13); //TIMERx_CTL0.UPDIS=0,使能更新事件的产生
//===中断配置===
timer_interrupt_enable(TIMER13,TIMER_INT_UP); //TIMERx_DMAINTEN.UPIE=1,使能更新事件中断
//===使能定时器===
timer_enable(TIMER13); //TIMERx_CTL0.CEN=1,产生PSC计数时钟
}
int main(void)
{
RCU_config();
NVIC_config();
GPIO_config();
TIMER13_config();
while(1)
{
}
}
void TIMER13_IRQHandler(void)
{
if(timer_interrupt_flag_get(TIMER13,TIMER_INT_FLAG_UP)) //如果发生了更新事件中断
{
if(gpio_output_bit_get(GPIOC,GPIO_PIN_13)) //翻转LED
gpio_bit_reset(GPIOC,GPIO_PIN_13);
else
gpio_bit_set(GPIOC,GPIO_PIN_13);
timer_interrupt_flag_clear(TIMER13,TIMER_FLAG_UP); //清除更新事件中断标志
}
}
时基单元是定时器外设的核心,但让定时器强大的却是它的各个通道。定时器的所有的通道相互独立,但共用一个时基单元。任何一个通道可以被配置成不同的功能,包括输入捕获,PWM输出等。不同的定时器支持的通道数不同。
输入捕获模式可以捕捉外部信号的边沿,主要用于测量外部信号的频率,脉宽等。当捕获事件发生时,时基单元的计数器TIMERx_CNT的值会拷贝锁存到通道0的捕获寄存器TIMERx_CH0CV中。
注:Timer13由于不支持DMA方式,外部脉冲频率如果太高会导致CPU频繁中断,效率低下,所以实际开发时,不建议使用它工作在输入捕获模式。
下图是Timer13的通道0的输入捕获模式的内部结构。图中CI1FE0是来自通道1的,但是TIMER13没有通道1,所以忽略。
通道0的输入信号可能会存在噪声,通过数字滤波器可以滤除噪声,达到精确测量输入信号频率的效果。
TIMERx_CHCTL0[7:4] = CH0CAPFLT[3:0] ,用于设置数字滤波器的工作参数,其值和定义如下:
如何解释呢?数字滤波器需要确定2个参数:fSAMP采样时钟和N采样计数值。假设fSAMP=8MHz,N=4,则fSAMP / N = 2MHz,意思是高于2MHz的信号成分将被滤除,永远不会被边沿检测器检测到。有些应用场合下,我们可以确定输入的信号绝不会高于某个阈值频率,则可以通过数字滤波器将高于这个阈值的频率滤除,这个特性对于减少误差非常重要。其中fDTS是通过TIMERx_CTL0寄存器的CKDIV位段设置的,它是定时器外设时钟TIMER_CK的分频后的频率。
TIMERx_CHCTL2寄存器中的CH0NP位和CH0P位组合起来配置捕获的目标沿。由于Timer13不支持互补输出,因此CH0NP的作用仅仅是与CH0P结合起来配置输入捕获模式时捕获的目标沿,在输出模式下无用。当引脚上出现一次目标沿时,发生通道0的输入捕获事件。
通过寄存器TIMERx_CHCTL0的CH0MS位段来配置触发信号选择。为了捕获通道0引脚上输入的信号,则应该设置CH0MS[1:0]=01,此时且TIMERx_CH0CV寄存器只读,它锁存了每次捕获事件时计数器CNT的值。通道0引脚的GPIO应该配置为复用输入模式。
通过设置捕获事件预分频器,可以配置每1次,2次,4次或者8次目标沿触发一次捕获事件,这样设计的作用是,当输入的信号频率过高时,降低触发中断频率,从而降低CPU的处理负担。
TIMERx_CHCTL0[3:2]=CH0CAPPSC[1:0],用于设置预分频值。
在输入捕获模式下,如果在CH0IF标志位为1的情况下发生捕获事件,代表上一次的捕获事件没来得及处理就又发生了一次捕获事件,这种情况就会触发过捕获事件,CH0OF标志位将硬件置1,此标志需要软件清0。过捕获事件没有触发中断的能力。
测量外部脉冲宽度或者频率:例如,外部输入一个方波信号,配置通道0捕获外部输入的上升沿。
实现外部中断:由于输入捕获模式带有边沿检测器,所以他可以实现外部中断检测。用捕获模式实现外部中断检测有个非常明显的好处,就是可以支持内部硬件消抖。首先可以通过数字滤波器预处理消抖,再就是通过捕获预分频器进一步消抖。另外,如果是接的外部按键,还可以非常方便的支持单击,双击的区分检测。
比较输出模式下,需设置CH0MS[1:0]=00,这个时候通道0为输出模式,此外还要将通道0的GPIO配置成复用输出模式。
当计数器的值与CHxCV寄存器的值匹配时,CHxIF位被置1,如果CHxIE = 1则会产生中断,如果CHxDEN=1则会产生DMA请求。
TIMERx_CHCTL0[6:4] = CH0COMCTL[2:0],这个位段决定了TIMERx_CH0CV与CNT寄存器的比较结果如何影响中间信号O0CPRE。中间信号有两种状态:有效和无效,这个状态取决于比较器的比较结果。
其实我们并不能说中间信号O0CPRE它是高电平还是低电平,因为它只是一个信号,而不是最终的输出,我们只能谈它是有效状态还是无效状态。寄存器TIMERx_CHCTL2的CH0P位决定了通道极性选择器得到有效信号时,通道引脚输出高电平还是低电平。
例如,当中间信号O0CPRE为有效信号时,这个有效信号给到通道极性选择器这里,如果我们配置CH0P=0,则将导致通道输出高电平。
由于Timer13不支持互补输出,因此CH0NP的作用仅仅是与CH0P结合起来配置输入捕获模式时捕获的目标沿,在输出模式下无用。
用来配置是否启用TIMERx_CH0CV的影子寄存器。如果启用,则在每次更新事件发生时,TIMERx_CH0CV影子寄存器的值会被更新为TIMERx_CH0CV寄存器的的。
此设置只有在PWM0或PWM1模式下才起作用。
扩展定时器
可以将通道本身当成一个定时器使用,这种情况下需要配置CH0COMCTL=000,此时通道0的引脚不再被通道0使用,可以当做GPIO等其他用法。
当TIMERx_CH0CV保持不变时,其定时周期与TIMERx_CH0CV寄存器值无关,与时基单元的定时周期相同。如果要实现定时周期小于(不同于)时基单元的定时周期,则需要每次通道0的比较中断中,给TIMERx_CH0CV步进一个固定的值。
例如时基单元计数器CNT计数频率为100KHz,其CAR寄存器为10000,即CNT最大计数到10000时就触发溢出事件,更新回零。如果TIMERx_CH0CV固定不变,那么除了第一次外,CNT每计数10000次后都会和TIMERx_CH0CV相等,触发一次输出比较事件,这样的话,相邻的两次输出比较事件中断都是10000*0.01ms=100ms,与时基单元的定时周期一致。如果想要定时周期为20ms怎么办呢?需要每次比较事件中断中,修改TIMERx_CH0CV的值,让CNT再计数2000次后才等于TIMERx_CH0CV,所以应该修改:TIMER_CH0CV= (TIMER_CH0CV+2000)%10000;
方波信号输出
这种情况下需要配置CH0COMCTL=011,即匹配时翻转通道0的引脚电平。如果TIMERx_CH0CV保持不变,则生成的波形的周期时间是时基单元溢出周期的两倍,且占空比为50%。
波形的初始相位是可以调整的,由TIMERx_CH0CV的值确定。 如下图是2个相位不同,频率和占空比相同的波形。
PWM模式是比较输出模式中的一种。需要将CH0COMCTL[2:0]配置为110或者111即可。
TIMERx_CHCTL0[6:4] = CH0COMCTL[2:0],这个位段决定了TIMERx_CH0CV与CNT寄存器的比较结果如何影响中间信号O0CPRE。中间信号有两种状态:有效和无效,这个状态取决于比较器的比较结果。
在PWM模式中,PWM频率就是时基单元的CNT溢出频率,由CAR寄存器决定 。而PWM的占空比则是由通道捕获比较寄存器CHxCV与时基单元的重载寄存器CAR的比值决定的。
下面代码实现了使用Timer13的通道0工作在PWM0模式,使得PA4引脚输出频率为20KHz,占空比为25%的PWM信号。通过改变CH0CV的值可以改变PWM的占空比。
#include "gd32f1x0.h"
#include
void NVIC_config(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
//nvic_irq_enable(TIMER13_IRQn,1,0); //使能Timer13的中断
}
void RCU_config(void)
{
rcu_periph_clock_enable(RCU_GPIOA); //打开GPIOA外设时钟
rcu_periph_clock_enable(RCU_GPIOC); //打开GPIOC外设时钟
rcu_periph_clock_enable(RCU_TIMER13); //打开TIMER13外设时钟
}
void GPIO_config(void)
{
//配置PC13推挽输出
//gpio_mode_set(GPIOC, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_13);
//gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_13);
//PA4-TIMER13_CH0-AF4
gpio_output_options_set(GPIOA,GPIO_OTYPE_PP,GPIO_OSPEED_10MHZ,GPIO_PIN_4);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_4);
gpio_af_set(GPIOA,GPIO_AF_4,GPIO_PIN_4);
}
/*Timer13外设时钟=72M
配置PSC=9-1,则分频系数为9,则计数器时钟为8MHz。
配置CAR=400-1,则PWM频率就是计数器溢出频率,则PWM频率为20KHz。
配置PWM模式0,CH0P=0,则CNT小于CH0CV时输出高电平,否则输出低电平
配置CH0CV=100,则PWM占空比为 100/400 = 25%
*/
void TIMER13_config(void)
{
//===复位TIMER13===
timer_deinit(TIMER13);
//===时基单元配置===
timer_parameter_struct tps; //定义时基单元初始化结构体
tps.prescaler = 9-1; //设置预分频系数,TIMER_PSC寄存器
tps.period = 400-1; //设置自动重载寄存器,TIMER_CAR寄存器
tps.repetitioncounter = 0; //重复计数寄存器,TIMER_CREP寄存器 #####Timer13无此设置
tps.alignedmode = TIMER_COUNTER_EDGE; //CNT计数对齐模式,TIMERx_CTL0.CAM #####Timer13无此设置
tps.counterdirection = TIMER_COUNTER_UP; //CNT寄存器计数方向,TIMERx_CTL0.DIR #####Timer13无此设置
tps.clockdivision = TIMER_CKDIV_DIV1; //设置CTL0.CKDIV位段,TIMERx_CTL0.CKDIV
timer_init(TIMER13,&tps); //初始化定时器
timer_counter_value_config(TIMER13,0); //TIMER_CNT寄存器
timer_auto_reload_shadow_enable(TIMER13); //TIMERx_CTL0.ARSE=1,使能自动重装寄存器的影子寄存器
timer_update_source_config(TIMER13,TIMER_UPDATE_SRC_GLOBAL); //TIMERx_CTL0.UPS=0,选择产生更新事件的触发源
timer_update_event_enable(TIMER13); //TIMERx_CTL0.UPDIS=0,使能更新事件的产生
//===通道0配置===
timer_oc_parameter_struct tocps; //定义通道0初始化结构体
tocps.outputstate = TIMER_CCX_ENABLE; //通道使能,TIMERx_CHCTL2.CH0EN
tocps.outputnstate = TIMER_CCXN_DISABLE; //互补输出通道使能,TIMERx_CHCTL2.CH0NEN #####Timer13无此设置
tocps.ocpolarity = TIMER_OC_POLARITY_HIGH; //输出极性,TIMER_CHCTL2.CH0P
tocps.ocnpolarity = TIMER_OCN_POLARITY_HIGH; //互补输出的极性,TIMER_CHCTL2.CH0NP
tocps.ocidlestate = TIMER_OC_IDLE_STATE_LOW; //通道 0 的空闲状态输出,TIMERx_CTL1.ISO0 #####Timer13无此设置
tocps.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; //通道 0 的互补通道空闲状态输出,TIMERx_CTL1.ISO0N #####Timer13无此设置
timer_channel_output_config(TIMER13,TIMER_CH_0,&tocps); //初始化并配置通道0为输出,TIMERx_CHCTL0.CH0MS[1:0]=00
//TIMERx_CHCTL0寄存器配置
timer_channel_output_mode_config(TIMER13,TIMER_CH_0,TIMER_OC_MODE_PWM0); //TIMERx_CHCTL0.CH0COMCTL[2:0]
timer_channel_output_shadow_config(TIMER13,TIMER_CH_0,TIMER_OC_SHADOW_ENABLE); //TIMERx_CHCTL0.CH0COMSEN
timer_channel_output_fast_config(TIMER13,TIMER_CH_0,TIMER_OC_FAST_ENABLE); //TIMERx_CHCTL0.CH0COMFEN
//TIMERx_CH0CV(比较/捕获寄存器)设置
timer_channel_output_pulse_value_config(TIMER13,TIMER_CH_0,100);
//===中断配置===
//timer_interrupt_enable(TIMER13,TIMER_INT_UP); //TIMERx_DMAINTEN.UPIE=1,使能更新事件中断
//timer_interrupt_enable(TIMER13,TIMER_INT_CH0); //TIMERx_DMAINTEN.CH0IE=1,使能通道0的比较捕获事件中断
//===使能定时器===
timer_enable(TIMER13); //TIMERx_CTL0.CEN=1,产生PSC计数时钟
}
int main(void)
{
NVIC_config();
RCU_config();
GPIO_config();
TIMER13_config();
while(1)
{
}
}