十五、定时器灯闪烁
一般使用定时器功能,都需要有以下几个步骤。
- 开启时钟(定时器时钟)
- 配置定时器参数
- 配置中断优先级
- 使能中断事件和定时器
- 编写中断服务函数
GD32E230C8T6单片机一共有7个定时器,包括高级定时器、通用定时器和基本定时器。不同类型的定时器功能不同。这一章就以LED灯1s闪烁为实验内容进行讲解。实现定时1秒钟进入中断,只需要实现基本的定时功能,GD32的7个定时器都可以使用,这里就以基本定时器5为例进行介绍。
1.1.开启时钟
先来看一下定时器的时钟来源,在数据手册的第18页,如图1-1-1所示。
从图1-1-1可以看到7个定时器的时钟来源是CK_AHB。
这里使用TIMER5,就要先使能TIMER5的时钟,从图1-1-3可以看到系统时钟等于
__SYSTEM_CLOCK_72M_PLL_HXTAL,跳转到这个宏定义可以得知,这个宏定义的值就是(uint32_t)(72000000),可见系统时钟为72MHZ。
首先编写TIMER5时钟的宏定义:
#define BSP_TIMER_RCU RCU_TIMER5 // 定时器时钟
开启定时器时钟:
/* 开启时钟 */
rcu_periph_clock_enable(BSP_TIMER_RCU); // 开启定时器时钟
2
1.2.配置定时器参数
开启时钟之后,需要配置一些定时器的参数,比如定时时间,计数模式等。
关于基本定时器的结构框图如图1-2-1所示。
宏定义TIMER5:
#define BSP_TIMER TIMER5 // 定时器
第一步先要复位定时器外设
timer_deinit(BSP_TIMER); // 复位定时器
然后我们配置定时器的预分频器参数,我们使用到的函数是(在 gd32e23x_timer.c 中可查看详细参数):
void timer_prescaler_config(uint32_t timer_periph, uint16_t prescaler, uint8_t pscreload);
具体的参数是
- timer_periph:定时器目标,可选 TIMERx(x=0,2,5,13..16)
- prescaler:预分频值,范围是0~65535
- pscreload:装载模式
- TIMER_PSC_RELOAD_NOW:立即加载
- TIMER_PSC_RELOAD_UPDATE:在下次事件更新时加载
然后在gd32e23x_time.h中有
void timer_init(uint32_t timer_periph, timer_parameter_struct* initpara);
这个函数是定时器初始化,并且配置参数。有两个参数,第一个参数就是定时器外设,第二个参数就是一个定时器参数结构体,关于结构体的定义如图1-2-2所示。
要初始化定时器就要先配置这个结构体,首先定义这个结构体
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 = 2000 -1; // 时钟预分频值 0-65535 psc_clk = CK_TIMER / pre
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_initpara.period = 36000 - 1; // 周期
/* 在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例 */
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
/* 只有高级定时器才有 配置为x,就重复x+1次进入中断 */
timer_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_TIMER,&timer_initpara); // 初始化定时器
2
3
4
5
6
7
8
9
10
1.3.配置中断优先级
定时器的参数配置好之后,定时器基本就配置好了。不过我们需要在中断函数中去执行对应的功能,就要对中断进行操作。前面介绍过,如果要使用中断功能,就需要配置中断优先级。中断分组还继续沿用之前的配置。
定义TIMER5中断:
#define BSP*TIMER_IRQ TIMER5_IRQn // 定时器中断
设置中断优先级根据实际项目去配置
/* 配置中断优先级 */
nvic_irq_enable(BSP_TIMER_IRQ, 1); // 设置中断优先级
2
1.4.使能中断事件和定时器
在配置好定时器中断之后还需要对中断进行使能,
void timer*interrupt_enable(uint32_t timer_periph, uint32_t interrupt);
这个函数是使能定时器中断,有两个参数,第一个参数就是定时器外设,第二个参数是中断源的选择。关于第二个参数的可选选项如图1-4-1所示。
从图1-4-1可以看到定时器中断有好多触发方式,这里选择第一个更新中断。
/* 使能中断 */
timer_interrupt_enable(BSP_TIMER,TIMER_INT_UP); // 使能更新事件中断
2
到这里还有最后一步,使能定时器,
void timer_enable(uint32_t timer_periph);
这个函数使能定时器,有一个参数就是要使能的定时器外设,
使能定时器配置如下:
/* 使能定时器 */
timer_enable(BSP_TIMER);
2
1.5.编写中断服务函数
使能中断之后,如果定时时间到,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名,这个是固定的,在startup_gd32f450_470.s启动文件中有定义。
宏定义为:
#define BSP_TIMER_IRQHandler TIMER5_IRQn// 定时器中断服务函数;
在中断处理函数里需要检测中断标志位是否被置位。
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
}
}
2
3
4
5
6
7
8
9
10
到此有关定时器中断的配置就完成了。
3.实验现象
关于这一章节的代码,在资源包的软件资料目录中。
烧写我们的代码之后,每隔1秒钟在串口助手上打印一次BSP_TIMER_IRQHandler,然后LED2会每隔1秒钟时间闪烁一下。