8.1 单片机最小系统的较深入认识
8.1.1 电源
对于STC89C52RC单片机而言正常工作电压是一个范围(3.7V到5.5V),5V是范围内最典型常用的电压
器件的正常工作电压并不是一个固定的值,只要在范围内即可
8.1.2 晶振
晶振可以分为有源晶振(oscillator)和无源晶振(crystal)
有源晶振也需要供电,其频率基准更精确,信号质量也比无源晶振要稳定
与芯片的连接方法如图:
对于无源晶振,有两个或者三个引脚,如果有三个引脚,第三个引脚与晶振外壳相连并接地
对于有源晶振,只需将OUT与单片机的输出引脚连接即可,E/NC是一个用不到的悬空引脚
8.1.3 复位电路
如图:
过程1:从没有电到上电过程<就是5V那个地方从0到5V>:上电瞬间电容可以看成一个没有电压的电压源(就是短路),RST引脚位置瞬间电压为5V,然后随着这条电路的电流越来越小,电阻分压也越来越小,导致RST越来越小直到0
过程2:已经上电以后手动按按钮复位:原来电容已经稳定,两端电压为5V,按下按钮电容被短路,两端电压发生了突变,产生了一个很大的电流冲击,这个大电流会造成电磁干扰,所以加上了一个R60作为限流电阻
8.4 按键
8.4.1 独立按键
按键按下和弹起的时候KEYIn的电压是不同的,通过这个电压来检测按键是否被按下
由于上拉电阻的作用,当按键处于弹起状态时候,无电流,故KeyIn为5V,而按键按下的时候,有电流,KeyIn的电压为0V <当输出是低电平时:按下,当输出为高电平时:弹起状态>
当按键接到IO口上时,IO口内部有一个上拉电阻,构成了准双向IO口
注意:必须保证IO口内部输出的是高电平,才能测出外部按键的状态
当IO口内部输出为高电平的时候,经过非门变低,三极管截止,如果按键弹起,内部输入为VCC也就是高电压,如果按键按下,内部输入为0V
当IO口内部输出为低电平的时候,经过非门变高,三极管导通,无论如何内部输入都是0
8.4.2 矩阵按键
其实与独立按键相差不多,矩阵按键是利用8个IO口实现了16个按键 <KEyOut1到4,KeyIn1到4共四个IO口>
如果KeyOut2到4全部取高电平,KeyOut1取低电平,那么实际上矩阵按键与图8-9没有区别,就是独立按键;同理使得三个Out取高电平,就不会对取低电平的那一路产生影响;
8.4.3 独立按键的扫描
8.4.4 按键消抖
如图,当按键刚按下时是在抖动的,不稳定,导致此时的键状态不是0
按键消抖可以分为硬件消抖和软件消抖
硬件消抖是在按键上并联电容,利用电容充放电对毛刺进行平滑处理,但是效果不是很好
软件消抖:最简单的软件消抖是通过delay延时实现,当确认到按键状态发生变化时侯(不管是弹起还是按下),先进行一个延时,如果经过延时以后按键的保存状态与按键的现状态相同说明现在不抖动了,如果测得是弹起动作,再进行数码管计数的更新
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
unsigned char code LedChar[] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};//实际上只需要其中的前十个就行
void delay();//声明
void main()
{
bit keybuf = 1; //按键4的缓冲状态保存
bit backup = 1; //按键4的前一个状态
unsigned char cnt = 0;
ENLED = 0;
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
P2 = 0xF7;
P0 = LedChar[cnt]; //显示初值,实际上就是0
while (1)
{
keybuf = KEY4; //先保存按键的状态
if (keybuf != backup) //说明状态有变
{
delay(); //先延时躲过抖动
if (keybuf == KEY4) //说明抖动已过去
{
if (backup == 0) //说明是弹起状态,只有弹起状态才计数
{
cnt++;
if (cnt >= 10)
{
cnt = 0;
}
P0 = LedChar[cnt]; //更新数码管
}
backup = keybuf; //按键状态发生了改变,要保存按键的状态
}
}
}
}
void delay()//延时大约10ms,可以躲过抖动
{
unsigned int i = 1000;
while (i--);
}
上面的这种消抖使用了delay延时,会造成一些阻塞,实际并不可取
如果改用定时中断,每过2ms就进行一次读取按键的状态,判断状态是否是连续的,如果是来回变化的,可以认为在抖动,如果进行了8次中断即进行了16ms按键状态没有改变过,说明状态稳定
那么应该怎么实现判断8次状态有无发生变化呢
在中断函数中我们用一个keybuf来保存一段时间的扫描值,这是一个8位的数字,保存了8次中断的数据,每次中断的时候更新一次,如果测得keybuf为0xFF说明一直是1,则更新或者覆盖keySta(按键状态为1)同理0x00说明是0 如果keybuf不满足上述两种状态,说明在抖动,不要改变keySta,如果在主函数中检测到按键的状态发生改变,就要进行数码管计数的更新
怎样更新并且保存keybuf:keybuf=(keybuf<<1)|KEY4;
如果keybuf为11111111,左移一位后变成了11111110,若KEY4是1,按位或可得11111111
若KEY4为0,按位或得11111110
也就是一定要左移,而最后一位是由KEY4来决定的,如果按键为1则最后是1,如果按键是0则最后一位是0
#include "reg52.h"
sbit ADDR0=P1^0;
sbit ADDR1=P1^1;
sbit ADDR2=P1^2;
sbit ADDR3=P1^3;
sbit ENLED=P1^4;
sbit KEY1=P2^4;
sbit KEY2=P2^5;
sbit KEY3=P2^6;
sbit KEY4=P2^7;
unsigned char code ledChar[]=
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit keySta=1;
void main()
{
bit backup=1;
unsigned char cnt=0;
EA=1;
ENLED=0;
ADDR3=1;
ADDR2=0;
ADDR1=0;
ADDR0=0;
TMOD=0x01;
TH0=0xF8;
TL0=0xCD;
ET0=1;
TR0=1;
P2=0xF7;
P0=ledChar[cnt];
while(1)
{
if(keySta!=backup)
{
if(backup==0)
{
cnt++;
if(cnt>=10)cnt=0;
P0=ledChar[cnt];
}
backup=keySta;
}
}
}
void InterruptTimer0 ()interrupt 1
{
static unsigned char keybuf=0xFF;
TH0=0xF8;
TL0=0xCD;
keybuf=(keybuf<<1)|KEY4;
if(keybuf==0xFF)keySta=1;
else if(keybuf==0x00)keySta=0;
}
每过一段时间定时器溢出,开始中断,更新keybuf缓冲区的最后一位,并且判断缓冲区内是否全部是1或者全部是0,以这个为依据来更新keySta(这样就可以消抖),回到主函数,当keySta与backup不同时,并且backup=0,此时是从按下到弹起,进行计数,并且改变backup
8.4.5 矩阵按键的扫描
已知每次按键按下的时间一般在100ms以上。
要实现在100ms内扫描所有的按键,因为有4个KeyOut要扫描,而每一路都要进行消抖,如果每次检测是2ms,要检测8次,每一路都要花费16ms,四路就要64ms相对较长,所以要缩短消抖时间,改为每次检测需要1ms,检测4次即可,四路一共要16ms,这16ms并不是先将第一路检测四次4ms再对第二路检测4ms,而是先将第一路检测一次再将第二路检测一次,四路要4ms,再对第一路检测第二次,对第一路缓冲区的四次检查,这中间有16ms
具体实现功能:实现检测哪一个按键被按下了,并且将按键的编号显示在单个数码管上
要用到二维数组存储按键的状态,以及backup(按键之前的状态),还有中断函数中的keybuf(按键扫描缓冲区,存储扫描的4次结果,可以判断抖动)
在中断函数中实现单路扫描状态并消抖,然后改变KeyOut的编号,我们先检测keyIn输入,然后再改变KeyOut的状态,这是由于信号到达稳定状态需要时间,每次改变KeyOut状态以后有1ms的中断间隔时间来让电路实现稳定,然后完成输入
#include "reg52.h"
sbit ADDR0=P1^0;
sbit ADDR1=P1^1;
sbit ADDR2=P1^2;
sbit ADDR3=P1^3;
sbit ENLED=P1^4;
sbit KEY_IN_1=P2^4;
sbit KEY_IN_2=P2^5;
sbit KEY_IN_3=P2^6;
sbit KEY_IN_4=P2^7;
sbit KEY_OUT_1=P2^3;
sbit KEY_OUT_2=P2^2;
sbit KEY_OUT_3=P2^1;
sbit KEY_OUT_4=P2^0;
unsigned char code ledChar[]=
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char keySta[4][4]=
{
{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};//按键的状态
void main()
{
unsigned char i,j;
unsigned char backup[4][4]={{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};
EA=1;
ENLED=0;
ADDR3=1;
ADDR2=0;
ADDR1=0;
ADDR0=0;
TMOD=0x01;
TH0=0xFC;
TL0=0x67;
ET0=1;
TR0=1;
P0=ledChar[0];
while(1)
{
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
if(keySta[i][j]!=backup[i][j])//发现状态改变
{
if(backup[i][j]!=0)//当按下的时候要刷新显示
{
P0=ledChar[4*i+j];
}
backup[i][j]=keySta[i][j];//更新状态
}
}
}
}
}
void InterruptTimer0 ()interrupt 1
{
unsigned char i;
static unsigned char keyout=0;//用static
static unsigned char keybuf[4][4]={{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF}};//按键状态存储
TH0=0xFC;
TL0=0x67;
keybuf[keyout][0]=(keybuf[keyout][0]<<1)|KEY_IN_1;//将这一路的状态都写入缓冲区,要四个
keybuf[keyout][1]=(keybuf[keyout][1]<<1)|KEY_IN_2;
keybuf[keyout][2]=(keybuf[keyout][2]<<1)|KEY_IN_3;
keybuf[keyout][3]=(keybuf[keyout][3]<<1)|KEY_IN_4;
for(i=0;i<4;i++)
{
if(keybuf[keyout][i]&0x0F==0x00)keySta[keyout][i]=0;//检测是否满足条件并更新状态
else if(keybuf[keyout][i]&0x0F==0x0F)keySta[keyout][i]=1;
}
//next,refresh the 'keyout' state
keyout++;//更新keyout
keyout=keyout&0x03;//keyout只能0,1,2,3,当到4的时候就变成0
switch(keyout)
{
case 0:KEY_OUT_4=1;KEY_OUT_1=0;break;//一个拉高,一个拉低
case 1:KEY_OUT_1=1;KEY_OUT_2=0;break;
case 2:KEY_OUT_2=1;KEY_OUT_3=0;break;
case 3:KEY_OUT_3=1;KEY_OUT_4=0;break;
default:break;
}
}