当前位置: 首页 > article >正文

Linux学习笔记15---定时器按键消抖实验

        在之前的实验中都用到了按键,用到按键就要处理因为机械结构带来的按键抖动问题,也就是按键消抖。前面的实验中都是直接使用了延时函数来实现消抖,因为简单,但是直接用延时函数来实现消抖会浪费 CPU 性能,因为在延时函数里面 CPU 什么都做不了。 如果按键使用中断的话更不能在中断里面使用延时函数,因为中断服务函数要快进快出!本章我们学习如何使用定时器来实现按键消抖,使用定时器既可以实现按键消抖,而且也不会浪费CPU 性能,这个也是 Linux 驱动里面按键消抖的做法。

1、 定时器按键消抖简介

        按键消抖的原理在第十一章已经详细的讲解了,其实就是在按键按下以后延时一段时间再去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。但是这有一个缺点,就是延时函数会浪费 CPU 性能,因为延时函数就是空跑。如果按键是用中断方式实现的,那就更不能在中断服务函数里面使用延时函数,因为中断服务函数最基本的要求就是快进快出!上一章我们学习了 EPIT 定时器,定时器设置好定时时间,然后 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 里面的按键驱动用的就是这个原理!
        关于定时器按键消抖的原理就介绍到这里,接下来讲解如何使用 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
编译如图:

利用Win32DiskImager软件将load.img执行文件写入SD卡,SD卡插入开发板上即可正常运行。本例程的效果和第十一章一样,按下 KEY 就会控制蜂鸣器的开关,并且 LED0 不断的闪烁,提示系统正在运行。

例程

【免费】Linux学习笔记15-定时器按键消抖实验例程资源-CSDN文库


http://www.kler.cn/a/535691.html

相关文章:

  • 基于docker搭建Kafka集群,使用KRaft方式搭建,摒弃Zookeeper
  • NSS-DAY2
  • UG NX二次开发(Python)-API函数介绍与应用实例(三)-UFLayer类操作
  • 【NR-NTN】3GPP Release 18中NR-NTN过程描述
  • 【R语言】获取数据
  • 解锁.NET Fiddle:在线编程的神奇之旅
  • ASP.NET Core JWT
  • DeepSeek 引发 AI 大模型战火,编程语言群雄激战谁夺胜利权杖?
  • 4G核心网的演变与创新:从传统到虚拟化的跨越
  • e2studio开发RA2E1(8)----GPT定时器频率与占空比的设置
  • 3. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--环境搭建
  • SpringBoot开发(五)SpringBoot接收请求参数
  • 【数据结构】单向链表(真正的零基础)
  • 六。自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
  • 虚拟机报错:处理器未启用 NX/XD,而 VMware Workstation 要求启用 NX/XD.没有可打开电源的虚拟机
  • k8sollama部署deepseek-R1模型,内网无坑
  • 【Leetcode 热题 100】72. 编辑距离
  • 【2025最新计算机毕业设计】基于springboot智能教师评价系统【提供源码+答辩PPT+文档+项目部署】(高质量源码,可定制,提供文档,免费部署到本地)
  • 深入理解C++的new和delete
  • linux 使用docker安装 postgres 教程,踩坑实践
  • Unity3D 切线空间及其应用详解
  • springboot011-G县乡村生活垃圾治理问题中运输地图系统
  • 网络安全配置
  • 从源码到上线:AI在线教育系统开发全流程与网校APP构建指南
  • 使用 TensorRT 和 Python 实现高性能图像推理服务器
  • LeetCode 415