目录
前言
在学习实验6:按键输入实验 和学习实验9:EPIT定时器实验中,我们都用了按键输入的功能,用到按键就要处理因为机械结构带来的按键抖动问题,也就是按键消抖,但每一次按键消抖我们都用的软件延时函数,这对于CPU的性能来说无疑是很大的浪费。
本讲实验我们使用正点原子开发板imx6ull,跟着官方例程来学习如何使用定时器来实现按键消抖,使用定时器既可以实现按键消抖,而且也不会浪费CPU 性能,这个也是 Linux 驱动里面按键消抖的做法。
在学习实验9:EPIT定时器实验 中我们学习了 EPIT 定时器,已经详细地介绍了配置EPIT定时器地原理,这是一个最简单的定时器;这一讲我们也使用定时器 EPTI1来完成整个实验。
实验介绍
原理
按键采用中断驱动方式,当按键按下以后触发按键中断,在按键中断中开启一个EPIT定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。
由图可知: t1~t3 这一段时间就是按键抖动,是需要消除的。
设置按键为下降沿触发,因此会在 t1、 t2 和 t3 这三个时刻会触发按键中断,每次进入中断处理函数都会重新开器定时器中断,所以会在 t1、 t2 和 t3 这三个时刻开器定时器中断。
但是 t1~t2 和 t2~t3 这两个时间段是小于我们设置的定时器中断周期(也就是消抖时间,比如 10ms),所以虽然 t1 开启了定时器,但是定时器定时时间还没到呢 t2 时刻就重置了定时器,最终只有 t3 时刻开启的定时器能完整的完成整个定时周期并触发中断。
我们就可以在t3 时刻的中断处理函数里面做按键处理了,这就是定时器实现按键防抖的原理。
实验步骤
使用 EPIT1 来配合按键 KEY来实现具体的消抖,有3个步骤:
- 配置按键 IO 中断:配置按键所使用的 IO,使用中断模式。
- 初始化消抖用的定时器:EPIT1 定时器,定时周期为 10ms。
- 编写中断处理函数:按键对应的 GPIO 中断处理函数和 EPIT1 定时器的中断处理函数。
- 按键的中断处理函数:主要用于开启 EPIT1 定时器,
- EPIT1 的中断处理函数:完成按键要做的具体任务,比如控制蜂鸣器打开或关闭。
实验资源
本讲实验效果:按下 KEY 会打开蜂鸣器,再次按下 KEY 就会关闭蜂鸣器。 LED0 作为系统提示灯不断的闪烁。
需要的硬件资源:
- 一个 LED 灯 LED0。
- 定时器 EPTI1。
- 一个按键 KEY。
- 一个蜂鸣器。
具体的原理分析、外设驱动,不理解的朋友们可以去我主页看看之前的几篇实验。
实验代码编写
bsp 文件夹下创建名为“keyfilter”的文件夹,然后在 bsp/keyfilter 中新建 bsp_keyfilter.c 和 bsp_keyfilter.h 这两个文件。
keyfilter.h
keyfilter.h 文件很简单,只是函数声明。
#ifndef _BSP_KEYFILTER_H
#define _BSP_KEYFILTER_H
/* 函数声明 */
void filterkey_init(void);
void filtertimer_init(unsigned int value);
void filtertimer_stop(void);
void filtertimer_restart(unsigned int value);
void filtertimer_irqhandler(void);
void gpio1_16_31_irqhandler(void);
#endif
keyfilter.c
文件 bsp_keyfilter.c 一共有 6 个函数:
- filterkey_init ( 按键初始化 )
- filtertimer_init(定时器 EPIT1 的初始化函数)
- filtertimer_stop(定时器EPIT1 关闭)
- filtertimer_restart(定时器EPIT1 重启)
- filtertimer_irqhandler(EPTI1 的中断处理函数)
- gpio1_16_31_irqhandler(GPIO1_IO18 的中断处理函数)
函数filterkey_init
此函数首先初始化了 KEY 所使用的 UART1_CTS 这个 IO,设置这个 IO 的中断模式,并且注册中断处理函数,最后调用函数 filtertimer_init 初始化定时器 EPIT1 定时周期为10ms。
/*
* @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 */
}
函数filtertimer_init
函数 filtertimer_init 是定时器 EPIT1 的初始化函数,内容基本和学习实验9中的 EPIT1 初始化函数一样。
/*
* @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);
}
函数 filtertimer_stop
函数 filtertimer_stop是定时器EPIT1 的关闭函数。
/*
* @description : 关闭定时器
* @param : 无
* @return : 无
*/
void filtertimer_stop(void)
{
EPIT1->CR &= ~(1<<0); /* 关闭定时器 */
}
函数filtertimer_restart
函数filtertimer_restart是定时器EPIT1 的重启函数。
/*
* @description : 重启定时器
* @param - value : 定时器EPIT计数值
* @return : 无
*/
void filtertimer_restart(unsigned int value)
{
EPIT1->CR &= ~(1<<0); /* 先关闭定时器 */
EPIT1->LR = value; /* 计数值 */
EPIT1->CR |= (1<<0); /* 打开定时器 */
}
函数filtertimer_irqhandler
函数filtertimer_irqhandler 是 EPTI1 的中断处理函数,此函数里面就是开启或者关闭蜂鸣器。
/*
* @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; /* 清除中断标志位 */
}
函数 gpio1_16_31_irqhandler
函数 gpio1_16_31_irqhandler 是 GPIO1_IO18 的中断处理函数,此函数负责重启定时。
/*
* @description : GPIO中断处理函数
* @param : 无
* @return : 无
*/
void gpio1_16_31_irqhandler(void)
{
/* 开启定时器 */
filtertimer_restart(66000000/100);
/* 清除中断标志位 */
gpio_clearintflags(GPIO1, 18);
}
main.c
main.c 文件只有一个 main 函数,在第 23 行调用函数 filterkey_init 来初始化带有消抖的按键,最后在 while 循环里面翻转 LED0,周期大约为 500ms。
整个实验代码就算是修改完成啦,是不是很简单。修改一下makefile,将我们的代码烧录试一试吧~
烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。
本例程的效果:按下 KEY 就会控制蜂鸣器的开关,并且 LED0 不断的闪烁,提示系统正在运行。