Personal nest in the information age

80C51单片机Proteus仿真实验:双机单工通信

功能

  • 波特率为9600bps;
  • 单片机1使用4×4计算器键盘,按下其中任意一个键,单片机2的数码管显示对应数字;
  • 数字显示范围:十六进制的0~F。

工具

  • Windows 10 x64
  • Proteus Professional 8.9 SP0
  • Keil C51 Version 9.59

仿真器件选型

  • 单片机:80C51,时钟频率(Clock Frequency)调整为11.0592MHz;
  • 数码管:7SEG-COM-AN-GRN,7段1位共阳极绿色数码管;
  • 键盘:KEYPAD-SMALLCALC,该键盘为4*4计算器键盘;
  • 电源:POWER一个。

定时器初值计算

本实验中发送端和接收端使用相同的定时器初始化参数,使波特率为9600 bps。计数初值计算如下:

$$\rm 计数初值=2^{8}-\frac{1s}{9600bps}\times\frac{11.0592MHz}{12}=160=(A0)_{16}$$

$$\rm 计数初值=2^{8}-\frac{1s}{9600bps}\times\frac{12MHz}{12}\approx 151.833\approx (97.D55)_{16}$$

由上式可以看出,当单片机时钟频率为11.0592 MHz时,定时器计数初值为整数,可以更方便的设置单片机的计数初值,从而得到更精准的波特率,因此本实验在Proteus中将单片机的的时钟频率(Clock Frequency)值调成了11.0592 MHz。

发送/接收时延控制方法

串口中断通信法

仿真器件连接方式与实验效果图

图1 80C51单片机串口通信Proteus仿真

Keil C51代码

发送端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//Proteus的80C51单片机默认使用12MHz晶振,注释掉后波特率用11.0592MHz计算

typedef unsigned char uchar;
typedef unsigned int uint;

void delay1ms(uint i){
	uchar j;
	while(i--){
		for(j=0;j<115;j++);	  //1ms基准延时程序
	}
}

int keyscan(){
	uchar temp;
	P1=0xff; //先向P1口写1;端口读状态
	P1=0xf0;
	temp=P1;
	if(temp!=0xf0) {
		delay1ms(50);
		temp=P1;
		if(temp!=0xf0){
			P1=0xFF;
			P1=0x7f;
			temp=P1;
			switch(temp) {
				case(0x7e):return 10;
				case(0x7d):return 11;
				case(0x7b):return 12;
				case(0x77):return 13;
			}
			P1=0xbf;
			temp=P1;
			switch(temp) {
				case(0xbe):return 9;
				case(0xbd):return 6;
				case(0xbb):return 3;
				case(0xb7):return 14;
			}
			P1=0xdf;
			temp=P1;
			switch(temp) {
				case(0xde):return 8;
				case(0xdd):return 5;
				case(0xdb):return 2;
				case(0xd7):return 0;
			}
			P1=0xef;
			temp=P1;
			switch(temp) {
				case(0xee):return 7;
				case(0xed):return 4;
				case(0xeb):return 1;
				case(0xe7):return 15;
			}
		}
	}
	return -1;
}

/******** 定时器、串行口初始化函数 ********/
void init(void){
	ET1=0;	// 禁止定时器1中断
	ES=1;	//开启外部中断
	EA=1;	//总中断控制(允许)
	TMOD=0x20;	//定时器1,工作方式2
#ifdef F_12MHz
	TH1=0x98;	//9600的波特率,晶振12MHz,2^8-(10^6/9600)us*12/12=151.8333=97.D553H
	TL1=0x98;
#else
	TH1=0xA0;	//9600的波特率,晶振11.0592MHz,2^8-(10^6/9600)us*11.0592/12=160=A0H
	TL1=0xA0;
#endif
	TR1=1;	//启动定时器1
	TF1=0;
	PCON=0x00;	//串行口波特率不加倍
	SCON=0x50;	//串行口位于工作方式1,允许接收
}

void send(uchar val){
	SBUF=val;	//发送
	while(!TI);	// 等待发送完毕
	TI=0;	// 清中断
}

void main(){
	int key=-1;
	init();
	while(1){
		key=keyscan();
		if(key != -1){	//如果检测到按键,发送键码
			send(key);
		}
	}
}
接收端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//由于12MHz的时钟频率实现9600bps得不到整数的计数初值,影响精确度,因此改用11.0592MHz

typedef unsigned char uchar;
uchar code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,0xBF};
	 //共阳数码管显示字型码数组  1011 1111 "-"
uchar rec=0x00;	//用于数码管显示

/********* 数码管显示函数 *********/
void display(unsigned char i) {
	P0=table[i];
}

/******** 定时器、串行口初始化函数 ********/
void init(void){
	ET1=0;	// 禁止定时器1中断
	ES=1;	//允许串行口中断
	EA=1;	//总中断控制(允许)
	TMOD=0x20;	//定时器1,工作方式2
#ifdef F_12MHz
	TH1=0x98;	//9600的波特率,晶振12MHz,2^8-(10^6/9600)us*12/12=151.8333=97.D553H
	TL1=0x98;
#else
	TH1=0xA0;	//9600的波特率,晶振11.0592MHz,2^8-(10^6/9600)us*11.0592/12=160=A0H
	TL1=0xA0;
#endif
	TR1=1;	//启动定时器1
	TF1=0;
	PCON=0x00;	//串行口波特率不加倍
	SCON=0x50;	//串行口位于工作方式1,允许接收
	RI=0;
}

void main(){
	init();
	while(1){
		display(rec);
	}
}

/**** 串行中断:接收数据 ******/
void receive() interrupt 4{
	RI=0;
	rec=SBUF;
}

I/O口模拟串口通信法

仿真器件连接方式与实验效果图

图2 80C51单片机I/O口模拟串口通信Proteus仿真

定时器0中断查询法

发送端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//Proteus的80C51单片机默认使用12MHz晶振,注释掉后波特率用11.0592MHz计算

typedef unsigned char uchar;
typedef unsigned int uint;

// 重新定义TXD引脚
sbit TX=P1^0;
#define TXD TX

void delay_9600(){
	TMOD=0X01;//开定时器0,工作方式为1
	TR0=1;//启动定时器0;
	TH0=0XFF;	//9600
	TL0=0XA0;
	while(!TF0);//等待,直到TF0为1
	TF0=0;	   //重置溢出位标志
	TR0=0;//停止定时器0;
}

void send(uchar input){
	//发送启始位
	uchar i=8;
	TXD=(bit)0;
	delay_9600();
	//发送8位数据位
	while(i--){
		TXD=(bit)(input&0x01);     //先传低位
		delay_9600();
		input>>=1;
	}
	//发送校验位(无)
	//发送结束位
	TXD=(bit)1;
	delay_9600();
}

void delay1ms(uint i){
	uchar j;
	while(i--){
		for(j=0;j<115;j++);	  //1ms基准延时程序
	}
}

uint keyscan(){
	uchar temp;
	P3=0xff; //先向P1口写1;端口读状态
	P3=0xf0;
	temp=P3;
	if(temp!=0xf0) {
		delay1ms(50);
		temp=P3;
		if(temp!=0xf0){
			P3=0xff;
			P3=0x7f;
			temp=P3;
			switch(temp) {
				case(0x7e):return 10;
				case(0x7d):return 11;
				case(0x7b):return 12;
				case(0x77):return 13;
			}
			P3=0xbf;
			temp=P3;
			switch(temp) {
				case(0xbe):return 9;
				case(0xbd):return 6;
				case(0xbb):return 3;
				case(0xb7):return 14;
			}
			P3=0xdf;
			temp=P3;
			switch(temp) {
				case(0xde):return 8;
				case(0xdd):return 5;
				case(0xdb):return 2;
				case(0xd7):return 0;
			}
			P3=0xef;
			temp=P3;
			switch(temp) {
				case(0xee):return 7;
				case(0xed):return 4;
				case(0xeb):return 1;
				case(0xe7):return 15;
			}
		}
	}
	return 100;
}

void main(){
	uint key;
	while(1){
		key=keyscan();
		if(key >=0 && key <=16){
			send(key);
		}
	}
}
接收端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//由于12MHz的时钟频率实现9600bps得不到整数的计数初值,影响精确度,因此改用11.0592MHz

typedef unsigned char uchar;

// 重新定义RXD引脚
sbit RX=P3^2;
#define RXD RX

uchar code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,0xBF};
	 //共阳数码管显示字型码数组  1011 1111 "-"
uchar rec=0x00;	//用于数码管显示

/********* 数码管显示函数 *********/
void display(unsigned char i) {
	P0=table[i];
}

void delay_9600(){
	TMOD=0X01;//开定时器0,工作方式为1
	TR0=1;//启动定时器0;
	TH0=0XFF;	//9600
	TL0=0XA0;
	while(!TF0);//等待,直到TF0为1
	TF0=0;	   //重置溢出位标志
	TR0=0;//停止定时器0;
}

uchar receive(){
	uchar output=0;
	uchar i=8;
	delay_9600();
	while(i--){
		output>>=1;
		if(RXD) output |=0x80;
		delay_9600();
	}
	return output;
}

void main(){
	IE|=0x81;	// EA=1 总中断控制位(允许),EX0=1 允许外部中断0
	IT0=0;	//使用电平触发方式,低电平有效
	while(1){
		display(rec);
	}
}

void intrr() interrupt 0{
	EX0=0;
	rec=receive();
	//rec=0x05;
	EX0=1;
}

定时器0中断法

发送端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//Proteus的80C51单片机默认使用12MHz晶振,注释掉后波特率用11.0592MHz计算

typedef unsigned char uchar;
typedef unsigned int uint;

// 重新定义TXD引脚
sbit TX=P1^0;
#define TXD TX

uchar input,i;	//两个数据发送辅助变量

void delay1ms(uint i){
	uchar j;
	while(i--){
		for(j=0;j<115;j++);	  //1ms基准延时程序
	}
}

int keyscan(){
	uchar temp;
	P3=0xff; //先向P1口写1;端口读状态
	P3=0xf0;
	temp=P3;
	if(temp!=0xf0) {
		delay1ms(50);
		temp=P3;
		if(temp!=0xf0){
			P3=0xFF;
			P3=0x7f;
			temp=P3;
			switch(temp) {
				case(0x7e):return 10;
				case(0x7d):return 11;
				case(0x7b):return 12;
				case(0x77):return 13;
			}
			P3=0xbf;
			temp=P3;
			switch(temp) {
				case(0xbe):return 9;
				case(0xbd):return 6;
				case(0xbb):return 3;
				case(0xb7):return 14;
			}
			P3=0xdf;
			temp=P3;
			switch(temp) {
				case(0xde):return 8;
				case(0xdd):return 5;
				case(0xdb):return 2;
				case(0xd7):return 0;
			}
			P3=0xef;
			temp=P3;
			switch(temp) {
				case(0xee):return 7;
				case(0xed):return 4;
				case(0xeb):return 1;
				case(0xe7):return 15;
			}
		}
	}
	return -1;
}

void initTimer(void){
	TMOD=0x02;	//定时器0,工作方式2
	ET0=1;	// 允许定时器0中断
	EA=1;	//总中断控制(允许)
#ifdef F_12MHz
	TH0=0x98;	//模拟9600的波特率,晶振12MHz,2^8-(10^6/9600)us*12/12=151.8333=97.D553H
	TL0=0x98;
#else
	TH0=0xA0;	//模拟9600的波特率,晶振11.0592MHz,2^8-(10^6/9600)us*11.0592/12=160=A0H
	TL0=0xA0;
#endif
	TR0=0;
	TF0=0;
}

void main(){
	int key=-1;
	initTimer();
	while(1){
		key=keyscan();
		if(key != -1){	//如果检测到按键,开启定时器0,初始化数据发送辅助变量
			TR0=1;
			input=key;
			i=8;
			TXD=(bit)0;	//发送起始位(space状态)
		}
	}
}

/************ 用于发送数据体(8 bit)和停止位(mark状态),不设奇偶校验位 **********/
void send() interrupt 1{
	if(i--){	// 发送数据体
		TXD=(bit)(input&0x01);
		input>>=1;
	}else{	// 数据体发送结束的后续处理
		TXD=(bit)1;	// 数据体发送结束,发送停止位
		TR0=0;	// 发送结束关闭定时器0
	}
}
接收端代码
// Haar Blog
// https://haar.xyz
// 2020-07-13 15:35

#include<reg51.h>

//#define F_12MHz	//由于12MHz的时钟频率实现9600bps得不到整数的计数初值,影响精确度,因此改用11.0592MHz

typedef unsigned char uchar;

// 重新定义RXD引脚
sbit RX=P3^2;
#define RXD RX

uchar code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,0xBF};
	 //共阳数码管显示字型码数组  1011 1111 "-"
uchar rec=0x00;	//用于数码管显示

/********* 数码管显示函数 *********/
void display(unsigned char i) {
	P0=table[i];
}

uchar output,i;	//数据发送辅助变量

/******** 定时器初始化函数 ********/
void initTimer(void){
	TMOD=0x02;	//定时器0,工作方式2
	ET0=1;	// 允许定时器0中断
	EA=1;	//总中断控制(允许)
#ifdef F_12MHz
	TH0=0x98;	//模拟9600的波特率,晶振12MHz,2^8-(10^6/9600)us*12/12=151.8333=97.D553H
	TL0=0x98;
#else
	TH0=0xA0;	//模拟9600的波特率,晶振11.0592MHz,2^8-(10^6/9600)us*11.0592/12=160=A0H
	TL0=0xA0;
#endif
	TR0=0;
	TF0=0;
}

void main(){
	IE |=0x83;	// EA=1 总中断控制位(允许),ET0=1 允许定时器0中断,EX0=1 允许外部中断0
	IT0=0;	//外部中断使用电平触发方式,低电平有效
	initTimer();
	while(1){
		display(rec);
	}
}

/************ 外部中断0,用于接收起始位,并启动后续步骤(定时器0) ************/
void interr() interrupt 0{
	EX0=0;	// 关闭外部中断
	TR0=1;	// 开启定时器0中断
	output=0;	//初始化讯息接收变量(清零)
	i=8;	// 初始化接收计数,用于接收一个Byte数据
}

/******** 定时器中断0,用于接收讯息部分(8 bit),结束后关闭定时器 *********/
void receive() interrupt 1{
	if(i--){	//接收讯息内容,存储于变量output
		output>>=1;
		if(RXD) output |=0x80;
	}else{	// 接收结束,因为终止位为高电平,可忽略
		rec=output;	//将接收到的讯息传给变量rec,用以数码管显示
		TR0=0;	//关闭定时器0
		EX0=1;	// 启动外部中断0,等待下一波数据
	}
}

Bibiliography

Leave a comment

Your email address will not be published. Required fields are marked *