定时器是最常用的外设,常常需要使用定时器来完成精准的定时功能,I.MX6U 提供了多 种硬件定时器,有些定时器功能非常强大。本章我们从最基本的 EPIT 定时器开始,学习如何配置EPIT 定时器,使其按照给定的时间,周期性的产生定时器中断,在定时器中断里面我们可以做其它的处理
首先我们也需要知道定时器的基本原理,定时器是一种根据时钟信号进行及时或产生中断的硬件模块,基本工作流程就是从时钟源获得时钟信号,并对时钟脉冲进行计数,通过计数这些脉冲,定时器能够在设定的时间后产生中断或信号
计数时钟脉冲,定时器通过对时钟脉冲的计数来实现计时。当计数器的值从一个初始值递减到零(或者递增到某个值)时,定时器认为计时完成。产生中断或信号,当计数器到达设定值时,定时器会触发中断,告知系统已达到预定时间。这种机制可以用于定时触发某个任务,例如在嵌入式系统中实现周期性任务调度。这里举个简单的例子,若时钟源是 32.768 kHz(1 秒 32,768 个脉冲),则每计数一个脉冲代表大约 30 微秒的时间。
1.获取时钟源
时钟源驱动定时器的工作,定时器通过计数时钟源的信号来实现计数功能,定时器的核心就是基于时钟信号来计数。在开始工作前需要获得一个时钟源,时钟源可以来自系统时钟或外部时钟。时钟源的频率决定了定时器的计数精度
2.预分频器调节时钟
定时器通常通常配置有预分频器,是介于时钟源和定时器之间的一个模块,用来进一步调整时钟频率。用于对输入的时钟源进行分频。预分频器通过将时钟信号频率降低,延长定时器的计数周期。预分频器的设置决定了定时器每次计数所代表的时间单位。
3.计数器的工作
(1) 定时器的核心是一个计数器,它根据分频后的时钟信号进行计数,一般分为两种计数方式,递增模式,计数器从0开始递增(时钟脉冲递增,在每次接收到时钟脉冲时就会加1),直到达到设定的比较值或者最大计数值。递减模式,计数器从预设值递减到0(在每次接收到时钟脉冲的时候就会减1)
(2) 计数器的增加是由时钟信号的边沿触发来句欸的那个,时钟信号的上升沿或下降沿会触发计数器的工作,一般为上升沿触发和下降沿触发,在时钟信号从低电平变为高电平的瞬间,计数器会响应并增加或减少计数值,或者时钟信号从高电平变为低电平的瞬间,计数器会响应并更新计数值。 时钟信号不仅是定时器技术的驱动力,还确保了计时的准确性和稳定性,每个时钟脉冲代表一个基本的时间单位,因此时钟源频率越高,计数的精度就越高。通过配置预分频器,定时器可以根据需要放慢时钟频率,从而控制计数周期。
例如,一个MHz的时钟信号意味着每秒有,1,000,000 个时钟脉冲,如果预分频器设为100,计数器每接受到100个时钟脉冲才递增或递减一次,从而将时钟周期调整为每秒10000次。
在硬件实现中,计数器的设计通常基于触发器(Flip-Flop)和加法器(Adder)。计数器的每一位由触发器保存,时钟脉冲通过加法器将当前计数值加 1(或减 1)。该过程在硬件层面完全由时钟信号驱动,确保了定时器的高效和稳定运行。在生成PWM信号时,计数器也通过时钟脉冲来递增,当计数器的值到达设定的比较值时,定时器输出信号会发现变化,从而调节PWM信号的占空比。因此PWM信号的周期长度与时钟频率和比较直直接相关
4. 比较与溢出
上面讲解了定时器会计数,与设定的比较值进行比较,当计数器的值与比较直相等的时候便会,触发中断或者输出信号等操作
5. 计时周期结束后的处理
当计数器到达设定的最大值或比较值后,定时器可以自动重载初始值,从而开始新一轮的计数。叫做自动重载,一般用于周期性产生中断和信号,还有一种模式为单次计时模式,在一些情况下,定时器可能只运行一次,当达到设定的计数值后停止工作,不会自动重载
6. 触发中断
如果有中断的情况,当定时器达到设定的比较值或发生溢出时,定时器会触发中断。此CPU会暂时停止当前任务,跳转到定时器的中断服务程序(ISR)中处理相应的任务
7. 清楚标志与重启
在中断处理完成后,定时器的状态寄存器中会设置标志位,表明中断已经触发。系统必须清楚这个标志位,以便定时器能够继续工作,开始下一次的计时
定时器有非常多的种类,在STM32中我们经常会用到通用定时器、高级定时器、滴答定时器以及高精度定时器等等,这些定时器之所以存在,主要是因为它们各自具有不同的特性和用途,适用于不同的应用场景和开发板。分别的作用我在这里也做一个简单的讲解,对于知道的读者也就再复习以一下吧
首先通用定时器是功能相对简单但是运用很广泛的定时器模块,具有最基本的定时功能,一般适用于多种应用场景,例如电机控制、测量系统、定时任务,通用 定时器因广泛适用性应用在各种开发板上
然后就是高级定时器,功能丰富,支持多种高级功能,如死区时间控制,刹车功能,简单来说,死区时间控制和刹车功能是电子控制系统中不可或缺的重要功能,而高级定时器因其功能集成度高、精度高、可靠性好以及具有丰富的外部引脚和接口等优点,成为实现这些功能的理想选择。总结来说一般就适用于需要高精度定时和复杂控制逻辑的场合,如电机控制、伺服系统等
高精度定时器,一般强调时间测量的高精度和准确性,适用于需要精确控制时间的场合,例如音频处理、视频同步等,高精度定时器能够提供纳秒级或更高精度的分辨率,以满足对事件精度要求极高的应用
最后再简单介绍一下滴答定时器(一般也称为系统滴答定时器),通常作为操作系统的内部时钟源,用于提供系统时间基准和调度任务。一般就主要用于操作系统的任务调度和时间测量,滴答定时器是操作系统运行的基础,提供了系统所需的时间基准和调度功能
定时器之所以搭载在不同的开发板上,主要是因为不同的开发板和应用场景对定时器的需求不同。例如,一些开发板可能需要高精度的定时器来确保系统的实时性和稳定性,而另一些开发板则可能更注重定时器的易用性和广泛适用性。因此,开发者在选择开发板时,会根据具体的应用需求和系统要求来选择合适的定时器类型和配置。总的来说,多种类型的定时器为开发者提供了更多的选择和灵活性,使他们能够根据具体的应用场景和需求来选择合适的定时器,从而实现最佳的系统性能和稳定性。
而这次实验我们要接触到的是EPIT寄存器,也是我们IMX6UL开发板上面最基本的EPIT定时器,首先按还是来做一个介绍
EPIT全称为(Enhanced Periodic Interruput Timer),翻译过来为增强的周期中断定时器,它的主要任务是为了完成周期性中断定时的。再上面介绍的定时器有很多功能,但是EPTI定时器只是完成周期性中断定时,并且只有这么一个功能
EPIT是一个32位的定时器,在处理器几乎不需要介入的情况下提供精准的定时中断,软件使能后EPIT就会开始运行,这个在具体介绍完EPTI定时器就会明白
那么为什么需要强调它是一个32位的定时器呢,32定时器又有什么样的优势呢,首先定时器是32位可以支持非常大的计数范围,而其余非32位定时器,技术范围可能相对较小
且32位定时器具有丰富的配置选项和功能,例如后面会讲解,它具有的工作模式,时钟源,分频器等,使开发者可以灵活的配置定时器,而相对于非32位定时器,可能具有一定的配置选项和功能,灵活性和可选性可能就相对较低
那么接下来就开始正式的介绍EPTI定时器,首先是它的结构图
①部分是一个多路选择器,用来选择EPTI定时器的时钟源,如上图可以看出EPTI一共有3个时钟源可以选择,在上面我们也说过了,时钟频率越高,时钟的计数精度越高
②是一个12位的分频器,主要就是对时钟源进行分频,12位对应的值是0~4095,也就是可以经过0~4095系数的分频,通过配置预分频器,定时器可以根据需要放慢时钟频率,从而控制计数周期。
③顺着框图的顺序,在时钟信号经过分频后,进入到EPIT的内部,在EPIT的内部有三个非常重要的寄存器,计数寄存器(EPIT_CNR)、加载寄存器(EPIT_LR)和比较寄存器(EPIT_CMPR),这三个寄存器都是32位的。
EPIT是一个向下计数器,在上面我们也讲过,向下计数器给予它们一个初值,它们会从这个给定的初值,它就会从这个给定的初值开始递减,直到减为0。
计数寄存器里面保存的就是当前的计数值。如EPIT如果工作在set-and-forget模式下,当计数寄存器里面的值减少到0。
EPIT 就会重新从加载寄存器读取数值到计数寄存器里面,重新开始向下计数。加载寄存器用于在计数寄存器递减到0时重新加载计数值。它是一个可写的寄存器,允许用户设置初始计数值或周期性计数值。
比较寄存器里面保存的数值用于和计数寄存器里面的计数值进行比较,如果相等的话,就会产生一个比较事件
那么,我们继续看下图,到CMP也就是比较器,主要负责将计数寄存器中的值与比较寄存器中的设定值进行比较,具体来说,当计数器中的当前值等于比较寄存器的值时,CMP会输出一个信号,信号会沿着这个框图继续走,经过CMP后,有一个信号箭头返回到Counter Register它的作用通常是,自动重载当计数器到达终点(比如达到比较值 CMP 后)时,计数器可能会自动加载一个预设值(通常来自Load Register)来重新开始计数。这种设计常用于需要重复计时的场景,如周期性触发中断或者产生周期性信号(比如 PWM)
ITIF(Interruput Flag, 标记为中断标志)是当计数器的值达到比较寄存器值时,由CMP比较产生的。ITIF本身是一个标志位,它表示计数器已经完成了一次及时或达到比较条件,产生ITFT并不等同于直接触发中断,只有满足ITIE(Interruput Enable, 中断使能位)被设置为有效才能触发,以及系统允许响应中断请求,如果ITIE被设置为有效,且 ITIF 被置位,信号就会传递给中断控制器,从而触发中断。否则,即使 ITIF 置位了,如果 ITIE 是无效的,中断也不会触发。
经过ITIF后,图中有一个OM(Output Mode)模块,它控制定时器的输出信号(EPITn_OUT1)。即使中断没有被触发,但任然可能通过OM单元产生一个输出信号后,(如PWM信号或其他外部信号),或者某些外部设备,需要注意的是ITIF 的产生取决于计数比较,而不是中断的触发。即使没有触发中断,ITIF 也会被设置,OM 模块依赖于ITIF 信号来产生输出,不依赖中断触发。也就是说,OM 只要检测到 ITIF 置位,它就会输出信号。
⑤可以设置引脚输出,如果设置了就会通过指定的引脚输出信号
⑥产生比较中断,也就是定时中断,触发条件自然通过与笔计时器的值与比较寄存器产生比较后,如果系统允许响应中断请求,或者 ITIE被允许使能
下面将介绍,EPIT定时器的两种工作模式,这个是EPIT控制寄存器,用于配置EPIT的操作设置
其中RLD就是决定EPIT工作方式的位
如图,置1就是set-and-forget模式,此模式下的EPIT计数器从加载寄存器EPITx_LE中获取初始值,不能直接向计数器寄存器写入数据。无论什么时候,只要计数器计数到0,那么就会从加载寄存器EPITx_LR重新加载数据到计数器中,周而复始
那么置0为free-running模式,EPITx_CR寄存器的RLD位清零的时候EPIT工作在此模式下,当计数器计数到0以后会重新从0XFFFFFFFF开始计数,并不是从加载寄存器EPITx_LE中获取数据
除了常用的用于EPIT的模式切换位,第三位以外还有其余一些重要的位,如25-24位,为0的时候关闭时钟源,为1的时候是选择Peripheral时钟(ipg_clk),为2的时候是选择High-frequency参考时钟(ipg_clk_highfreq),为3的时候选择Low_frequency参考时钟(ipg_clk_32k)
以及第15位-4位,PRESCALAR,
EPIT时钟源分频值,可设置范围0~4095,分别对应1~4096分频
以及刚刚,提到的第三位,用于模式切换,还有第二位,OCIEN,主要作用就是比较中断使能位,为0的时候关闭比较中断,为1的时候使能比较中断
还有bit1位,ENMOD,设置计数器初始值,为 0 时计数器初始值等于上次关闭 EPIT 定时器以后计数器里面的值,为 1 的时候来源于加载寄存器
下面翻译为,当EPIT被禁用(EN=0)时,主计数器和预分频计数器都会将其计数值冻结在当前值。ENMOD位是一个可读/可写位,用于确定当通过设置EN位再次启用EPIT时计数器的值。如果ENMOD位被设置,那么在EPIT启用(EN=1)时,主计数器将被加载为加载值(如果RLD=1)/0xFFFF_FFFF(如果RLD=0),预分频计数器将被重置。如果ENMOD被编程为0,那么在EPIT启用(EN=1)时,主计数器和预分频计数器将从它们被冻结的值重新开始计数。如果EPIT被编程为在低功耗模式(STOP/WAIT/DEBUG)下禁用,那么当EPIT进入低功耗模式时,主计数器和预分频计数器都会冻结在其当前的计数值。当EPIT退出低功耗模式时,无论ENMOD位如何,主计数器和预分频计数器都会从它们被冻结的值开始计数。此位由硬件复位重置。软件复位不会影响此位。
0:计数器从它被禁用时的值开始计数。
1:计数器从加载值(如果RLD=1)或0xFFFF_FFFF(如果RLD=0)开始计数。
还有第0位,EN位,主要的作用使能EPIT,此位为EPIT 使能位,为 0 的时候关闭 EPIT,为 1 的时候使能 EPIT。 就是翻译为,
这一位用于启用EPIT。当EPIT被启用(EN = 1)时,EPIT计数器和预分频器的值取决于ENMOD位和RLD位,如ENMOD位描述中所述。建议在设置此位之前正确编程所有寄存器。此位由硬件复位重置,软件复位不会影响此位。
0:EPIT被禁用
1:EPIT被启用
还要介绍一个寄存器为EPITx_SR,此寄存器为EPIT的状态寄存器,有一个用于输出比较事件的状态位。这个位是一个写1清零位
31-1位全部作为此寄存器的保留位,而第一个字节位,0CIF位,翻译为
输出比较中断标志位。此位是中断标志位,当计数器的内容与比较寄存器(EPIT_CMPR)的内容相等时设置。该位是一个写1清零位。
0:比较事件未发生
1:比较事件发生
简单来说,此寄存器只有一个有效位,就是OCIF(bit0),这个位是比较中断标志位,为0的时候表示没有比较事件发生,为1的时候表示由比较事件发生。当比较中断发生以后需要手动清除此位,也就是需要写1去清零
剩下还有三个寄存器,分别为EPITx_LR,EPITx_CMPR,EPITx_CNR
这三个寄存器对应加载寄存器,比较寄存器,和计数寄存器 ,它们都只有一个作痛就是来存放数据,它们的存放数据的目的在上面EPIT的定时器原理当中也讲过,这里不过多赘述
在上面我们根据EPIT的框图对EPTI进行了大致讲解,现在我们根据框图来进一步完善对于EPIT定时器的配置思路
1.首先就是选择时钟源,一共有三种时钟源可以选择,就需要通过EPIT_CR寄存器的CLKSRC(bit25:24)位,来选择
2.设置分频值,需要通过EPIT1_CR寄存器的PRESCALAR(bit15:4)位,来进行设置
3.设置EPIT的工作模式,通过寄存器EPIT1_CR的RLD(bit3)位
4.设置计数器的初始值来源,通过寄存器EPIT_CR的ENMOD(bit1)位,设置计数器的初始值来源,也就是设置计数器初始值
5.因为我们后面的实验,需要通过比较中断来进行实验,产生比较中断自然就需要使能对应的寄存器,EPIT_CR的OCIEN(bit2)位
6.使能了比较中断,那么如何比较,既然是将EPIT1_LR中的加载值和EPIT2_CMPR中的比较值,通过这两个就可以决定定时器的中断周期
7.设置了中断周期,就需要在中断中完成我们需要完成的事情,使能GIC中对应的EPIT中断,注册中断服务函数,如果有特殊要求的话可以设置中断优先级,再编写中断函数
8.最终配置好EPIT后,就可以使能EPIT1了,通过寄存器EPIT1_CR的EN(bit0)位来设置
原理这方面就讲解完成了,最后就开始具体的代码实现部分,首先在之前中断的基础上我们将文件复制一份,然后改名为10_EPIT_Timer,然后在BSP的文件夹下,创建一个文件夹epittimer,再文件夹的下方创建两个文件分别为bsp_epittimer.h和bsp_epittimer.c
在.h文件当中写入以下代码
#ifndef _BSP_EPITTIMER_H
#define _BSP_EPITTIMER_H
#include "imx6ul.h"
#endif
然后就开始编写.c文件,首先按照思路的分析,第一步就是要先初始化EPIT定时器,初始化函数代码如下
#include "bsp_epittimer.h"
#include "bsp_int.h"
#include "bsp_led.h"
/* 初始化EPIT定时器
@param - 分频值,范围为0~4095,也就是1~4096分频
@param - 倒计时值
*/
void epit1_init(unsigned int frac ,unsigned int value)
{
if(frac > 0XFFF)/*如果分频值超过4095,则最大设置为4095*/
{
frac = 0XFFF;
}
EPIT1->CR = 0; /*清零CR寄存器*/
/*
*配置CR寄存器
*bit25:24 选择Peripheral clock = 66Hz时钟源
*bit15:4 frac分频率值
*bit3:1 当计数器到0的话从LR重新加载数值
*bit2:1 比较中断位使能
*bit1:1 初始计数值来源于LR寄存器值
*bit0:0 先暂时性关闭EPIT1
*/
EPIT1->CR = (1<<24 | frac << 4 | 1<<3 | 1<<2 | 1<<1);
EPIT1->LR = value; /*加载寄存器*/
EPIT1->CMPR = 0; /*比较寄存器*/
/*使能GIC对应的EPTI中断*/
GIC_EnableIRQ(EPIT1_IRQn);
/*注册中断服务函数*/
system_register_irqhandler(EPIT1_IRQn,(system_irq_handler_t)epit1_irqhandler,NULL);
EPIT1->CR |= 1<<0; /*重新开启EPIT1*/
}
/*编写中断处理函数*/
void epit1_irqhandler(void)
{
static unsigned char state = 0;
state = !state;
if(EPIT1->SR & (1<<0)) /* 判断比较事件发生 */
{
led_switch(LED0, state); /* 定时器周期到,反转LED */
}
EPIT1->SR |= 1<<0;
}
每个代码的大致作用我已经注释了,这边再大致梳理一下思路,首先函数接受两个参数,一个是unsigned int frac ,负责接受我们预期的分频值,unsigned int value决定我们预期的周期时间
函数里面内容,先对我们写入的frac分频值进行一个判断,因为我们的寄存器最多进行1-4096的分频,所以不能超出寄存器的值,所以判断是否大于规定值,如果超出则frac就默认输出为最大分频值4096
我们要配置EPIT控制寄存器,首先还是代码标准性,对此EPTIx_CR进行清零,然后就开始配置,这个32位的寄存器每一位为什么需要配置为这么多,上面的注释也做了讲解,所以下面我们就直接对位进行操作就可以了
将之后我们要写入的Value传入到加载寄存器,为什么说这个Value值会决定这个定时器的周期,待会儿我们进行讲解
使能中断控制器中控制的EPIT1_IRQn(这个是中断号),这个函数即是在 GIC 中使能编号为 EPIT1_IRQn 的中断,这样一来,当 EPIT1 定时器产生中断时,GIC 就会将这个中断传递给 CPU,并触发中断处理程序
下面就是注册中断服务函数,也就是产生中断过后,需要执行的程序,将指定的中断号与处理该中断的服务程序关联起来。将指定的中断号与处理该中断的服务程序关联起来这个函数是我们在讲解中断的时候就已经写好了的,EPTI1_IRQn就对应触发中断的中断号,这个参数表示你要为哪个中断号注册中断服务程序,这里就是为 EPIT1 定时器的中断号注册处理函数。
epit1_irqhandler这是中断服务函数(ISR)的名称。当 EPIT1 产生中断时,系统会自动调用这个函数来处理定时器中断事件。最后一个参数是传递给中断处理函数的上下文参数,通常用于传递额外的信息。如果不需要额外信息,则传递 NULL。
然后最后我们重新开启EPTI定时器
下一步就是开始编写我们的中断服务函数,既然产生了中断,自然就需要有对于相关的中断处理,还是先把代码放在下面
/*编写中断处理函数*/
void epit1_irqhandler(void)
{
static unsigned char state = 0;
state = !state;
if(EPIT1->SR &(1<<0))
{
led_switch(LED0,state);
}
EPIT1->SR |= 1<<0;
}
首先创建一个变量来定义灯的状态,然后再通过!来进行取反,下一步则进行一个IF判断,因为SR是用来判断比较事件是否发生,所以IF为真也就是判断事件发生,则执行里面的led_switch这个函数,最后将SR寄存器给清零,也就是清除中断标志位
然后现在现在讲一下为什么Value决定是如何控制这个定时器的周期时长,还是说回来epit1_init 有两个参数 frac 和 value,其中 frac 是分频值,value 是加载值。
在第 29 行设置比较寄存器为 0,也就是当计数器倒计数到 0 以后就会触发比较中断,因此分频 值 frac 和 value 就可以决定中断频率
计算公式如下 Tout = ((frac+1)*value)/Tclk
Value和Frac的参数都已经讲解过了,Tclk是EPIT1的输入时钟频率(单位Hz),Tout是溢出时间(单位S),例如再钱买你我们已经设置了时钟源是ipg_clk=66Mhz,我们再这里假如要设定周期500ms,可以设置分频值为0,也就是一分频,进入EPIT1的时钟就是66MHz。那么如果要设置500ms的中断,那EPIT加载寄存器就应该是66000000/2=33000000
所以bsp_epittimer.c的文件,最后只需要将在.c文件里面编写的函数在.h文件里面声明一下就可以了bsp_epittimer.h文件代码如下
#ifndef _BSP_EPITTIMER_H
#define _BSP_EPITTIMER_H
#include "imx6ul.h"
void epit1_init(unsigned int frac ,unsigned int value);
void epit1_irqhandler(void);
#endif
下一步就是编写main.c主函数,
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"
#include "bsp_epittimer.h"
int main(void)
{
int_init(); /* 初始化中断(一定要最先调用!) */
imx6u_clkinit(); /* 初始化系统时钟 */
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
key_init(); /* 初始化key */
exit_init(); /* 初始化按键中断 */
epit1_init(0, 66000000/2); /* 初始化EPIT1定时器,1分频
* 计数值为:66000000/2,也就是
* 定时周期为500ms。
*/
while(1)
{
delay(500);
}
return 0;
}
所有的代码就编写完成了,最后将代码烧写到板子当中,就可以看到LED灯以500ms为周期在进行闪烁,那么如果使用IMX6UL的EPIT定时器这一节实验就讲解到这里,欢迎大家的评论与指正