0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

按键操作的驱动设计与实现

CHANBAEK 来源:木南创智 作者:尹家军 2022-12-08 10:57 次阅读

按键在我们的项目中是经常使用到的组件。一般来说,我们都是在用到按键时直接针对编码,但这样每次都做很多重复性的工作。所以在这里我们考虑做一般性抽象得到一个可应用于按键操作的通用性驱动程序。

1、功能概述

按键操作在我们的产品种经常用到,一般都是在特定的应用环境中直接有针对性的操作。但这些按键的操作往往有很多的共性,这就为代码复用提供了可能。

1.1、按键的定义

在开始考虑按键操作之前,我们先来分析一下究竟什么是按键。按键一般来讲就是用于信号输入的按钮,通过响应它的操作我们可以实现想要的功能。但我们这里所说的按键不仅包括普通的单体按键,还包括如组合键、键盘等。

对于这些种类的按键它们的形态、功能或许有较大的差异,但我们可以对它们所进行的操作却很类似。这也是我们能够统一考虑它们的基础。

1.2、原理分析

我们已经给我们要操作的按键划分了范围,在此基础上我们简单分析实现按键操作的基本原理。

首先我们来考虑按钮操作的原理,其实很简单,无非就是按下或者弹起两种状态。至于按钮本身是常开或者常闭,是低电平有效还是高电平有效都没有问题,我们只要能检测出其状态就可以了。我们考虑按键的按下、弹起、连击和长按等状态,如下图:

Dingtalk_20221206154648.jpg

其次我们来考虑按键状态的存储。在系统中的多个按键需要操作时,如何处理响应事件就会是一个问题。我们考虑以先入先出队列来存储按键的状态,进而根据状态进行操作。我们需要设计一个队列,这是一个先入先出的队列,拥有一定的存储空间和读写操作指针,具体如下图所示:

Dingtalk_20221206154648.jpg

在上图中,当读指针与写指针一样时,则表示队列为空。当写入一个数据,则写指针加一;当读出一个数据,则读指针加一;当读指针遇到写指针则表示在没有数据了。

最后来说一说按键状态的响应。所谓响应其实就是对不同的状态我们来处理不同的事件。对于每个按键我们根据其状态定义事件。在不同的事件中处理我们需要的功能。

Dingtalk_20221206154648.jpg

在上图中,状态和时间都可以在我们的对象中声明,但具体的实现形式在应用中完成。

2、驱动设计与实现

我们已经简单分析了按键的基本操作原理,接下来我们将以此为基础来分析并设计按键操作的通用驱动方法。

2.1、对象定义

我们依然采用基于对象的操作方式。当然前提是我们得到了可用于操作的对象,所以我们先来分析一下如何抽象面向按键操作的对象。

2.1.1、定义对象类型

一般来讲,一个对象会包括属性和操作。接下来我们就从这两个方面来考虑按键对象问题。

首先我们来考虑按键对象的属性问题。我们的系统中总有多个按键,为了区分这些按键我们为每一个按键分配一个ID,用于区别这些按键。所以我们将按键ID作为其一个属性。对于按键操作我们一般都会有软件滤波来实现消抖,我们一如一个滤波计数用以实现这一过程,我们将滤波计数也当作它的一个属性。长按键我们需要预设检测时长,同时需要一个计数来记录这一过程,所以我们将其设为属性。同样连续按键的周期需要预设,而且需要计数来记录过程,所以也将这两个作为属性。当然按键当前的状态,我们也可能需要记录一下,按键按下时的有效电平,我们也需要分辨,这些我们也都将其作为属性。综上所述按键对象的类型定义如下:

/*定义按键对象类型*/
    typedef struct KeyObject {
     uint8_t id;//按键的ID
     uint8_t Count;//滤波器计数器
     uint16_t LongCount;//长按计数器
     uint16_t LongTime;  //按键按下持续时间, 0 表示不检测长按
     uint8_t  State;//按键当前状态(按下还是弹起)
     uint8_t  RepeatPeriod;//连续按键周期
     uint8_t  RepeatCount;//连续按键计数器
     uint8_t ActiveLevel;//激活电平
    }KeyObjectType;

除了按键对象,其实我们还需要定义一个数据队列的对象。者如我们前面所说,队列除了一个数据存储区外还需要读写指针。我们定义如下:

/*定义键值存储队列的类型*/
    typedef struct KeyStateQueue{
      uint8_t queue[KEY_FIFO_SIZE];//键值存储队列
     uint8_t pRead;//读队列指针
     uint8_t pWrite;//写队列指针
    }KeyStateQueueType;

2.1.2、对象初始化配置

对象定义之后并不能立即使用我们还需要对其进行初始化。所以这里我们来考虑按键对象的初始化函数。关于对象的初始化,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此思路我们设计按键对象的初始化函数如下:

/*按键读取初始化*/
    void KeysInitialization(KeyObjectType *pKey,uint8_t id,uint16_t longTime, uint8_t repeatPeriod,KeyActiveLevelType level)
    {
     if(pKey==NULL)
     {
     return;
     }
     
     pKey->id=id;
     pKey->Count=0;
     pKey->LongCount=0;
     pKey->RepeatCount=0;
     pKey->State=0;
     
     pKey->ActiveLevel=level;
     
     pKey->LongTime=longTime;
     pKey->RepeatPeriod=repeatPeriod;
    }

2.2、对象操作

我们已经抽象了按键对象类型,也设计了对象的初始化函数。接下来我们需要考虑使用对象如何实现操作。根据我们前面的分析,操作可分为量个部分:按键状态的检测和键值队列的操作。

2.2.1、按键状态检测

需要周期性的检测按键的状态以便我们响应按键的操作。我们一般10ms检测一次状态,并持续一定的滤波周期用于消抖。我们检测到按键的不同状态后将状态存入到相关的键值队列中。

/*按键周期扫描程序*/
    void KeyValueDetect(KeyObjectType *pKey)
    {
     
     if (CheckKeyDown(pKey))
     {
     if (pKey->Count < KEY_FILTER_TIME)
     {
     pKey->Count = KEY_FILTER_TIME;
     }
     else if(pKey->Count < 2 * KEY_FILTER_TIME)
     {
     pKey->Count++;
     }
     else
     {
     if (pKey->State == 0)
     {
     pKey->State = 1;
     
     /*发送按键按下事件消息*/
     KeyValueEnQueue((uint8_t)((pKey->id<<2) + KeyDown));
     }
     
     if (pKey->LongTime > 0)
     {
     if (pKey->LongCount < pKey->LongTime)
     {
     /* 发送按建持续按下的事件消息 */
     if (++pKey->LongCount == pKey->LongTime)
     {
     /* 键值放入按键FIFO */
     KeyValueEnQueue((uint8_t)((pKey->id<<2) + KeyLong));
     }
     }
     else
     {
     if (pKey->RepeatPeriod > 0)
     {
     if (++pKey->RepeatCount >= pKey->RepeatPeriod)
     {
     pKey->RepeatCount = 0;
     /*长按键后,每隔10ms发送1个按键*/
     KeyValueEnQueue((uint8_t)((pKey->id<<2) + KeyDown));
     }
     }
     }
     }
     }
     }
     else
     {
     if(pKey->Count > KEY_FILTER_TIME)
     {
     pKey->Count = KEY_FILTER_TIME;
     }
     else if(pKey->Count != 0)
     {
     pKey->Count--;
     }
     else
     {
     if (pKey->State == 1)
     {
     pKey->State = 0;
     
     /*发送按键弹起事件消息*/
     KeyValueEnQueue((uint8_t)((pKey->id<<2)+ KeyUP));
     }
     }
     
     pKey->LongCount = 0;
     pKey->RepeatCount = 0;
     }
    }

2.2.2、键值队列的操作

键值队列的操作就简单了,主要包括数据的写入、读出、清空队列以及队列是否为空。需要说的是键值的存储,包括量方面类容:按键的ID和按键的状态。我们使用一个字节来存储这些信息,前六个位存储ID,后两位存储状态。具体如下图所示:

Dingtalk_20221206154648.jpg

这样一种存储格式,我们最多可以存储64个按键和4种状态,当然这还要看队列的大小。

/*键值出队列程序*/
    uint8_t KeyValueDeQueue(void)
    {
     uint8_t result; 
     
     if(keyState.pRead==keyState.pWrite)
     {
     result=0;
     }
     else
     {
     result=keyState.queue[keyState.pRead];
     
     if(++keyState.pRead>=KEY_FIFO_SIZE)
     {
     keyState.pRead=0;
     }
     }
     return result;
    }
     
    /*键值入队列程序*/
    void KeyValueEnQueue(uint8_t keyCode)
    {
     keyState.queue[keyState.pWrite]=keyCode;
     
     if(++keyState.pWrite >= KEY_FIFO_SIZE)
     {
     keyState.pWrite=0;
     }
    }

3、驱动的使用

我们已经设计了按键操作的驱动程序,还需要对这一设计进行验证。这一节我们将以前面的设计为基础,用一个简单的应用来验证。我们设计4个单体按键,并由它们生出两组组合键,所以我们的应用程序就是面向这6个按键对象进行操作。

3.1、声明并初始化对象

在开始面向一个对象的操作之前,我们需要得到这个对象的一个实例。那么我们要先声明对象。我们前面已经定义了按键对象类型KeyObjectType和存储键值的队列类型KeyStateQueueType。我们使用这两个类型先声明两个对象变量如下:

KeyObjectType keys[6];

KeyStateQueueType keyState;

声明了对象还需要对变量进行初始化。在驱动的设计中我们已经设计了初始化函数,对象变量的初始化操作就通过这一函数来实现。初始化函数需要一些输入参数:

KeyObjectType *pKey,按键对象

uint8_t id,按键ID

uint16_t longTime,长按有效时间

uint8_t repeatPeriod,连按间隔周期

KeyActiveLevelType level,按键按下有效电平

在这些参数中pKey为按键对象,是我们要初始化的对象。而其它参数只需要根据实际设置输入就可以了。说一初始化函数可调用为:

/*按键硬件初始化配置*/
    static void Key_Init_Configuration(void)
    {
      KeyIDType id;
      for(id=KEY1;idid++)
      {
     KeysInitialization(&keys[id],id,KEY_LONG_TIME,0,KeyHighLevel);
      }
    }

关于按键ID,我们使用枚举来定义。与我们前面定义的按键对象数组配合能够起到很好的效果。在这一我们定义按键ID为:

/*定义按键枚举*/
    typedef enum KeyID {
     KEY1,
     KEY2,
     KEY3,
     KEY4,
    KEY1KEY2,
     KEY3KEY4,
     KEYNUM
    }KeyIDType;

按键ID作为作为按键的唯一标识,不但在我们的按键状态记录中要使用到,同时也可作为我们按键对象数组的下标来使用。

3.2、基于对象进行操作

我们定义了对象,接下来就可以基于对象实现我们的应用。对于按键操作我们需要考虑2个方面的事情:一是周期型的检查按键状态并压如队列;二是读取队列中的按键状态触发不同的操作。

首先我们来说一说周期型的检查按键的状态。我们采用10ms的周期来检查按键,所以我们需要使用定时中端的方式来实现,将如下函数加入到10ms定时中端即可。

/*按键扫描程序*/
    void KeyScanHandle(void)
    {
      KeyIDType id;
      for(id=KEY1;idid++)
      {
         KeyValueDetect(&keys[id]);
      }
    }

其实还有一个回调函数需要实现,其原型如下:

/*检查某个ID的按键(包括组合键)是否按下*/
    __weak uint8_t CheckKeyDown(KeyObjectType *pKey)

根据我们定义的按键对象和ID枚举我们实现这个回调函数并不困难,我们实现其如下:

/*检查某个ID的按键(包括组合键)是否按下*/
    uint8_t CheckKeyDown(KeyObjectType *pKey)
    {
     /* 实体单键 */
     if (pKey->id < KEY1KEY2)
     {
     uint8_t i;
     uint8_t count = 0;
     uint8_t save = 255;
     
     /* 判断有几个键按下 */
      for (i = 0; i < KEY1KEY2; i++)
     {
     if (KeyPinActive(pKey)) 
     {
     count++;
     save = i;
     }
     }
     
     if (count == 1 && save == pKey->id)
     {
     return 1;/* 只有1个键按下时才有效 */
     }
     return 0;
     }
     
     /* 组合键 K1K2 */
     if (pKey->id == KEY1KEY2)
     {
     if (KeyPinActive(&keys[KEY1]) && KeyPinActive(&keys[KEY2]))
     {
     return 1;
     }
     else
     {
     return 0;
     }
     }
     
      /* 组合键 K3K4 */
     if (pKey->id == KEY3KEY4)
     {
     if (KeyPinActive(&keys[KEY3]) && KeyPinActive(&keys[KEY4]))
     {
     return 1;
     }
     else
     {
     return 0;
     }
     }
     return 0;
    }

此外,我们还需要读取按键的状态并进行相应的响应。我们实现一个简单的处理函数如下:

/*按键处理函数*/
    static void KeyProcessing(void)
    {
      uint8_t keyCode;
     keyCode=KeyValueDeQueue();
    
     if(keyCode==((keys[KEY1].id<<2)+KeyDown))
     {
     //key1按下时触发的事件
     }
     else if(keyCode==((keys[KEY1].id<<2)+KeyUP))
     {
     //key1弹起时触发的事件
     }
    }

4、应用总结

我们已经实现了按键对象的操作,并在次基础上实现了简单的验证。操作的结果符合我们的期望。而且扩展性也很强。

按照我们对信息存储方式和消息队列的设计,最多可以存储64个按键和4中状态,当然这需要看定义的队列的大小。队列不应太小,太小有可能会造成某些按键操不会响应;也不应太大,太大可能会造成操作迟缓和空间浪费。

在应用中,我们建议定义按键ID时最好使用枚举,使用枚举的好处有几点。一是不会出现重复,每个按键能保证有唯一的ID值。二是便于与按键对象数组组合操作,简化编码。三是使用枚举扩展很方便,代码改动比较小。当然,枚举值最好是连续的而且从0开始。

在使用驱动是还需要注意,检测按键操作是只对个体单键的硬件有效,如果可能也使用数组操作,能与ID枚举配合使用简化操作。对于组合键要检测多个物理硬件,但也是对这些但体检的检测,所以在硬件上不需要定义。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 按键
    +关注

    关注

    4

    文章

    223

    浏览量

    57572
  • 对象
    +关注

    关注

    1

    文章

    38

    浏览量

    17378
  • 驱动设计
    +关注

    关注

    1

    文章

    110

    浏览量

    15270
收藏 人收藏

    评论

    相关推荐

    Linux下如何使用中断的方式来驱动按键

    Linux下的按键输入驱动开发模板一文中介绍了基本的按键输入捕获流程,这里将进一步介绍如何使用中断的方式来驱动按键,同时通过定时器
    发表于 07-29 08:59 961次阅读

    linux系统中裸机按键中断的驱动​方法

    今天主要和大家聊一聊,如何实现按键中断的驱动方法。
    发表于 12-09 11:59 653次阅读

    EmbeddedButton嵌入式按键驱动设计实现

    EmbeddedButton是一个轻量级简单易用的嵌入式按键驱动模块,可无限拓展按键,支持多连击、长按、短按长按等多种按键事件;该模块通过异步回调方式来简化程序结构,根据几个简单原则完
    的头像 发表于 08-28 15:47 1154次阅读
    EmbeddedButton嵌入式<b class='flag-5'>按键</b><b class='flag-5'>驱动</b>设计<b class='flag-5'>实现</b>

    如何使用软件Proteus和Keil uVision4实现多个按键操作

    如何使用软件Proteus和Keil uVision4实现多个按键操作
    发表于 10-20 07:22

    如何使用STM32扩展板实现按键驱动

    树莓派和STM32之间如何连线?如何使用STM32扩展板实现按键驱动
    发表于 01-17 07:46

    独立按键操作方法

    慧净HL-1 配套C实验例程100例【实验11】独立按键操作方法),很好的C51学习资料程序。
    发表于 03-21 17:01 4次下载

    使用单片机C语言实现独立按键检测与矩阵键盘操作的资料和程序

    所有的电子产品几乎到涉及到按键操作。所以微控制器是如何识别一个按键是否被按下,按下后又该如何做出反应,又如何防止按键抖动呢?更深入一点,微控制器又是如何识别矩阵键盘的?本文将详细阐述如
    发表于 07-16 17:39 2次下载
    使用单片机C语言<b class='flag-5'>实现</b>独立<b class='flag-5'>按键</b>检测与矩阵键盘<b class='flag-5'>操作</b>的资料和程序

    基于鸿蒙OS的按键驱动

    按键作为常用的输入系统,如何准确并高效的获取按键值,是一个经常要面对的问题,今天我们看看在鸿蒙系统中,如何得到独立按键按键值。 实现目标
    发表于 11-11 10:03 710次阅读

    使用单片机实现2按键加减操作的C语言实例免费下载

    本文档的主要内容详细介绍的是使用单片机实现2按键加减操作的C语言实例免费下载。
    发表于 11-18 17:44 18次下载

    嵌入式LinuxQT操作自定义按键

    嵌入式Linux系统中,用QT做的应用层程序,需要检测自定义的按键状态。使用的QT的按键事件,驱动层使用的Linux的input子系统。环境如下:硬件:Imx6ullQT版本:5.5在QT中使用
    发表于 10-20 19:21 9次下载
    嵌入式LinuxQT<b class='flag-5'>操作</b>自定义<b class='flag-5'>按键</b>

    MCU之按键驱动 -剥离按键驱动和事件处理

    ButtonDrive 自己写的一个按键驱动,支持单双击、连按、长按;采用回调处理按键事件(自定义消抖时间),使用只需3步,创建按键按键
    发表于 10-28 19:21 18次下载
    MCU之<b class='flag-5'>按键</b><b class='flag-5'>驱动</b> -剥离<b class='flag-5'>按键</b><b class='flag-5'>驱动</b>和事件处理

    Nand Flash驱动(实现初始化以及读操作)

    Nand Flash驱动(实现初始化以及读操作)
    发表于 12-02 12:36 11次下载
    Nand Flash<b class='flag-5'>驱动</b>(<b class='flag-5'>实现</b>初始化以及读<b class='flag-5'>操作</b>)

    按键驱动的实验

    按键驱动实验与LED以及Beep在整体使用逻辑上一样,只是按键是输入模式。
    的头像 发表于 03-02 16:25 723次阅读
    <b class='flag-5'>按键</b><b class='flag-5'>驱动</b>的实验

    基于状态机的按键驱动设计

    按键作为单片机的输入设备,可以向单片机输入数据、传输命令等,是设置参数和控制设备的常用接口。所以,学会按键驱动也是初学者必不可少的能力。说到按键驱动
    的头像 发表于 07-04 11:43 1322次阅读
    基于状态机的<b class='flag-5'>按键</b><b class='flag-5'>驱动</b>设计

    如何在FPGA中实现按键消抖

    按键操作。因此,实现有效的按键消抖机制对于提高系统的稳定性和可靠性至关重要。以下是在FPGA中实现按键
    的头像 发表于 08-19 18:15 1426次阅读