8个IO口检测64个按键,算法实现和心法要点讲解
Posted perseverance52
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8个IO口检测64个按键,算法实现和心法要点讲解相关的知识,希望对你有一定的参考价值。
8个IO口检测64个按键,算法实现和心法要点讲解
在此篇基础上《8个IO口检测64个按键,数码管显示(Proteus仿真)》进行了算法实现原理讲解和注意要领。
扫描原理想通了很简单,就是通过一个IO拉低,其检测这一组其他7个IO口状态,如果检测到有被拉低了,说明就有按键按下了,因为检测到拉低的IO口,是被被用来检测的IO口拉低的。相当于我用一个接GND的探针去碰一个高电平的IO口,肯定是会拉低的。电路原理的话,利用二极管单向导通特性,在检查过程中,如果IO口被拉低,只能构成一个回路。
- 知道了按键扫描原理然后去实现的过程并不是很艰难,我觉得最困难的是调试过程遇到的各种问题。掌握其原理并不那么复杂,在写完之后,代码并不是很顺利按照自己的思路去运行的,也许就是一个项目的学习过程。一个DIY创意可能很简单,真正让其按照自己的想法运行还是有很多细节点要打通的。
- 小bug折腾的时间比整个写代码的花费的经历和时间多得多,想想一个稳定好用的产品都需要几个版本的迭代。
总结要领
- 最难的地方是,控制56-64最后一排的数码管显示,因为,这个你是对P0总线端口自身的扫描,最容易出问题的地方,也是卡在这个地方最长时间。一定要了解其单片机运行和按键扫描原理。在没有延时或打断的情况下,按键动作的时间一定是快不过单片机运行的速度。所以在处理最后一排按键时,需要特别注意,显示时要比其他行扫描处理的时间留长一点,不然就很容易跳数,按下的按键,和显示的数值不是你想要的结果,下面我会将经验一一写下来。
在处理最后一排按键,我有想过两个办法来处理逻辑判断问题:
- 利用复合逻辑来写,很直观,但是代码阅读和可执行性看起来相对很臃肿一样,写的时候很爽,单片机处理逻辑,运行的时间会多一些。
最先想到的第一种办法,写法如下:
if(P0==0x7f||P0==0xbf||P0==0xdf||P0==0xef||P0==0xf7||P0==0xfb||P0==0xfd||P0==0xfe)
第二种办法,通过二分查找的方式:(为什么可以采用二分查找算法来快速筛查对象,是有讲究的):二分查找的条件就是注意事项,定义的数组必须是有序序列才行。最优方法看不懂不要紧,直接搜算法拿来用就行!写法如下:
int Search(uchar arr[], int len, int flag)
{
int right = len - 1;
int left = 0;
while (left <= right)
{
int mid = (right + left) / 2;
if (arr[mid] > flag)
{
right = mid - 1;
}
else if (arr[mid] < flag)
{
left = mid + 1;
}
else
{
return arr[mid];
}
}
return 0;
}
第三种遍历方法,就不需要参照二分查找算法那样考虑什么注意事项了,随便写一个简单的遍历程序即可,执行效率虽然慢一点,起码实现起来简单,代码可敲性强,比起二分查找算法写起来。
uchar libian(uchar a[], int value, int n)
{
int i;
for (i = 0; i < n; i++)
{
if (value == a[i])
{
return a[i];
}
}
return 0;
}
0-64
个按键,需要考虑消抖的只有最后一排56-64
的8个按键的响应。为什么这么说呢?这是因为按键从设计原理和实现来看的。0-56
的按键不管你怎么长按还是短按,单片机给你的响应数值都是一样的不会变,但是在处理56-64
这8个按键时,是做了特殊处理,连接的是GND
,如果敲代码没注意的话,就很容易造成跳数字,单片机扫描是通过按照规定先给指定的IO
口拉点,再去检测其他7个IO
的电平状态,所以在处理第56-64
按键时,如果你操作的按键按下时,单片机扫描按键的速度已经从你按下那一到弹起前已经超过了你的速度,那么会造成,单片机读取到错误的响应数据,处理方式如下:
/****************自身端口读取*********************/
P0=0xff;//扫描第8行
// delay(5);
tmp=P0;
if (Search(arr, 8, tmp))
{
//将检测到的P0状态值赋值给临时变量
switch(Search(arr, 8, tmp))
{ //临时变量对逐个IO口进行查询
case 0xfe:
keynum=57;
break;//第1行第1个按键按下
case 0xfd:
keynum=58;
break;//第1行第2个按键按下
case 0xfb:
keynum=59;
break;//第1行第3个按键按下
case 0xf7:
keynum=60;
break;//第1行第4个按键按下
case 0xef:
keynum=61;
break;//第1行第5个按键按下
case 0xdf:
keynum=62;
break;//第1行第6个按键按下
case 0xbf:
keynum=63;
break;//第1行第7个按键按下
case 0x7f:
keynum=64;
break;//第1行第8个按键按下
}
display();//这里必须单独处理P0的IO检测和显示,否则容易跳数
delay(80);//阻塞按键扫描,防止数码管跳变,其他行扫描不需要此处的延时。
}
完整代码
#include <reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
//共阴极数码管0~9
uchar code table[]= {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//0-9共阴数码管
uchar code arr[] = {0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe};
uchar duanZhi[]= {0,0};//保存每段数码管显示位数的数值
sbit P36=P3^6;//数码管时能端
sbit P37=P3^7;
sbit ST=P3^0;//定义74HC595移位寄存器
sbit SH=P3^2;
sbit DS=P3^1;
sbit P33=P3^3;
sbit P34=P3^4;
sbit P35=P3^5;
uchar shi,ge;//数码管个位和十位显示
uchar tmp;//暂存P0的值
static uchar keynum=0;//按键值
unsigned char Trg;
unsigned char Cont;
static char count=1;
//毫秒级延时
void delay(uint z)
{
uint x,y;
for(x=z; x>>0; x--)
for(y=110; y>>0; y--);
}
void SendTo595(uchar byteData);
int Search(uchar arr[], int len, int flag)
{
int right = len - 1;
int left = 0;
while (left <= right)
{
int mid = (right + left) / 2;
if (arr[mid] > flag)
{
right = mid - 1;
}
else if (arr[mid] < flag)
{
left = mid + 1;
}
else
{
return arr[mid];
}
}
return 0;
}
/*----------------------------------------------------------------------------------
显示
void display2()
{
ge = keynum%10;
shi = keynum/10;
duanZhi[0]=table[ge];
duanZhi[1]=table[shi];
P34=0x00;
SendTo595(duanZhi[0]); //
delay(5);
P34=0x01;//消隐
P33=0x00;
SendTo595(duanZhi[1]);//
delay(5);
P33=0x01;//消隐
}
----------------------------------------------------------------------------------*/
void display()
{
ge = keynum%10;
shi = keynum/10;
duanZhi[0]=table[ge];
duanZhi[1]=table[shi];
//显示个位
P37=0;
SendTo595(duanZhi[0]); //
delay(2);
P37=1;//消隐
//显示十位
P36=0;
SendTo595(duanZhi[1]);//
delay(2);
P36=1;//消隐
}
/***********************************************************
*函数名 :SendTo595
*功能 :串行发送8个比特(一个字节)的数据给595,再并行输出
*参数 :byteData
************************************************************/
void SendTo595(uchar byteData)
{
uchar i=0;
ST = 0; //ST //先拉低,为后面的上升沿做准备
for(i; i<8; i++)
{
SH = 0;//先拉低,
if(byteData&0x80)DS=1;
else DS=0;
// DS = (byteData&0x80)?1:0;
byteData = byteData <<1; //该字节右移一位
SH = 1;//上升沿,让串行输入时钟变为高电平,并延时2个时钟周期
_nop_();
_nop_();
SH = 0; //上升沿,让串行输入时钟变为高电平,并延时2个时钟周期
}
/*位移寄存器数据准备完毕,转移到存储寄存器*/
ST =1;
_nop_();
_nop_();
ST = 0;
}
void key_scan()
{
// P0=0xff;
// delay(6);
/********************第1行扫描**************************/
P0=0x7F;//扫描第1行0111 1111
delay(5);
if (!Search(arr, 8, tmp))//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0x7e:
keynum=50;
break;//第1行第1个按键按下
case 0x7d:
keynum=51;
break;//第1行第2个按键按下
case 0x7b:
keynum=52;
break;//第1行第3个按键按下
case 0x77:
keynum=53;
break;//第1行第4个按键按下
case 0x6f:
keynum=54;
break;//第1行第5个按键按下
case 0x5f:
keynum=55;
break;//第1行第6个按键按下
case 0x3f:
keynum=56;
break;//第1行第7个按键按下
}
}
/********************第2行扫描**************************/
P0=0xbf;//扫描第2行
delay(5);
if(P0!=0xbf)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xbe:
keynum=43;
break;//第2行第1个按键按下
case 0xbd:
keynum=44;
break;//第2行第2个按键按下
case 0xbb:
keynum=45;
break;//第2行第3个按键按下
case 0xb7:
keynum=46;
break;//第2行第4个按键按下
case 0xaf:
keynum=47;
break;//第2行第5个按键按下
case 0x9f:
keynum=48;
break;//第2行第6个按键按下
case 0x3f:
keynum=49;
break;//第2行第7个按键按下
}
}
/********************第3行扫描**************************/
P0=0xdf;//扫描第3行
delay(5);
if(P0!=0xdf)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xde:
keynum=36;
break;//第3行第1个按键按下
case 0xdd:
keynum=37;
break;//第3行第2个按键按下
case 0xdb:
keynum=38;
break;//第3行第3个按键按下
case 0xd7:
keynum=39;
break;//第3行第4个按键按下
case 0xcf:
keynum=40;
break;//第3行第5个按键按下
case 0x9f:
keynum=41;
break;//第3行第6个按键按下
case 0x5f:
keynum=42;
break;//第3行第7个按键按下
}
}
/********************第4行扫描**************************/
P0=0xef;//扫描第4行
delay(5);
if(P0!=0xef)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xee:
keynum=29;
break;//第4行第1个按键按下
case 0xed:
keynum=30;
break;//第4行第2个按键按下
case 0xeb:
keynum=31;
break;//第4行第3个按键按下
case 0xe7:
keynum=32;
break;//第3行第4个按键按下
case 0xcf:
keynum=33;
break;//第4行第5个按键按下
case 0xaf:
keynum=34;
break;//第4行第6个按键按下
case 0x6f:
keynum=35;
break;//第4行第7个按键按下
}
}
/********************第5行扫描**************************/
P0=0xf7;//扫描第5行
delay(5);
if(P0!=0xf7)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xf6:
keynum=22;
break;//第5行第1个按键按下
case 0xf5:
keynum=23;
break;//第5行第2个按键按下
case 0xf3:
keynum=24;
break;//第5行第3个按键按下
case 0xe7:
keynum=25;
break;//第5行第4个按键按下
case 0xd7:
keynum=26;
break;//第5行第5个按键按下
case 0xb7:
keynum=27;
break;//第5行第6个按键按下
case 0x77:
keynum=28;
break;//第5行第7个按键按下
}
}
/********************第6行扫描**************************/
P0=0xfb;//扫描第6行
delay(5);
if(P0!=0xfb)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xfa:
keynum=15;
break;//第6行第1个按键按下
case 0xf9:
keynum=16;
break;//第6行第2个按键按下
case 0xf3:
keynum=17;
break;//第6行第3个按键按下
case 0xeb:
keynum=18;
break;//第6行第4个按键按下
case 0xdb:
keynum=19;
break;//第6行第5个按键按下
case 0xbb:
keynum=20;
break;//第6行第6个按键按下
case 0x7b:
keynum=21;
break;//第6行第7个按键按下
}
}
/********************第7行扫描**************************/
P0=0xfd;//扫描第7行
delay(5);
if(P0!=0xfd)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
{ //临时变量对逐个IO口进行查询
case 0xfc:
keynum=8;
break;//第7行第1个按键按下
case 0xf9:
keynum=9;
break;//第7行第2个按键按下
case 0xf5:
keynum=10;
break;//第7行第3个按键按下
case 0xed:
keynum=11;
break;//第7行第4个按键按下
case 0xdd:
keynum=12;
break;//第7行第5个按键按下
case 0xbd:
keynum=13;
break;//第7行第6个按键按下
case 0x7d:
keynum=14;
break;//第7行第7个按键按下
}
}
/********************第8行扫描**************************/
P0=0xfe;//扫描第8行
delay(5);
if(P0!=0xfe)//有按键按下
{
tmp=P0;//将检测到的P0状态值赋值给临时变量
switch(tmp)
51单片机8个IO口检测64个按键,数码管显示
51单片机 3个IO口 + TM1638+24个独立按键扫描(非自锁按键)+驱动8位共阳数码管显示+Proteus仿真
51单片机 3个IO口 + TM1638+24个独立按键扫描+驱动8位共阳数码管显示+Proteus仿真
Arduino UNO 利用3个IO口 + TM1638+驱动8位数码管显示+24个独立按键