GD32F4定时器:利用溢出中断实现定时
1. 系统环境
- 系统:win10
- IDE: Keil5
- 开发板:GD32官方开发板GD32F427R-START
- GD库版本:GD32F4xx_Firmware_Library_V3.0.0
2. 定时器描述
对于任何的外设,在使用的时候看一下时钟树都是重要的,因此对于定时器的使用,先看gd32f4的时钟树对定时器的描述如下:
从时钟树我们可知,定时器1、2、3、4、5、6、11、12、13是在APB1总线下面,定时器0、7、8、9、10是在APB2总线下面。
同时在时钟树下面有这样一段关于time的描述:
TIMER时钟由AHB时钟分频获得,它的频率可以等于CK_APBx、CK_APBx的两倍或CK_APBx的四倍。
详细信息请参考RCU_CFG1寄存器的TIMERSEL位。
既然如此,我们就看看RCU_CFG1寄存器的TIMERSEL位的描述:
可能你看到这一下就懵了,没关系我们先写定时器程序,边写边过来分析,就会恍然大悟。
3. 定时器程序编写
下面我们实现一个定时器溢出中断,定时器虽然有高级、普通之分,但是那是其它功能(如PWM),对于定时器溢出中断可以说没区别。因此我们就以定时器1为例来写一个定时器最简单的初始化。
void timer_init(void)
{
timer_parameter_struct timer_initpara; //定时器结构体
rcu_periph_clock_enable(RCU_TIMER1); //开启定时器时钟
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);//配置定时器时钟,这个后面重点说
timer_struct_para_init(&timer_initpara);//将定时器结构体内参数配置成默认参数
timer_deinit(TIMER1); //复位定时器
//配置TIMER1,时钟为200M/200/1000 = 1k
timer_initpara.prescaler = 200-1;//预分频
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数方式
timer_initpara.period = 1000-1; //计数值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0; //设置重复计数器值,0表示不重复计数,每次溢出都产生更新事件
timer_init(TIMER1,&timer_initpara);
timer_auto_reload_shadow_enable(TIMER1);//使能自动重加载
timer_enable(TIMER1);//使能定时器
}
下面我们分析一下函数rcu_timer_clock_prescaler_config
,因为它决定了定时器的时钟究竟是多少:
rcu_timer_clock_prescaler_config
有一个参数,值为RCU_TIMER_PSC_MUL2或RCU_TIMER_PSC_MUL4,我们对照时钟树详细来说一下参数的意思:
这个图里面有三部分被红框标识,我们暂时称之上图1、上图2和上图3,后面分析的时候会用到。
定时器的TIMERSEL寄存器:
看上面时钟树局部图,对于GD32F425来讲,CK_AHB最高可以配置到200M,方法请参考我的另一篇文章。
因为APBx的分频器(上图1)和定时器的时钟倍频器(上图2)是耦合在一起的,定时器的时钟倍频器不可以单独设置,因此有如下情况,
当函数rcu_timer_clock_prescaler_config
参数为RCU_TIMER_PSC_MUL2
的时候TIMERSEL的值将被设置为0,此时:
- 若APBx的分频系数为不分频(上图1),那么定时器倍频器(上图2)将不倍频,此时CK_TIMERx = CK_AHB,即定时器时钟就是AHB总线时钟。
- 若APBx的分频系数为2分频(上图1),那么定时器倍频器(上图2)将进行2倍频,此时依旧CK_TIMERx = CK_AHB,即定时器时钟就是AHB总线时钟。
- 若APBx设置的分频系数是除不分频、2分频外的其它分频(上图1),那么定时器倍频器(上图2)将进行2倍频,此时CK_TIMERx = 2 x CK_APBx,即定时器时钟就是APBx总线时钟的2倍,具体多少要看APBx的时钟。
当函数rcu_timer_clock_prescaler_config
参数为RCU_TIMER_PSC_MUL4
的时候TIMERSEL的值将被设置为1,此时:
- 若APBx的分频系数为不分频(上图1),那么定时器倍频器(上图2)将不倍频,此时CK_TIMERx = CK_AHB,即定时器时钟就是AHB总线时钟。
- 若APBx的分频系数为2分频(上图1),那么定时器倍频器(上图2)将进行2倍频,此时依旧CK_TIMERx = CK_AHB,即定时器时钟就是AHB总线时钟。
- 若APBx的分频系数为4分频(上图1),那么定时器倍频器(上图2)将进行4倍频,此时依旧CK_TIMERx = CK_AHB,即定时器时钟就是AHB总线时钟。
- 若APBx设置的分频系数是除不分频、2分频、4分频外的其它分频(上图1),那么定时器倍频器(上图2)将进行4倍频,此时CK_TIMERx = 4 x CK_APBx,即定时器时钟就是APBx总线时钟的4倍,具体多少要看APBx的时钟。
现在我们指导,这个定时器的时钟和APBx的分频系数相关,那么我们就看一下在时钟初始化的时候,设置情况:
在system_gd32f4xx.c
的system_clock_200m_25m_hxtal(void)
函数里面(因为我用的是25m晶振,所以是这个函数),里面有段代码如下:
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;
现在我们知道,对于我的工程APB1进行了4分频,APB2进行了2分频,那么为了保证挂在APB1总线上的timer1的输入时钟为200M,那么就要选择 rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
。
基于其它的设置,就和STM32的定时器基本没有区别了,下面代码prescaler是将定时器的时钟先进行200分频,period是计数周期值为1000一个循环,如下:
timer_initpara.prescaler = 200-1;//预分频
timer_initpara.period = 1000-1; //计数值
4. 一个完整的代码例子
下面我们将完成一个定时器1毫秒溢出中断的程序,注意我这个的前提的APB1 = AHB/4, AHB = 200M,具体配置请看上面的描述;
//定时器初始化
void time1_init(void)
{
timer_parameter_struct timer_initpara;//定时器结构体
rcu_periph_clock_enable(RCU_TIMER1);//开启定时器时钟
//因为APB1是AHB的4分频,因此定时器时钟CK_TIMERx = CK_AHB = 200m
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);//配置定时器时钟等于CK_AHB
timer_struct_para_init(&timer_initpara);//将定时器结构体内参数配置成默认参数
timer_deinit(TIMER1); //复位定时器
/* TIMER1 configuration */
//200M/200/1000 = 1k
timer_initpara.prescaler = 200-1;//预分频
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数方式
timer_initpara.period = 1000-1; //计数值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0; //每次溢出都产生更新是件
timer_init(TIMER1,&timer_initpara);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER1);//使能自动重加载
timer_interrupt_enable(TIMER1,TIMER_INT_UP);//使能溢出中断
nvic_irq_enable(TIMER1_IRQn, 0, 1);//配置中断优先级
/* TIMER1 enable */
timer_enable(TIMER1);//使能定时器
}
//main函数
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);//配置优先级分组
timer1_init();
}
//中断函数
void TIMER1_IRQHandler(void)
{
if(SET == timer_interrupt_flag_get(TIMER1,TIMER_INT_UP)){
//用户代码
/* clear TIMER interrupt flag */
timer_interrupt_flag_clear(TIMER1,TIMER_INT_UP);
}
}
代码简述:
利用rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4)将定时器时钟CK_TIMERx = CK_AHB = 200m
进入定时器配置,200M首先进行200分频,然后在计数1000次触发一次中断,此时200M/200/1000 = 1k,也就是说定时器中断周期为1k,即中断时间为1ms。
若需要1秒中断只需要将上面程序中的
timer_initpara.period = 1000-1; //计数值
改为
timer_initpara.period = 1000000-1; //计数值
注意:我们都知道STM32的timer1的溢出中断是有专门的中断函数的,而GD32的timer0对标的STM32的timer1,因此GD32的timer0也有专门的溢出中断函数,而对于GD32的timer1,所有的中断共用一个TIMER1_IRQHandler函数。也有些定时器好几个定时器共用一个中断函数,在编写的时候这点要注意。