十.定时器EPIT——1EPIT的入门
今天来讲一个常用的外设:定时器,I.MX6U提供了多种硬件计时器,有些定时器的作用非常强大,我们从最基础的EPIT定时器开始学习。
EPIT定时器简介
EPIT(Enhanced Periodic Interrupt Timer)增强型周期中断定时器,主要功能就是用来完成周期性中断定时。对Cortex-M类型的STM32来说,定时器还具备捕获输入、PWM输出等作用,但是Cortex-A7架构的I.MX6UL的EPIT只用来完成周期性中断定时作用。
EPIT是一个32位定时器,在处理器不用介入的情况下可以提供精准的定时中断,在通过软件设定后EPIT就会开始运行。先来看下《IMX6ULL参考手册.pdf》第24章上的说明
EPIT主要功能
- 可以选择时钟源的32位向下计数器(上图中显示,包括关闭时钟源有4种选项)
- 12位分频器(2^12=4095),对应1~4096分频可选择
- 计数器在到达比较值相等时会产生中断
- 计数器值可以动态变成
- 在低功耗模式和调试模式下可以编程
我们主要关注的是前3点。
按照上面的图示,EPIT可以用的有3个时钟源:ipg_clk,ipg_clk_32k和ipg_clk_highfreq。其中ipg_clk就是我们在系统时钟树设置时候设置的66MHz的IPG_ROOT_CLK。
具体时钟源在参考手册上有详细的说明。
分频器是12位,可以对时钟源进行1~4096分频。
经过分频的时钟进入EPIT,在EPIT内有3个比较重要的32位寄存器:Counter Register(EPIT_CNR),Load Register(EPIT_LR)和Compare Register(EPIT_CMPR)。在前面说过,EPIT是个向下计数器,可以给他设置一个初始值(加载寄存器),他就会从这个初始值开始向下递减,直到减到0或者比较器里设定的值。这个计数寄存器里就是保存着这个递减的数值。
EPIT的工作模式
EPIT有两种工作模式:
- set-and-forget mode
- free-running mode
两种工作模式通过寄存器EPITx_CR的RLD位(bit[3])来控制(x为1或2,I.MX6UL提供2个EPIT),RLD=1时为set-and-forget模式,在此模式下EPIT的计数器从加载计数器EPITx_LR中获取初始值,不能向计数器寄存器写入数据,计数器自减到0后就会从EPITx_LR中重新加载数据到计数器中,循环进行。
free-running模式是EPITx_CR[RLD]为0,在此模式下计数器计数到0时会从0xFFFFFFFF重新开始计数,不再从EPITx_LR中加载数据。
EPIT的寄存器
前面讲过,I.MX6UL提供了2个EPIT定时器,两套计时器的寄存器是一样的,通过EPITx的x值区分。每个定时器由5个寄存器控制。
控制寄存器Control register
控制寄存器的结构如下
每个bit的作用也给了相应的说明
其中比较重要的位要着重说明一下:
CLKSRC(bit[25:24])时钟源,我们使用01对应ipg_clk_root,也就是66MHz。
OM(bit[23:22]),EPIT对应有GPIO的IO功能,可以根据用途设置。
可以查一下参考手册,可以通过设置GPIO1_IO25复用为EPIT1的输出
PRESCALAR(bit[15:4])共12位,EPIT时钟源,分频器可以设置0~4095对应1~4096分频。
RLD(bit[3])模式设置,可以切换是否从LR里调取初始值
OCIEN(bit[2])使能中断时钟(Output Compare Interrupt Enable),1时使能中断,当计数器减到指定值时触发中断。
ENMOD(bit[1])计数器EPIT使能模式,大概意思就是指定初始值,0时初始值等于最近一次关闭EPIT定时器后计数器里的值,1时从LR寄存器里获取初始值。
EN([bit0]),使能EPIT,1时使能0时禁止。
寄存器状态Status Register
EPITx_SR只有一个标志位可以用OCIF(Output Compare Interrupt Flag,bit[0]),
但是有两种作用,读取bit,当标志位为1时表明通过比较器有中断发生。当比较发生中断以后要手动清除此位。注意写bit的时候是w1c,意思是write 1 clear。就是要写1。
加载寄存器Load Register
加载寄存器EPITx_LR,前面已经说过很多回了,保存一个预设的值,如果 EPITx_CR[RLD]=1时,每次循环完毕后都从该寄存器里获取值,然后从这个值倒数。寄存器为32位,设置范围从0~4294967295。
比较寄存器Compare Register
EPITx_CMPR比较寄存器保存一个值,当计数器倒数到这个值时循环结束,如果允许产生中断的话就发生中断。32位。
计数寄存器Counter Register
EPITx_CNR保存倒计数的值,32位。
下面的图表说明了中断及输出和Counter,LoadRegister、CompareRegister三者之间的关系
上面的图中,LR值为4,CMP的值为2,注意观察Interrupt的信号
要注意的是,在设置完比较值Compare的时候,计数器数到比较值后触发中断,但是counter还是继续往下数的,一直到数到0才从LR里取值。
EPIT使用
使用EPIT和其他的功能差不多,因为EPIT属于中断、也是先初始化、定义中断服务函数,注册中断服务函数等。我们利用蜂鸣器来验证其功能(教程上使用的是LED,我觉得LED在main函数里一直保持闪烁可以证明程序运行良好,就不要动了,用另外的外设调用EPIT即可。)
初始化
EPIT初始化的步骤如下:
初始化EPITx_CR寄存器,包括时钟源、分频值、工作模式、计数器初始值来源(来自LR还是上一次停止前最后的值)、使能比较中断,注册中断服务函数,应该包含一下内容
- 初始化EPITx_LR的值,决定起始值(计数器从多少倒数)
- 初始化EPITx_CMPR的值,决定终止值(计数器数到多少停止)
- 后两条通过直接对寄存器赋值就可以实现,第一条要进行位操作
- 时钟源:bit[25:24],一般都为1,直接通过1<<24设置
- 分频值:bit[15:4],具体情况具体分析
- 工作模式:bit[3],建议值为1,从LR寄存器获取初始值,通过1<<3设置
- 使能中断:bit[2],设置为1,计数器减至CMP寄存器值或0时产生中断,通过1<<2设置
- 初始值来源:bit[1],建议为1,从LD获取或从0xFFFFFFFF开始,通过1<<1设置。
最后的bit[0]的使能EPIT应该在中断服务函数注册完成后进行,在讲中断服务函数的时候说过,如果使能EPIT在注册服务函数前执行,基本上一上电的话就进入中断,程序基本上就不知道卡到哪了。
更换时钟源
这个在教程里没有讲,但是参考手册里写的比较清楚,虽然我们一般情况是不会更换时钟源的,但是专门强调了更换时钟源的流程
- 通过EPITx_CR的EN禁止EPIT(EN=0)
- 通过EPITx_OM的EN禁止EPIT输出(OM=00)
- 禁止EPIT中断
- 设置时钟源
- 清除EPITx_SR标志位(bit[1]写1)
- 将设置ENMOD=1,使EPIT从LR设定值或者0xFFFFFFFF开始倒数
- 使能EPIT(EN=1)
- 使能EPIT中断
按照参考手册上所说,更换EPIT时钟源应该遵循上述流程。
代码结构成
这里除了初始化的代码还加上中断服务函数,主要就是说明中断服务函数最后清除SR标志位的用法。
#include "bsp_epit.h" #include "bsp_int.h" #include "bsp_beep.h" #include "bsp_led.h" /* * @description : EPIT定时器初始化 * @param-flac : 分频器:0~4095 * @param-count_start : 计数器起始值 * @param-count_sto : 计数器起始值 * @return : None */ void epit1_init(unsigned int flac, unsigned int count_start, unsigned int count_stop) { if(flac>4095){flac=4095;} EPIT1->CR =0; //EPIT1_CR清零 EPIT1->CR = (1<<1) |(1<<2) |(1<<3) |(flac<<4)|(1<<24); //ENMOD=1 OCIEN=1 RLD=1(从LR取值) CLKSRC=1 EPIT1->LR = count_start; //设定EPIT1_LR寄存器 EPIT1->CMPR = count_stop; //设定EPIT1_CMPR寄存器 GIC_EnableIRQ(EPIT1_IRQn); //GIC中使能EPIT1 system_register_irqHandler(EPIT1_IRQn,epit1_irqhandler,NULL);//注册中断函数 EPIT1->CR |=1<<0; //使能EPIC1 } /* * @description : EPIT1中断服务函数 * @param : 这里不需要没有实际参数,但是定义函数的时候是按照前面声明函数是定义的,就把直接把样式复制过来了 * @return : None */ void epit1_irqhandler(unsigned int gcciar,void *param) { static unsigned char state=OFF; state = !state; if(EPIT1->SR &(1<<0)) { beep_switch(state); } EPIT1->SR |=(1<<0); //清除中断标志位 }
代码的注释也是比较清楚的,这里的函数和教程里的有些区别,教程里函数定义了两个形参,一个是分频值一个是LR的值,这里给了3个,加了比较器的值(仔细想想反而没什么用,计算时间反而还麻烦了些)。
头文件里就是声明了两个函数
#ifndef __BSP_EPIT_H #define __BSP_EPIT_H #include "imx6ul.h" void epit1_init(unsigned int flac,unsigned int count_start,unsigned int count_stop); void epit1_irqhandler(unsigned int gcciar,void *param); #endif
make的时候记得在makefile里添加路径!
使用定时器
这里使用定时器是在main.c函数中,导入bsp_epit.h头文件后调用初始化函数就可以了
epit1_init(0,33000000,0);
注意三个参数:第一个0是分频器,使用1分频,对应时钟66MHz,LR的值为33000000,CMPR的值为0,就是计数器数33000000次触发中断,而33000000刚好是66MHz的一般,刚好对应0.5s。整理一下,可以按照下面的公式计算
Tout = ((frac +1 )* value) / Tclk——Tout为EPIT的溢出时间(s)
frac为分频值,
Tclk为EPIT的时钟源(Hz)注意这个是Hz
按照上述流程,我们就可以使用EPIT定时器了。
PS:这个定时器不知道为什么,我使用了一个GPIO1_IO09做输出,在1分频下,LR值设置为3300时,示波器测量输出为10KHz,占空比50%,但是在高就只能到50KHz了,不知道是不是指令占的周期过长了。有时间用汇编写一个看看能好一些不!