Linux学习笔记15---定时器按键消抖实验
在之前的实验中都用到了按键,用到按键就要处理因为机械结构带来的按键抖动问题,也就是按键消抖。前面的实验中都是直接使用了延时函数来实现消抖,因为简单,但是直接用延时函数来实现消抖会浪费 CPU
性能,因为在延时函数里面
CPU
什么都做不了。 如果按键使用中断的话更不能在中断里面使用延时函数,因为中断服务函数要快进快出!本章我们学习如何使用定时器来实现按键消抖,使用定时器既可以实现按键消抖,而且也不会浪费CPU 性能,这个也是
Linux
驱动里面按键消抖的做法。
1、 定时器按键消抖简介
按键消抖的原理在第十一章已经详细的讲解了,其实就是在按键按下以后延时一段时间再去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。但是这有一个缺点,就是延时函数会浪费 CPU
性能,因为延时函数就是空跑。如果按键是用中断方式实现的,那就更不能在中断服务函数里面使用延时函数,因为中断服务函数最基本的要求就是快进快出!上一章我们学习了 EPIT
定时器,定时器设置好定时时间,然后
CPU
就可以做其他事情去了,定时时间到了以后就会触发中断,然后在中断中做相应的处理即可。因此,我们可以借助定时器来实现消抖,按键采用中断驱动方式,当按键按下以后触发按键中断,在按键中断中开启一个定时器,定时周期为 10ms
,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。定时器按键消抖如图 19.1.1
所示:
![](https://i-blog.csdnimg.cn/direct/e225a48285524c0e920fd5546da92125.png)
在图 19.1.1
中
t1~t3
这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发,因此会在 t1
、
t2
和
t3
这三个时刻会触发按键中断,每次进入中断处理函数都会重新开启定时器中断,所以会在 t1
、
t2
和
t3
这三个时刻开器定时器中断。但是
t1~t2
和
t2~t3
这两个时间段是小于 我们设置的定时器中断周期(
也就是消抖时间,比如
10ms)
,所以虽然
t1
开启了定时器,但是定时器定时时间还没到呢 t2
时刻就重置了定时器,最终只有
t3
时刻开启的定时器能完整的完成整个定时周期并触发中断,我们就可以在中断处理函数里面做按键处理了,这就是定时器实现按键防抖的原理,Linux
里面的按键驱动用的就是这个原理!
关于定时器按键消抖的原理就介绍到这里,接下来讲解如何使用 EPIT1
来配合按键
KEY来实现具体的消抖,步骤如下:
1、配置按键 IO 中断
配置按键所使用的 IO
,因为要使用到中断驱动按键,所以要配置
IO
的中断模式。
2、初始化消抖用的定时器
上面已经讲的很清楚了,消抖要用定时器来完成,所以需要初始化一个定时器,这里使用上一章讲解的 EPIT1
定时器,也算是对
EPIT1
定时器的一次巩固。定时器的定时周期为
10ms
,也可根据实际情况调整定时周期。
3、编写中断处理函数
需要编写两个中断处理函数:按键对应的 GPIO
中断处理函数和
EPIT1
定时器的中断处理函数。在按键的中断处理函数中主要用于开启 EPIT1
定时器,
EPIT1
的中断处理函数才是重点,按键要做的具体任务都是在定时器 EPIT1
的中断处理函数中完成的,比如控制蜂鸣器打开或关闭。
2、硬件原理分析
本试验用到的资源如下:
①、一个
LED
灯
LED0
。
②、定时器
EPTI1
。
③、一个按键
KEY
。
④、一个蜂鸣器。
本试验效果和第十五章的试验效果一样,按下
KEY
会打开蜂鸣器,再次按下
KEY
就会关闭蜂鸣器。LED0
作为系统提示灯不断的闪烁。
3、试验程序编写
本章实验在上一章例程的基础上完成,更改工程名字为“
key_filter
”,然后在
bsp
文件夹下创建名为“keyfilter
”的文件夹,然后在
bsp/keyfilter
中新建
bsp_keyfilter.c
和
bsp_keyfilter.h
这两个文件。在 bsp_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
bsp_keyfilter.h 文件很简单,只是函数声明。在 bsp_keyfilter.c 中输入如下内容:
#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);
}
文件 bsp_keyfilter.c
一共有
6
个函数,这
6
个函数其实都很简单。
filterkey_init
是本试验的 初始化函数,此函数首先初始化了 KEY
所使用的
UART1_CTS
这个
IO
,设置这个
IO
的中断模式,并且注册中断处理函数,最后调用函数 filtertimer_init
初始化定时器
EPIT1
定时周期为 10ms。函数
filtertimer_init
是定时器
EPIT1
的初始化函数,内容基本和上一章实验的
EPIT1
初始化函数一样。函数 filtertimer_stop
和
filtertimer_restart
分别是
EPIT1
的关闭和重启函数。 filtertimer_irqhandler 是
EPTI1
的中断处理函数,此函数里面就是按键要做的工作,在本例程里面就是开启或者关闭蜂鸣器。函数 gpio1_16_31_irqhandler
是
GPIO1_IO18
的中断处理函数,此函数只有一个工作,那就是重启定时器 EPIT1
。
bsp_keyfilter.c 文件内容总体来说并不难,基本就是第十七章和第十八章实验的综合。最后在 main.c
中输入如下所示代码:
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_keyfilter.h"
/*
* @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;
}
main.c 文件只有一个
main
函数,在第
23
行调用函数
filterkey_init
来初始化带有消抖的按键,最后在 while
循环里面翻转
LED0
,周期大约为
500ms
。
4、编写 Makefile 和链接脚本
修改
Makefile
中的
TARGET
为
keyfilter
,在
INCDIRS
和
SRCDIRS
中加入“
bsp/keyfilter
”, 修改后的 Makefile
如下:
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= keyfilter
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
第
2
行修改变量
TARGET
为“
keyfilter
”,也就是目标名称为“
keyfilter
”。
第
16
行在变量
INCDIRS
中添加按键消抖驱动头文件
(.h)
路径。
第
28
行在变量
SRCDIRS
中添加按键消抖驱动文件
(.c)
路径。
链接脚本保持不变。
5、编译下载
使用 Make 命令编译代码,编译成功以后使用软件 imxdownload2 将编译完成的 bsp.bin 文件生成可执行的img文件,命令如下:
make
./imxdownload2 int.bin
如果 imxdownload2无权限,可用以下命令添加权限
chmod 777 imxdownload2
编译如图:
![](https://i-blog.csdnimg.cn/direct/50aa61f139444966af03c736515e1e00.png)
利用Win32DiskImager软件将load.img执行文件写入SD卡,SD卡插入开发板上即可正常运行。本例程的效果和第十一章一样,按下 KEY 就会控制蜂鸣器的开关,并且 LED0 不断的闪烁,提示系统正在运行。
例程
【免费】Linux学习笔记15-定时器按键消抖实验例程资源-CSDN文库