十.定时器EPIT——2按键消抖
在讲中断那一章我们留了个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
除了最后控制蜂鸣器的函数整个流程的备注还是很清楚的。在main.c里直接引用就可以了。
keyfilter_init(); //使用通过中断消抖的按键