标题:
行列扫描法,反转法,状态机法三种矩阵键盘扫描方法详解 带程序
[打印本页]
作者:
51hei学习技术中
时间:
2017-3-13 01:50
标题:
行列扫描法,反转法,状态机法三种矩阵键盘扫描方法详解 带程序
实现矩阵键盘扫描的三种方法(代码见附件):
1.行列扫描法
2.反转法
3.状态机法(结合定时器中断)
第一种和第二种方法的本质都是进行循环查询,大量占用MCU的时间,效率比较低。初学者一般会学这两种
第三种方法属于状态机方法,它是结合定时器中断的,相比于前两种采用延时函数的方法,大大提高了MCU的效率。
我们设计矩阵键盘的扫描函数时,要保证:既要及时的判断按键是否被按下,又要让MCU有时间去做其他的事。
代码所对应的硬件电路:
0.png
(47.32 KB, 下载次数: 149)
下载附件
2017-3-13 01:54 上传
0.png
(89.41 KB, 下载次数: 133)
下载附件
2017-3-13 01:54 上传
完整的原理图:
51start单片机开发板V3.0原理图.pdf
(805.5 KB, 下载次数: 88)
2017-3-13 01:44 上传
点击文件名下载附件
下载积分: 黑币 -5
全部源码下载:
矩阵键盘 反转法.zip
(55.58 KB, 下载次数: 220)
2017-3-13 01:44 上传
点击文件名下载附件
下载积分: 黑币 -5
矩阵键盘 状态机法.zip
(40.12 KB, 下载次数: 243)
2017-3-13 01:44 上传
点击文件名下载附件
下载积分: 黑币 -5
矩阵键盘(行列扫描法).zip
(33.98 KB, 下载次数: 182)
2017-3-13 01:44 上传
点击文件名下载附件
下载积分: 黑币 -5
意见整理:
这几个方法其中包含的一些问题。(先说明一点,我只看了 “反转法” 和 “状态机” 法。)
“反转法” 里面的松手检测使用了 " 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!
矩阵键盘 状态机法主程序:
/*==========================================================
* 开发人员:laowang
* 当前版本:V1.0
* 创建时间:11/26
* 修改时间:
* 功能说明:对4*4矩阵键盘扫描,用8位数码管进行显示,刚开始时不亮,依次按下按键时
数码管依次显示0-F,扫描方法为状态机方法+定时器中断
*==========================================================*/
#include<reg52.h>
#include"Define.h"
#include"display.h"
#include"matrixkeyscan.h"
void Timer0_init(); //定时器初始化函数
uint flag=0; //按键扫描标志,每中断一次,扫描一次
//主函数
void main()
{
uchar key_state=0;
uchar readkey;
readkey=0xff;
Timer0_init();
Display_init(); //使之不亮
while(1)
{
if(flag)
{
flag=0;
readkey=Keyscan();
Display(readkey);
}
}
}
void Timer0() interrupt 1
{
TH0=0xd8; //10Ms产生一次中断
TL0=0xf0;
flag=1;
}
void Timer0_init()
{
TMOD=0x01;
TH0=0xd8; //12MHz 10Ms
TL0=0xf0;
EA=1;
ET0=1;
TR0=1;
}
复制代码
行列扫描法的主程序:
/*==========================================================
* 开发人员:laowang
* 当前版本:V1.0
* 创建时间:11/19
* 修改时间:
* 功能说明:让数码管的前两位显示显示一个二位数(0-59),
通过三个按键控制,按下key1num加1,按下key2num减1,按下key3num清零
*==========================================================*/
/*==================硬件电路=============================================
*
*
* MCU=89x51/52 8位数码管(共阴)
* +---------------+ +------+ +------------+------------+
* | p1.0|-----| |---->a| | |
* | . | | 573 | . |8. 8. 8. 8. |8. 8. 8. 8. |
* | . | | | . | | |
* | p1.7|-----| |--->dp| | |
* +--key1--|P2.0 p3.4|-----| 段选 | +------------+------------+
* | | | +------+ | |
* |--key2--|P2.1 | | . . . . . . |
* | | | +------+ | |
* |--key3--|P2.2 p3.5|-----| | | |
* | | p1.0|-----| 573 |-----------| |
* GND | . | | | |
* | . | | | |
* | p1.7|-----| 位选 |--------------------------|
* | | +------+
* | |
* | |
* +---------------+
*
* 说明:此例程中只用到了数码管的前两位
*=================================================================================*/
#include<reg52.h>
#include"display.h"
#include"matrixkey.h"
void main()
{
while(1)
{
Matrixkeyscan();
Display(key);
}
}
复制代码
完整的源码请到本帖顶部下载附件.
作者:
uaeng
时间:
2017-3-13 16:45
在刘平老师的《深入浅出玩转51单片机》里学习过状态机法,看得不是太懂。谢楼主分享了。
作者:
2721412lcy
时间:
2017-4-15 17:14
就是没有金币了
作者:
2721412lcy
时间:
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;
}
作者:
2721412lcy
时间:
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;
}
作者:
2721412lcy
时间:
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;
}
作者:
小~灰~灰
时间:
2017-7-24 14:24
目前用的反转,出了些问题,刚好学习学习
多谢楼主了
作者:
GaryHobson
时间:
2018-3-7 13:33
之前都是用反转法,现在试试状态机
作者:
wandiyh
时间:
2018-5-24 10:46
感谢楼主分享
,学习一下
作者:
aw3aw3
时间:
2018-5-26 15:32
很有用。非常感谢分享
作者:
yuhang98
时间:
2018-11-20 20:07
学习一下
作者:
黑米粥粥
时间:
2018-12-2 14:45
感谢楼主分享,学习一下
作者:
hulilanyua
时间:
2019-6-10 00:03
多谢楼主了
作者:
L1BB
时间:
2019-7-13 22:00
感谢楼主
作者:
2448289424
时间:
2019-7-14 17:20
写得挺好的,点赞,,我下载了你的状态机程序,,为了省点黑币,其他两个我就没下载了,,我看了后自己有些看法,,如果我同一行一个按键压死,,然后我在同一行按下另一个按键呢????还有主函数10Ms检测一次,,为什么数码管显示也放在这个里面???上电数码管亮我觉得不是上面6#说的, readkey=Keyscan();
Display(readkey);
上电按键没按的换Keyscan()返回的应该是0吧,,display(0)对应你的数组不就是全亮???,我觉得应该是这样,试试把数组的全灭数据也就是0x00,放在第一个,,剩下的就按你的按键去排显示的数据就好了
作者:
ZzzZM
时间:
2019-11-18 21:52
哇,感谢楼主的分享,给我们这些小白提供了学习的资料
作者:
Why丶
时间:
2019-12-8 15:50
感谢分享
欢迎光临 (http://www.51hei.com/bbs/)
Powered by Discuz! X3.1