单片机的上电复位功能,
这是单片机的上电复位功能电路,RST与51单片机的第9脚(RST端)相连,给单片机上电的瞬间单片机就完成了复位功能。一般51单片机完成复位功能只需2个机器周期(2us)即RST脚高电平保持2us,本案的单片机用的是无源晶振11.0592M。
每个单片机的复位电压不完成一样,按照通常值的0.7VCC作为复位电压值。本案就是0.7*5=3.5V
即高于3.5V被认为是高电平。
对于本案的复位电路来说,当一上电,电容类似一条导线,5V电压全部在R31的电阻上,随着电容充电,电容两端慢慢(这个所谓的慢慢其实极快是微秒级的)变大,这个过程的状态是
5V=Uc+I*R31,
对于电压大于3.5V复位,复位时间的计算教材给出的解释是难以计算,有个经验式t1=1.2RC
可以得出t=1.2*4700*0.0000001=0.000564=564us,远远大于2us。
按键相关
独立按键原理图 图示1
图示2 双向IO口内部图,现在大部分单片机这个三极管换成功能类似的mos管
图示3
图示1开关弹起的时候keyin1是高电平,当按下开关keyin1就接地了。
图示2可知对于单片机IO口
当内部输出位为低电平的时候,经过非门,三极管基极为高电平,该三极管导通(饱和导通CE两端电压几乎相同),单片机IO口输出低电平,无论外部的按键开关按下还是弹开,IO口一直保持低电平。即不受外部信号的控制。
当内部输出为高电平的时候,经过非门,三极管基极为低电平,三极管未导通(CE两端的阻值很大,电阻R与之相比可以忽略不计)这时IO口输出高电平。当按下按键,IO口就被接地,这时IO口输出低电平。
由此可知只有当IO口输出为高电平的时候,IO口的输出才受外部电路的控制。
矩阵按键
在某个系统中,如果需要使用很多的按键,做成独立的按键会大量的占用IO口,因此引入了矩阵按键的涉及。如图示4
配合图示3可知
Keyout1 - Keyout4是单片机的P2.3 - P2.0 4个端口。
Keyin1 - Keyin4是单片夹的P2.4 - P2.7 4个端口。
从图中不难看出当Keyout1输出1个低电平就相当于GND,这时K1、K2、K3、K4就能控制Keyout1、Keyout2、Keyout3、Keyout4的电压了。我们控制1个LED点亮来演示看程序
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit LED9 = P0^7;
sbit LED8 = P0^6;
sbit LED7 = P0^5;
sbit LED6 = P0^4;
sbit key1 = P2^4;
sbit key2 = P2^5;
sbit key3 = P2^6;
sbit key4 = P2^7;
void main()
{
ENLED = 0;
ADDR3 = 1; //ADDR这里取值是38译码器使LED输出使能
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
P2 = 0xF3;
while(1)
{
LED9 = key1; //若keyout1和keyout2都置0则K5功能和K1一致
LED8 = key2;
LED7 = key3;
LED6 = key4;
}
}
P2 = 0xF3=1111 0011 可以看到P2端口高4位都置1,即keyin1 - keyin4端口都被置1。根据我们前文IO口的说明,若想外部电路控制IO,它的输出需要是高电平。然后是P2.3和P2.2端口置0根据图4的原理图可知是 keyout1、keyout2都置0了。
按照程序LED9 = key1;
LED8 = key2;
LED7 = key3;
LED6 = key4;
在循环执行的函数中可知当Key1输出低电平的时候LED9=P0.7=0这时开发版上的LED9就会被点亮。
由程序可知key1=p2.4 ,P2.4端口的电压是来自keyin1的电压。 而keyin1的电压由开发版上的开关K1,K5控制。也就是按下K1.和K5都能使LED9点亮。
按键控制数码管0-9刷新
现在做个程序使按键可以控制数码管刷新。
按一次按键开关,就会产生“按下”和“弹起”两个动态的动作,选择在“弹起”时对数码管进去加1操作。
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
//sbit LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^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 main()
{
bit backup = 1; //定义一个变量,保存前一次扫描的按键值
unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
ENLED = 0;
ADDR3 = 1; //选择数码管DS1进行显示
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
P2 = 0xF7; //1111 0111
P0 = LedChar[cnt]; //显示按键初值
while(1)
{
if(key4 != backup) //当前值与前次值不相对说明按键有动作
{
if(backup == 0) // 如果当前值为0,则说明当前是由0变1,即按键弹起
{
cnt++; //按键次数+1
if(cnt >= 10) //只用1个数码管显示,加到10就清零重新开始
{
cnt = 0;
}
P0 = LedChar[cnt]; //计数值显示到数码管上
}
backup = key4;//跟新备份当前值,以备进行下次比较
}
}
}
可以看到每次按下开关数码管有时进1为有时进2位。这是怎么产生的呢?
通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上就稳定接通,在断开时也不会一下子彻底断开,而且在闭合和断开的瞬间伴随一连串的抖动。如图
按键稳定闭合时间长短由操作人员决定,通常都会在100ms以上,刻意快速按的话能达到40-50ms,很难在低了。抖动时间是由按键的机械特性决定的,一般都会在10ms以内。为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是去响应动作,而是先等待闭合或者断开稳定后再进行处理。
按键消抖
可以分为硬件消抖和软件消抖。硬件消抖是在按键上并联一个电容,利用电容的充放电特性来对抖动中产生的电压毛刺进行平滑处理。但在实际应用中,这种方式的效果往往不是很好,还增加了成本,因此实际中使用的并不多。
觉大多数是用软件即程序来消抖。最简单的消抖原理,就是当检测到按键状态变化后,先等待1个10ms左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确定按键已经稳定动作了。看程序
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
//sbit LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^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 backup = 1; //定义一个变量,保存前一次扫描的按键值,
bit keybuf = 1; //按键值暂存,临时保存按键的扫描值
unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
ENLED = 0; //选择数码管DS1来进行显示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
P2 = 0xF7; //P2.3置0,即keyout1输出低电平,这个赋值让P2^7为高电平即key4 = 1;
P0 = LedChar[cnt]; //显示按键初值
while(1)
{
keybuf = key4; //实时监测Key4的状态把key4的状态值给keybuf储存起来
if (keybuf != backup) //检测到当前值与前次值不相等说明此时按键有动作
{
delay(); //delay 10ms
if(keybuf ==key4) //判断扫描值有没有发生改变,即按键抖动
{
if(backup == 0) //如果前次的值为0,则说明当前是弹起动作
{
cnt++; //按键次数加1
if(cnt >= 10)
{
cnt = 0; //只用1个数码管显示,所以到10就被清零重新开始
}
P0 = LedChar[cnt]; //计数值显示到数码管上
}
backup = keybuf; //更新备份为当前值,以备进行下次比较
}
}
}
}
void delay()
{
unsigned int i =1000;
while(i--);
}
弹起加1_哔哩哔哩_bilibili 这个程序来自教材
可以看到按动按键的时候不会发生之前按一次跳两次的状况。并且是在按键弹起的时候加1;那如果要求按住按键的时候加1要如何操作呢。 把判断语句if(backup = = 0)改成if(backup = = 1)就可以了。视频就不放出了。
这个程序的关键是这个语句backup = keybuf;为什么放在这?和为什么用if(backup = = 0)这个语句来判断?
可以看到程序
看到程序这句是放在if(keybuf == key4;)下面的,它的逻辑是经过延时后如果扫描值确实没有变化,则把这个变化的新值赋值给backup。无论是否进入if判断语句它都会发生。backup获得一个新稳态的值。根据程序如果这个新稳态的值是0,就会进入if判断语句让cnt++。
逻辑反推:若想进入if(backup == 0),必然backup依然被赋值为0,而程序是只有稳态发生变化的时候才能进入,则这个过程必然是按键按住(0)到按键弹起的过程(1)。
倘若backup=1呢,从程序可以从按键弹起状态(1)到按下按键(0)这个状态才能进入第1个if语句,但是显然无法进入第二if语句if(backup == 0),直接执行backup = keybuf;这句。这个时候backup的状态值就是0。然后又执行前文所说的动作。
第二个为什么用一定if(backup == 0)来判断,直接用if(key4==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 LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^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 past = 1; //实时态的前一稳态,有两种稳态 按住是0 保持弹起是1
bit now = 1; // 按键开关实时状态
unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
//unsigned int j = 50000;
ENLED = 0;
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
P2 = 0xF7; //key4即p2.7端口电压是高电平 即key4= 1;
P0 = LedChar[cnt];
while(1)
{
now = key4; //把按键开关的状态赋值给now
if(past != now) //确认前一个稳态和现在这个实时态不同进入程序,说明状态变化
{
delay(); //延时10ms
if(now == key4) // 延时后按键开关保持的状态和之前的实时态相同,说明进入新稳态
{
if(key4 == 0) //开关的实时态处于按住状态
{
cnt++; //数码管输出+1
if(cnt >= 10)
{
cnt = 0;
}
P0 = LedChar[cnt]; //数码管输出
}
past = now; //把这个实时态也是新稳态赋值给past
// while(j--);
}
}
}
}
void delay()
{
unsigned int i = 1000;
while(i--);
}
看下结果视频。该程序笔者的命名是延时10ms延时10ms_哔哩哔哩_bilibili可以看到结果正常,与上一个程序相比这个是按住的时候数码管加1。
使语句if(key4 ==1)这得到和之前相同的结果,弹起的时候数码管加1。看结果视频
注意:当i=1000的时候while(i--)延时是10ms笔者做过debug,笔者的晶振是11.0592M的因此晶振不同这个时间也是不一样的
100次是1ms,1000次就是10ms
然后笔者在调试的时候拍了一些视频也发给各位瞧瞧
1:延时10ms去掉past = now这句会发生什么?延时10mskey4=1无past=now_哔哩哔哩_bilibili
2:延时50ms去掉past = now这句会发生什么?延时50ms无past=now_哔哩哔哩_bilibili
3:延时100ms去掉past = now这句会发生什么?延时100ms无past=now_哔哩哔哩_bilibili
4:延时100ms(没有去掉past = mow)会发生什么?延时100ms_哔哩哔哩_bilibili
5:不要past = now用while(50000 --)代替会发生什么?延时500ms无past=now_哔哩哔哩_bilibili
6:留个问题,保留past = now然后后面加上while(50000--)会发生什么呢?
本案提供一种消抖方式,下篇博文将提供第二种消抖方式也是相对更好的方式。