EPIT定时器中断实现—基于I.MX6UL嵌入式SoC

1、前言

定时器是嵌入式SoC常用的外设,通过使用定时器能达到精准定时的功能,一款嵌入式SoC往往会存在多个定时器外设,在嵌入式裸机开发过程中,对定时器的原理以及定时器的使用了解是非常有必要的,本文将简单介绍I.MX6UL嵌入式SoC中的EPIT定时器原理。

2、EPIT基本概述

EPIT的全称为Enhanced Periodic Interrupt Timer,也就是增强型的周期中断定时器,它是一个32bit的定时器,主要用来完成周期性定时功能的,该定时器能在处理器几乎不用介入的情况下完成准确的定时中断功能,使用软件使能便可以开始运行,EPIT定时器具有以下的特性:

  •  时钟源可选择的32bit递减计数器;
  • 12bit可设置的预分频器,用于输入时钟频率分频;
  • 可以即时编程的计数器值;
  • 在低功耗和调试模式下能够编程为活动状态;
  • 计数器计数值达到比较值时可以产生中断。

EPIT定时器的总体结构框图如下所示:

框图中的各部分要实现的功能如下:

(1)EPIT定时器的多路时钟源选择器,用来提供EPIT的时钟源,时钟源的选择如下:

  • ipg_clk:由时钟控制模块CCM提供的Peripheral clock;
  • ipg_clk_32k:低频率参考时钟,32kHz的参考时钟,来自外部的32kHz晶振;
  • ipg_clk_highfreq:由时钟控制模块CCM提供的高频率参考时钟。

(2)12bit的预分频器,用于对输入的时钟源进行分频,12bit对应的值为0~4095,也就是可以设置为1~4096分频。

(3)在EPIT内部有3个重要的寄存器,分别为Counter Register、Load Resiger和Compare Register,这3个寄存器都是32bit长度的,Counter Register就是计数寄存器,EPIT是一个向下计数的定时器,给予它一个初值,便会从这个初始值开始递减,直到为0,Counter Register就保存了当前的计数值,Load Register为装载寄存器,当EPIT被设置为set-and-forget模式,当计数器递减到0后,EPIT就会读取Load Register保存的值到Counter Register,并且重新开始计数,Compare Register为比较寄存器,用于和Counter Register中的值进行比较,如果相等的话能产生一个比较事件。

(4)比较器,用于Counter Register和Compare Register中的值进行比较,可以产生比较事件。

(5)EPIT能通过相关的寄存器设置外设引脚输出,如果进行了相应的配置的话,能通过指定引脚输出信号。

(6)EPIT定时器中断。

接下来,简单分析一下EPIT定时器的相关工作模式:

EPIT定时器具有两种工作模式,分别为free-running模式和set-and-forget模式,通过EPIT_CR[RLD]能设置相关的工作模式,两种工作模式的区别如下:

  • 当EPIT_CR寄存器的RLD被清0的时候,EPIT工作在free-running模式,当计数器的值到0后,计数器的值会进行翻转变为0xFFFF_FFFF,并重新开始计数,而不是从Load Register中获取数据;
  • 当EPIT_CR寄存器的RLD被置1的时候,EPIT工作在set-and-forget模式,在该模式下,EPIT定时器的计数值从Load Register中获取初始值,此时不能直接向Counter Register中写入数据,当计数器的值递减为0后,将会从Load Register中加载数据到计数器,周而复始运行。

EPIT定时器中使用到的寄存器有EPITx_CR、EPITx_SR、EPITx_LR、EPITx_CMPR以及EPITx_CNR,常用的寄存器介绍如下:

首先是EPITx_CR,也叫做EPIT控制寄存器,该寄存器的描述如下:

EPITx_CR寄存器中比较重要的bit如下:

CLKSRC(bit[25:24]):用来设置EPIT的时钟源,能进行配置的选项如下:

CLKSRC(bit[25:24]) 时钟源
00 关闭Clock
01 Peripheral clock
10 High-frequency reference clock
11 Low-frequency reference clock

PRESCALAR(bit[15:4]):EPIT时钟源的预分频值,12bit,能设置的值为0x000~0xFFF,对应分频的值为1~4096。

RLD(bit3):用于设置EPIT的工作模式,该bit为0的时候工作在free-running模式,为1的时候工作在set-and-forget模式。

OCIEN(bit2):比较中断使能位,设置为0的时候将禁止比较中断,设置为1的时候将使能比较中断。

ENMOD(bit1):用来设置计数器的初始值,设置为0时,计数器的初始值等于上次关闭EPIT定时器中计数器的值,设置为1时,初始值取决于RLD的配置,如果RLD=1时,计数器的值来源于Load Register,如果RLD=0,则计数器的值为0xFFFF_FFFF。

EN(bit0):EPIT定时器使能位,设置为0时禁止EPIT定时器,设置为1时使能EPIT定时器。

接下来是EPITx_SR,也叫做EPIT状态寄存器,该寄存器具有一个状态位是用于输出比较事件的,寄存器的描述如下:

OCIF就是状态位,该位为0表示没有比较事件发生,该位为1表示有比较事件发生,当比较中断发生以后需要手动清除此位,对该位写1能进行清0。

EPITx_LR就是EPIT装载寄存器,当EPIT_CR的RLD=1时,EPIT的计数器递减到0后,EPITx_LR中的数据值就会被装载到计数器中,并重新开始计数,该寄存器的描述如下:

EPITx_CMPR为EPIT比较寄存器,该寄存器中的数据值决定了一个比较事件什么时候产生,该寄存器的描述如下:

EPITx_CNR为EPIT计数器,该寄存器包含了当前的计数值,为只读寄存器,该寄存器的描述如下:

关于EPIT定时器相关寄存器介绍就先到这,接下来使用一个实例讲解EPIT定时器中断实现的具体步骤。

3、EPIT定时器中断实现

EPIT定时器编程的使用步骤如下:

  • 设置EPIT定时器的时钟源,可以通过设置EPITx_CR寄存器中的CLKSRC(bit[25:24]),选择EPIT定时器的时钟源;
  • 设置EPIT定时器的预分频值,可以通过设置EPITx_CR寄存器中的PRESCALAR(bit[15:4]),配置EPIT定时器时钟分频值;
  • 设置EPIT定时器的工作模式,可以通过设置EPITx_CR寄存器中的RLD(bit3),选择EPIT定时器的工作模式;
  • 设置EPIT定时器中计数器的初始值来源,可以通过设置EPITx_CR的ENMOD(bit1),配置计数器的初值来源;
  • 使能EPIT定时器的比较中断功能,可以通过设置EPITx_CR的OCIEN(bit2),需要使用定时器比较中断功能的话,要将该bit置1;
  • 设置EPIT定时器的装载值以及比较值,分别通过设置EPITx_LR和EPITx_CMPR寄存器来实现;
  • 实现EPIT定时器的中断处理服务函数;
  • 使能EPIT定时器以及EPIT对应的中断。

在NXP提供的SDK包的MCIMX6G2.h文件中,包含了EPIT相关寄存器的封装,如下:

/** EPIT - Register Layout Typedef */
typedef struct {
  __IO uint32_t CR;                                /**< Control register, offset: 0x0 */
  __IO uint32_t SR;                                /**< Status register, offset: 0x4 */
  __IO uint32_t LR;                                /**< Load register, offset: 0x8 */
  __IO uint32_t CMPR;                              /**< Compare register, offset: 0xC */
  __I  uint32_t CNR;                               /**< Counter register, offset: 0x10 */
} EPIT_Type;

/* EPIT - Peripheral instance base addresses */
/** Peripheral EPIT1 base address */
#define EPIT1_BASE                               (0x20D0000u)
/** Peripheral EPIT1 base pointer */
#define EPIT1                                    ((EPIT_Type *)EPIT1_BASE)
/** Peripheral EPIT2 base address */
#define EPIT2_BASE                               (0x20D4000u)
/** Peripheral EPIT2 base pointer */
#define EPIT2                                    ((EPIT_Type *)EPIT2_BASE)

在编写EPIT定时器中断裸机实例时,配置EPIT相关寄存器时,直接使用EPIT_Type*结构体指针即可,接下来,在前面的裸机基础上进行开发:

实现EPIT定时器中断的驱动模块,进入到工程的bsp目录,新创建epit目录:

$ cd bsp/bsp
$ mkdir epit
$ cd epit
$ touch bsp_epit.h
$ touch bsp_epit.c

新创建的bsp_epit.h文件内容如下:

#ifndef __BSP_EPIT_H
#define __BSP_EPIT_H

#include "imx6ul.h"

/* 相关函数声明 */
void epit1_init(unsigned int prescalar, unsigned int value);
void epit1_irqhandler(unsigned int giccIar, void *userParam);

#endif

该文件是EPIT1定时器驱动函数的声明,新创建的bsp_epit.c文件内容如下:

#include "bsp_epit.h"
#include "bsp_int.h"
#include "bsp_led.h"

/**
 * epit1_init() - EPIT1定时器初始化函数
 * 
 * @prescalar: 分频值,能设置的范围为1~4096
 * @value: 装载值 
 * 
 * @return: 无
 */
void epit1_init(unsigned int prescalar, unsigned int value)
{
    if (prescalar > 4096)
        prescalar = 4096;
    
    EPIT1->CR = 0;  /* 将CR寄存器清0 */

    /**
     * 配置EPIT1的CR控制寄存器 
     * bit [25:24]: 01 EPIT1时钟选择Peripheral Clock=66MHz
     * bit [15:4]: prescalar-1 分频器值
     * bit [3]: 1 计数器向下计数到0后从LR重新加载计数器
     * bit [2]: 1 比较中断使能
     * bit [1]: 1 初始计数值来于LR寄存器值 
     * bit [0]: 0 先关闭EPIT1定时器
     */
    EPIT1->CR = (1 << 24 | (prescalar - 1) << 4 | 0xe << 0);
    EPIT1->LR = value;  /* 定时器加载寄存器 */
    EPIT1->CMPR = 0;    /* 定时器比较寄存器 */

    GIC_EnableIRQ(EPIT1_IRQn);  /* 使能GIC相应的EPIT1中断 */

    /* 注册EPIT1的中断服务函数 */
    system_register_irqhandler(EPIT1_IRQn,
        (system_irq_handler_t)epit1_irqhandler, NULL);
    
    EPIT1->CR |= 1 << 0; /* 使能EPIT1定时器 */
}

/**
 * epit1_irqhandler() - EPIT1中断服务处理函数
 * 
 * @giccIar: 中断号
 * @userParam: 用户参数 
 * 
 * @return: 无
 */
void epit1_irqhandler(unsigned int giccIar, void *userParam)
{
    static unsigned char led1_state;
    
    led1_state = !led1_state;   /* LED1状态翻转 */
    if (EPIT1->SR & (1 << 0))
        led_switch(LED1, led1_state);

    EPIT1->SR |= 1 << 0;    /* 清除EPIT1中断标志 */   
}

bsp_epit.c文件中的两个函数epit1_init()和epit1_irqhandler(),分别是EPIT1定时器的初始化函数和中断服务处理函数,在epit1_init()函数中,设置比较寄存器的值为0,当定时器的计数器计数到0后,将会触发中断,epit1_init()函数具有两个参数,prescalar是定时器时钟的分频值,value是定时器的装载值,通过这两个参数以及输入的时钟频率,能决定定时器的中断频率,计算如下:

Tout = (prescalar * value) / Tclk

其中,Tclk为EPIT1定时器的输入时钟频率,单位为Hz,Tout为EPIT1的定时时间,单位为S。

例如,当设置EPIT1定时器的工作模式为set-and-forget,并且选择输入的时钟源为ipg_clk=66MHz,要设置定时中断周期为1S,可以设置时钟分频值prescalar为66,也就是66分频,哪么装载值就是66000000/66=1000000,value的值就应该填入1000000。

函数epit1_irqhandler()为EPIT1定时器的中断处理函数,该函数调用后,会先读取EPIT1_SR寄存器的值,判断是否有中断比较事件产生,如果是的话,将进行相应的处理,另外,在函数调用的最后,需要清除中断标志位。

app.c文件中的内容如下:

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_gpio.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"
#include "bsp_epit.h"

/**
 * main() - 主函数
 */
int main(void)
{
    interrupt_init();       /* 中断初始化 */
    imx6ul_clk_init();      /* 初始化相关时钟 */
    system_clk_enable();    /* 系统外设时钟使能 */
    led_init();             /* LED灯初始化 */
    gpio_exit_init();       /* GPIO外部中断初始化 */
    epit1_init(66, 1000000);/* EPIT1定时器初始化 */

    while (1) {

    }

    return 0;
}

main()函数比较简单,需要调用epit1_init()函数进行定时器的初始化,最后,编译相应的文件生成可执行的.imx文件,烧写到I.MX6UL目标板中验证即可。

4、小结

本文主要简单介绍了I.MX6UL嵌入式SoC中的EPIT定时器原理以及使用机制。

原文地址:https://www.cnblogs.com/Cqlismy/p/12977491.html