EPIT定时器实现按键消抖—基于I.MX6UL嵌入式SoC

1、前言

在前面的文章《GPIO外部中断处理—基于I.MX6UL嵌入式SoC》中,链接如下:

https://www.cnblogs.com/Cqlismy/p/12776320.html

实现了一个简单的按键读取功能,是基于GPIO的外部中断功能来实现的,对于按键的消抖则是采用了简单延时的方式,在实际项目中的按键处理,并不会这样操作,因为延时消抖会造成了处理器性能的浪费,并且,对于中断服务处理函数的执行,需要快进快出,本篇文章将简单介绍使用EPIT定时器的方式实现按键消抖的功能,对于EPIT定时器的基本机制,可以参考文章《EPIT定时器中断实现—基于I.MX6UL嵌入式SoC》,链接如下:

https://www.cnblogs.com/Cqlismy/p/12977491.html

此文,将不会对EPIT定时器的机制进行介绍了。

2、定时器按键消抖机制

按键的硬件实现原理以及按键按下的实际波形如下所示:

我们都知道,当按键按下的一瞬间会产生杂波,也就是一定的抖动现象,按键消抖的机制就是,当按键的电平稳定后,再去读取相应的IO口,例如上图,如果此时还是低电平的话,就可以证明确实是有按键按下了,处理器才去认为是有按键事件产生了,再进行相应的处理。

采用定时器进行消抖的机制就是,当按键按下以后,触发外部中断,在按键的外部中断服务处理函数中,开启定时器,可以设置定时器的定时周期为10~15ms,当定时器的定时周期到了之后,将会触发定时器中断,我们可以在定时器的中断服务处理函数中去读取按键值,如果按键还是处于按下状态的话,说明此次是一次有效的按键按下事件。

3、定时器消抖程序实现

接下来,实现使用定时器进行按键消抖的程序,大致的编程思路如下所示:

  • 设置按键IO口的复用功能以及引脚的电气属性;
  • 设置按键GPIO口的方向为输入,并且配置GPIO中断方式为下降沿触发,使能相应的中断号;
  • 配置EPIT定时器,使用定时器来实现按键消抖;
  • 编写按键的中断服务处理函数;
  • 编写定时器中断服务处理函数。

在前面的ARM裸机基本程序上进行开发,进入到bsp目录,新创建keyfilter目录:

$ cd bsp/bsp
$ mkdir keyfilter
$ cd keyfilter
$ touch bsp_keyfilter.h
$ touch bsp_keyfilter.c

新创建的bsp_keyfilter.h文件的内容如下:

#ifndef __BSP_KEYFILTER_H
#define __BSP_KEYFILTER_H

#include "imx6ul.h"

/* 相关函数声明 */
void keyfilter_init(void);
void keyfiltertimer_init(unsigned int prescalar, unsigned int value);
void keyfiltergpio_irqhander(unsigned int giccIar, void *userParam);
void keyfiltertimer_irqhandler(unsigned int giccIar, void *userParam);
void keyfiltertimer_start(unsigned int value);
void keyfiltertimer_stop(void);

#endif

该文件主要是相关函数的声明,新创建的bsp_keyfilter.c文件主要是函数的实现,该文件的内容如下所示:

#include "bsp_keyfilter.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_led.h"

/**
 * keyfilter_init() - 按键消抖功能初始化
 * 
 * @param: 无
 * @return: 无
 */
void keyfilter_init(void)
{
    /* 1、按键IO口初始化以及外部中断初始化 */
    gpio_pin_config_t keyfilter_config;

    /* 设置CSI_DATA02引脚IO口复用为GPIO4_IO23 */
    IOMUXC_SetPinMux(IOMUXC_CSI_DATA02_GPIO4_IO23, 0);

    /**
     * 配置GPIO4_IO23引脚电气属性
     * bit[16]: 0 关闭HYS
     * bit[15:14]: 11 pull up 22k
     * bit[13]: 1 pull
     * bit[12]: 1 pull/keeper使能
     * bit[11]: 0 禁止开路输出
     * bit[10:8]: 000 resered
     * bit[7:6]: 10 速度为100MHz
     * bit[5:3]: 000 关闭输出
     * bit[2:1]: 00 resered
     * bit[0]: 0 低摆率
     */
    IOMUXC_SetPinConfig(IOMUXC_CSI_DATA02_GPIO4_IO23, 0xF080);

    /* 将按键相关的GPIO方向设置为输入,并配置中断 */
    keyfilter_config.direction = kGPIO_DigitalInput;
    keyfilter_config.interruptmode = kGPIO_IntFallingEdge;
    keyfilter_config.value = 1;
    gpio_init(GPIO4, 23, &keyfilter_config);

    /* 使能GIC中断控制器,注册GPIO中断服务函数并使能中断 */
    GIC_EnableIRQ(GPIO4_Combined_16_31_IRQn);
    system_register_irqhandler(GPIO4_Combined_16_31_IRQn,
        (system_irq_handler_t)keyfiltergpio_irqhander, NULL);
    gpio_enable_interrupt(GPIO4, 23);

    /* 2、按键定时器(EPIT2)消抖初始化 */
    keyfiltertimer_init(66, 1000000 / 100); /* 定时10ms */
}

/**
 * keyfiltertimer_init() - 按键消抖EPIT2定时器初始化函数
 * 
 * @prescalar: 分频值,能设置的范围为1~4096
 * @value: 装载值 
 * 
 * @return: 无
 */
void keyfiltertimer_init(unsigned int prescalar, unsigned int value)
{
    if (prescalar > 4096)
        prescalar = 4096;
    
    EPIT2->CR = 0;  /* 将CR寄存器清0 */

    /**
     * 配置EPIT2的CR控制寄存器 
     * bit [25:24]: 01 EPIT2时钟选择Peripheral Clock=66MHz
     * bit [15:4]: prescalar-1 分频器值
     * bit [3]: 1 计数器向下计数到0后从LR重新加载计数器
     * bit [2]: 1 比较中断使能
     * bit [1]: 1 初始计数值来于LR寄存器值 
     * bit [0]: 0 先关闭EPIT2定时器
     */
    EPIT2->CR = (1 << 24 | (prescalar - 1) << 4 | 0xe << 0);
    EPIT2->LR = value;  /* 定时器加载寄存器 */
    EPIT2->CMPR = 0;    /* 定时器比较寄存器 */

    GIC_EnableIRQ(EPIT2_IRQn);  /* 使能GIC相应的EPIT2中断 */

    /* 注册EPIT2的中断服务函数 */
    system_register_irqhandler(EPIT2_IRQn,
        (system_irq_handler_t)keyfiltertimer_irqhandler, NULL);
}

/**
 * keyfiltergpio_irqhander() - 按键GPIO4_IO23中断服务处理函数
 * 
 * @giccIar: 中断号
 * @userParam: 用户参数
 * 
 * @return: 无
 */
void keyfiltergpio_irqhander(unsigned int giccIar, void *userParam)
{
    keyfiltertimer_start(1000000 / 100); /* 开启定时器(定时10ms) */
    gpio_clear_int_flags(GPIO4, 23); /* 清除GPIO4_IO23中断标志位 */
}

/**
 * keyfiltertimer_irqhandler() - 按键消抖定时器(EPIT2)中断服务处理函数
 * 
 * @giccIar: 中断号
 * @userParam: 用户参数 
 * 
 * @return: 无
 */
void keyfiltertimer_irqhandler(unsigned int giccIar, void *userParam)
{
    static unsigned char led_state = OFF;

    if (EPIT2->SR & (1 << 0)) { /* 判断比较中断事件是否产生 */
        keyfiltertimer_stop();
        if (gpio_pin_read(GPIO4, 23) == 0) {
            led_state = !led_state;
            led_switch(LED2, led_state);
        }
    }

    EPIT2->SR |= (1 << 0);  /* 清除定时器中断标志位 */
}

/**
 * keyfiltertimer_start() - 按键消抖定时器(EPIT2)启动
 * 
 * @value: EPIT2定时器装载值
 * 
 * @return: 无
 */
void keyfiltertimer_start(unsigned int value)
{
    EPIT2->CR &= ~(1 << 0);
    EPIT2->LR = value;
    EPIT2->CR |= (1 << 0);
}

/**
 * keyfiltertimer_stop() - 按键消抖定时器(EPIT2)停止
 * 
 * @value: EPIT2定时器装载值
 * 
 * @return: 无
 */
void keyfiltertimer_stop(void)
{
    EPIT2->CR &= ~(1 << 0);
}

bsp_keyfilter.c文件实现了6个函数,函数都比较简单,keyfilter_init()函数实现的功能为按键初始化以及消抖定时器的初始化,消抖使用的定时器为EPIT2,定时器的定时周期设置为10ms,keyfiltergpio_irqhander()则是按键相应的IO口的外部中断服务处理函数,函数调用后,需要开启EPIT定时器,并且清除掉相应的中断标志位,keyfiltertimer_irqhandler()是定时器的中断服务处理函数,定时周期到了之后,将会调用此函数,在该函数中需要读取按键IO口的电平值,判断是否为一次有效的按键按下,如果是有效的按键按下,则进行相应的处理,另外,在函数退出来之前,需要清除定时器的中断标志位。

app.c的文件内容如下:

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_gpio.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_epit.h"
#include "bsp_keyfilter.h"

/**
 * main() - 主函数
 */
int main(void)
{
    interrupt_init();           /* 中断初始化 */
    imx6ul_clk_init();          /* 初始化相关时钟 */
    system_clk_enable();        /* 系统外设时钟使能 */
    led_init();                 /* LED灯初始化 */
    epit1_init(66, 1000000);    /* EPIT1定时器初始化 */
    keyfilter_init();           /* 按键初始化(EPIT2定时器消抖) */

    while (1) {

    }

    return 0;
}

main()函数比较简单,需要调用keyfilter_init()函数进行按键以及消抖定时器的初始化,最后,编译相应的文件生成可执行的.imx文件,烧写到I.MX6UL目标中验证即可。

4、小结

本篇文章主要简单介绍如何使用定时器的方式去实现按键消抖的功能。

原文地址:https://www.cnblogs.com/Cqlismy/p/13216544.html