找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 28022|回复: 16
打印 上一主题 下一主题
收起左侧

行列扫描法,反转法,状态机法三种矩阵键盘扫描方法详解 带程序

  [复制链接]
跳转到指定楼层
楼主
实现矩阵键盘扫描的三种方法(代码见附件):
1.行列扫描法
2.反转法
3.状态机法(结合定时器中断)
第一种和第二种方法的本质都是进行循环查询,大量占用MCU的时间,效率比较低。初学者一般会学这两种
第三种方法属于状态机方法,它是结合定时器中断的,相比于前两种采用延时函数的方法,大大提高了MCU的效率。
我们设计矩阵键盘的扫描函数时,要保证:既要及时的判断按键是否被按下,又要让MCU有时间去做其他的事。
代码所对应的硬件电路:


完整的原理图:
51start单片机开发板V3.0原理图.pdf (805.5 KB, 下载次数: 85)


全部源码下载:
矩阵键盘 反转法.zip (55.58 KB, 下载次数: 214)

矩阵键盘 状态机法.zip (40.12 KB, 下载次数: 231)

矩阵键盘(行列扫描法).zip (33.98 KB, 下载次数: 174)


意见整理:
这几个方法其中包含的一些问题。(先说明一点,我只看了 “反转法” 和 “状态机” 法。)

“反转法” 里面的松手检测使用了 " while(...) " 这种方式(本人从前也是运用这种方式,所以现在就遇到问题了。所以在此建议大家在了解了基本的按键检测方法后,要及时的学习更加高级和完善的方法,不要满足现状。), 这种松手检测只能运用在极少数情况下才能使用,因为等待释放的过程中 “CPU” 一直在 “等待”释放,导致其余的任务都不被执行。假设一个系统中有动态数码管显示(不考虑定时中断。这里说一点题外话,其实我们应该尽可能减少对外设的依赖。这样做,一来能够提高自己的编程能力;二来能提高系统稳定性,特别是中断,开多了是件很  “危险” 的事情。),则数码管会熄灭,又或者其他实时性较高任务无法被执行。所以这里提供的 “反转法” 我认为只能提供一种思路,不大可能应用到实际中。

接着说一下 “状态机法”(与其说是 “状态机法” 倒不如说是“ 状态机 + 线反转 法” ) 。第一个问题我认为倒不是楼主所说的 “上电数码管就亮” ,而是 “松手检测” 没有起作用。其实也只差一步,那就是判断。源码中不论经过任何 “状态” 都会 “return” 最新的值,那么按下了就会被立即 “return” 。所以在 “return” 前加一个 “释放” 判断: “ if(key_state==key_state0) ” 便可。问题解决了么? 还真没有,我们会在编译时(在 keil 环境下) 获得一条警告 “warning C291: not every exit path returns a value(大意是:某些情况下没有返回值)” ,选择 “无视” , 然后烧写,运行(我开始也是这么做的)。好了,的确有了松手检测,但是又出现 “反转法” 中出现的情况——数码管全灭。虽然无法判断是 “卡死” ,还是 “return” 了个未知数,但可以确定的是,这显然也是不符合我们的需要的。我们再回到当初的判断那里,总结前面的问题,任何情况下我们都要 “return” 。静下心来分析发现,只有两种情况:一是没有释放, 那么就要“return” 上一次按键的值;二是已经释放,则“return”这一次的值。于是我们就再引入一个变量  “last_key_value” ,用来存放 “上一次” 的按键值(这里我们要清楚,“上一次” 是一个相对的情况,因为 “这一次” 的按键值立刻就会变成 “上一次”)。 添加的代码如下:
/***************CHANGE--START******************************/
if(key_state==key_state0) {
    last_key_value=key_value;
     return key_value;  
}
else    return last_key_value;
/***************CHANGE--END******************************/

说了一堆 “废话” 终于把第一个问题解决了,接下来解决楼主所说的那个问题。其实这是一个识别的问题,也就是说要 “ 如何识别是首次上电呢? ”。
这里我们在 “ main.c ” 文件中引入一个标志位“  bit power_on=1;                                          /* 标志位为 1 表示上电后未按下任何按键的状态 */    ”  
并在 “main” 函数中加入                                    
“  if (power_on==0)    Display(readkey);            /* 上电后有按键被按下,才显示对应数据*/                ”
到这里,我们又引入了另一个问题, “ power_on ” 需要在哪里被 “清零” ,你也许会说,很简单啊,在 “ if(key_state==key_state0) {...} ” 清零啊。我们把  “ power_on = 0 ; /*  注意要声明  extern bit power_on;   */  ”  直接加入其中,然后编译、烧写、运行。接着发现,咦!怎么还亮。然后,凭借我的直觉又把它放到了 “ else ” ,再来一遍,然后你发现直觉真管用,不亮了。 赶紧按一个键试试,显示没错,不过你会突然发现好像下去的时候显示的是 “ 0 ”  松手以后,才显示对应的数字。复位再来一遍,真的是这样,怎么办?认真分析代码发现(这里就不废话了,留给各位“ 废 ”好了),你会发现,的确应该在 “ if(key_state==key_state0) {...} ” 中清零(第一感觉真的很准)。不过还要添加一个 “标志位” 可以是 “释放标志位” 也可以是 “按下标志位”,为什么?就留给各位来想了。这里选择 “释放标志位”。修改的代码如下:
/***************CHANGE--START******************************/
case key_state2:{                                                        //状态2,判断是否被释放
    if(key_temp==no_key){                                        //释放,转回到状态0
        key_state=key_state0;
        shifang=1;
    }
}break;

......
if(key_state==key_state0)   {
    last_key_value=key_value;
    if(bit_release)    power_on=0;     /* bit_release 也就是释放标志位 */
    return key_value;
}
/***************CHANGE--END******************************/
完成以后,编译、烧写、运行,OK!

矩阵键盘 状态机法主程序:
  1. /*==========================================================
  2. * 开发人员:laowang
  3. * 当前版本:V1.0
  4. * 创建时间:11/26
  5. * 修改时间:
  6. * 功能说明:对4*4矩阵键盘扫描,用8位数码管进行显示,刚开始时不亮,依次按下按键时
  7.                           数码管依次显示0-F,扫描方法为状态机方法+定时器中断
  8. *==========================================================*/
  9. #include<reg52.h>
  10. #include"Define.h"
  11. #include"display.h"
  12. #include"matrixkeyscan.h"

  13. void Timer0_init();                        //定时器初始化函数
  14. uint flag=0;                                  //按键扫描标志,每中断一次,扫描一次

  15. //主函数
  16. void main()
  17. {
  18.         uchar key_state=0;        
  19.         uchar readkey;
  20.         readkey=0xff;

  21.         Timer0_init();
  22.         Display_init();                        //使之不亮
  23.         while(1)
  24.          {
  25.                   if(flag)
  26.                   {
  27.                            flag=0;
  28.                            readkey=Keyscan();
  29.                            Display(readkey);
  30.                   }
  31.          }
  32. }

  33. void Timer0() interrupt 1                     
  34. {
  35.         TH0=0xd8;          //10Ms产生一次中断
  36.          TL0=0xf0;
  37.          flag=1;
  38. }

  39. void Timer0_init()
  40. {
  41.         TMOD=0x01;
  42.          TH0=0xd8;                          //12MHz  10Ms
  43.          TL0=0xf0;
  44.         EA=1;
  45.         ET0=1;
  46.          TR0=1;        
  47. }
复制代码

行列扫描法的主程序:
  1. /*==========================================================
  2. * 开发人员:laowang
  3. * 当前版本:V1.0
  4. * 创建时间:11/19
  5. * 修改时间:
  6. * 功能说明:让数码管的前两位显示显示一个二位数(0-59),
  7.              通过三个按键控制,按下key1num加1,按下key2num减1,按下key3num清零
  8. *==========================================================*/
  9. /*==================硬件电路=============================================
  10. *
  11. *                                                     
  12. *                  MCU=89x51/52                         8位数码管(共阴)
  13. *              +---------------+     +------+      +------------+------------+     
  14. *              |           p1.0|-----|      |---->a|                |                  |  
  15. *              |             . |     | 573  |  .   |8. 8. 8. 8. |8. 8. 8. 8. |
  16. *              |             . |     |      |  .   |                 |                  |
  17. *              |           p1.7|-----|      |--->dp|                |                   |        
  18. *     +--key1--|P2.0       p3.4|-----| 段选 |      +------------+------------+  
  19. *     |        |               |     +------+           |                  |
  20. *     |--key2--|P2.1           |                                      | . . . . .  .        |
  21. *     |        |               |     +------+           |              |                                 
  22. *     |--key3--|P2.2       p3.5|-----|      |           |                       |
  23. *     |        |           p1.0|-----|        573  |-----------|                        |
  24. *    GND       |            .  |     |      |                                            |
  25. *              |                  .  |     |      |                                        |
  26. *                         |                p1.7|-----| 位选 |--------------------------|                                       
  27. *                    |                         |     +------+
  28. *                        |                     |
  29. *              |               |
  30. *              +---------------+                  
  31. *                                       
  32. * 说明:此例程中只用到了数码管的前两位
  33. *=================================================================================*/
  34. #include<reg52.h>
  35. #include"display.h"
  36. #include"matrixkey.h"

  37. void main()
  38. {
  39.         while(1)
  40.         {
  41.                 Matrixkeyscan();
  42.                 Display(key);        
  43.         }

  44. }
复制代码

完整的源码请到本帖顶部下载附件.




分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏13 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:135835 发表于 2017-3-13 16:45 | 只看该作者
在刘平老师的《深入浅出玩转51单片机》里学习过状态机法,看得不是太懂。谢楼主分享了。
回复

使用道具 举报

板凳
ID:184846 发表于 2017-4-15 17:14 | 只看该作者
就是没有金币了
回复

使用道具 举报

地板
ID:184846 发表于 2017-4-19 08:30 | 只看该作者
下载看了,觉得上电数码管亮,好像是初始化显示函数Display_init();那里一开始就把位选全打开了,而每10Ms产生一次中断,主函数中一开始readkey=0xff;然后if(flag){flag=0;readkey=Keyscan();Display(readkey);一开始10ms就显示0XFF,觉得这个位选应该在按键松手才打开,修改如下
void Display_init()
{
        P1=0xFF;                 //将所有的位选关闭,所有数码管不亮,
        wela=1;
        wela=0;
        P1=0x00;           //将段选关闭,也就是刚开始的时候数码管都不亮
        dula=1;
        dula=0;
}
void Display(uchar num1)
{
        wela=1;
        P1=0x00;                 //将所有的位选打开,所有数码管同时亮,
        wela=0;
        dula=1;
        P1=smg_duan[num1];
        dula=0;       
}
回复

使用道具 举报

5#
ID:184846 发表于 2017-4-19 08:31 | 只看该作者
哦,我看的是状态机那个。
下载看了,觉得上电数码管亮,好像是初始化显示函数Display_init();那里一开始就把位选全打开了,而每10Ms产生一次中断,主函数中一开始readkey=0xff;然后if(flag){flag=0;readkey=Keyscan();Display(readkey);一开始10ms就显示0XFF,觉得这个位选应该在按键松手才打开,修改如下
void Display_init()
{
        P1=0xFF;                 //将所有的位选关闭,所有数码管不亮,
        wela=1;
        wela=0;
        P1=0x00;           //将段选关闭,也就是刚开始的时候数码管都不亮
        dula=1;
        dula=0;
}
void Display(uchar num1)
{
        wela=1;
        P1=0x00;                 //将所有的位选打开,所有数码管同时亮,
        wela=0;
        dula=1;
        P1=smg_duan[num1];
        dula=0;       
}
回复

使用道具 举报

6#
ID:184846 发表于 2017-4-19 09:11 | 只看该作者
下载看了状态机,觉得上电数码管亮,好像是初始化显示函数Display_init();那里一开始就把位选全打开了,而每10Ms产生一次中断,主函数中一开始readkey=0xff;然后if(flag){flag=0;readkey=Keyscan();Display(readkey);一开始10ms就显示0XFF,觉得这个位选应该在按键松手才打开,修改如下
void Display_init()
{
        P1=0xFF;                 //将所有的位选关闭,所有数码管不亮,
        wela=1;
        wela=0;
        P1=0x00;           //将段选关闭,也就是刚开始的时候数码管都不亮
        dula=1;
        dula=0;
}
void Display(uchar num1)
{
        wela=1;
        P1=0x00;                 //将所有的位选打开,所有数码管同时亮,
        wela=0;
        dula=1;
        P1=smg_duan[num1];
        dula=0;       
}

评分

参与人数 1黑币 +100 收起 理由
admin + 100 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

7#
ID:213328 发表于 2017-7-24 14:24 | 只看该作者
目前用的反转,出了些问题,刚好学习学习
多谢楼主了
回复

使用道具 举报

8#
ID:168002 发表于 2018-3-7 13:33 | 只看该作者
之前都是用反转法,现在试试状态机
回复

使用道具 举报

9#
ID:203654 发表于 2018-5-24 10:46 | 只看该作者
感谢楼主分享,学习一下
回复

使用道具 举报

10#
ID:286251 发表于 2018-5-26 15:32 | 只看该作者
很有用。非常感谢分享
回复

使用道具 举报

11#
ID:430148 发表于 2018-11-20 20:07 | 只看该作者
学习一下
回复

使用道具 举报

12#
ID:380988 发表于 2018-12-2 14:45 | 只看该作者
感谢楼主分享,学习一下
回复

使用道具 举报

13#
ID:413383 发表于 2019-6-10 00:03 | 只看该作者
多谢楼主了
回复

使用道具 举报

14#
ID:558624 发表于 2019-7-13 22:00 | 只看该作者
感谢楼主
回复

使用道具 举报

15#
ID:207882 发表于 2019-7-14 17:20 | 只看该作者
写得挺好的,点赞,,我下载了你的状态机程序,,为了省点黑币,其他两个我就没下载了,,我看了后自己有些看法,,如果我同一行一个按键压死,,然后我在同一行按下另一个按键呢????还有主函数10Ms检测一次,,为什么数码管显示也放在这个里面???上电数码管亮我觉得不是上面6#说的,                           readkey=Keyscan();
Display(readkey);
上电按键没按的换Keyscan()返回的应该是0吧,,display(0)对应你的数组不就是全亮???,我觉得应该是这样,试试把数组的全灭数据也就是0x00,放在第一个,,剩下的就按你的按键去排显示的数据就好了
回复

使用道具 举报

16#
ID:644656 发表于 2019-11-18 21:52 | 只看该作者
哇,感谢楼主的分享,给我们这些小白提供了学习的资料
回复

使用道具 举报

17#
ID:658887 发表于 2019-12-8 15:50 | 只看该作者
感谢分享
回复

使用道具 举报

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

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表