EPIT定时器
EPIT 定时器简介
EPIT 的全称是: Enhanced Periodic Interrupt Timer,直译过来就是增强的周期中断定时器,它主要是完成周期性中断定时的。
关于这些寄存器详细的描述,请参考《I.MX6ULL 参考
手册》第 1174 页的 24.6 小节。
EPIT 是一个 32 位定时器,在处理器几乎不用介入的情况下提供精准的定时中断,软件使能以后 EPIT 就会开始运行, EPIT 定时器有如下特点:
①、时钟源可选的 32 位向下计数器。
②、 12 位的分频值。
③、当计数值和比较值相等的时候产生中断。
EPIT 定时器框图各部分的功能如下:
①、这是个多路选择器,用来选择 EPIT 定时器的时钟源, EPIT 共有 3 个时钟源可选择,ipg_clk、 ipg_clk_32k 和 ipg_clk_highfreq。
②、这是一个 12 位的分频器,负责对时钟源进行分频, 12 位对应的值是 0~4095,对应着1 ~ 4096 分频。
③、经过分频的时钟进入到 EPIT 内部,在 EPIT 内部有三个重要的寄存器:计数寄存器(EPIT_CNR)、加载寄存器(EPIT_LR)和比较寄存器(EPIT_CMPR),这三个寄存器都是 32 位的。EPIT 是一个向下计数器,也就是说给它一个初值,它就会从这个给定的初值开始递减,直到减为 0,计数寄存器里面保存的就是当前的计数值。如果 EPIT 工作在 set-and-forget 模式下,当计数寄存器里面的值减少到 0, EPIT 就会重新从加载寄存器读取数值到计数寄存器里面,重新开始向下计数。比较寄存器里面保存的数值用于和计数寄存器里面的计数值比较,如果相等的话就会产生一个比较事件。
④、比较器。
⑤、 EPIT 可以设置引脚输出,如果设置了的话就会通过指定的引脚输出信号。
⑥、产生比较中断请求,也就是定时中断。
EPIT 定时器有两种工作模式:
1、set-and-forget 模式: EPITx_CR(x=1, 2)寄存器的 RLD位 置1 的时候 EPIT 工作在此模式下,在此模式下 EPIT 的计数器从加载寄存器 EPITx_LR 中获取初始值,不能直接向计数器寄存器写入数据。不管什么时候,只要计数器计数到 0,那么就会从加载寄存器 EPITx_LR 中重新加载数据到计数器中,周而复始。(也就是51单片机中的方式2自动重装载。)
2、free-running 模式: EPITx_CR 寄存器的 RLD 位 置0的时候 EPIT 工作在此模式下,当计数器计数到 0以后会重新从 0XFFFFFFFF开始计数,并不是从加载寄存器 EPITx_LR中获取数据。
EPIT 的配置寄存器 EPITx_CR,控制EPIT整体模式。
寄存器 EPITx_CR 我们用到的重要位如下:
CLKSRC(bit25:24): EPIT 时钟源选择位,
为 0 的时候关闭时钟源,
为1 的时候选择选择Peripheral 时钟(ipg_clk),
为 2 的时候选择 High-frequency 参考时钟(ipg_clk_highfreq),
为 3 的时候选择 Low-frequency 参考时钟(ipg_clk_32k)。
在本例程中,我们设置为 1,也就是选择 ipg_clk作为 EPIT 的时钟源, ipg_clk=66MHz。
PRESCALAR(bit15:4): EPIT 时钟源分频值,可设置范围 0~ 4095,分别对应 1~ 4096 分频。
RLD(bit3): EPIT 工作模式,
为 0 的时候工作在 free-running 模式,
为 1 的时候工作在 set-and-forget 模式。
本章例程设置为 1,也就是工作在 set-and-forget 模式。
OCIEN(bit2):比较中断使能位,
为 0 的时候关闭比较中断,
为 1 的时候使能比较中断,
本章试验要使能比较中断。
ENMOD(bit1):设置计数器初始值,
为 0 时计数器初始值等于上次关闭 EPIT 定时器以后计数器里面的值,
为 1 的时候来源于加载寄存器。
EN(bit0): EPIT 使能位,
为 0 的时候关闭 EPIT,
为 1 的时候使能 EPIT。
寄存器 EPITx_SR ,
寄存器 EPITx_SR 只有一个位有效,那就是 OCIF(bit0),这个位是比较中断标志位,
为 0 的时候表示没有比较事件发生,
为 1 的时候表示有比较事件发生。
当比较中断发生以后需要手动清除此位,此位是写 1 清零的。
EPIT 的配置步骤如下:
1、设置 EPIT1 的时钟源
设置寄存器 EPIT1_CR 寄存器的 CLKSRC(bit25:24)位,选择 EPIT1 的时钟源。
2、设置分频值
设置寄存器 EPIT1_CR 寄存器的 PRESCALAR(bit15:4)位,设置分频值。
3、设置工作模式
设置寄存器 EPIT1_CR 的 RLD(bit3)位,设置 EPTI1 的工作模式。
4、设置计数器的初始值来源
设置寄存器 EPIT1_CR 的 ENMOD(bit1)位, 设置计数器的初始值来源。
5、 使能比较中断
我们要使用到比较中断,因此需要设置寄存器 EPIT1_CR 的 OCIEN(bit2)位,使能比较中断。
6、设置加载值和比较值
设置寄存器 EPIT1_LR 中的加载值和寄存器 EPIT1_CMPR 中的比较值,通过这两个寄存器
就可以决定定时器的中断周期。
7、 EPIT1 中断设置和中断服务函数编写
使能 GIC 中对应的 EPIT1 中断,注册中断服务函数,如果需要的话还可以设置中断优先级。最后编写中断服务函数。
8、使能 EPIT1 定时器
配置好 EPIT1 以后就可以使能 EPIT1 了,通过寄存器 EPIT1_CR 的 EN(bit0)位来设置。
总结:
步骤1-6可以通过EPIT定时器框图记忆,一步一步顺着来,设置时钟源、分频值、工作模式、初始值、比较、加载值和比较值;
步骤7、8就是中断系统那部分,EPIT想要使用中断,先开启GIC中断号的对应中断源中断,再向中断服务函数表注册自己的中断服务函数,最后使能自己的中断。
驱动编写
bsp_epittimer.h
#ifndef _BSP_EPITTIMER_H
#define _BSP_EPITTIMER_H
#include "imx6ul.h"
#include "bsp_int.h"
#include "bsp_led.h"
void epit1_init(unsigned int frac,unsigned int value);
void epit1_irqhandler(void);
#endif
bsp_epittimer.c
#include "bsp_epittimer.h"
/*
* @description : 初始化 EPIT 定时器.
* EPIT 定时器是 32 位向下计数器,时钟源使用 ipg=66Mhz
* @param – frac : 分频值,范围为 0~4095,分别对应 1~4096 分频。
* @param - value : 倒计数值。
* @return : 无
*/
void epit1_init(unsigned int frac,unsigned int value)
{
frac=(frac>0xfff?0xfff:frac); //限幅
//1、EPIT寄存器配置
EPIT1->CR = 0; //配置先清零 方便后续配置 都可
EPIT1->CR |= (1<<24);//时钟源选择 Peripheral clock=66MHz
EPIT1->CR |= (frac<<4);//设置分频值
EPIT1->CR |= (1<<3);//set-and-forget 模式 LR 重新加载数值
EPIT1->CR |= (1<<2);//比较中断使能
EPIT1->CR |= (1<<1);//初始计数值来源于 LR 寄存器值
EPIT1->LR = value;//加载寄存器值
EPIT1->CMPR = 0;//比较寄存器值
//2、向GIC申请中断
GIC_EnableIRQ(EPIT1_IRQn);
system_register_irqhandler(EPIT1_IRQn,(system_irq_handler_t)epit1_irqhandler,NULL);
EPIT1->CR |= (1<<0); //EPIT1使能中断
}
/*EPIT 中断处理函数 */
void epit1_irqhandler(void)
{
static unsigned char state = 0;
//查看中断请求再次确认中断
if(EPIT1->SR & 0x01)
{
state=!state;
led_switch(state);
}
EPIT1->SR |= (1<<0);//写1清除中断标志
}
总结
学完定时器,发现目前最重要的驱动就是时钟系统和中断系统的配置,其他的灯、蜂鸣器、定时器、GPIO都属于外设,如果外设想使用中断就找中断系统的GIC,把中断号给GIC,让它开启中断源中断,再在它那儿注册自己的中断服务函数,最后开启自己这边儿的外设中断即可。
已经写了这么多驱动程序了,最终都是配置寄存器,记住那种框图,一步一步来,重要的是学会配置的这个框架思路。
定时器按键消抖
定时器按键消抖原理
按键消抖:按键按下以后延时一段时间再去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。
常用方法有:延时函数消抖、定时器消抖。
延时函数消抖有一个缺点,就是延时函数会浪费 CPU 性能,因为延时函数就是空跑。
定时器按键消抖采用中断驱动方式,当按键按下以后触发按键中断,在按键中断中开启一个定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。
在图中 t1~t3 这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发,因此会 在 t1、 t2 和 t3 这三个时刻会触发按键中断,每次进入中断处理函数都会重新开器定时器中断,所以会在 t1、 t2 和 t3 这三个时刻开启定时器中断。 但是 t1 ~ t2 和 t2 ~ t3 这两个时间段是小于我们设置的定时器中断周期(也就是消抖时间,比如 10ms),所以虽然 t1 开启了定时器,但是定时器定时时间还没到呢 t2 时刻就重置了定时器,最终只有 t3 时刻开启的定时器能完整的完成整个定时周期并触发中断,我们就可以在中断处理函数里面做按键处理了,这就是定时器实现按键防抖的原理, Linux 里面的按键驱动用的就是这个原理!
使用 EPIT1 来配合按键 KEY来实现具体的消抖,步骤如下:
1、配置按键 IO 中断
配置按键所使用的 IO,因为要使用到中断驱动按键,所以要配置 IO 的中断模式。
2、初始化消抖用的定时器
定时器的定时周期为 10ms,也可根据实际情况调整定时周期。
3、编写中断处理函数
需要编写两个中断处理函数:按键对应的 GPIO 中断处理函数和 EPIT1 定时器的中断处理函数。在按键的中断处理函数中主要用于开启 EPIT1 定时器,EPIT1 的中断处理函数才是重点,按键要做的具体任务都是在定时器 EPIT1 的中断处理函数中完成的,比如控制蜂鸣器打开或关闭。
驱动编写
bsp_keyfilter.h
#ifndef _BSP_KEYFILTER_H
#define _BSP_KEYFILTER_H
#include "imx6ul.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_led.h"
void keyfilter_init(unsigned int value);
void filtertimer_stop(void);
void filtertimer_restart(unsigned int value);
void gpio1_16_31_irqhandler(void);
void filtertimer_irqhandler(void);
#endif
bsp_keyfilter.c
#include "bsp_keyfilter.h"
//按键初始化 gpio epit 初始化
void keyfilter_init(unsigned int value)
{
//1、IO复用 IO属性配置
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF0B0);
//2、GPIO输入输出中断等配置
gpio_pin_config_t key_config;
key_config.direction = kGPIO_DigitalInput;
key_config.outputLogic = 1;
key_config.interruptMode = kGPIO_IntFallingEdge;
gpio_init(GPIO1,18,&key_config);
//3、GPIO外设中断初始化
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_16_31_irqhandler,NULL);
gpio_enableint(GPIO1,18);
//1、定时器初始化
EPIT1->CR = 0;
EPIT1->CR |= (1<<24);
EPIT1->CR |= (1<<3);
EPIT1->CR |= (1<<2);
EPIT1->CR |= (1<<1);
EPIT1->CR |= (0<<4); //默认1分频
EPIT1->LR = value;
EPIT1->CMPR = 0;
//2、定时器中断初始化
GIC_EnableIRQ(EPIT1_IRQn);
system_register_irqhandler(EPIT1_IRQn,(system_irq_handler_t)filtertimer_irqhandler,NULL);
}
//关闭定时器
void filtertimer_stop(void)
{
EPIT1->CR &= ~(1<<0);
}
//重启定时器
void filtertimer_restart(unsigned int value)
{
EPIT1->CR &= ~(1<<0);
EPIT1->LR = value;
EPIT1->CR |= (1<<0);
}
//GPIO 中断处理函数
void gpio1_16_31_irqhandler(void)
{
filtertimer_restart(66000000/100); //重新计数
gpio_clearintflags(GPIO1,18);
}
//定时器中断处理函数
void filtertimer_irqhandler(void)
{
static unsigned char state=0;
if(EPIT1->SR & 0x01)
{
filtertimer_stop();
if(gpio_pinread(GPIO1,18)==0)
{
state=!state;
led_switch(state);
}
}
EPIT1->SR |= (1<<0);
}
总结
本文使用GPIO下降沿中断和定时器延时共同完成按键中断,使用两个中断的好处是不会将CPU的资源浪费在延时函数上,大大提高执行效率,前人造好的轮子,只要能看懂就能拿来用,同时加入自己的思想,思路是先判断外部中断并开启定时器,再判断定时器中断,在定时器写相关任务,按键结束定时器关闭。