1 按键消抖的原理
其实就是在按键按下以后延时一段时间再 去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。
延时函数实现 会浪费 CPU 性能,因为延时函数就是空跑。
我们可以借助定时器来实现消抖,按键采用中断驱动方式,
当按键按下以后触发按键中断,在 按键中断中开启一个定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最 后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的 按键。定时器按键消抖如图 19.1.1 所示:
在图 19.1.1 中 t1~t3 这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发,因 此会在 t1、t2 和 t3 这三个时刻会触发按键中断,每次进入中断处理函数都会重新开器定时器中 断,所以会在 t1、t2 和 t3 这三个时刻开器定时器中断。但是 t1~t2 和 t2~t3 这两个时间段是小于 我们设置的定时器中断周期(也就是消抖时间,比如 10ms),所以虽然 t1 开启了定时器,但是定 时器定时时间还没到呢 t2 时刻就重置了定时器,最终只有 t3 时刻开启的定时器能完整的完成 整个定时周期并触发中断,我们就可以在中断处理函数里面做按键处理了,这就是定时器实现 按键防抖的原理,Linux 里面的按键驱动用的就是这个原理!
2 编写驱动代码
整合按键中断及EPTI定时器中断,按键中断服务函数内开启EPTI定时器中断 ,下一步,将按键中断中的延迟函数 通过EPTI定时器触发 在服务函数中检查电平输出值 ,按键按下 触发蜂鸣器 ,关闭EPTI定时器中断 按键中断标志位
- 初始化IO 按键 GPIO)IOMUXC_UART1_CTS_B_GPIO1_IO18
- 初始化GPIO为中断
- 使能 GPIO 中断,并且注册中断处理函数 gpio1_16_31_irqhandler
- 初始化EPTI1定时器 并且注册中断处理函数 filtertimer_irqhandler
- 按键中断服务函数 gpio1_16_31_irqhandler 开启定时器 清除中断标志 图 5-1
- filtertimer_irqhandler 关闭 filtertimer_stop 定时器读取按键引脚电平 清除定时器中断标志位 图 6-1
图 5-1
void gpio1_16_31_irqhandler(void)
{
/* 开启定时器 */
filtertimer_restart(66000000/100);
/* 清除中断标志位 */
gpio_clearintflags(GPIO1, 18);
}
图 6-1
/*
* @description : 定时器中断处理函数
* @param : 无
* @return : 无
*/
void filtertimer_irqhandler(void)
{
static unsigned char state = OFF;if(EPIT1->SR & (1<<0)) /* 判断比较事件是否发生 */
{
filtertimer_stop(); /* 关闭定时器 */
if(gpio_pinread(GPIO1, 18) == 0) /* KEY0 */
{
state = !state;
beep_switch(state); /* 反转蜂鸣器 */
}
}
EPIT1->SR |= 1<<0; /* 清除中断标志位 */
}
完整代码 bsp_keyfilter.c
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_keyfilter.c
作者 : 左忠凯
版本 : V1.0
描述 : 定时器按键消抖驱动。
其他 : 按键采用中断方式,按下按键触发按键中断,在按键中断里面
使能定时器定时中断。使用定时器定时中断来完成消抖延时,
定时器中断周期就是延时时间。如果定时器定时中断触发,
表示消抖完成(延时周期完成),即可执行按键处理函数。
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/5 左忠凯创建
***************************************************************/
#include "bsp_key.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_beep.h"
#include "bsp_keyfilter.h"
/*
* @description : 按键初始化
* @param : 无
* @return : 无
*/
void filterkey_init(void)
{
gpio_pin_config_t key_config;
/* 1、初始化IO复用 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */
/* 2、、配置GPIO1_IO18的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 11 默认22K上拉
*bit [13]: 1 pull功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 000 关闭输出
*bit [0]: 0 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
/* 3、初始化GPIO为中断 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
key_config.outputLogic = 1;
gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
/* 注册中断服务函数 */
system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
(system_irq_handler_t)gpio1_16_31_irqhandler,
NULL);
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
filtertimer_init(66000000/100); /* 初始化定时器,10ms */
}
/*
* @description : 初始化用于消抖的定时器,默认关闭定时器
* @param - value : 定时器EPIT计数值
* @return : 无
*/
void filtertimer_init(unsigned int value)
{
EPIT1->CR = 0; //先清零
/*
* CR寄存器:
* bit25:24 01 时钟源选择Peripheral clock=66MHz
* bit15:4 0 1分频
* bit3: 1 当计数器到0的话从LR重新加载数值
* bit2: 1 比较中断使能
* bit1: 1 初始计数值来源于LR寄存器值
* bit0: 0 先关闭EPIT1
*/
EPIT1->CR = (1<<24 | 1<<3 | 1<<2 | 1<<1);
/* 计数值 */
EPIT1->LR = value;
/* 比较寄存器,当计数器值和此寄存器值相等的话就会产生中断 */
EPIT1->CMPR = 0;
GIC_EnableIRQ(EPIT1_IRQn); /* 使能GIC中对应的中断 */
/* 注册中断服务函数 */
system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)filtertimer_irqhandler, NULL);
}
/*
* @description : 关闭定时器
* @param : 无
* @return : 无
*/
void filtertimer_stop(void)
{
EPIT1->CR &= ~(1<<0); /* 关闭定时器 */
}
/*
* @description : 重启定时器
* @param - value : 定时器EPIT计数值
* @return : 无
*/
void filtertimer_restart(unsigned int value)
{
EPIT1->CR &= ~(1<<0); /* 先关闭定时器 */
EPIT1->LR = value; /* 计数值 */
EPIT1->CR |= (1<<0); /* 打开定时器 */
}
/*
* @description : 定时器中断处理函数
* @param : 无
* @return : 无
*/
void filtertimer_irqhandler(void)
{
static unsigned char state = OFF;
if(EPIT1->SR & (1<<0)) /* 判断比较事件是否发生 */
{
filtertimer_stop(); /* 关闭定时器 */
if(gpio_pinread(GPIO1, 18) == 0) /* KEY0 */
{
state = !state;
beep_switch(state); /* 反转蜂鸣器 */
}
}
EPIT1->SR |= 1<<0; /* 清除中断标志位 */
}
/*
* @description : GPIO中断处理函数
* @param : 无
* @return : 无
*/
void gpio1_16_31_irqhandler(void)
{
/* 开启定时器 */
filtertimer_restart(66000000/100);
/* 清除中断标志位 */
gpio_clearintflags(GPIO1, 18);
}
main函数 后续代码逻辑同通用BSP 业务处理流程
/*
* @description : main函数
* @param : 无
* @return : 无
*/
int main(void)
{
unsigned char state = OFF;
int_init(); /* 初始化中断(一定要最先调用!) */
imx6u_clkinit(); /* 初始化系统时钟 */
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
filterkey_init(); /* 带有消抖功能的按键 */
while(1)
{
state = !state;
led_switch(LED0, state);
delay(500);
}
return 0;
}