在讲中断那一章我们留了个BUG:中断服务函数应该是快进快出的,中间是严禁使用定时函数的,那样会严重浪费CPU的性能,在delay中什么事都做不了。而机械按键确实要考虑到消抖的效果,所以使用定时器来实现功能。
定时器消抖原理
按键消抖的原理网上一搜一大把,这里就不说了,总之就是按下按钮当时不反应,过一会再去确认一下按键的值。这个“过一会”我们前面都是通过延时去刷新的,那么在延时这段时间里,CPU就阻塞了,什么也干不了。如果改用定时器,就可以设置好定时,时间到了触发中断,那么在定时器计时这段时间里是不需要CPU资源的,只需要等时间到了触发中断事件即可。
借用教程里面的图例,上面的电平就是类似按键抖动的电平变化效果,我们可以定义按键下降沿的时候启动EPIT定时器,比如t1,启动了10ms的定时,但是还没到10ms时t2处又将定时器重置了,而t3时间将定时器重置,t3过10ms内没有新的下降沿重置定时器,就触发定时中断,在这个中断事件响应我们要实现的功能就可以了。
使用EPIT进行按键消抖的流程
根据前面说的消抖原理,整个流程应该是这样的:
- 初始化按键中断、EPIT定时器(EPIT只初始化,不启动)
- 按键按下,触发按键中断
- 按键中断服务启动EPIT定时器,清除中断标志位
- 定时器未到时,按键抖动,重新触发中断引发定时器重新计时
- 定时器到时,EPIT定时中断触发,清除中断标志位
- EPIT中断服务函数被调用。要注意的是,我们需要响应按键按下功能的函数应该是对应EPIT中断服务函数,按键的中断只是引发EPIT计时器启动。
代码构成
bsp文件夹下新建目录
这个代码没什么细节需要讲,主要就是通过一个中断启动另一个中断。并且在定时中断中只执行一个定时循环。
#include "bsp_keyfilter.h"
#include "bsp_int.h"
#include "bsp_beep.h"
/*
* @description : 带消抖的按键初始化
* @param : None
* @return : None
*/
void keyfilter_init(void)
{
gpio_pin_config_t key_config;
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //GPIO复用初始化
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080); //GPIO电气初始化
key_config.direction = kGPIO_DigitalInput; //GPIO设置为输入方向
key_config.interruptMode = kGPIO_IntFallingEdge; //GPIO中断信号为下降沿触发
gpio_init(GPIO1,18,&key_config); //GPIO初始化
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); //GIC使能GPIO1中断
system_register_irqHandler(GPIO1_Combined_16_31_IRQn,
gpio1_16_31_irqhandler,
NULL); //注册中断函数
gpio_intenable(GPIO1,18); //GPIO中断使能
// filtertimer_init(66000000/100); //按键消抖定时器初始化
filtertimer_init();
}
/*
* @description : 按键消抖定时器初始化
* @param-value : EPIT_LR寄存器设定值
* @return : None
*/
void filtertimer_init(void)
{
EPIT1->CR = 0; //清除EPIT1_CR
EPIT1->CR |= (1<<1) |(1<<2) |(1<<3) |(1<<24); //设置EPIT1_CR
EPIT1->LR = 0; //设置EPIT1_LR
EPIT1->CMPR = 0; //设置EPIT1_CMPR
GIC_EnableIRQ(EPIT1_IRQn); //GIC使能EPIT1
system_register_irqHandler(EPIT1_IRQn,
filter_irqhandler,
NULL); //EPIT1中断服务函数注册
}
/*
* @description : GPIO1_IO16~31对应中断服务
* @param : None
* @return : None
*/
void gpio1_16_31_irqhandler(int gicciar,void *param)
{
/*开启定时器*/
filtertimer_restart(66000000/100); //重启EPIT定时器,定时10ms
gpio_clearIntFlags(GPIO1,18); //清除gpio中断标志位
}
/*
* @description : EPIT1定时停止
* @param : None
* @return : None
*/
void filtertimer_stop(void)
{
EPIT1->CR &= ~(1<<0);
}
/*
* @description : EPIT1定时功能重启
* @param-value : LR的寄存器的值
* @return : None
*/
void filtertimer_restart(unsigned int value)
{
EPIT1->CR &= ~(1<<0);
EPIT1->LR = value;
EPIT1->CR |= 1<<0;
}
/*
* @description : EPIT1中断服务函数
* @param : None
* @return : None
*/
void filter_irqhandler(int gicciar,void *param)
{
static unsigned char state = OFF;
if(EPIT1->SR &= (1<<0)) //EPIT定时10ms时间到
{
filtertimer_stop();
if(gpio_pinread(GPIO1,18) == 0)
{
state = !state;
beep_switch(state);
}
}
EPIT1->SR |= (1<<0); //清除EPIT1_SR标志位
}
当然还有头文件
#ifndef __BSP_KEYFILTER_H
#define __BSP_KEYFILTER_H
#include "imx6ul.h"
#include "bsp_gpio.h"
void filtertimer_stop(void);
void filtertimer_restart(unsigned int value);
void keyfilter_init(void);
void filtertimer_init(void);
void gpio1_16_31_irqhandler(int gicciar,void *param);
void filter_irqhandler(int gicciar,void *param);
#endif#ifndef __BSP_KEYFILTER_H
#define __BSP_KEYFILTER_H
#include "imx6ul.h"
#include "bsp_gpio.h"
void filtertimer_stop(void);
void filtertimer_restart(unsigned int value);
void keyfilter_init(void);
void filtertimer_init(void);
void gpio1_16_31_irqhandler(int gicciar,void *param);
void filter_irqhandler(int gicciar,void *param);
#endif
bsp_keyfilter.h
除了最后控制蜂鸣器的函数整个流程的备注还是很清楚的。在main.c里直接引用就可以了。
keyfilter_init(); //使用通过中断消抖的按键