10316|18

167

帖子

0

TA的资源

纯净的硅(高级)

楼主
 

【TI原创】矩阵键盘扫描新思路 [复制链接]

用过单片机的人应该都熟悉矩阵键盘扫描程序。矩阵键盘一般是依次扫描输出管脚,需要N(N为输出管脚的数目)次扫描才能完成整个键盘的一次完整扫描。不过,你见过一次就可以扫描一个完整键盘的程序吗?
呵呵,这个你应该见过的,这就是键盘扫描中很有特色的线反转法,在不少教科书中都介绍过。
所以我今天要介绍的不是线反转法这个扫描算法,而是将定时器和线反转法结合在一起的一种扫描算法。引入定时器后,由于整个扫描过程都是在中断中进行的,所以我给他起了个名字:全中断矩阵键盘。
不知道大家还记不记得我以前写的那个全中断键盘,那是针对独立按键的,当将其引入到矩阵键盘中后,带来的一系列问题更加的有意思。
下面我就详细介绍一下这个“全中断矩阵键盘”。
首先回忆一下全中断键盘这个算法,在键盘中,作为按键输入的GPIO管脚开启中断,在GPIO中断到来时,关闭GPIO中断,同时打开定时器开始计时(消抖)。在定时器中断中读取按键的值,通俗关闭自己的终端,打开GPIO中断,准备迎接下一次中断的到来。两个中断就是这样接力完成整个扫描过程,所有的扫描过程都是在中断中的一个简短运算,对软件资源消耗极少。
好了,秀完了我所谓的中断键盘,再来介绍一下线反转法这个经典的矩阵键盘扫描算法吧。
线反转法,故名思义,在其扫描过程中有一次输入输出反向的过程。其扫描算法是(假设输入管脚上拉,低电平按键有效):扫描时,所有的输入管脚都上拉,所有的输出管脚都输出低电平,然后检测输入管脚。当输入管脚检测到低电平时(不论在哪个管脚上的),读入作为行号。然后将输入和输出管脚反过来,原来的输出脚做输入,原来的输入脚做输出,将在它上面读取的整组值原封不动的输出。如果只有一个按键按下,则此时的输出管脚中,只有一只管脚是低电平,(相当与直接取有键按下的行来进行扫描,效率当然高了)再从现在的输入管脚读取数据作为列号,然后根据行号和列号来解码。
最基本的键盘扫描程序如下:

 

  1. void IntGPIOb(void)
    {
    IntDisable(INT_GPIOB);
    GPIOPinIntClear(GPIO_PORTB_BASE,0x0f);
    SysTickEnable();
    }

  2. void IntSysTick(void)
    {
     SysTickDisable();
     ReadData=GPIOPinRead(GPIO_PORTB_BASE,0x0f);
     if(ReadData!=0x0f);
     {
      GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_IN);
      GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_OUT);
      GPIOPinWrite(GPIO_PORTB_BASE,0x0f,ReadData);
      KeyCoder=(ReadData & 0x0f)|(GPIOPinRead(GPIO_PORTB_BASE,0xf0) & 0xf0);
      Key_Change=1;
     }
     GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_IN);
     GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_OUT);
     GPIOPinWrite(GPIO_PORTB_BASE,0xf0,0x0f);
     IntEnable(INT_GPIOB);
    }

复制代码

 

GPIO中断根原来的独立按键模式一样,都是打开定时器中断即可。这里采用的是M3内核处理器特有的Systick定时器,比通用定时器操作起来更加的简便。
在定时器中断中,首先再次读取产生中断的端口,看按键是否依然按下。如果没有按键按下,则说明当前是抖动,此时恢复各个端口及中断的状态到待命状态,然后退出。当有按键按下时,将数据反转输出,然后计算按键键值编码并设置按键更改标志。
在这里因为是4×4键盘,每组读取的数据有四位有效位,所以直接将两组数据拼接起来即可完成按键编码。
这就结束了吗?当然没有了,这只是一个最基本的,偷工减料的键盘扫描程序。
普通的矩阵键盘是不支持多个按键同时按下的,普通的逐行扫描的程序也可以检测到按键按下的数量,从而避免无效的键值出现。但是这个程序不具备这项功能。
为了检测无效键值,可以在每次读取时采用移位的方法对每次读取的值进行计数,如果0的个数超过一个则放弃本次结果。但这不是我喜欢的风格,我不喜欢循环好多次之为了检测一个0,所以就有了后面的文章。
我不喜欢在运算上花费时间,但是对于内存的消耗还是可以忍受的,在FPGA设计中常常会碰到两个折中的原则,一是用面积换取速度,另一个则是用速度(牺牲速度)换取面积(即资源数量)。在单片机中也同样有类似的问题,现在就到了用面积换取速度的时候了。
最简单的方法,就是定义一个数组,在下标号为有效键值的存储单元中,定义为我们想要的键值,其他区域,可以定义为一个无效键值,程序如下:

 

  1. void IntSysTick(void)
    {
     SysTickDisable();
     ReadData=GPIOPinRead(GPIO_PORTB_BASE,0x0f);
     if(ReadData!=0x0f);
     {
      GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_IN);
      GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_OUT);
      GPIOPinWrite(GPIO_PORTB_BASE,0x0f,ReadData);
      ReadData=(ReadData & 0x0f)|(GPIOPinRead(GPIO_PORTB_BASE,0xf0) & 0xf0);
      ReadData=Key_Num_Tabel[ReadData];
      if(ReadData!=Err_Num)
       {
        KeyCoder=ReadData;
        Key_Change=1;
       }
     }
     GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_IN);
     GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_OUT);
     GPIOPinWrite(GPIO_PORTB_BASE,0xf0,0x0f);
     IntEnable(INT_GPIOB);
    }

复制代码

 

这样写的好处是,按键的值可以任意定义,不存在运算公式的束缚。比如我曾经在公司想采用AVR单片机代替掉ZLG的7290芯片,这时候这样定义就有好处了,可以完全模仿7290芯片输出的值,而且完全不用动主处理器(ARM)的程序。再者,有时候一个键值的定义往往也会对程序的执行效率起到很大的作用。比如说我们要根据按键的值来选择执行的程序,这时候最好的键值是什么?当然是其功能程序的入口地址了,这样定义后,就完全不用再进行按键判断了。
不过有些朋友可能不买这一套,你自己写的程序占用内存多你就说占内存多好?典型的忽悠人嘛。我就不要那么多好处,内存还我!
呵呵好吧,虽然用面积换取速度有时候是必然的,但是内存用量这个问题则是我们要考虑是否采取这种兑换方式的首要问题,我们要考虑考虑这桩买卖是否划算。不过还好,这个问题也不是完全的铁公鸡,一毛不拔。在内存用量上其实还是有一些讨价还价的余地的。
前面这种方法要占用256字节以上的内存,不过这个按键的初始编码本来就是由两个编码组合起来的,最终编码也可以采用组合的方式来实现嘛。这样,定义一个数组,只要其长度大于半个字节的计数范围即可,典型的是16个字节,然后高低字节分别用初始编码的查出最终编码后再组合在一起即可。这样数组占用内存一下子从256字节减小到了16字节,我想大多数“客户”的脸上应该浮出笑意了吧。
其具体方法是:该键盘为四行四列,无论是行号还是列号都可以用两位来表示。在最终键值中用00、01、02和03分别表示按下的行号和列号。这也是和逐行扫描得出的紧密型键值一样了,对于无效键值,则统一赋成0xFF。程序如下:

 

  1. void IntSysTick(void)
    {
     SysTickDisable();
     ReadData=GPIOPinRead(GPIO_PORTB_BASE,0x0f);
     if(ReadData!=0x0f);
     {
      GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_IN);
      GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_OUT);
      GPIOPinWrite(GPIO_PORTB_BASE,0x0f,ReadData);
      //ReadData=(ReadData & 0x0f)|(GPIOPinRead(GPIO_PORTB_BASE,0xf0) & 0xf0);
      ReadData=Key_Num_Tab[ReadData & 0x0f];
      ReadData=(ReadData & 0xfc)|(Key_Num_Tab[(GPIOPinRead(GPIO_PORTB_BASE,0xf0)>>4)]<<2);
      if(ReadData<=0x0f)
      {
       Key_Read=ReadData;
       Key_Change=1;
      }
     }
     GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_IN);
     GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_OUT);
     GPIOPinWrite(GPIO_PORTB_BASE,0xf0,0x0f);
     IntEnable(INT_GPIOB);
    }

复制代码

 

两次查表计算中,第一次最终键值中没有需要保护的单元,所以采用直接赋值。而第二次只需要保护最低2位即可,高位虽然不用,但是要保护好每次写入高位的1,以避免清除掉错误标志,所以高位从不与0进行与运算。
好了,现在开始对这个程序感兴趣了吧?在推销中有一个很重要的方法就是趁热打铁。那我现在就要在这块铁上再砸一锤!将数组大小减小到8字节!
这可能吗?
当然可能,横列扫描读取的初始码中只有一位是与其他三位不同的,如果这一位取的是低电平,那似乎看不出什么,但是如果这一位变成了高电平,那就有意思了。一个四位二进制串中,只有一位是1,这个数字只能是1、2、4、8。将其减一,其范围正好在0—7中,此时定义一个8字节的数组就已经足够存储按键编码了。只是不同的是,还需要对大于8的初始值编码进行一次筛选。程序代码如下(GPIO中断代码不变,就不给出了,只是输入GPIO设置中要使能下拉电阻,采用上升沿中断):

 

  1. void IntSysTick(void)
    {
     SysTickDisable();
     ReadData=GPIOPinRead(GPIO_PORTB_BASE,0x0f);
     if(ReadData!=0x00);
     {
      GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_IN);
      GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_OUT);
      GPIOPinWrite(GPIO_PORTB_BASE,0x0f,ReadData);
      //ReadData=(ReadData & 0x0f)|(GPIOPinRead(GPIO_PORTB_BASE,0xf0) & 0xf0);
      if((ReadData<=8)&&(GPIOPinRead(GPIO_PORTB_BASE,0xf0)<=0x80))
      {
       ReadData=Key_Num_Tab[ReadData - 1];
       ReadData=(ReadData & 0xfc)|(Key_Num_Tab[(GPIOPinRead(GPIO_PORTB_BASE,0xf0)>>4)-1]<<2);
       if(ReadData<=0x0f)
       {
        Key_Read=ReadData;
        Key_Change=1;
       }
      }
     }
     GPIODirModeSet(GPIO_PORTB_BASE,0x0f,GPIO_DIR_MODE_IN);
     GPIODirModeSet(GPIO_PORTB_BASE,0xf0,GPIO_DIR_MODE_OUT);
     GPIOPinWrite(GPIO_PORTB_BASE,0xf0,0xf0);
     IntEnable(INT_GPIOB);
    }

复制代码

 


好了,铁打完了,我也累了,休息一下~~

查看本帖全部内容,请登录或者注册

最新回复

MARK 下次看  详情 回复 发表于 2012-2-27 17:34
 
点赞 关注(1)

回复
举报

85

帖子

0

TA的资源

一粒金砂(中级)

沙发
 
学习学习~ LZ辛苦啦~
 
 

回复

1803

帖子

0

TA的资源

五彩晶圆(高级)

板凳
 

回复 楼主 柳叶舟 的帖子

很认真的总结,谢谢!
 
 
 

回复

80

帖子

0

TA的资源

纯净的硅(初级)

4
 
好吧,其实我一直没有搞清楚如果四个键同时按下,四个键为四边形关系时,和两个键同时按下,这两个键为这四个按键的对角关系时,这种方法怎么区分。此时读出来的行号和列号在两种情况下的值是一样的。
 
 
 

回复

2749

帖子

0

TA的资源

裸片初长成(初级)

5
 

原帖由 weirgu 于 2011-10-13 10:44 发表 好吧,其实我一直没有搞清楚如果四个键同时按下,四个键为四边形关系时,和两个键同时按下,这两个键为这四个按键的对角关系时,这种方法怎么区分。此时读出来的行号和列号在两种情况下的值是一样的。

 

如下图,两者依次扫出来的值是不一样的。高低低低指的是MCU输出的扫描电平,依次过去,高低低低、低高低低、低低高低、低低低高。

那么在左侧MCU得到的实际按键后的输入电平,两者是有区别的。输入使能下拉电阻,从上到下,则:

左图依次为:高低低低、低高低低、低低低低、低低低低。

右图依次为:高高低低、高高低低、低低低低、低低低低。

这也是矩阵键盘的扫描原理。

 
 
 

回复

2749

帖子

0

TA的资源

裸片初长成(初级)

6
 

原帖由 weirgu 于 2011-10-13 10:44 发表 好吧,其实我一直没有搞清楚如果四个键同时按下,四个键为四边形关系时,和两个键同时按下,这两个键为这四个按键的对角关系时,这种方法怎么区分。此时读出来的行号和列号在两种情况下的值是一样的。

 

对于多键按下也要检测的话,若仅依靠行号和列号来判别,那只有4*4=16种情况。

而实际上,若包含多键扫描,应当有2^16=65536种情况。显然是不够用的。

当然,实际也应该用不到那么多按键同时按的情况。

 
 
 

回复

80

帖子

0

TA的资源

纯净的硅(初级)

7
 
逐行扫描看来还是必须的
 
 
 

回复

167

帖子

0

TA的资源

纯净的硅(高级)

8
 

回复 5楼 David_Lee 的帖子

嗯,你是对的,矩阵键盘支持多键按下的,我自从看到线翻转法后对其非常喜欢,所以一直用它,进而产生矩阵键盘不支持多键按下的看法,而且也忽略了传统方法中的很多深层应用。看来后面要好好研究下传统方法了

[ 本帖最后由 柳叶舟 于 2011-10-13 23:24 编辑 ]
 
 
 

回复

1803

帖子

0

TA的资源

五彩晶圆(高级)

9
 

回复 5楼 David_Lee 的帖子

赞一个,这么漂亮的图是用什么画出来的?
 
 
 

回复

2749

帖子

0

TA的资源

裸片初长成(初级)

10
 

原帖由 Study_Stellaris 于 2011-10-13 23:24 发表 赞一个,这么漂亮的图是用什么画出来的?

 

AutoCAD

 
 
 

回复

133

帖子

0

TA的资源

纯净的硅(初级)

11
 
终于,看懂楼上说的是什么东西了。看着那画的图。我以为是用线反转法,那我是怎么也没有看明白,原来是用的逐行扫描的。呵呵 。
 
 
 

回复

61

帖子

0

TA的资源

一粒金砂(中级)

12
 
好东西,好好学习一下,楼主辛苦了!
 
 
 

回复

2749

帖子

0

TA的资源

裸片初长成(初级)

13
 

原帖由 radxiaohe 于 2011-10-14 18:29 发表 终于,看懂楼上说的是什么东西了。看着那画的图。我以为是用线反转法,那我是怎么也没有看明白,原来是用的逐行扫描的。呵呵 。

 

如weirgu所言,如果支持多建的话,线翻转还真是有局限性。

 
 
 

回复

167

帖子

0

TA的资源

纯净的硅(高级)

14
 

回复 13楼 David_Lee 的帖子

嗯,出差时偷空去了一下网吧,看到了你前面的回复,正在思考引入定时器的逐行扫描,支持有限的自定义功能键,今天应该会有结果了。
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

15
 
辛苦了,很好的文章。
 
 
 

回复

170

帖子

0

TA的资源

一粒金砂(中级)

16
 
支持一下
 
 
 

回复

23

帖子

0

TA的资源

一粒金砂(中级)

17
 
不错,好好学习一下。
 
 
 

回复

53

帖子

0

TA的资源

一粒金砂(中级)

18
 

谢谢分享

谢谢分享,我想请教下5*5的矩阵键盘扫描程序
 
 
 

回复

231

帖子

0

TA的资源

一粒金砂(中级)

19
 
MARK
下次看
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
Microchip 直播|利用motorBench开发套件高效开发电机磁场定向控制方案 报名中!
直播主题:利用motorBench开发套件高效开发电机磁场定向控制方案
直播时间:2025年3月25日(星期二)上午10:30-11:30
快来报名!

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表