【电子电路基础实验】非阻塞按键

本文记录一种非阻塞按键的软件管理机制。

前言

  大多数单片机的按键教程中,按键防抖大多使用延时来处理,一般延时阻塞个10ms,然而就算对于51单片机的运行速度来说,10ms也能做很多很多事情,比如前面讨论过的刷新8位数码管消耗15ms,显然刷4位就要不了10ms。再比如之前常讨论的协调中断与主循环的运行时间问题,10ms的时间可太长了。

一、按键的硬件电路连接

  使用最简单的按键电路,组件仅包含一个单片机IO和一个按键。由于STC89C52RC的P3口内部有上拉电阻,所以也不需要在外面加什么东西,K1按下的时候单片机P3.1的电平是低电平,没按下P3.1就是高电平。
请添加图片描述

二、软件管理机制

1.实体按键与虚拟按键,按键管理结构与按键轮询

  有一些东西的参数设置页面需要长按按键才能进入,可以有效地杜绝大多数的误触,不小心碰到了呀,小孩子好奇这里摸一下那里摸一下呀。
  按键既可以短按也可以长按,可以看作是一个实体按键衍生出了两个按键。按键长短按的判别本质上是通过计算按键按下的时间来进行的,通过设定不同的目标时间,就可以区别出按键的长短按。因而可以构造出按键管理器。

typedef struct
{
    unsigned int    keyTrigCounter; 
    unsigned char   activeState;
    unsigned int    keyTrigCounterTarget1;	//短按计数目标值
    unsigned int    keyTrigCounterTarget2;	//长按计数目标值
}realKeyTrigMrg_t;

  约定keyTrigCounterTarget1是短按计数目标值,keyTrigCounterTarget2对应长按。当程序轮询到按键低电平时,keyTrigCounter就加一,并且不等待继续向下执行,如果按键无效,那么keyTrigCounter总是被清零以去抖动。程序如此往复运行,当keyTrigCounter加到目标值1时认为是按键短按,加到目标值2时认为是按键长按。但是这里面有几个说道,直接看下面的按键轮询伪代码。

    if("识别到按键管脚低电平"){
        if(keyTrigCounter < 0xFFFF)
            keyTrigCounter++;
        if(keyTrigCounter >= keyTrigCounterTarget2){
            if(activeState == 0){   //防止长按重复触发,但是如果要使能长按快加/快减就不应使用
                "将虚拟按键2添加到活动的按键列表";
                activeState = 1;
            }   
        }
    }
    else{  //短按模式,抬手才进行统计
        if(keyTrigCounter >= keyTrigCounterTarget1){
            if(activeState == 0){   //防止长按抬手导致的短按触发           
                "将虚拟按键1添加到活动的按键列表";
            }
        }
        keyTrigCounter = 0;
        activeState = 0;
    }
  1. 通常来说人们有可能会知道按键有长按功能,但不见得知道长按需要保持多少秒。所以按键扫描机制里面长按识别不等待按键释放,只要按下的时间够了就立即认为长按有效。短按的话就需要按键释放之后才开始统计判定比较贴近实际。
  2. keyTrigCounter自加之前要先判断是否小于某个值,因为这是个双字节整型变量,取值范围0-65535,当变量为65535时再加一就回零了。
  3. 由于要到达长按一定会经过短按,所以增加了一个activeState标志用于使长按和短按成为互斥事件。
  4. activeState标志在长按判定中的使用,如果不使用它,那么长按触发一次之后如果不曾释放按键,那么长按事件就会重复触发,这个功能在长按快速增加或减少设定值时比较有用。如果不希望长按持续触发,那就需要在长按中加入activeState判定标志。
  5. keyTrigCounterTarget1和keyTrigCounterTarget2的值的设定问题,由于按键运行涉及到实际手按输入,并且扫描代码中有不少if分支,所以就依靠感觉就可以的那种感觉,装载一个初值实际慢慢试。

考虑当按键按下时显示某个帮助页面,当按键释放时回到主页面,讨论主题是按键释放检测。在上面的按键轮询伪代码的基础上,进行简单的改动如下所示(修改1,2)。经过修改,程序就获得了按键释放检测的能力,不过这时候只能识别出按下(伪代码中:虚拟按键2)和释放(伪代码中:虚拟按键3)两种状态。

    if("识别到按键管脚低电平"){
        if(keyTrigCounter < 0xFFFF)
            keyTrigCounter++;
        if(keyTrigCounter >= keyTrigCounterTarget2){
        	if(activeState == 0){   //防止长按重复触发,但是如果要使能长按快加/快减就不应使用
                "将虚拟按键2添加到活动的按键列表";
                activeState = 1;
            }
        }
    }
    else{  //短按模式,抬手才进行统计
    	if(keyTrigCounter >= keyTrigCounterTarget1){
        	if(activeState == 0)   //防止长按抬手导致的短按触发
                "将虚拟按键1添加到活动的按键列表";
        }
    	#error "修改1:在下面增加长按检测的代码并删除activeState内嵌判断语句"
    	#error "修改2:将keyTrigCounterTarget2缩短为和keyTrigCounterTarget1一样的值使得长按变成短按"
        if(keyTrigCounter >= keyTrigCounterTarget2){           
        	"按键有效并且正确释放,在这里将虚拟按键3添加到活动的按键列表";
        }
        keyTrigCounter = 0;
        activeState = 0;
    }

2.事件

  所有的按键行为都可以抽象为事件,比如单个按键长按、短按、释放,组合按键等等,不管实际表现出来的是什么方式,都可以抽象为各种独立的事件。
  事件的好处是使系统的程序模块之间的接口更明晰,因为按键识别的任务仅仅只是将按键行为转化成虚拟事件。而处理函数只接收事件输入,这样一来它并不需要知道输入是由按键产生的,事件源也可以是外部中断、电源保护等等。

//虚拟按键事件,支持最多16个事件
#define KEY_SW_1_PRESSED 0x0001     
#define KEY_SW_2_PRESSED 0x0002
#define KEY_SW_3_PRESSED 0x0004
#define KEY_SW_4_PRESSED 0x0008
...
#define KEY_SW_16_PRESSED 0x8000

  上面定义的每个事件都占用双字节的某一位,那么实际的事件列表只需要一个双字节变量就可以容纳,假定使用virtualKeys来充当事件列表。要往事件列表里面添加虚拟按键事件1,使用下面的操作。

static unsigned int virtualKeys = 0x0000;

virtualKeys |= KEY_SW_1_PRESSED;	//向事件列表中添加虚拟按键事件1

  要查看事件列表中是否存在虚拟按键事件1,使用下面的操作。

if(virtualKeys & KEY_SW_1_PRESSED){
	//to do
}

  要从事件列表中清除虚拟按键事件1,使用下面的操作。

virtualKeys ^= KEY_SW_1_PRESSED;

3.多按键管理

  有多个实体按键的情况只是单个按键情况的组合,并且得益于正在讨论的按键扫描机制,组合按键检测得以实现,不过组合按键检测从另一个角度来看是“独立互斥事件”的组合,这显然是处理函数该考虑的事情。
  为了使管理器更加清晰明了,使用结构体数组和宏来进行管理。

#define NUMBER_OF_REAL_KEY      3
#define KEY_REAL_A              0
#define KEY_REAL_B              1
#define KEY_REAL_C              2

typedef struct
{
    unsigned int    keyTrigCounter; 
    unsigned char   activeState;
    unsigned int    keyTrigCounterTarget1;
    unsigned int    keyTrigCounterTarget2;
}realKeyTrigMrg_t;

static realKeyTrigMrg_t realKeyGroupTrigMrg[NUMBER_OF_REAL_KEY];

4. 完整的按键模块代码

头文件。

#ifndef _KEY_H_
#define _KEY_H_

/*********与单片机平台相关的内容开始*********/
#include "reg52.h"
sbit KEY_REAL_A_PIN = P3^1;
/*********与单片机平台相关的内容结束*********/

//虚拟按键事件,支持最多16个事件
#define KEY_SW_1_PRESSED 0x0001     
#define KEY_SW_2_PRESSED 0x0002
#define KEY_SW_3_PRESSED 0x0004
#define KEY_SW_4_PRESSED 0x0008

typedef  void  (*keyResultProcess_p)(unsigned int keyMasks);

extern void keyInit(keyResultProcess_p keyResultProcessFn); //按键初始化,初始化时将用户按键处理函数传入
extern void getKeyStateAndDoUserProcess(void);              //扫描按键并执行用户函数
extern void addKeyEventToVirtualKeys(unsigned int keyMasks);//向按键列表添加指定的按键事件

#endif

C文件。

#include "key.h"
#include <string.h>

#define NUMBER_OF_REAL_KEY      1
#define KEY_REAL_A              0

typedef struct
{
    unsigned int    keyTrigCounter; 
    unsigned char   activeState;
    unsigned int    keyTrigCounterTarget1;
    unsigned int    keyTrigCounterTarget2;
}realKeyTrigMrg_t;

static unsigned int virtualKeys = 0x0000;
static keyResultProcess_p keyResultProcessFunction = NULL;
static realKeyTrigMrg_t realKeyGroupTrigMrg[NUMBER_OF_REAL_KEY];

void keyInit(keyResultProcess_p keyResultProcessFn)
{
	if(keyResultProcessFn != NULL)
        keyResultProcessFunction = keyResultProcessFn;
    memset(realKeyGroupTrigMrg,0,sizeof(realKeyGroupTrigMrg));
    realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounterTarget1 = 305;   //短按
    realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounterTarget2 = 15000;   //长按大概3秒
}

void getKeyStateAndDoUserProcess(void){
//按键A
    if(KEY_REAL_A_PIN == 0){
        if(realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter < 0xFFFF)
            realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter++;
        if(realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter >= realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounterTarget2){
            if(realKeyGroupTrigMrg[KEY_REAL_A].activeState == 0){   //防止长按重复触发
                virtualKeys |= KEY_SW_2_PRESSED;
                realKeyGroupTrigMrg[KEY_REAL_A].activeState = 1;
            }   
        }
    }
    else{  //短按模式,抬手才进行统计
        if(realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter >= realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounterTarget1){
            if(realKeyGroupTrigMrg[KEY_REAL_A].activeState == 0){   //防止长按抬手导致的短按触发           
                virtualKeys |= KEY_SW_1_PRESSED;
            }
        }
        if(realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter >= realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounterTarget2){
            virtualKeys |= KEY_SW_3_PRESSED;    //按键释放事件,将长按时间改短充当短按,屏蔽掉防止长按重复触发,释放得以执行
        }
        realKeyGroupTrigMrg[KEY_REAL_A].keyTrigCounter = 0;
        realKeyGroupTrigMrg[KEY_REAL_A].activeState = 0;
    }
	if(keyResultProcessFunction != NULL)
		keyResultProcessFunction(virtualKeys);
	virtualKeys = 0x0000;
}

//所有的输入控制源均被抽象为虚拟按键
void addKeyEventToVirtualKeys(unsigned int keyMasks)
{
    virtualKeys |= keyMasks;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值