控制/MCU
51单片机使用独立按键
我们使用51单片机时要怎么来检查按键操作呢?下面先根据我们仿真电路中使用的电路图来具体说明。
图中K1K4是四个独立按键,分别接在了P3.0P3.3这4个端口上。那当按键按下时我们要怎么让单片机内部检测出是哪个按键按下的呢?从图中可以看出在默认情况下这四个按键的输入电平都是高电平,当按键被按下时其对应引脚输入电平被拉到地。这时我们在程序中就可以设置一个不断扫码端口输入数据的代码段,被按下的按键对应的引脚数据寄存器中为0,其他引脚对应位数据依旧为1,接下来我们通过电平对比是不是就可以得出是哪个按键被按下了?
但是当按键按这个动作并不像一个方波信号那样干脆利落,就像我们平时拔插插头时可能会出现火花一样,按键过程中也会有杂波信号抖动干扰,按键过程电平变化波形如下图所示:
如果我们程序中一旦检测到电平变化就进行后续操作,这时如果只是一个抖动干扰信号,那不是就误判了?这就会造成程序功能错误,在某些应用中可能造成严重的后果,那怎么才能避免这个问题呢?这些抖动信号一般在几个ms内(这相对以秒计数的整个过程来说是非常的短暂的),所以在简单应用中我们一般在检测到第一次按键后隔几个或十几个ms之后再检测引脚输入电平。如果第二次确认电平变化了才认为按键按下了,否则就是干扰信号。可以发现这么处理就避免了干扰信号引发的程序错误。下面我们来看一下程序的实现过程。
/*
*这是一个按键测试程序
*目的是实现按键监测功能
*/
#include
#include
sbit com1 = P2^0; //定义数码管com1引脚
sbit com2 = P2^1; //定义数码管com2引脚
sbit key1 = P3^0;
sbit key2 = P3^1;
sbit key3 = P3^2;
sbit key4 = P3^3;
typedef unsigned char u8;
typedef unsigned int u16;
u8 temp_key = 0; //定义一个变量用来存放临时按键值
u8 code num_codelist[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void delay(u8 ms);
void main()
{
//读取P3端口,判断是否有按键按下
if((P3&0x0f)^0x0f)
{
//保存当前按键值
temp_key = ((P3&0x0f)^0x0f);
//进行一个延时消抖
delay(15);
//再次读取P3端口数据,与temp_key对比,判断按键值是否发生变化
if(temp_key == ((P3&0x0f)^0x0f))
{
//确定按键按下则不同按键显示对应的数字
switch(temp_key)
{
case 0x01://按键1
com1 = 0;//将第一位的com端设置为低电平
com2 = 1;
P0 = num_codelist[1];
break;
case 0x02://按键2
com1 = 0;//将第一位的com端设置为低电平
com2 = 1;
P0 = num_codelist[2];
break;
case 0x04://按键3
com1 = 0;//将第一位的com端设置为低电平
com2 = 1;
P0 = num_codelist[3];
break;
case 0x08://按键4
com1 = 0;//将第一位的com端设置为低电平
com2 = 1;
P0 = num_codelist[4];
break;
default:
break;
}
}
}
}
void delay(u8 ms)
{
u8 i,j;
for(i=0;i
}
程序功能设置为检测到按键按下后数码管就显示对应的按键数值,现在再来看一下仿真结果验证一下。待会在进行代码分析。
现在我们来分析程序的实现过程,一些以前说明过的程序就不做介绍了。主要讲解关键程序段,如果有看不懂的可以留言。
u8 temp_key = 0; //定义一个变量用来存放临时按键值。这里定义了一个变量来存放按键的临时值,在第一次按键按下时用来存储当前按键值,在第二次检测时用它与第二次扫描的按键值进行对比判断是否是真实按键操作。
if((P3&0x0f)^0x0f)这一行是读取P3端口的数据,&操作符时只获取低4位数据,^操作符是得到具体是哪个按键被按下。
temp_key = ((P3&0x0f)^0x0f); 这行是将当前按键值暂存起来,后面使用时再调用。
delay(15);这一行是进行延时消抖处理,避免干扰信号引起的误判。
if(if(temp_key == ((P3&0x0f)^0x0f))== ((P3&0x0f)^0x0f))这一行是消抖后再次进行键值判断,如果当前读取到的端口数据与temp_key 一致说明当前键值还是第一次检测时的按键值,则说明确实有按键按下了,否则说明是干扰信号。
switch(temp_key)这里使用了switch语句,进行处理不同键值按下时数码管显示对应键值。
case 语句内容就是依据对应的按键值进行相应的显示处理。
当然这里也可以使用if判断语句来处理,我这里使用switch语句是正好可以趁机会让各位来温习一下switch语句的用法。有兴趣的朋友可以尝试修改一下。
按键基础程序就讲解完了,当然这里只是按键程序的最基础例程,实际使用时根据具体需求做相应修改。
51单片机外部中断
上面键的按键程序大家应该对着注释和说明能够看明白吧,但是如果让程序一直扫描端口数据如果芯片还要处理其他事情那不就没空处理了,这么使用是不是太浪费芯片资源了呢?确实,一般情况下我们做的产品不可能都是一直扫描按键吧,所以这时候我们就需要一些更好的办法来解决这些资源浪费的问题吧。这就是中断系统,在这里的具体表现就是程序中不需要一种扫描端口数据,你按键按下了中断系统检查出来就将结果通知CPU,CPU在发出信号,这时我们收到CPU的信号再进行后续相应的处理。如果没按键按下CPU就去忙其他事情,有了这个中断系统后CPU的工作效率就提高了吧!
前面介绍基础内容时简单说明了单片机中断相关的知识,但没有讲解中断的具体使用方法,这节内容我们一起来详细了解一下51系列单片机的中断控制器及其使用。
51系列单片机的中断相关控制寄存器包括了中断控制寄存器(Interrupt Enable register,IE)和中断优先级控制寄存器(Interrupt Priority register,IP),前者用于对单片机的中断工作状态进行控制,后者用于对51单片机的中断优先级进行控制。
以上两个中断寄存器是单片机所有中断源的设置寄存器,当然单片机还有其他一些中断控制寄存器,TCON和SCON。TCON是外部中断和定时器中断的控制寄存器,SCON是串口输入和输出中断控制寄存器。简单说这两个寄存器就是使能这几种中断源的小开关,开关打开之后各中断事件才能被CPU获取。现在来认识一下这几一节内容我们需要用到的TCON寄存器:
IT0:INT0 触发方式控制位。
可由软件进行置位和复位,IT0=0,INT0 为低电平触发方式,IT0=1,INT0 为下降沿触发方式。
IE0:INT0 中断请求标志位。
当有外部的中断请求时,这位就会置1(这由硬件来完成),在CPU 响应中断后,由硬件将IE0 清0,即不需要用指令来清0。
IT1:INT1 触发方式控制位。
可由软件进行置位和复位,IT1=0,INT1 为低电平触发方式,IT1=1,INT1 为下降沿触发方式。
IE1:INT1 中断请求标志位。
当有外部的中断请求时,这位就会置1(这由硬件来完成),在CPU 响应中断后,由硬件将IE1 清0。
TR0:T0 启动控制位。
TR0=1 时,启动T0 工作;TR0=0 时,T0 停止工作。
TF0:定时器T0 的溢出中断标志位。
当T0 计数产生溢出时,由硬件置位TF0。当CPU 响应中断后,再由硬件将TF0清0。
TR1:T1 启动控制位。
TR1=1 时,启动T1 工作;TR1=0 时,T1 停止工作。
TF1:定时器T1 的溢出中断标志位。
当T1 计数产生溢出时,由硬件置位TF1。当CPU 响应中断后,再由硬件将TF1清0。
外部中断控制需要使用的就是其中的IE0和IE1这两位,剩下的是与定时器相关的位,这将是我们下一节要讲解的内容。
现在市面上的51单片机扩展了更多中断源,肯定就会有更多其他的中断相关的寄存器,所以使用时一定要参考芯片手册上的寄存器内容,不可照搬!但是使用方法都是类似的,根据数据手册上的操作说明逐步配置相关寄存器就好,包括以后如果使用更复杂的单片机也是一样的道理。
在51系列单片机中P3.2和P3.3两个端口的第二功能就是外部中断输入端口,现在我们来看一下单片机内部中断控制系统处理机制:
从上图可以看出TCON和SCON寄存器里面相当于一个小开关,直接决定各个中断源的使用。IE寄存器中的前面几位就像一个桥梁,决定现在你要接通哪些中断源,而它的EA位就是一个总闸,决定是否把中断信号发送给CPU。所以我们程序中配置中断时这3个地方都有进行配置,缺一不可!至于IP寄存器是用来确定中断信号优先级的,本来单片机内部是有默认的优先级机制的,所以这个寄存器不设置也可以,它的作用就像皇帝身边的小太监,大臣们上奏的奏折本来有规定的优先顺序,如果谁买通了它就可以给奏折换个顺序。这么说这些寄存器之间的关系是不是就很好理解了?
现在再来了解一下CPU收到中断之后的处理机制:
图中硬件处理部分我们的程序是可以不用参与的,由CPU自动完成(前提是中断寄存器都配置好了),CPU处理完就会跳转到我们的代码段中执行。
这里一定要注意我们使用C语言编程时编写中断函数的格式:
void your_fuction_name(void) interrupt 0 //外部中断0服务例程
{
//中断处理语句段
}
其中函数名可以自行确定一个合法,易懂的。interrupt 0 代表这是外部中断0中断源入口,其他中断源就不会跳转到这里来,这件相当于各回各家,各找各妈。这是中断函数中不可或缺的一部分,其他每种中断源都有对应的数字号,可以自行查看中断源入口地址表。另外值得注意的是,这些中断函数不用声明,也不可被调用,只要正确定义了编译器编译代码时就能识别到它,这也是它与其他普通函数的区别。
现在再详细了解一下我们的中断程序中各部分的内容。
关闭中断与打开中断:当中断被正确响应后,如果中断服务例程在执行过程中,不想被更高级的中断打断。则可以在进入中断服务例程后置EA=0,关闭所有中断,或者关闭某些中断,这样可以保证中断服务例程的顺利执行。在中断服务例程结束时,可以将关闭的中断开启,以便于单片机能够接收新的中断请求。
现场保护与恢复现场:一般来说,在主程序和中断服务程序中都会用到累加器和寄存器等。51单片机在中断服务程序中使用这些寄存器的时候将会改变其中的内容,再返回主程序的时候容易引起错误。因此,在进入中断服务程序后,应该首先将相应的寄存器保存起来,即保护现场;当中断服务例程结束时,应该将这些寄存器的内容恢复,即现场恢复。当然在C51语言程序中,这部分工作是由编译器自动完成的。
中断服务程序:这是我们需要在中断中进行操作的内容,根据程序功能确定。
现在你头脑中对外部中断使用应该有个大致的框架了吧?我们使用前面的仿真电路运用单片机外部中断来做一个按键加减数字的程序,可以自己动手试试,下面参考一下我写的测试程序:
/*
*这是一个按键的外部中断处理程序
*目的是通过外部中断监测按键功能
*/
#include
#include
sbit com1 = P2^0; //定义数码管com1引脚
sbit com2 = P2^1; //定义数码管com2引脚
sbit key1 = P3^0;
sbit key2 = P3^1;
sbit key3 = P3^2;
sbit key4 = P3^3;
typedef unsigned char u8;
typedef unsigned int u16;
u8 temp_key = 0; //定义一个变量用来存放临时按键值
u8 key_num = 0;//定义一个数字,用来显示实时数值
u8 code num_codelist[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void delay(u8 ms);
void main()
{
IT0=1; //外部中断0为下降沿触发
IT1=1; //外部中断1为下降沿触发
EX0=1; //开EX0中断
EX1=1; //开EX1中断
EA=1; //开总中断
//有了中断服务程序,在主循环中可以什么都不用做,当然你也可以让它干一些事情
while(1)
{
//在循环中显示当前数字
com1 = 0;//将第一位的com端设置为低电平
com2 = 1;
P0 = num_codelist[key_num];
}
}
void delay(u8 ms)
{
u8 i,j;
for(i=0;i
}
void P3_2_key_func(void) interrupt 0 //外部中断0服务例程
{
//将外部中断0对应的按键K3设置为按键加功能
if(0 == key3)
{
delay(15);
if(0 == key3)
{
if(key_num < 9)
{
key_num++;
}
else
{
key_num = 0;
}
}
}
}
void P3_3_key_func(void) interrupt 2 //外部中断1服务例程
{
//将外部中断0对应的按键K4设置为按键减功能
if(0 == key4)
{
delay(15);
if(0 == key4)
{
if(key_num > 0)
{
key_num--;
}
else
{
key_num = 9;
}
}
}
}
现在我们还是来看一下仿真结果:
有了前面的文字说明这个程序应该是很简单的吧,下面来我们一起来分析一下代码含义:
u8 key_num = 0;这一句是定义个变量来存放实时的按键值,在按键中断程序中将对这个变量进行加减操作。
IT0=1;和IT1=1;这两句是将两外部中断都设为下降沿触发中断。
EX0=1;和EX1=1;这两句是使能两外部中断源。
EA=1;这一句是开启总中断。
中断配置中需要将这3步都配置好才能生效。
while(1)这一句是就是主循环程序了,需要重复进行的程序都可以放在这里。
void P3_2_key_func(void) interrupt 0 和void P3_3_key_func(void) interrupt 2 这两个函数就是我们定义的中断处理函数,在函数内部我们确定对应的按键之后在if(){}else{}语句中对key_num 变量进行循环加减操作。这样主函数中就可以显示当前数值是多少了。
看完了整个程序是不是感觉非常简单呢?平时可以把自己的想法写下来通过程序实现,看是否可以正确运用。
矩阵按键
说完了按键和外部中断内容,我们的按键部分基础知识就介绍完了,但是实际使用过程中我们还会碰到各种各样的按键电路,这就需要我们运用所学基础知识进行分析了。比如矩阵按键就是我们运用按键的扩展知识,它是怎么样的呢?
如图所示的矩阵按键电路就是最常见的一种。你是不是想问它使用8个引脚是怎么实现的那么多按键检测的呢?答案就是通过行列扫描,还记得前面讲动态数码管显示的内容中怎么实现两位数码管同时显示的?那里是快速进行端口输出数据及com引脚切换,这里也是差不多的道理,在程序中快速的进行段端口数据扫描按键扫描将每个按键按下时产生的键值存储起来对结果进行对比就实现了。运用这个原理8个引脚甚至还可以接更多的按键,但这只是作为一个扩展知识,平时使用不多,有兴趣的朋友可以查阅一下资料了解一下。那么矩阵键盘具体是怎么实现功能的呢?不妨自己先动手试试。
全部0条评论
快来发表一下你的评论吧 !