GD32F4定时器:利用溢出中断实现定时

文章目录

  • GD32F4定时器:利用溢出中断实现定时
  • 1. 系统环境
  • 2. 定时器描述
  • 3. 定时器程序编写
  • 4. 一个完整的代码例子
  • 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.csystem_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函数。也有些定时器好几个定时器共用一个中断函数,在编写的时候这点要注意。

    物联沃分享整理
    物联沃-IOTWORD物联网 » GD32F4:定时器的使用

    发表评论