上面的图应该就能很好的理解51是具有4个8位并行I/O口了,
主要包括P0、P1、P2、P3口(大多数端口具有复用功能如P3.0、P3.1是I/O与串口的复用),还有读写使能输出和时钟输入端等。
然后上面的51板是8位的CPU,片内有128位的RAM数据存储器和4KB的ROM程序存储器,而外部数据存储器地址空间和程序存储器地址空间都是64KB。
这是51的最基本配置,当然我们还得看看它的复位电路和时钟电路。
51单片机需要一个硬件复位电路,用于用户的手动复位,并且51单片机是高电平复位有效,上图就是最简单的一个复位电路了。
在引脚XTAL1和XTAL2之间接一个晶振和两个电容就构成了内部震荡方式,而加上在单片机内部的一个高增益反相放大器,外接一个晶振后,就构成了自激振荡器并产生震荡时钟脉冲,上面就是简单的电路图了。
那么这里有一个概念,什么是单片机最小系统呢?
它是指以51系列单片机作为核心,再附带一些使单片机能够运行的最小资源,主要包括时钟电路、复位电路和扩展接口电路等部分就构成了单片机的最小系统。
介绍了这些,学过C语言的话,你就能实现一个简单的LED灯程序了。当当当~~
#include //reg52.h包含52单片机的寄存器库
sbit led = P1^0; //只有地址可以被8整除的才可以用sbit单独定义某一位,P0——P3口都可以sbit单独定义
int main(void)
{
led = 1; //初始化P1^0,使之输出状态
while(1)
{
led = 0; //将P1^0输出低电平
}
}
上面的程序应该是很好读懂的,这样其实就入门了。在Protues上的仿真结果
发现P1^0口的灯被点亮了!
为了实现LED的闪烁,需要一个延时函数实现一定时间的延时。
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit led = P1^0;
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
{
; //延时1ms
}
}
int main(void)
{
led = 1; //LED输出模式
while(1)
{
led = 0;
delay(500);
led = 1;
delay(500);
}
}
上面的程序就是简单使用两重for循环实现了延时。同时使得LED能够闪烁!
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit led1 = P1^0;
sbit led2 = P1^1;
sbit led3 = P1^2;
sbit led4 = P1^3;
sbit led5 = P1^4;
sbit led6 = P1^5;
sbit led7 = P1^6;
sbit led8 = P1^7;
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
{
; // 延时1ms
}
}
int main(void)
{
P1 = 0xff; //led输出模式
while(1)
{
led1 = 0;
delay(500);
led1 = 1;
led2 = 0;
delay(500);
led2 = 1;
led3 = 0;
delay(500);
led3 = 1;
led4 = 0;
delay(500);
led4 = 1;
led5 = 0;
delay(500);
led5 = 1;
led6 = 0;
delay(500);
led6 = 1;
led7 = 0;
delay(500);
led7 = 1;
led8 = 0;
delay(500);
led8 = 1;
}
}
流水灯实际上是单个LED闪烁的扩展,只需多次调用delay函数,多次改变P1口输出状态即可。
我们发现上面的程序使用了大量的重复代码,所以其实是有更简单的写法的,记住就好。
#include
typedef unsigned int u16;
typedef unsigned char u8;
#define PORT P1
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=0; y>0; --y)
{
; //延时1ms
}
}
int main(void)
{
u8 num, temp;
PORT = 0xff;
while(1)
{
PORT = 0xfe;
temp =0xfe;
delay(500);
for(num=7; num>0; num--)
{
temp = (temp << 1) | 0x01;
PORT = temp;
delay(500);
}
}
}
是采用左移的操作实现P1口的状态改变的!
因为是在proteus中仿真的,除了用到两块数码管之外,还有两个74hc573的锁存器,大致接线如下:
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit seg_sel = P1^4;
sbit bit_sel = P1^5;
#define DATA P0
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
{
; //延时1ms
}
}
int main(void)
{
P0 = 0xff;
P1 = 0xff;//端口初始化
seg_sel = 0; //IE禁止锁存
bit_sel = 0;
while(1)
{
bit_sel = 1; //Q[7..0]=D[7..0]
DATA = 0x80; //先选中最右边的数码管
bit_sel = 0;
delay(5);
seg_sel = 1; //Q[7..0]=D[7..0]
DATA = 0xc0; //使数码管显示0的字样
seg_sel = 0;
delay(5);
}
}
上面就是相应的程序,注意P0口与其他口不同,它的输出部分没有上拉电阻,因此在作为通用I/O口或地址数据总线时,需外接上拉电阻,作为输入时,应该向锁存器写“1”。
进一步的,我们可以写一个函数方便的调用使得数码管的任意一位显示任意一个数字,当当当~~
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit seg_sel = P1^4;
sbit bit_sel = P1^5;
#define DATA P0
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
{
; //延时1ms
}
}
u8 code seg_tab[]={ //使用code是为了让数据存入程序存储器,节省数据存储器(比较小)的空间
0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,
0xc6,0xa1,0x86,0x8e
};
u8 code bit_tab[]={
0x80,0x40,0x20,0x10,
0x08,0x04,0x02,0x01
};
void display_led(u8 which_bit, u8 which_number)
{
bit_sel = 1; //Q[7..0]=D[7..0]
DATA = bit_tab[which_bit];
bit_sel = 0; //BIT[7..0]=0x80
delay(5);
seg_sel = 1; //Q[7..0]=D[7..0]
seg_sel = seg_tab[which_number];
seg_sel = 0;
delay(5);
}
int main(void)
{
P0 = 0xff;
P1 = 0xff; //端口初始化
seg_sel = 0; //LE禁止锁存
bit_sel = 0;
while(1)
{
display_led(4, 8);
}
}
上面的display_led函数就可以实现上面的那个功能。
下面就是让8个数码管全部点亮,显示一定的数字啦~是不是很熟悉?对,就是循环实现。
#include
typedef unsigned char u8;
typedef unsigned int u16;
sbit seg_sel = P1^4;
sbit bit_sel = P1^5;
#define DATA P0
u8 code seg_tab[]={
0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,
0xc6,0xa1,0x86,0x8e
};
u8 code bit_tab[]={
0x10,0x20,0x40,0x80,
0x01,0x02,0x04,0x08
};
void delay(u16 num)
{
u16 x,y;
for(x=num; x>0; x--)
for(y=110; y>0; y--)
{
;//延时1ms
}
}
void display_led(u8 which_bit, u8 which_number)
{
bit_sel = 1;//Q[7..0]=D[7..0]
DATA = bit_tab[which_bit];
bit_sel = 0;//BIT[7..0]=0x80
seg_sel = 1;//Q[7..0]=D[7..0]
DATA = seg_tab[which_number];
seg_sel = 0;
delay(2);
}
int main (void)
{
u8 num;
P0 = 0xff;
P1 = 0xff;//端口初始化
seg_sel = 0;//LE禁止锁存
bit_sel = 0;
while (1)
{
for(num=8; num>=1; num--)
display_led(num, 2);
}
}
唯一改变的就是加上了一个for循环。
下面就开始我们的按键操作啦~
首先是单个按键:
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit key = P3^2;
sbit led = P1^0;
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
; //延时1ms
}
int main(void)
{
led = 1;
while(1)
{
if(key == 0)
{
delay(10);
if(key == 0) //消抖处理
{
led = ~led;
}
while(!key); //按键检测
}
}
}
之所以使用两次if判断key是否为0,是为了消除按键的机械抖动效应。
上图就是我们的按键连接图。
下一个就是矩阵键盘的实现啦
首先来看矩阵键盘的原理图:
对应的在proteus上的电路图是
可见键盘使用了P2口。
按键扫描程序的流程:
按键编码的规则:
这样,相比于单个按键的程序,我们只需要添加编码和解码的函数代码即可实现矩阵键盘的功能。
#include
typedef unsigned int u16;
typedef unsigned char u8;
sbit seg_sel = P1^4;
sbit bit_sel = P1^5;
#define DATA P0
u8 code seg_tab[]={
0xc0,0xf9,0xa4,0xb0,
0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,
0xc6,0xa1,0x86,0x8e
};
u8 code bit_tab[]={
0x10,0x20,0x40,0x80,
0x01,0x02,0x04,0x08
};
void display_led(u8 which_bit, u8 which_number)
{
bit_sel = 1; //Q[7..0]=D[7..0]
DATA = bit_tab[which_bit];
bit_sel = 0; //BIT[7..0]=0x80
seg_sel = 1; //Q[7..0]=D[7..0]
DATA = seg_tab[which_number];
seg_sel = 0;
delay(2);
}
void delay(u16 num)
{
u16 x, y;
for(x=num; x>0; --x)
for(y=110; y>0; --y)
; //延时1ms
}
u8 key_scan()
{
u8 temp, temp2;
P2 = 0xf0; //让P2输出0xf0
temp = P2; //读入P2的值
if(temp != 0xf0) //当P2读入的值不等于0xf0
{
delay(5);
temp = P2; //再读入P2口的值
if(temp != 0xf0) //如果P2读入的值不等于0xf0,就说明有按键按下
{
temp2 = temp & 0xf0; //保留P2读入值得高四位
P2 = 0x0f; //再让P2口输出0x0f
delay(1);
temp = P2; //再读入P2口的值
temp2 |= temp; //保留此读入值的低四位
return temp2;
}
}
}
u8 encode()
{
u8 num;
switch(cod)
{
case 0xee: num = 0;break;
case 0xde: num = 1;break;
case 0xbe: num = 2;break;
case 0x7e: num = 3;break;
case 0xed: num = 4;break;
case 0xdd: num = 5;break;
case 0xbd: num = 6;break;
case 0x7d: num = 7;break;
case 0xeb: num = 8;break;
case 0xdb: num = 9;break;
case 0xbb: num = 10;break;
case 0x7b: num = 11;break;
case 0xe7: num = 12;break;
case 0xd7: num = 13;break;
case 0xb7: num = 14;break;
case 0x77: num = 15;break;
default : break;
}
return num;
}
int main(void)
{
u8 cod, num;
P0 = 0xff;
P1 = 0xff;//端口初始化
seg_sel = 0;//LE禁止锁存
bit_sel = 0;
while(1)
{
cod = key_scan(); //判断及编码
num = encode(cod); //解码程序,得出数字
display_led(1, num); //显示程序
}
}