嵌入式微系统
上QQ阅读APP看书,第一时间看更新

1.3.5 按键扫描

按键获取一般有两种方式(图1-14):一是按键触发输入脚中断;二是扫描读取输入脚电平。然而,常规按键是一种机械结构,当按下和释放时,存在毛刺问题,如图1-15所示。并且这个毛刺的严重程度跟按键触点的接触良好性有关,典型的例子是鼠标时间用久了,会出现按键失效或者是一次按键出现两次连击现象,这往往是鼠标触点氧化导致的,触点清洗一下即可。

图1-14 按键获取电路图

图1-15 按键出现问题

为了解决按键接触引起的误触发,需要引入一个小抖动处理,中断读取的方式,在中断产生后,关闭中断,延时一定时间在按键稳定区读取按键值,之后再延时一段时间,重新打开中断响应,以避开释放时的抖动二次中断。扫描方式一般利用节拍来采样,当检测到有连续两次及以上有效按键值的,认为产生了一次按键事件,为了防止按键释放时抖动引起二次按键,一般还需要加入防止双击处理。

MS的按键处理是第二种扫描方式,利用节拍例行处理来扫描GPIO获取,考虑到实际按键往往超过10个,直接利用MCU51的P1口的8个脚作为按键扫描脚(图1-16),采用4×4交叉扫描方式实现16个按键(图1-17),解决MCU51端口少的问题。

图1-16 MCU51的P1口按键扫描脚

图1-17 MS按键交叉的扫描方式

4×4按键如图1-16所示,P1口8个端口都配置为双向端口,其中输出为OC门输出,带上拉电阻,在干扰大的场合,需要外部扩展上拉电阻,提高抗干扰能力。假设按下了“A”按键,我们来分析4×4扫描按键的原理。

1)设置X=0,获取Y=4’b1011。

2)设置Y=0,获取X=4’b1101。

3)合并X、Y得到扫描码8’b10111101。

4)扫描码8’b10111101映射到按键码“A”。

这种4×4扫描方式,很适合一次只按一个按键的操作,这时,扫描码与按键码是一一映射的,具有唯一性。若存在多个按键同时按下的情况时,比如PC的Crtl+Alt+Z这种组合按键,这种扫描方式就不适合了,因为存在不唯一性问题。

MS的按键扫描流程如图1-18所示,间隔10ms的系统节拍例行查询按键扫描码,当发现扫描码不为invalid,也就是不等于8’b11111111,说明有按键按下,保存第一次扫描码和第二次扫描码,之后等待按键释放。当检测到扫描码为invalid时,说明按键已经释放。第一步检测当前按键是否跟上一次按键间隔太紧,也就是说是否存在二次连击、按键接触不良引起的误操作,若有,则需要一个二次连击的计数器避免误操作。之后再判断按键值是否正确,若不正确直接退出;若正确,区分按键操作类型,是长按键,还是短按键,(工业设备,按键不是很多)往往用长按和短按来增加按键数,这个类似PC按键的Shift按键或者是Caps Lock按键,把字母分为大写和小写一个道理,工业设备,按键一般没有连按,怕误操作。区分了长短按键之后,再发送按键消息,一个完整的按键获取处理完成,按键设备复位。

图1-18 按键扫描流程

按键设备的数据结构定义如下。

代码清单1-17:按键宏及变量定义

#define ShortInterval       2                             // 短按按键间隔 >= 2
#define LongInterval        40                            // 长按按键间隔
#define DoubleHitInterval   20                            // 防按键双击误动间隔
static Byte idata ScanFirstData = invalid;                // 第一次采集按键值
static Byte idata ScanSecondData = invalid;               // 第二次采集按键值
static Byte idata ScanCounter = 0;                        // 按下按键计数器
static Byte idata DoubleHitCounter = 0;                   // 防按键双击误动计数器

一般采用MCU51的P1口作为按键扫描口,采用4×4矩阵扫描方式,用户只需要修改矩阵扫描部分硬件及代码即可。建议添加上拉电阻,电阻值在10kΩ左右可以提高按键准确性,且抗干扰能力强,若干扰严重时,可将上拉电阻阻值下调。代码如下。

代码清单1-18:按键扫描

static Byte ScanPin(void)
{
      Byte scan;
      P1 = 0x0F; Delay(1);                // P1.7~P1.40,延时,等待电路稳定
      scan = P1;                          // 读取低4位扫描值
      P1 = 0xF0; Delay(1);                // P1.3~P1.00,延时,等待电路稳定
      scan = scan | P1 ;                  // 读取高四位扫描值并与低四位合并
      return(scan);                       // 返回值
}

按键扫描必须要基于系统节拍例行扫描,所以要有一个系统节拍例行处理函数,代码如下。

代码清单1-19:按键系统节拍例行程序

void KeySystickRoutine(void) 
{
    Byte scan, key;
    scan = ScanPin();                                        // 获取扫描码
    if (scan != invalid)                                     // 判断是否有按键按下
    {
        ScanCounter++;
        if (ScanCounter == 1)                                // 保存第一次扫描码
            ScanFirstData = scan;
        else if(ScanCounter == 2)                            // 保存第二次扫描码
            ScanSecondData = scan;
        else if (ScanCounter > LongInterval)                 // 防止计数器循环
            ScanCounter = LongInterval;
    }
    else 
    {   
        if(DoubleHitCounter)                                 // 防止连续双击按键
        {
            DoubleHitCounter--;
            ScanCounter = 0;
            return;
        }
        if (ScanCounter == 0) return                       // 若没有扫描码,退出
        if ((ScanFirstData == invalid) || (ScanFirstData != ScanSecondData)) 
        {
            ScanCounter = 0;                                 // 判断扫描码是否正确
            return;
        }
        if (ScanCounter == LongInterval)                     // 判断是否为长按键
            key = RemapLongKey(ScanFirstData);               // 通过扫描码获取长按键值
        else if (ScanCounter >= ShortInterval)               // 判断是否为短按键
            key = RemapKey(ScanFirstData);                   // 通过扫描码获取短按键值
        else
            key = invalid;                                   // 误操作,无效
        if (key != invalid) 
        {
            PostMessage(MessageKey, key);                    // 发送按键
            DoubleHitCounter=DoubleHitInterval;              // 设定二次连击计数器
        }
        ScanFirstData = invalid;                             // 按键设备复位
        ScanSecondData = invalid;
        ScanCounter = 0;
    }
}