跳到主要内容

【十六】定时器灯闪烁

1.1.配置流程

一般使用定时器功能,都需要有以下几个步骤

  • 开启时钟(定时器时钟)
  • 配置定时器参数
  • 配置中断优先级
  • 使能中断事件和定时器
  • 编写中断服务函数

GD32F450ZGT6单片机一共有14个定时器,包括高级定时器、通用定时器和基本定时器。不同类型的定时器功能不同。这一章就以LED灯1s闪烁为实验内容进行讲解。 实现定时1秒钟进入中断,只需要实现基本的定时功能,GD32的14个定时器都可以使用,这里就以基本定时器5为例进行介绍

1.1.1.开启时钟

先来看一下定时器的时钟来源,在数据手册的第18页,如图1-1-1所示

1

从图1-1-1可以看到14个定时器的时钟来源主要分为两部分,第一部分来源于CK_APB1,第二部分来源于CK_APB2。 然后经过时钟配置寄存器(RCU_CFG1)决定是APB频率的2倍还是4倍,但这个频率不能超过AHB(max = 200MHZ)

这里使用TIMER5,就要先使能TIMER5的时钟,又因为TIMER5时钟来源于CK_APB1,CK_APB1的时钟在system_gd32f4xx.c中定义,如图1-1-2所示。 从图1-1-2可以看到APB1的时钟等于AHB的时钟4分频,AHB的时钟等于系统时钟SYSCLK。系统时钟定义如图1-1-3所示。 从图1-1-3可以看到系统时钟等于__SYSTEM_CLOCK_200M_PLL_25M_HXTAL,跳转到这个宏定义可以得知,这个宏定义的值就是(uint32_t)(200000000), 可见系统时钟为200MHZ。回过来看CK_APB1的时钟为200MHZ的4分频等于50MHZ。要设置定时器的时钟为200MHZ,从时钟树上可以看到还需要进行4倍频处理, 在代码里面还需要配置为4倍频

1
1

首先编写TIMER5时钟的宏定义:

#define  BSP_TIMER_RCU    RCU_TIMER5  // 定时器时钟

开启定时器时钟:

/* 开启时钟 */rcu_periph_clock_enable(BSP_TIMER_RCU); // 开启定时器时钟

然后配置定时器时钟4倍频。在 gd32f4xx_rcu.h 中寻找相关函数

void rcu_timer_clock_prescaler_config(uint32_t timer_clock_prescaler);

这个函数是配置定时器时钟。有一个参数是要配置时钟的倍频系数。可选项如图1-1-4所示

1

定时器配置为200MHZ,要进行4倍频,顾选择RCU_TIMER_PSC_MUL4

配置定时器时钟为200MHZ:

    /* CK_TIMERx = 4 x CK_APB1  = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟

1.1.2.配置定时器参数

开启时钟之后,需要配置一些定时器的参数,比如定时时间,计数模式等

关于基本定时器的结构框图如图1-2-1所示

1

宏定义TIMER5:

#define    BSP_TIMER   TIMER5   // 定时器

第一步先要复位定时器外设

timer_deinit(BSP_TIMER); // 复位定时器

然后在 gd32f4xx_time.h 中有

void timer_init(uint32_t timer_periph, timer_parameter_struct* initpara);

这个函数是定时器初始化,并且配置参数。有两个参数,第一个参数就是定时器外设,第二个参数就是一个定时器参数结构体,关于结构体的定义如图1-2-2所示

1

要初始化定时器就要先配置这个结构体,首先定义这个结构体

timer_parameter_struct timer_initpara; // 定义定时器结构体

然后配置结构体的参数

  • prescaler:这个参数是时钟的预分频值,是16位的,取值范围为1-65535。从图1-2-1可以看到,TIMER_CK经过预分频之后得到PSC_CLK。每经过一个PSC_CLK都会产生一个计数周期,prescaler参数将决定一个计数周期的时间。设预分频值为pre,则计数器时钟频率PSC_CLK=TIMER_CK / (pre + 1)
  • alignedmode:对齐模式暂没用到
  • counterdirection:计数模式,基本定时器只有向上计数模式,所以配置为TIMER_COUNTER_UP
  • period:周期值,是一个16位的计数器,最大值为65535,当计数器达到设置的周期数值(自动重装载寄存器)时数值清零,配合计数器时钟频率可以计算中断时间
  • clockdivision:时钟分频,在输入捕获的时候使用,定时器时钟频率与死区发生器和数字滤波器使用的采样频率之间的分频比
  • repetitioncounter:重复计数器(只有高级定时器有),取值范围为0-255,配置为x,就重复x+1次才进入中断

配置好参数之后,就可以初始化定时器了

关于定时器参数配置和初始化定时器代码如下:

/* 配置定时器参数 */
timer_initpara.prescaler = pre -1; // 时钟预分频值 0-65535 psc_clk = CK_TIMER / pre
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_initpara.period = per - 1; // 周期
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timer_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_TIMER,&timer_initpara); // 初始化定时器

注意这里有两个参数pre和per,这两个参数是决定定时时间的,为了使用更灵活,在调用函数的时候传递

函数原型:

void basic_timer_config(uint16_t pre,uint16_t per)

在主函数中调用,配置成1s进入一次中断:

basic_timer_config(20000,10000); // 定时时间 = 20000 / 200 * 10000us = 1s

/* 定时时间为:time = pre / 200 000 000 * per = 20000 / 200 000 000 * 10000 = 1s */

1.1.3.配置中断优先级

定时器的参数配置好之后,定时器基本就配置好了。不过我们需要在中断函数中去执行对应的功能,就要对中断进行操作。前面介绍过,如果要使用中断功能,就需要配置中断优先级。中断分组还继续沿用之前的配置

定义TIMER5中断:

#define  BSP_TIMER_IRQ  TIMER5_DAC_IRQn   // 定时器中断

这里要特别注意一下,TIMER5的中断和其它定时器有一点不同,TIMER5的中断和DAC的中断是在一起的,具体定义在 gd32f4xx.h 文件中,感兴趣的可以去查看一下

这个函数是使能定时器中断,有两个参数,第一个参数就是定时器外设,第二个参数是中断源的选择。关于第二个参数的可选选项如图1-4-1所示

1

从图1-4-1可以看到定时器中断有好多触发方式,这里选择第一个更新中断

/* 使能中断 */
timer_interrupt_enable(BSP_TIMER,TIMER_INT_UP); // 使能更新事件中断

到这里还有最后一步,使能定时器,

void timer_enable(uint32_t timer_periph);

这个函数使能定时器,有一个参数就是要使能的定时器外设,

使能定时器配置如下:

/* 使能定时器 */
timer_enable(BSP_TIMER);

1.1.5.编写中断服务函数

使能中断之后,如果定时时间到,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名,这个是固定的,在 startup_gd32f450_470.s 启动文件中有定义

宏定义为:

#define BSP_TIMER_IRQHandler TIMER5_DAC_IRQHandler // 定时器中断服务函数;

在中断处理函数里需要检测中断标志位是否被置位

FlagStatus timer_interrupt_flag_get(uint32_t timer_periph, uint32_t interrupt);

这个函数是获取中断标志位。有两个参数,第一个参数就是要检测的定时器外设,第二个参数就是触发的中断源。 有一个返回值FlagStatus,返回值的状态为SET和RESET。需要注意的是每次中断执行完毕之后都需要清除一下中断标志位等待下一次中断发生

中断服务函数编写代码如下:

void BSP_TIMER_IRQHandler(void){
/* 这里是定时器中断 /
if(timer_interrupt_flag_get(BSP_TIMER,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(BSP_TIMER,TIMER_INT_FLAG_UP); // 清除中断标志位
/ 执行操作 */
printf("BSP_TIMER_IRQHandler\r\n");
gpio_bit_toggle(BSP_LED2_PORT,BSP_LED2_PIN); // 翻转led
}
}

到此有关定时器中断的配置就完成了

1.2.举一反三

前面TIMER5的配置可以完成,那换成其它的定时器该怎么配置呢?

前面编写了很多的宏定义,这里换成TIMER2的定时器只需要修改宏定义为TIMER2即可

TIMER2的宏定义如下:

/* TIMER2 */
#define BSP_TIMER_RCU RCU_TIMER2 // 定时器时钟
#define BSP_TIMER TIMER2 // 定时器
#define BSP_TIMER_IRQ TIMER2_IRQn // 定时器中断
#define BSP_TIMER_IRQHandler TIMER2_IRQHandler // 定时器中断服务函数

替换掉这个宏定义之后可以直接编译使用

1.3.实验现象

关于这一章节的代码,在资源包/04软件资料/代码例程/里面的008定时器灯闪烁

烧写我们的代码之后,每隔1秒钟在串口助手上打印一次BSP_TIMER_IRQHandler,然后LED2会每隔1秒钟时间闪烁一下