功能
- 波特率为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。
发送/接收时延控制方法
串口中断通信法
仿真器件连接方式与实验效果图

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口模拟串口通信法
仿真器件连接方式与实验效果图

定时器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
- 李广弟, 朱月秀, 冷祖祁 编著. 《单片机基础》. 第3版. 北京航空航天大学出版社. 2007.06. ISBN 978-7-81077-837-4.
- supergame111. 单片机IO口模拟串口程序(发送+接收 ). CSDN博客. 2014-01-11 01:12:38.
- 51黑ff. 单片机io模拟串口进行通信的proteus仿真及源程序. 51黑电子论坛. 发表于2016-10-9 22:55