4*4键盘扫描、去抖、去错

zhongyuanceshi 2008-11-17 05:45:03
.INCLUDE hardware.inc
.IRAM
.VAR I_KeyBuf = 0 //保存键值
.VAR I_Key_Delay = 0 //扫描有键按下的次数
.EXTERNAL _KeyFlag //有键按下标识符
.CODE
.PUBLIC _SP_Init_IOA;
_SP_Init_IOA: .PROC

R1 = 0x00c3;
[P_IOA_Attrib] = R1;
[P_IOA_Dir] = R1;
[P_IOA_Data] = R1;

R1=0x

RETF
.ENDP;

//============================================================================================
//函数: F_Key_Scan()
//语法:void F_Key_Scan()
//描述:键盘扫描函数
//参数:无
//返回:无
//=============================================================================================
.PUBLIC _F_Key_Scan;
_F_Key_Scan: .PROC

R1 = 0x00F0 //扫描键盘
[P_IOA_Data] = R1
R1 = 0x000f
R1 &= [P_IOA_Data]
JNZ L_Have_Key_Pressed
L_NoKey_Press: //无键按下
R1 = 0
[I_KeyBuf] = R1 //清键盘缓冲区
[_KeyFlag] = R1
[I_Key_Delay]=R1;
RETF
L_Have_Key_Pressed: //有键按下处理
CALL F_Key_Scaning //行扫描确定键
CALL Enter_Key; //确认有键按下
RETF
.ENDP
//============================================================================================
//函数: unsigned F_Key_Scaning()
//语法:void unsigned F_Key_Scaning()
//描述:行扫描函数
//参数:无
//返回:扫描键值
//=============================================================================================
.PUBLIC F_Key_Scaning;
F_Key_Scaning:
F_Key_Scan_B7:
R2=0x0080 //扫描第一行//changed by abin 扫描第四列
[P_IOA_Data] = R2
R1 = 0x000f
R1& =[P_IOA_Data]
CMP R1,0x0000 //是否该行有键按下
JNZ F_Key_Pressed
F_Key_Scan_B6:
R2=0x0040 //扫描第二行
[P_IOA_Data] = R2
R1 = 0x000f
R1& =[P_IOA_Data]
CMP R1,0x0000 //是否该行有键按下
JNZ F_Key_Pressed
F_Key_Scan_B5:
R2=0x0020 //扫描第三行
[P_IOA_Data] = R2
R1 = 0x000f
R1& =[P_IOA_Data]
CMP R1,0x0000 //是否该行有键按下
JNZ F_Key_Pressed
F_Key_Scan_B4:
R2=0x0010 //扫描第四行
[P_IOA_Data] = R2
R1 = 0x000f
R1& =[P_IOA_Data]
CMP R1,0x0000 //是否该行有键按下
JNZ F_Key_Pressed
RETF
F_Key_Pressed:
R2|=R1 //R2--B11~B8:输出 R1--B0~B7,输入
RETF

//============================================================================================
//函数: Enter_Key()
//语法:Enter_Key()
//描述:确定有键按下函数
//参数:无
//返回:无
//============================================================================================
Enter_Key:
Scan_first:
R3 = [I_Key_Delay]
CMP R3,0x0000
JNZ L_Scan_many;
[I_KeyBuf] = R2;
R3 += 1;
[I_Key_Delay] = R3;
RETF;
L_Scan_many: //去抖
R1 = [I_KeyBuf]
CMP R1,R2
JNE L_Error_Key_pro;
R3 = [I_Key_Delay]
R3 += 1;
[I_Key_Delay] = R3;
CMP R3, 0x0010;
JE L_Enter_Have_KeyDown;
RETF;
L_Enter_Have_KeyDown:
R1 = 1;
[_KeyFlag] = R1
R3 = 0;
[I_Key_Delay]=R3
RETF
L_Error_Key_pro:
R3 = 0;
[I_Key_Delay]=R3
[I_KeyBuf] = R3
RETF;

//============================================================================================
//函数: unsigned F_Get_Key()
//语法:unsigned F_Get_Key()
//描述:取键值函数
//参数:无
//返回:键值
//============================================================================================
.PUBLIC _F_Get_Key;
_F_Get_Key: .PROC
R3 = 1
R2 = [I_KeyBuf]
L_Key_value_Loop:
BP = R3 + Key_Table
R1 = [bp]
CMP R1,R2
JE L_KeyCode_Return
R3 += 1
CMP R3,18
// CMP R3,11
JBE L_Key_value_Loop
R3=0
L_KeyCode_Return:
R1 = 0;
[I_KeyBuf] = R1;
R1=R3
RETF //清键盘缓冲区
.endp


Key_Table:
.DW 0x0088,0x0084,0x0082,0x0081
.DW 0x0048,0x0044,0x0042,0x0041
.DW 0x0028,0x0024,0x0022,0x0021
.DW 0x0018,0x0014,0x0012,0x0011

//.DW 0x0011,0x0021,0x0041,0x0081
//.DW 0x0012,0x0022,0x0042,0x0082
//.DW 0x0014,0x0024,0x0044,0x0084
//.DW 0x0018,0x0028,0x0048,0x0088

// .DW 0x0000,0x0014,0x0024,0x0044
// .DW 0x0012,0x0022,0x0042,0x0011
// .DW 0x0021,0x0041,0x0028,0x0018
// .DW 0x0081,0x0082,0x0084,0x0048


#define HaveKey 1
#define NoKey 0
#define P_Watchdog_Clear (unsigned int *)0x7012
unsigned int KeyVal;
unsigned int KeyDownTimes; //保存按键次数
unsigned int KeyFlag; //按键标识
//===============================================================================================
// 函数: main()
// 描述:主函数
// 利用IOA的IOA0~3和IOA4~7作为4X4键盘的行列扫描
//============================================================================================
int main()
{
SP_Init_IOA(); //初始化A口低字节的高四位为输出口,并初始化输出为高电平,
//低四位为带下接电阻输入口
KeyDownTimes = 0;
KeyFlag = NoKey;
while(1)
{
F_Key_Scan();
while(KeyFlag == HaveKey)
{
KeyVal=F_Get_Key();
KeyFlag = NoKey;
}
*P_Watchdog_Clear = 0x0001; //清看门狗操作
}
}


现在我需将IOA2~IOA5设为输入,IOB2~IOB5设为输出。应该两点改变:1.IO控制寄存器改过来 2.把扫描线和接收线的设置一一对应就好
我现在也在慢慢修改,同时也望达人帮忙修改,谢谢!
...全文
3618 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
xujianglun 2010-08-17
  • 打赏
  • 举报
回复
请23楼详细说点,我遇到这种问题,
到后面程序跑死了
rainforest0 2009-05-14
  • 打赏
  • 举报
回复
用delay10ms();不跑死才怪 ,而且进不进休眠?休眠后怎么唤醒?
ltjish2 2008-11-24
  • 打赏
  • 举报
回复
绝对的太繁琐!!
ltjish2 2008-11-24
  • 打赏
  • 举报
回复
绝对的太繁琐!!
gooogleman 2008-11-22
  • 打赏
  • 举报
回复
楼主还没有搞定吗?
其实比较简单,你可以使用一些工具测试一下,比如读取IO, 发送给串口,显示,就是控制IO而已。
kool86 2008-11-19
  • 打赏
  • 举报
回复

//kk为键盘接口,这里为P3,注意这里没有消抖
//有键按下键码返回为keyboard,没有键按下则返回-1

#include "reg51.h"
#include "stdio.h"
#define kk P3//键盘接口

unsigned char code keycode[16]={0x07,0x08,0x09,0x0f, //自定义键盘编码
0x04,0x05,0x06,0x0e,
0x01,0x02,0x03,0x0d,
0x0a,0x00,0x0b,0x0c};
//键盘扫描函数------行列反转法-------//
char key(void)
{
unsigned char L0,L1,keyboard;//行扫描值,列扫描值,键值
kk = 0x0f; //输出扫描行
L0 = ~kk & 0x0f; //读入扫描行
kk = 0xf0; //输出扫描列
L1 = ~kk&0xf0; //读入扫描列
if((L0 | L1) == 0)return(-1); //没有按键按下则返回-1
L0 = (L0 >> 1) * 4; //计算行值
if(L0 >= 0x0c)L0 = 0x0c;
L1 = L1 >> 5; //计算列值
if(L1 >= 0x03)L1 = 0x03;
keyboard = keycode[L0 + L1]; //计算键码
return(keyboard); //键码返回
}
kool86 2008-11-19
  • 打赏
  • 举报
回复
键盘接口技术
一、键盘分类
键盘分为外壳、按键和电路板3大类。
根据按键开关结构对键盘分类,有触点式和无触点式两大类。有触点式按键开关有机械式开关、薄膜开关、导电橡胶式开关和磁簧式开关等;无触点式按键开关有电容式开关、电磁感应式开关和磁场效应式开关。有触点式键盘手感差、易磨损、故障率高;无触点式键盘手感好、寿命长。无论采用什么形式的按键,作用都是一个使电路接通或断开的开关。
根据键盘的按键码识别方式分类,有编码键盘和非编码键盘。编码键盘主要依靠硬件电路完成扫描、编码和传送,直接提供与按键相对应的编码信息,其特点是响应速度快,但硬件结构复杂。非编码键盘的扫描、编码和传送则是由硬件和软件来共同完成,其响应速度不如编码键盘快,但是因为可以通过对软件的修改重新定义按键,在需要扩充键盘功能的时候很方便。
二、键盘的工作原理
常用的非编码键盘有线性键盘和矩阵键盘。线性键盘主要适用于小的专用键盘,上面按键不多,每个按键都有一条数据线送到计算机接口。每个按键对应一根数据线,当按键断开时,数据线上为高电平,当按键按下时,数据线上为低电平。显然,当按键数增多时,输入到计算机接口的数据线也增多,这样就受到输入线宽度的限制了。
矩阵键盘就克服了线性键盘的上述缺点。在矩阵键盘上,其按键按行列排放。例如一个4*4的矩阵键盘,共有按键16个,但数据输入线只有8条。这样可以适合按键较多的场合,因此等到了广泛的应用。
键码识别是指矩阵结构的键盘识别被按键的方法。一般有行扫描法、行列反转法和行列扫描法。
1、行扫描法
行扫描法的工作原理是这样的:CPU首先向所有行输出低电平,如果没有按键按下,则所有列线输出为高电平。如果有某一键按下,则该键所在的列因为与行线低电平短路,该列线变为低电平。CPU在此时通过读取列线的值即可判断有无键按下。
在有键按下的情况下,CPU再来确定是哪一个键按下,采用的方法是行扫描法。先向第0行输出低电平,其余行输出高电平,然后读取所有列线的电平值。如果有某一列为低电平,则说明0行和该列跨接位置的那个键被按下了。确定了键的位置就可以退出扫描了。如果列线全为高电平,说明本行没有键按下,则继续将下一行输出低电平,其余行输出高电平,然后读取所有列线的电平值。依此类推,直到找到按下键的位置,则退出扫描。
找到按下键的位置,即该键的行号和列号,就可以获得该键的键码。这种通过行列位置表示的键码称为行列码或扫描码,也称为键盘扫描码。
2、行列反转法
行列反转法也是常用的识别闭合键的方法。其工作原理是:首先对所有行线输出低电平,列线输出高电平,同时读入列线。如果有键按下,则该键盘所在的列线为低电平,而其它列线为高电平。由此获得列号。然后向所有列线输出低电平,行线输出高电平,读行线,确定按键的行号。通过行号和列号确定按键的位置和编码。
3、行列扫描法
行列扫描法也是键盘使用的主要键码识别方法。其工作原理如下:首先向每一行依次输出低电平,其余各行为高电平,每扫描一行,读取一次列线。如果列线全为高电平,说明没有键按下,如果有一列为低电平,则说明有键按下,此时可以确定行号和列号。行扫描完成。接下来依次向每一列输出低电平,读行线,再次确定按键的行号和列号。两次获得的行号和列号相同,则键码正确,即获得按下键的行列扫描码。
4、注意事项
在键盘设计过程中,除了识别有无键按下,按下键的行列扫描码以外,还要解决抖动和重键问题。一个键按下和释放的时候,按键开关会在闭合和断开位置间跳动几次后达到稳定状态,这就是抖动问题。抖动的存在会使得脉冲的开头和尾部出现一些毛刺波,持续时间一般小于10ms。如果不处理抖动问题,就可能被误作多次按键。抖动的消除可以通过硬件方法,采用RC滤波电路消除抖动的波形。也可以采用软件方法,在读取键码的时候延时一段时间,等信号稳定后再去识别键码。重键问题是指由于误操作,两个或以上的键被同时按下,此时行列扫描码中就会产生错误的行列值。重键处理的方法有连锁法和顺序法。连锁法是不停地扫描键盘,仅承认最后一个闭合键。顺序法是识别到一个闭合键后,直到该键被释放后再去识别其他按键。
azmao 2008-11-18
  • 打赏
  • 举报
回复
delay10ms(); //这一句不好
键盘的去颤还是用状态机比较好
[code=C/C++ ]{void KeyScan() //每10mS执行一次键盘扫描任务
{
switch(KeyState)
{
case 0:
if(KeyIsKeyDown()) //有键
{
KeyState = 1;
KeyDownTmr = 0;
}
break;
case 1:
if(KeyIsKeyDown()) //有键
{
KeyState = 2;
KeyBufIn();
KeyStartRptCnt = 0;
}
else
{
KeyState = 0;
}
break;
case 2:
if(KeyIsKeyDown()) //有键
{
if(KeyStartRptCnt++ >= 30)
{
KeyState = 3;
KeyRptCnt = 0;
KeyBufIn();
}
}
else
{
KeyState = 1;
}
break;
case 3:
if(KeyIsKeyDown()) //有键
{
if(KeyRptCnt++ >= 20)
{
KeyRptCnt = 0;
KeyBufIn();
}
}
else
{
KeyState = 1;
}
break;
default:
break;
}
}}[/code]
键值的取得用线反转法非常好。
小蚂蚁大智慧 2008-11-18
  • 打赏
  • 举报
回复
楼主其实线反真的很简单了,也很实用!如果不爽我给个扫描法的


#include <at89x52.h>
#include <intrins.h>

#define JP P2
char *p
void jians() //键盘扫描
{
uchar temp;
JP=0xf0;
if(JP!=0xf0)
{
delay_ms(10); //延时10ms
if(JP!=0xf0)
{
for(temp=0xef;temp!=0xfe;temp=_crol_(temp,1)) //对高四位从最后一位特循环左移一位,检查键盘的状态
{
JP=temp;
switch(JP)
{
case 0xee: p="1"; break;
case 0xed: p="2"; break;
case 0xeb: p="3"; break;
case 0xe7: p="4"; break;

case 0xde: p="5"; break;
case 0xdd: p="6"; break;
case 0xdb: p="7"; break;
case 0xd7: p="8"; break;

case 0xbe: p="9"; break;
case 0xbd: p="a"; break;
case 0xbb: p="b"; break;
case 0xb7: p="c"; break;

case 0x7e: p="d"; break;
case 0x7d: p="e"; break;
case 0x7b: p="f"; break;
case 0x77: p="g"; break;

default: break;
}
}
}
}
}
zhongyuanceshi 2008-11-18
  • 打赏
  • 举报
回复
函数: F_Key_Scan() //描述:键盘扫描函数中的
R1 = 0x00F0 //扫描键盘
[P_IOA_Data] = R1
R1 = 0x000f
R1 &= [P_IOA_Data]
JNZ L_Have_Key_Pressed
为什么R1要先赋值,赋的值0x00F0是否理解为端口初始化的值? R1 &= [P_IOA_Data]中的[P_IOA_Data]是否是 [P_IOA_Data] = R1 这个语句中的R1呢?还是响应键时会给[P_IOA_Data]一个值再与R1 &(这样的话,前面的赋值就没有作用了),根据结果再判断是否有键按下

zhongyuanceshi 2008-11-18
  • 打赏
  • 举报
回复
是呀,我现在就是基础不明白,是刚刚接触,这应该是一个过程了
zhyl10428 2008-11-18
  • 打赏
  • 举报
回复
key就是返回值
zhyl10428 2008-11-18
  • 打赏
  • 举报
回复
uchar board[4][4]={0, 1, 2, 3, //键盘对应的0--9
4, 5, 6, 7,
8, 9, 10,11,
12,13,14,15 };

uchar star_scan=0; //键盘按键有效
uchar key_e=1; //键盘正确扫描标志
uchar key;

void scan_key(void) //扫描键盘函数(PORTB0-3纵向)(PORTB4-7横向)
{
uchar R=0,C=0;
P3=0xf0;
delay(2); //延时消抖
R=P3;
R=R>>4;
if(0x0f==R) //没键按下
star_scan=1;
if(star_scan)
{
P3=0xf0;
delay(1);
R=P3; //延时消抖
R=R>>4;
P3=0x0f;
delay(1);
C=P3; //扫描哪一列有键按下
switch(R)
{
case 0x07 : R=3; break;
case 0x0b : R=2; break;
case 0x0d : R=1; break;
case 0x0e : R=0; break;
default : key_e=0;
}
switch(C)
{
case 0x07 : C=3; break;
case 0x0b : C=2; break;
case 0x0d : C=1; break;
case 0x0e : C=0; break;
default : key_e=0;
}
if(1==key_e)
{
star_scan=0;
key=board[R][C];
}
key_e=1;
}
}
键盘扫描?好像俺51和AVR的都有。。。。
zhongyuanceshi 2008-11-18
  • 打赏
  • 举报
回复
不会吧?我现在工作上用的就是凌阳的。没办法了,是工作上用到的。你的建议还是蛮中肯的,谢谢
gooogleman 2008-11-18
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 zhongyuanceshi 的回复:]
想问问:main函数中的P3好像没什么作用?函数Getch()中的P3是不是输入寄存器?P0是不是输出寄存器呢?那main中的P2是输出吗?为什么要用两个输出?另外seg7code数组是根据什么这样设置的。我现在将IOA2~IOA5设为输入,IOB2~IOB5设为输出,没有指示灯,我想只能通过万用表测输出端的电压了?修改起来还是有点困难
[/Quote]

你的是什么单片机啊,这个线反转法就是个矩阵原理啊,每一个行和每一列交叉唯一确定一个值。
这个东西无非是输入输出,然后读外部引脚电平的问题。

——这个网上很多资料的,你到处可以看原理。

P3接4*4矩阵键盘,P0自然是控制数码管的了。

——还有,楼主,对一个初学者,我建议你不要找一个很高级的单片机来学习。因为起点高,造成你学的慢,我当年也是差点被凌阳单片机害死。

现在我觉得搞懂AT89S51就够了,直接学ARM9,也没有遇到多大的困难,反而初学的时候不要太贪心,总想什么都学,结果什么都学不好的
——在这里多嘴了,楼主莫怪。
zhongyuanceshi 2008-11-18
  • 打赏
  • 举报
回复
想问问:main函数中的P3好像没什么作用?函数Getch()中的P3是不是输入寄存器?P0是不是输出寄存器呢?那main中的P2是输出吗?为什么要用两个输出?另外seg7code数组是根据什么这样设置的。我现在将IOA2~IOA5设为输入,IOB2~IOB5设为输出,没有指示灯,我想只能通过万用表测输出端的电压了?修改起来还是有点困难
-狙击手- 2008-11-17
  • 打赏
  • 举报
回复
http://blog.csdn.net/happyflystone/archive/2008/07/27/2721270.aspx
MBWQ 2008-11-17
  • 打赏
  • 举报
回复
delay10ms(); //这一句不好
gooogleman 2008-11-17
  • 打赏
  • 举报
回复
现在已经不弄单片机了,

--------我现在想,那时候怎么就是不明白那些基础呢?

楼主可以根据自己的CPU的端口设置方法,随便更改一下就可以了。
gooogleman 2008-11-17
  • 打赏
  • 举报
回复
#include <reg52.h>
unsigned char code seg7code[]={0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c, 0x39,0x5e,0x79,0x71}; //LED段码
unsigned char k;

void delay10ms(void) //延时程序
{
unsigned char i,j;
for(i=20;i>0;i--)
for(j=248;j>0;j--);
}
void Getch ( )
{ //线反转法非常简单
unsigned char X,Y,Z;
P3=0xff;
P3=0x0f; //先对P3置数 行扫描
if(P3!=0x0f) //判断是否有键按下
{delay10ms(); //延时,软件去干扰
if(P3!=0x0f) //确认按键按下X = P3;
{
X=P3; //保存行扫描时有键按下时状态
P3=0xf0; //列扫描
Y=P3; //保存列扫描时有键按下时状态
Z=X|Y; //取出键值
switch ( Z ) //判断键值(那一个键按下)
{
case 0xee: k=0; break; //对键值赋值
case 0xde: k=1; break;
case 0xbe: k=2; break;
case 0x7e: k=3; break;
case 0xed: k=4; break;
case 0xdd: k=5; break;
case 0xbd: k=6; break;
case 0x7d: k=7; break;
case 0xeb: k=8; break;
case 0xdb: k=9; break;
case 0xbb: k=10;break;
case 0x7b: k=11;break;
case 0xe7: k=12;break;
case 0xd7: k=13;break;
case 0xb7: k=14;break;
case 0x77: k=15;break;
}
}
}
}
void main(void)
{
while(1)
{ P3=0xff;
Getch();
P0=seg7code[k];//查表LED输出
P2=0x0f; //输出相同的四位数据。
}
}
加载更多回复(3)

27,382

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 单片机/工控
社区管理员
  • 单片机/工控社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧