【单片机/嵌入式】【梁山派】学习日志11:定时器Timer

一、定时器原理

1.1基础知识

1.1.1什么是定时器

定时器是单片机内部集成,可以通过编程控制。单片机的定时功能是通过计数来实现的,当单片机每一个机器周期产生一个脉冲时,计数器就加一。定时器的主要功能是用来计时,时间到达之后可以产生中断,提醒计时时间到,然后可以在中断函数中去执行功能。比如我们想让一个led灯1秒钟翻转一次,就可以使用定时器配置为1秒钟触发中断,然后在中断函数中执行led翻转的程序。

1.1.2 GD32定时器

GD32F450ZGT6一共有14个定时器,可以分为五种类型,高级定时器0/7、通用定时器(L0)1-4、通用定时器(L1)8/11、通用定时器(L2)9/10/12/13和基本定时器5/6。不同类型的定时器所拥有的功能数量不同,一般高级定时器的功能最多,通用定时器次之,基本定时器功能最少。具体可以查看用户手册的第348

1.1.3高级定时器

高级定时器(TIMER0TIMER7)是四通道定时器,支持输入捕获和输出比较。可以产生PWM信号控制电机和电源管理。高级定时器包含了一个死区时间插入模块,非常适合电机控制。

高级定时器的主要特性如图所示。(户手册的第349

关于更多高级定时器的功能描述可以查看用户手册,具体功能目录如图所示。

 

1.1.4通用定时器(L0)

通用定时器L0(TIMER1TIMER2TIMER3TIMER4)是4通道定时器,支持输入捕获,输出比较,产生PWM信号控制电机和电源管理。

通用定时器(L0)的主要特性如图所示。

对比可知,通用定时器(L0)比高级定时器少了可编程的死区时间、可编程的计数器重复功能和中止输入功能使定时器按照用户的配置输出的功能。

关于更多通用定时器(L0)的功能描述可以查看用户手册的第400

1.1.5通用定时器(L1)

通用定时器L1(TIMER8TIMER11)是两通道定时器,支持输入捕获和输出比较,可以产生PWM信号控制电机和电源管理。

通用定时器(L1)的主要特性如图所示。

关于更多通用定时器(L1)的功能描述可以查看用户手册的第440

1.1.6通用定时器(L2)

通用定时器L2(TIMER9TIMER10TIMER12TIMER13)是单通道定时器,支持输入捕获,输出比较,产生PWM信号控制电机和电源管理。

通用定时器(L2)的主要特性如图所示。

注意一下图中红线部分,这个在用户手册里写错了,通用定时器(L2)只有向上计数模式。

关于更多通用定时器(L2)的功能描述可以查看用户手册的第462

1.1.7基本定时器

基本定时器(TIMER5TIMER6)包含一个无符号的16位计数器。可以被用作通用定时器和为DAC(数字到模拟转换器)提供时钟。基本定时器可以配置产生DMA请求,TRGO触发连接到DAC。

基本定时器的主要特性如图所示。

关于更多基本定时器的功能描述可以查看用户手册的第481页

1.2基本参数

1.2.1预分频

预分频器可以将定时器的时钟(TIMER_CK)频率1到65536之间的任意值分频,分频后的时钟PSC_CLK驱动计数器计数分频系数预分频器TIMERx_PSC控制。这个控制寄存器带有缓冲器(影子寄存器),它能够在运行时被改变。新的预分频器的参数在下一次更新事件到来时被采用。

分频器的分频公式为:PSC_CLK = TIMER_CK/ (TIMERx_PSC +1)

1.2.2向上计数模式

计数器从0开始向上连续计数到自动加载值(定义在TIMERx_CAR寄存器中),一旦计数器计数到自动加载值,会重新从0开始向上计数并产生上溢事件

1.2.3更新事件

更新事件是当计数器上溢或者下溢而开始新的计数周期时触发的。更新事件可以触发DMA请求,以用于在下一个计数周期开始时及时更新定时器的运行参数,特别适合用于实时控制。

1.3应用

如果项目中需要定时执行或者需要重复执行一定的工作, 定时器的使用尤为重要。在下一章将会介绍如何去配置定时器,定时1秒钟进入一次中断,然后在定时器中断服务函数中实现1秒钟led电平状态翻转1次。最后实现的效果就是led亮1秒钟灭1秒钟。

二、定时器小灯闪烁

2.1配置流程

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

l  开启时钟(RCU开启定时器时钟选择时钟源并倍频

l  配置定时器参数

l  配置中断优先级

l  使能中断事件和定时器

l  编写中断服务函数

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

2.1.1开启时钟

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

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

这里使用TIMER5,就要先使能TIMER5的时钟,又因为TIMER5时钟来源于CK_APB1,CK_APB1的时钟在system_gd32f4xx.c中定义,如下图所示。从图可以看到APB1的时钟等于AHB的时钟4分频AHB的时钟等于系统时钟SYSCLK

系统时钟定义如上图所示。从上图可以看到系统时钟等于__SYSTEM_CLOCK_200M_PLL_25M_HXTAL,跳转到这个宏定义可以得知,这个宏定义的值就是(uint32_t)(200000000),可见系统时钟为200MHZ。回过来看CK_APB1的时钟为200MHZ的4分频等于50MHZ。要设置定时器的时钟为200MHZ,从时钟树上可以看到还需要进行4倍频处理,在代码里面还需要配置为4倍频

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

#define  BSP_TIMER_RCU    RCU_TIMER5  // 定时器时钟

开启定时器时钟:

/* 开启时钟 */

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

固件库手册478

然后配置定时器时钟4倍频。在gd32f4xx_rcu.h中寻找相关函数void rcu_timer_clock_prescaler_config(uint32_t timer_clock_prescaler);

固件库手册502

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

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

配置定时器时钟为200MHZ:

/* CK_TIMERx = 4 x CK_APB1  = 4x50M = 200MHZ */   

rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟 

2.1.2配置定时器参数

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

由于TIMER5是基本定时器,所以关于基本定时器的结构框图如图所示。

宏定义TIMER5:

#define    BSP_TIMER   TIMER5   // 定时器

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

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

固件库手册636

gd32f4xx_time.h中有void timer_init(uint32_t timer_periph, timer_parameter_struct* initpara);函数

固件库手册637

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

固件库手册634

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

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

固件库手册636

然后配置结构体的参数

prescaler:这个参数是时钟的预分频值,是16位的,取值范围为1-65535。TIMER_CK经过预分频之后得到PSC_CLK。每经过一个PSC_CLK都会产生一个计数周期,prescaler参数将决定一个计数周期的时间。设预分频值为pre,则计数器时钟频率PSC_CLK=TIMER_CK / (pre + 1)。

l  alignedmode:对齐模式暂没用到。

counterdirection:计数模式,基本定时器只有向上计数模式,所以配置为TIMER_COUNTER_UP。

period:周期值,是一个16位的计数器,最大值为65535,当计数器达到设置的周期数值(自动重装载寄存器)时数值清零,配合计数器时钟频率可以计算中断时间。

l  clockdivision:时钟分频,在输入捕获的时候使用,定时器时钟频率与死区发生器和数字滤波器使用的采样频率之间的分频比。

l  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); // 初始化定时器

注意这里有两个参数preper,这两个参数是决定定时时间的,为了使用更灵活,在调用函数的时候传递。(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

2.1.3配置中断优先级

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

定义TIMER5中断:

#define  BSP_TIMER_IRQ  TIMER5_DAC_IRQn   // 定时器中断

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

设置中断优先级根据实际项目去配置,这里就设置为抢占优先级为3,响应优先级为2。

/* 配置中断优先级 */

nvic_irq_enable(BSP_TIMER_IRQ,3,2); // 设置中断优先级,抢占优先级为3,响应优先级为2

2.1.4使能中断事件和定时器

在配置好定时器中断之后还需要对中断进行使能,void timer_interrupt_enable(uint32_t timer_periph, uint32_t interrupt);固件库手册686

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

2.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   

    }

}

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

2.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 // 定时器中断服务函数

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

2.3实验现象

 

posted @ 2022-11-12 16:25  U羊U  阅读(920)  评论(0编辑  收藏  举报