外观
十一、定时器中断实验
定时器基础知识
什么是定时器
定时器是单片机内部集成,可以通过编程控制。单片机的定时功能是通过计数来实现的,当单片机每一个机器周期产生一个脉冲时,计数器就加一。定时器的主要功能是用来计时,时间到达之后可以产生中断,提醒计时时间到,然后可以在中断函数中去执行功能。比如我们想让一个led灯1秒钟翻转一次,就可以使用定时器配置为1秒钟触发中断,然后在中断函数中执行led翻转的程序。
GD32定时器
GD32E230C8T6一共有7个定时器,可以分为六种类型:
- 高级定时器0
- 通用定时器(L0)2
- 通用定时器(L2)13
- 通用定时器(L3)14
- 通用定时器(L4)15/16
- 基本定时器5
不同类型的定时器所拥有的功能数量不同,一般高级定时器的功能最多,通用定时器次之,基本定时器功能最少。具体功能对照可以查看《GD32用户手册》的第206页。
定时器基本参数
预分频
预分频器可以将定时器的时钟(TIMER_CK)频率按1到65536(uint16_t)之间的任意值分频,分频后的时钟PSC_CLK驱动计数器计数。分频系数受预分频器TIMERx_PSC控制。这个控制寄存器带有缓冲器,它能够在运行时被改变。新的预分频器的参数在下一次更新事件到来时被采用。
分频器的分频公式为:PSC_CLK = TIMER_CK/ (TIMERx_PSC +1)
周期值
计数器溢出最大值,在初始化结构体配置时是uint32_t类型,在使用函数单独设置周期值时是uint16_t类型。
计数模式
GD32E230定时最多支持以下三种计数模式:
- 向上计数模式:计数器从0计数到自动加载值(周期值),然后重新从0开始计数并产生一个计数器溢出事件;
- 向下计数模式:计数器从自动加载值(周期值)开始向下计数到0,然后从自动装载值重新开始,并产生一个计数器向下溢出事件。
- 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装载值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
定时器灯闪烁
项目分析
本次实验通过配置通用定时器15,使其每隔0.5s进入一次中断,让LED灯闪烁。
配置流程
一般使用定时器功能,都需要以下几个步骤:
- 开启时钟(定时器时钟)
- 配置定时器参数
- 配置中断优先级
- 使能中断事件和定时器
- 编写中断服务函数
开启时钟
先来看一下定时器的时钟来源,在《GD32E230数据手册》的第18页;从图中可以看到所有定时器时钟来源都是CK_AHB(MAX=72Mhz)。
这里使用Timer15,就需要使能Timer15的时钟,从时钟树中可以看到,Timer15的时钟来源于APB2分频器/2,APB2时钟又需要外设使能,所以我们需要使能定时器外设时钟;
在时钟树下方还有一句话,如果APB1分频器设置为1,则定时器时钟是AHB时钟/1,否则是AHB时钟/2;
默认情况下APB1分频器是设置为1的,所以时钟的最大频率就是AHB时钟=72Mhz。
C
//使能时钟
rcu_periph_clock_enable(RCU_TIMER15);
1
2
2
配置定时器参数
开启时钟后,需要配置定时器的参数,比如预分频值、周期值、计数模式等等。
在配置参数前,建议先复位定时器外设与参数结构体,保证定时器处于初始状态,注意定时器参数结构体需要在函数最开始处进行声明:
C
//定时器初始化参数结构体
timer_parameter_struct timer_initpara;
//复位定时器15
timer_deinit(TIMER15);
// 初始化结构体
timer_struct_para_init(&timer_initpara);
1
2
3
4
5
6
2
3
4
5
6
定时器参数使用结构体类型进行整合,这里依次对结构体成员进行赋值完成配置:
这里介绍一下分频值参数与周期参数应该如何设置:
定时器时钟=72Mhz(72_000_000);
此时预分频值是7199,实际预分频值会+1,也就是7200;
此时频率为(72_000_000)/ 7200=10000Hz
若此时将周期设置为4999;计时时间=(5000)/10000=0.5s
在设置分频值与周期时需要注意最大值限制,prescaler(uint16_t)、period(uint32_t);
C
// 配置定时器参数
timer_initpara.prescaler = 7199; // 时钟预分频值 0-65535 psc_clk = CK_TIMER / pre
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timer_initpara.period = 4999; // 周期
//在输入捕获的时候使用 数字滤波器使用的采样频率之间的分频比例
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
//只有高级定时器才有 配置为x,就重复x+1次进入中断
timer_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(TIMER15,&timer_initpara); // 初始化定时器
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
自此,定时器参数设置完成;
配置中断优先级
定时器参数配置完成后,需要配置对应的中断源与优先级,才可以在定时器计时时间到来后触发中断源中断执行对应的功能;
C
//中断优先级
nvic_irq_enable(TIMER15_IRQn,0U);
1
2
2
使能中断事件和定时器
通过配置中断事件来使定时器按照指定情况触发中断,在使能更新中断前,建议先清除中断标志位,确保中断处于初始状态;
定时器中断事件有很多,此处设置为更新中断事件,当计时器计数完成更新时进入中断;
C
//定时器更新中断标志位清除
timer_interrupt_flag_clear(TIMER15,TIMER_INT_FLAG_UP);
//使能更新事件中断
timer_interrupt_enable(TIMER15,TIMER_INT_UP);
1
2
3
4
2
3
4
使能定时器
定时器配置完成,需打开总开关才可使定时器正常工作;
注意,当使能定时器后,定时器自动开始计数,时间到后会按照设置的中断事件进入中断;若不想此时打开定时器,可先不使能;
C
//使能定时器15
timer_enable(TIMER15);
1
2
2
编写中断服务函数
使能中断、使能定时器后,如果中断事件到来,就会跳转到中断服务函数中执行。需要编写对应的中断服务函数,来实现指定的功能;
中断服务函数名是固定的,在startup_gd32e23x.s启动文件中有定义。
中断服务函数并不只为某一个特殊的中断事件服务,所以,在进入中断服务函数后,需要先进行判断,检测对应中断标志位是否有被置位,确定是指定中断事件产生后再进行相应的处理。
处理完成后续将相应的中断标志位进行清除,以待下一次进入。
C
void TIMER15_IRQHandler(void)
{
static uint8_t num=0;
if(timer_interrupt_flag_get(TIMER15,TIMER_INT_FLAG_UP) == SET)
{
if(num == 1){
Open_LED();
}
else{
num=0;
CLose_LED();
}
num++;
//定时器更新中断标志位清除
timer_interrupt_flag_clear(TIMER15,TIMER_INT_FLAG_UP);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
资料下载
工程代码可前往gitee下载资料包,简易数字示波器资料包
注意事项
程序运行死机
中断服务函数是响应中断事件的处理,处理内容应简明扼要,如果需要延时或复杂逻辑,最好是通过全局变量标志位将其放置在顺序逻辑内执行。