十.定时器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主要功能

  1. 可以选择时钟源的32位向下计数器(上图中显示,包括关闭时钟源有4种选项)
  2. 12位分频器(2^12=4095),对应1~4096分频可选择
  3. 计数器在到达比较值相等时会产生中断
  4. 计数器值可以动态变成
  5. 在低功耗模式和调试模式下可以编程

我们主要关注的是前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在注册服务函数前执行,基本上一上电的话就进入中断,程序基本上就不知道卡到哪了。

更换时钟源

这个在教程里没有讲,但是参考手册里写的比较清楚,虽然我们一般情况是不会更换时钟源的,但是专门强调了更换时钟源的流程

  1. 通过EPITx_CR的EN禁止EPIT(EN=0)
  2. 通过EPITx_OM的EN禁止EPIT输出(OM=00)
  3. 禁止EPIT中断
  4. 设置时钟源
  5. 清除EPITx_SR标志位(bit[1]写1)
  6. 将设置ENMOD=1,使EPIT从LR设定值或者0xFFFFFFFF开始倒数
  7. 使能EPIT(EN=1)
  8. 使能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
bsp_epit.h

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了,不知道是不是指令占的周期过长了。有时间用汇编写一个看看能好一些不!

 

posted @ 2022-01-12 23:57  银色的音色  阅读(508)  评论(0编辑  收藏  举报