单片机最小系统
电源
我们要使用STC89C52RC单片机的时候,找到它的数据手册第11页,看第二项——工作电压:5.5~3.4V(5V单片机),这个地方就说明这个单片机正常的工作电压是个范围值,只要电源VCC在5.5~3.4V之间都可以正常工作,电压超过5.5V是绝对不允许的,会烧坏单片机,电压如果低于3.4V,单片机不会损坏,但是也不能正常工作。
再顺便多了解一点,打开74HC138的数据手册,会发现74HC138手册的第二页也有一个表格,上边写了74HC138的工作电压范围,最小值是4.75V,额定值是5V,最大值是5.25V,可以得知它的工作电压范围是4.75~5.25V。这个地方讲这些的目的是让大家清楚地了解,获取器件工作参数的一个最重要、最权威的途径就是查阅该器件的数据手册。
晶振
晶振通常分为无源晶振和有源晶振两种类型,无源晶振一般称之为crystal(晶体),而有源晶振则叫作oscillator(振荡器)。
有源晶振是一个完整的谐振振荡器,它是利用石英晶体的压电效应来起振,所以有源晶振需要供电,当把有源晶振电路做好后,不需要外接其他器件,只要给它供电,它就可以主动产生振荡频率,并且可以提供高精度的频率基准,信号质量也比无源信号稳定。
无源晶振自身无法振荡起来,它需要芯片内部的振荡电路一起工作才能振荡,它允许不同的电压,但是信号质量和精度较有源晶振差一些。相对价格来说,无源晶振要比有源晶振价格便宜很多。无源晶振两侧通常都会有个电容,一般其容值都选在10~40pF之间,如果手册中有具体电容大小的要求则要根据要求来选电容,如果手册没有要求,用20pF是比较好的选择,这是一个长久以来的经验值,具有极其普遍的适用性。
有源晶振通常有4个引脚,VCC、GND、晶振输出引脚和一个没有用到的悬空引脚(有些晶振也把该引脚作为使能引脚)。无源晶振有2个或3个引脚,如果是3个引脚的话,中间引脚接晶振的外壳,使用时要接到GND,两侧的引脚就是晶体的两个引出脚了,这两个引脚作用是等同的,就像是电阻的两个引脚一样,没有正负之分。
复位电路
当这个电路处于稳态时,电容起到隔离直流的作用,隔离了+5V,而左侧的复位按键是弹起状态,下边部分电路就没有电压差的产生,所以按键和电容C11以下部分的电位都是和GND相等的,也就是0V。这个单片机是高电平复位,低电平正常工作,所以正常工作的电压是0V。
再来分析从没有电到上电的瞬间,电容C11上方电压是5V,下方是0V,根据初中所学的知识,电容C11要进行充电,正离子从上往下充电,负电子从GND往上充电,这个时候电容对电路来说相当于一根导线,全部电压都加在了R31这个电阻上,RST端口位置的电压就是5V,随着电容充电越来越多,即将充满的时候,电流会越来越小,那RST端口上的电压值等于电流乘以R31的阻值,也就会越来越小,一直到电容完全充满后,线路上不再有电流,这个时候RST和GND的电位就相等了也就是0V了。
那这个“一小段时间”(即RST高电平的时间)到底是多少才合适呢?每种单片机不完全一样,51单片机手册里写的是持续时间不少于两个按下按键的瞬间。
电容两端的5V电压(注意不是电源的5V和GND之间)会被直接接通,此刻会有一个瞬间的大电流冲击,会在局部范围内产生电磁干扰,为了抑制这个大电流所引起的干扰,这里在电容放电回路中串入一个18Ω的电阻来限流。机器周期的时间。
函数的调用
在实际工程项目中,一个程序通常都是由很多个子程序模块组成的,一个模块实现一个特定的功能,在C语言中,这个模块就用函数来表示。一个C程序一般由一个主函数和若干个其他函数构成。主函数可以调用其他函数,其他函数也可以相互调用,但其他函数不能调用主函数。在51单片机程序中,还有中断服务函数,当相应的中断到来后自动调用,不需要也不能由其他函数来调用。
- 函数调用的时候,不需要加函数类型。
- 调用函数与被调用函数的位置关系。
- 函数声明的时候必须加函数类型,函数的形式参数,最后加上一个分号表示结束。
函数的形式参数和实际参数
在调用一个有参数的函数时,函数名后边括号中的参数叫作实际参数,简称实参。而被调用的函数在进行定义时,括号里的参数叫作形式参数,简称形参。
关于形参和实参,还有以下几点需要注意:
- 函数定义中指定的形参,在未发生函数调用时不占内存,只有函数调用时,函数add中的形参才被分配内存单元。
- 实参可以是常量,也可以是简单或者复杂的表达式,但是要求它们必须有确定的值,在调用发生时将实参的值传递给形参。
- 形参必须要指定数据类型,和定义变量一样,因为它本来就是局部变量。
- 实参和形参的数据类型应该相同或者赋值兼容。
-
主调函数在调用函数之前,应对被调函数做原型声明。
-
实参向形参的数据传递是单向传递,不能由形参再回传给实参。
按键
独立按键
常用的按键电路有两种形式:独立式按键和矩阵式按键。
4条输入线接到单片机的IO口上,当按键K1按下时,+5V通过电阻R1然后再通过按键K1最终进入GND形成一条通路,这条线路的全部电压都加到了R1这个电阻上,KeyIn1这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,KeyIn1和+5V就应该是等电位,是一个高电平。我们就可以通过KeyIn1这个IO口的高低电平来判断是否有按键按下。
这个电路中按键的原理讲清楚了,但是实际上在单片机IO口内部,也有一个上拉电阻的存在。按键是接到了P2口上,P2口上电默认是准双向IO口,下面来简单了解一下准双向IO口的电路。
当内部输出是高电平,经过一个反向器变成低电平,NPN三极管不会导通,单片机IO口从内部来看,由于上拉电阻R的存在,所以是一个高电平。当外部没有按键按下将电平拉低的话,VCC也是+5V,它们之间虽然有两个电阻,但是没有压差,就不会有电流,线上所有的位置都是高电平,这个时候就可以正常读取到按键的状态了。当内部输出是个低电平,经过一个反相器变成高电平,NPN三极管导通,单片机的内部IO口就是个低电平,这个时候,外部虽然也有上拉电阻的存在,但是两个电阻是并联关系,不管按键是否按下,单片机的IO口上输入到单片机内部的状态都是低电平,就无法正常读取到按键的状态了。
从上面的分析可以得出一个结论,这种具有上拉的准双向IO口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是1,如果内部输出0,则无论外部信号是1还是0,这个引脚读进来的都是0。
矩阵键盘
独立按键的扫描
#include <REG52.H>
sbit K1=P3^1;
sbit K2=P3^0;
sbit K3=P3^2;
sbit K4=P3^3;
sbit LED1=P2^0;
sbit LED2=P2^1;
sbit LED3=P2^2;
sbit LED4=P2^3;
void main(){
K1=1;K2=1;K3=1;K4=1;
P2=0xff;
while(1){
LED1=K1;
LED2=K2;
LED3=K3;
LED4=K4;
}
}
按键消抖
通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图8-10所示。
硬件消抖
硬件消抖就是在按键上并联一个电容,如图8-11所示,利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。但实际应用中,这种方式的效果往往不是很好,而且还增加了成本和电路复杂度,所以实际中使用的并不多。
软件消抖
if(K3==0){
for(j=0;j<833;j++);//约10ms延时
if(K3 == 0){
LED3=K3
}
}
矩阵按键的扫描
常用有2种办法,一种是行列扫描和线翻转法。
行列扫描法检测时,先送一列为低电平,其余列全为高电平(此时确定列数),然后立即轮流检测各行是否有低电平,若检测到有低电平(即确定行数),就可以知道具体是按下了哪一个按键。
#include <REG52.H>
unsigned char matrix_keys(){
unsigned char ret,j;
//ret -> key's value
//j -> time delay
ret=0;
P1=0xf7;//the first column is zero, theh rest is one
if(P1!=0xf7){
for(j=0;j<9;j++);//dithering elimination
switch (P1)
{
case 0x77:ret =1;break;
case 0xb7:ret=5;break;
case 0xd7:ret = 9;break;
case 0xe7:ret = 13;break;
default:
break;
}
}
while(P1!=0xf7);//wait for key realease
P1=0xfb;//the second column is zero, theh rest is one
if(P1!=0xfb){
for(j=0;j<9;j++);//dithering elimination
switch (P1)
{
case 0x7b:ret =2;break;
case 0xbb:ret=6;break;
case 0xdb:ret = 10;break;
case 0xeb:ret = 14;break;
default:
break;
}
}
while(P1!=0xfb);//wait for key realease
P1=0xfd;//the third column is zero, theh rest is one
if(P1!=0xfd){
for(j=0;j<9;j++);//dithering elimination
switch (P1)
{
case 0x7d:ret =3;break;
case 0xbd:ret=7;break;
case 0xdd:ret = 11;break;
case 0xed:ret = 15;break;
default:
break;
}
}
while(P1!=0xfd);//wait for key realease
P1=0xfe;//the fourth column is zero, theh rest is one
if(P1!=0xfe){
for(j=0;j<9;j++);//dithering elimination
switch (P1)
{
case 0x7e:ret =4;break;
case 0xbe:ret=8;break;
case 0xde:ret = 12;break;
case 0xee:ret = 16;break;
default:
break;
}
}
while(P1!=0xfe);//wait for key realease
return ret;
}
线翻转法:
使所有行线为低电平时,检测所有列线是否有低电平,如果有,记录列线;然后再翻转,使所有列线为低电平,检测所有行线的值,由于有按键按下,行线的值也会发生改变,记录下行线的值;就可以确定具体是哪一个按键按下。
简易加法计算器
原理大致如下,还没编好
#include <REG52.H>
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit KEY_IN_1 = P1^4;
sbit KEY_IN_2 = P1^5;
sbit KEY_IN_3 = P1^6;
sbit KEY_IN_4 = P1^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[]={
//numberal display character conversion table
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,
};
unsigned char LedBuff[8]={
//nixie display buffer
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
unsigned char code KeyCodeMap[4][4]={
//mapping table of matrix key numbers to standard keyboard key codes
{0x31,0x32,0x33,0x26},//1,2,3,up key
{0x34,0x35,0x36,0x25},//4,5,6,left key
{0x37,0x38,0x39,0x28},//7,8,9,down key
{0x30,0x1b,0x0d,0x27},//0,ESC,Enter,right key
};
unsigned char KeySta[4][4]={
//current state of all matrix keys
{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1},
};
void KeyDriver();
void main(){
EA=1;//open general interrupt
TMOD= 0x01;//set T0 to mode 1
TH0 = 0xfc;//set T0 value to 0xfc67,timing 1ms
TL0 = 0x67;
ET0 = 1;//description T0 interruption was enabled
TR0 = 1;//open T0
LedBuff[0] = LedChar[0];//display 0 when power-on
while(1){
KeyDriver();
}
}
void ShowNumber(unsigned long num){
char i;
unsigned char buf[8];
for(i=0;i<8;i++){
//converts num to an 8-bit decimal array
buf[i] = num % 10;
num /= 10;
}
for(i=7;i>=1;i--){
//from top,come across 0 then turn 0 into space, while come across a number not 0 then exit the loop
if(buf[i] == 0){
LedBuff[i] = 0x00;
}else break;
}
for(;i>=0;i--){
//the rest convert to matrix characters
LedBuff[i]=LedChar[buf[i]];
}
}
void KeyAction(unsigned char keycode){
//function of key action, and take actions according to keycode
static unsigned long result = 0;//save the result
static unsigned long addend =0;//save the input addition
if((keycode>=0x30) && (keycode <= 0x39)){
//input number from 0 to 9
addend = (addend*10)+(keycode - 0x30);
ShowNumber(addend);
}else if (keycode == 0x26)//up key do add
{
result += addend;
addend = 0;
ShowNumber(result);
}else if (keycode == 0x0d)//enter key do add,like up key
{
result += addend;
addend =0 ;
ShowNumber(result);
}else if (keycode == 0x1b)//esc key do reset to 0
{
addend = 0;
result = 0;
ShowNumber(addend);
}
}
void KeyDriver(){
unsigned char i,j;
static unsigned char backup[4][4]={
//backup key,save the last value
{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};
for(i=0;i<4;i++){
//loop detection matrix key
for(j=0;j<4;j++){
if(backup[i][j] != KeySta[i][j]){//detect key action
if(backup[i][j]!=0){
KeyAction(KeyCodeMap[i][j]);
}
backup[i][j] = KeySta[i][j];
}
}
}
}
void KeyScan(){
unsigned char i;
static unsigned char keyout = 0;//matrix keyboard output index
static unsigned char keybuf[4][4]={
//matrix keys scan buffer
{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},{0xff,0xff,0xff,0xff},
};
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][1]<<1)|KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][1]<<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;
}
}
keyout++;//output index increment
keyout = keyout & 0x03;//the index value is zero when added to four
switch (keyout)//according to the index, realease the current output pin and pull the output oin of the lower order
{
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;
}
}
void LedScan(){
static unsigned char i =0;//dynamic scan index
P0 = 0x00;
switch (i)
{
case 0:
LSC=0;LSB=0;LSA=0;i++;P0=LedBuff[0];
break;
case 1:
LSC=0;LSB=0;LSA=1;i++;P0=LedBuff[1];
break;
case 2:
LSC=0;LSB=1;LSA=0;i++;P0=LedBuff[2];
break;
case 3:
LSC=0;LSB=1;LSA=1;i++;P0=LedBuff[3];
break;
case 4:
LSC=1;LSB=0;LSA=0;i++;P0=LedBuff[4];
break;
case 5:
LSC=1;LSB=0;LSA=1;i++;P0=LedBuff[5];
break;
case 6:
LSC=1;LSB=1;LSA=0;i++;P0=LedBuff[6];
break;
case 7:
LSC=1;LSB=1;LSA=1;i++;P0=LedBuff[7];
break;
default:
break;
}
}
void InterruptTimer0()interrupt 1{
TH0=0xfc;
TL0=0x67;
LedScan();
KeyScan();
}