前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正文:
本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第17 讲” 的读书笔记。第17讲主要是介绍I.MX6U处理器的EPIT定时器的按键消抖。本节将参考正点原子的视频教程第17讲和配套的正点原子开发指南文档进行学习。
0. 概述
在第15章和第17章实验都使用到了按键,用到按键就要处理因为机械结构带来的抖动问题,也就是按键消抖。前面的时延中都是直接使用了延时函数来实现消抖,因为简单,但是直接使用延时函数来实现消抖会浪费CPU的性能,因为在延时函数里面CPU什么都做不了。如果按键使用中断的话更不能再中断里面使用延时函数,因为中断服务函数要快进快出!本章我们学习如何使用定时器来实现按键消抖,使用定时器既可以实现按键消抖,而且也不会浪费CPU性能,这个也是Linux驱动里面按键消抖的做法。
1. 定时器按键消抖简介
按键消抖的原理已经在第十五章详细的讲解过了,起始就是在按键按下以后延时一段时间再去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。
但是这有一个缺点,就是已按时函数会浪费CPU性能,应为延时函数就是空跑。如果按键是用中断方式实现的,那就更不应该在中断服务函数里使用延时函数,因为中断服务函数最基本的要求就是快进快出!上一章我们学习了EPIT定时器,定时器设置好定时时间,然后CPU就可以做其他事情去了,定时时间到了以后就会触发中断,然后在中断中做相应的处理即可。
因此,我们可以借助定时器来实现消抖,
- 按键采用中断驱动的方式,当按下按键触发按键中断,在按键中断中开启一个定时器,
- 定时周期为10ms,当定时时间到了以后就会触发定时器中断
- 最后在定时器中断处理函数中读取按键的值,如果按键还是按下的状态那就表示这是一次有效的按键。
定时器按键消抖如下图所示:
在图19.1.1中的t1~t3这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发,因此会在t1,t2,和t3这三个时刻触发那件中断,每次进入中断处理函数都会重开定时器中断,所以会在t1,t2,和t3这三个时刻开定时器中断。但是t1~t2和t2~t3这两段时间是小于我们设置的定时器中断周期(也就是消抖时间,比如10ms),所以虽然t1开启了定时器,但是定时器时间没有到呢t2时刻就重置了定时器,最终之后t3时刻开启的定时器能完整的完成整个定时周期并触发中断,我们就可以在中断处理函数里面做按键处理了,这就是定时器完成按键消抖的原理,Linux里面的按键驱动用的就是这个原理!
关于定时器消毒的原理就介绍到这里,接下来讲解如何使用EPIT1来配置按键KEY来实现具体的消抖,步骤如下:
- 配置按键IO中断
配置按键所使用的IO,因为要使用到中断驱动按键,所以要配置IO的中断模式。- 初始化消抖用的定时器
上面已经讲的很清楚了,消抖要用定时器来完成,所以需要初始化一个定时器,这是使用上一章讲解的EPIT1定时器,也算是对EPIT1定时器的一次巩固。定时器的定时周期为10ms,也可以根据实际情况调整定时周期。- 编写中断处理函数
需要编写两个中断处理器函数:按键对应的GPIO中断处理函数和EPIT1定时器的中断处理函数。在按键的中断处理函数中主要用于开启EPIT1定时器,EPIT1定时器处理函数才是重点,按键要做的具体任务都是在定时器EPIT1的中断处理函数中完成的,比如控制蜂鸣器打开或关闭。
2. 定时器按键消抖程序编写
更具上面分析的定时器按键消抖的原理和定时器按键消抖实验的步骤,编写定时器按键消抖程序源码如下:
bsp/keyfilter/bsp_keyfilter.h
#ifndef __BSP_KEYFILTER_H__
#define __BSP_KEYFILTER_H__
#include "imx6u.h"
void keyfilter_init(void);
void keyfilter_timer_init(int value);
void keyfilter_timer_stop(void);
void keyfilter_timer_restart(int value);
void keyfilter_timer_irqhandler(IRQn_Type irq, void *userparam);
void gpio1_16_31_irqhandler(IRQn_Type irq, void *userparam);
#endif
bsp/keyfilter/bsp_keyfilter.c
#include "bsp_keyfilter.h"
#include "bsp_beep.h"
#include "bsp_led.h"
#include "bsp_int.h"
#include "bsp_gpio.h"
#include "bsp_epittimer.h"
void keyfilter_init(void)
{
/* GPIO1_IO18 */
gpio_pin_config_t config;
/* 1. 初始化IO复用,复用为GPIO1_IO18 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
/* 2. 设置 UART1_CTS_B IO 的电气特性 */
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xf080);
/* 3. 初始化 GPIO1_IO18 设置为输入 */
config.directioin = kGPIO_DigitalInput;
config.intMode = kGPIO_FalllingEdgeInt;
gpio_init(GPIO1, 18, &config);
/* 启用GIC IRQ */
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
/* 注册IRQ处理函数 */
system_irqhandler_register(GPIO1_Combined_16_31_IRQn, gpio1_16_31_irqhandler, NULL);
/* EPIT1定时器初始化 */
keyfilter_timer_init(66000000/100);
/* 启用gpio中断 */
gpio_int_enable(GPIO1, 18);
}
void keyfilter_timer_init(int value){
EPIT1->CR = 0;
EPIT1->CR = (1 << 24 )| (1 << 3) | (1 << 2) | (1 << 1);
EPIT1->LR = value;
EPIT1->CMPR = 0;
/* 使能GIC EPIT1_IRQn 中断 */
GIC_EnableIRQ(EPIT1_IRQn);
/* 注册中断处理函数 */
system_irqhandler_register(EPIT1_IRQn, keyfilter_timer_irqhandler, NULL);
}
void keyfilter_timer_stop(void)
{
EPIT1->CR &= ~(1 << 0);
}
void keyfilter_timer_restart(int value){
EPIT1->CR &= ~(1 << 0); /* 关闭EPIT1 */
EPIT1->LR = value; /* EPIT1加载值寄存器 */
EPIT1->CR |= (1 << 0); /* 打开EPIT1 */
}
void keyfilter_timer_irqhandler(IRQn_Type irq, void *userparam){
static int beep_state = 0;
if(EPIT1->SR & (1 << 0)){ /* 检查EPIT1中断标志 */
keyfilter_timer_stop(); /* 关闭EPIT1定时器 */
if(gpio_pinread(GPIO1, 18) == 0) /* 检查gpio引脚电平值 */
{
beep_state = !beep_state; /* 翻转蜂鸣器 */
beep_switch(beep_state);
}
}
/* 清除中断标志位 */
EPIT1->SR |= (1<<0);
}
void gpio1_16_31_irqhandler(IRQn_Type irq, void *userparam){
if(GPIO1->ISR & (1 << 18)){
keyfilter_timer_restart(66000000/100); /* 重启EPIT1定时器,定时器周期10ms */
}
/* 清除中断标志位 */
gpio_int_cleanFlag(GPIO1, 18);
}
在如上的源码中,初始化按键KEY0 对应GPIO1_IO18的 IO 复用,IO特性,启用IO中断,并注册GPIO1_IO18的中断处理函数,在按键中断处理函数中重启 EPIT1定时器设置定时周期为10ms,当定时周期完成时触发EPIT1定时器比较事件中断,在EPIT1定时器中断里再次检查gpio引脚的输入电平如果还是有效说明此次按键按下是有效的,此时在EPIT1定时器中断里翻转蜂鸣器的鸣叫。
3. 编译烧写SD卡验证实验结果
译修改主频后源码烧录SD卡验证本节的EPIT定时器消抖实验是否生效。预期烧录SD卡后正点原子I.MX6ULL ALPHA/Mini 开发板后,按下按键蜂鸣器鸣叫,再次按下按键蜂鸣器停止鸣叫,多次测试按键按下都能翻转蜂鸣器开关。
我本地验证的结果是EPIT定时器按键消抖实验结果正常,多次按下按键都能正确的翻转蜂鸣器鸣叫开关。
4. 总结和实验遇到的问题记录
4.1 问题1:EPIT定时器消抖实验程序烧录SD,发现有时按下并松开按键后蜂鸣器只鸣叫一声就停止,预期应该一直鸣叫。
原因分析如下:
- 由于按键的机械结构,不仅仅在按键按下的瞬间有电平的多次抖动,从而在按键按下的瞬间多次在按键gpio电平的下降沿触发GPIO中断,进而重置EPIT1定时器最终出发EPIT1定时器中断。
- 按键的机械结构决定了,在按键松开的瞬间也有多次的gpio引脚电平抖动,也会在按键松开的瞬间由于抖动在电平的下降沿触发GPIO中断,进而重置EPIT1定时器最终出发EPIT1定时器中断。
- 由上分析可知,由于按键的机械结构,在按键按下的时会有电平抖动从而触发GPIO中断;在按键松开的时同样也会有电平抖动从而触发GPIO中断;
所以,必须在EPIT1定时器中断里再次检查 GPIO 引脚的电平是否有效,对于本实验,在按键按下的时候EPIT1中断处理函数读取到的gpio引脚为低电平证明按键按下有效。
(EPIT1定时中断处理函数里读取到gpio引脚为高电平,说明是按键松开。)
5. 结束
本文至此结束