C51从中断系统到串口通信(难点)

**

前言

**
这一部分也是相当重要的基础内容。
虽然目前的学习过程中,每个程序的大小都不大,但是每个程序都让自己明白了一点知识,这已经足够了。
上一篇单片机,我想用8X8led点阵实现一个小鸟游戏。但是没有想到自己还不够熟悉,到写这篇的时候,那个小东西还是坨烂泥。


**

中断系统?

**
中断系统的原理,可以看看大佬的解释。
https://blog.csdn.net/ckkyjtqlt/article/details/76293827

想要睡觉?来看一篇中断系统的详解。

在这里插入图片描述
中断系统是程序多线程操作的底层实现。无论是几核的CPU,对于其上面的每一个单核来说,执行程序时都是用的假的多线程。CPU运行速度之快,让人难以察觉到,其实它是执行了一个又去执行另一个线程。而这样的操作,离不开底层的中断系统的支撑。

原理都挺简单,但接下来的事就变得越来越复杂了。

由上自下

先凭借上面的原理理解一下下面这个中断示例程序:
代码参考:https://blog.csdn.net/chenjuan0530/article/details/71424143

#include<reg52.h>
typedef unsigned char u8;
typedef unsigned int u16;

//延迟函数
void delay(u16 i){
	while(i--){}
}

void main(){
	P2=0xfe;
	delay(100000);//看看初始的状态
	
	EA=1;//全局中断1 
	EX0=1;外部中断1 
	IT0=0;//低电平触发
	while(1){}
	
}
//中断服务程序
void reverse()interrupt 0 using 1{
	P2=~P2;
}

下面是显示效果:

在这里插入图片描述

  • 开始明显发现,当轻轻触碰时,触发中断服务程序,于是P2口的灯翻转显示。
  • 还可以明白中断程序的无需声明,其在触发后被系统自动调用-------这让我回忆起来学Win32程序的时候,系统在程序内部完成了许多别的操作。
  • 至于中断程序的关键字interrupt 和 using 及全局中断、外部中断和电平触发后面会讲到。

其实中断应该按独立按键K3,但按电源开关显示效果比较好。

中断系统的硬件结构

在这里插入图片描述
为了触发中断程序,需要让中断源向中断入口发送中断信号。那么如何由外部引起中断呢?
让我们来打通外部到中断入口这条路。

1. 先看左边的,IT0,IT1是外部中断(因为离中断源比较源,又是由外部触发的)向下的箭头表示下降沿触发,反相器表示低电平触发。
2. 先不管TF0,TF1。IT0或IT1开关弄明白后,然后是IE寄存器和IP中断优先级寄存器,都取值1才启用中断。
3. TF0,TF1是定时器/计数器中断---------------如果是作为定时器,CPU将对内部时钟脉冲进行计数;如果是作为计数功能使用,则是对外部脉冲信号进行计数。设定时间到了或者外部脉冲计数溢出时,将会在ET0,ET1寄存器中记录1 \dots
4. RI,TI串行口中断(UART),CPU通过RXD,TXD引脚接受中断信号或者发送中断信号。
5. 中断优先级,这个由IP寄存器判断:INT0 > TF0 >INT1 >TF1 >RI/TI >TF2/EXF2(最低优先级)。优先级高的程序可以把优先级低的程序打断。程序不能被同级的打断。

下面的信息,在使用中断时需要查看:
(在程序中只有一种中断信号时,一般没有管中断优先级寄存器IP)

中断启用寄存器IE
	 IE(SFR特殊寄存器中的位置,A8H)
IE.7    EA 		中断总开关 EA=1启用所有中断
IE.6    --		
IE.5	ET2		TF2的中断开关(8052才有)ET2=1启用中断功能,ET2=0停用中断功能
IE.4	ES		串行口的中断开关 ES=1启用
IE.3	ET1		TF1的中断开关 ET1=1启用
IE.2	EX1		INT1的中断开关 EX1=1启用
IE.1	ET0		TF0的中断开关 ET0=1启用
IE.0	EX0		INT0的中断开关 EX0=1启用





中断优先级寄存器IP
	IP(SFR特殊寄存器中的位置,B8H)
IP.7	--
IP.6	--
IP.5	PT2		TF2的中断优先级(8052才有)PT2=1启用TF2中断高优先级,PT2=0表示TF2具有低优先级
IP.4	PS		串行口的中断优先级 PS=1表示串行口具有高优先级
IP.3	PT1		TF1的中断优先级 PT1=1启用TF1高优先级
IP.2	PX1		INT1的中断优先级 PX1=1启用INT1高优先级
IP.1	PT0		TF0的中断优先级 PT0=1启用TF0高优先级
IP.0	PX0		INT0的中断优先级 PX0=1启用INT0高优先级




定时、计数器控制寄存器TCON
	TCON(SFR特殊寄存器中的位置,88H)
TCON.7	TF1 	定时、计数器1的中断标志位 Timer1中断结束时,CPU设定TF1=0,Timer1中断时,CPU设定TF1=1
TCON.6	TR1 	Timer1的启动开关
TCON.5	TF0 	定时、计数器0的中断标志位 Timer0中断结束时,CPU设定TF0=0,Timer0中断时,CPU设定TF0=1
TCON.4	TR0		Timer2的启动开关
TCON.3	IE1		INT1的中断标志位 IT1中断结束时,CPU设定IE1=0,IT1中断时,CPU设定IE1=1
TCON.2	IT1 	INT1的信号种类 IT1=1,负边沿触发。IT1=0,低电平触发。
TCON.1	IE0		INT0的中断标志位 IT0中断结束时,CPU设定IE0=0,IT0中断时,CPU设定IE0=1
TCON.0	IT0		INT0的信号种类 IT0=1,负边沿触发。IT0=0,低电平触发。

什么是嵌套中断

在A执行时有优先级更高的B来打断,这叫中断。于此同时,在B执行时又有优先级更高的C来打断,这就叫嵌套中断。
在这里插入图片描述

**

中断程序如何编写

**
1.满足CPU(可能)执行中断(中断响应)的条件

  1. 中断源有中断请求
  2. 此中断源的中断允许位为1
  3. CPU开总中断(EA=1)

在这里插入图片描述
2.上面显示的中断号与中断程序的关键字有关,interrupt X using N ,X为中断号0~4,N是寄存器组0 ~ 32

开关(中断系统)控制流水灯:

#include<reg52.h>
#define LED P2

void delay(int x){
	int i,j;
	for(i=0;i<x;i++){
		for(j=1;j<120;j++){
		
		}
	}
}

void left_move(int x){
	
	int i,j;
	for(i=0;i<x;++i){
		LED=0xfe;
		for(j=0;j<8;++j){
			delay(250);
			LED=(LED<<1)|0x01;//保证最后一定有一个1
		}
	}

}
void main(){

	IE=0x81;
	TCON=0x01;
	LED=0xff;
	while(1){
		delay(250);
		LED=~LED;
		
	}


}
void my_int0()interrupt 0{ //0 表示外部中断 INT0

	unsigned char saveLED=LED;
	EA=0;
	left_move(2);
	EA=1;
	LED=saveLED=LED;

}
  • 上面中断程序里为什么要暂时保存LED值和关掉EA总中断,是为了避免嵌套中断。
  • 对IE、TCON的操作是字节操作。
  • 程序中断靠开关K3(INT0 / P32)

**

定时器与计数器

**

振荡周期(时钟周期)>>> 为单片机提供定时信号的振荡源的周期,有晶振周期和外加振荡周期等
状态周期 (S周期) >>> 1个状态周期为两个振荡周期
机器周期 >>> 1个机器周期为6个状态周期,12个振荡周期
指令周期 >>> 完成一条指令所占用的全部时间,以机器周期为单位

关于频率与时间的换算: 1 M H Z = 1 1 u s 1 MHZ = \frac{1}{1 us }
对于12MHZ的晶振的单片机来说,其机器周期为 1us (上一篇的延时函数_nop_()就是延时一个机器周期)

需要注意的一些有:

  1. 51单片机有两组定时、计数器----------- 一组定时、计数器两个字节
  2. 定时、计数器和单片机CPU是相互独立的
  3. 有了定时、计数器后,可以增加单片机效率,一些简单的重复加一的工作可以交给定时、计数器来完成,CPU转而处理复杂的事。同时实现精确定时。

定时、计数器的工作原理

定时、计数器是一个加一计数器。
随着计数器的输入脉冲进行自加1,就是每来一个脉冲,计数器就加一。
当加到计数器全为1时,再来一个脉冲就能使计数器归零,且计数器的溢出使得相应的中断标志位置1 ,向CPU发出中断请求(定时、计数器中断允许时)。
如果定时、计数器工作于定时模式,则表示定时时间已到
如果工作于计数模式,则表示计数值已满。

定时、计数器结构

在这里插入图片描述
定时、计数器实质是加一计数器(16位),由高8位和低8位两个寄存器THx、TLx组成。

TMOD是定时、计数器的工作方式寄存器。

低四位用于T0,高四位用于T1。
字节地址是89H。

GATE:门控位。
GATE = 0时,用于控制定时器TCON的启动是否受外部中断源信号的影响。只要用软件使TCON中的TR0或TR1为1,就可以启动定时、计数操作。
GATE = 1时,如果需要启动定时、计数操作,需要先使TR0或TR1为1,同时外部中断引脚INT0或INT1也为高电平时,才能启动定时、计数操作。

C/T#:定时、计数模式选择位。
C/T# = 0为定时模式。
C/T# = 1为计数模式。

M1M0:工作方式设置位。
M1M0 = 00,方式0。13位定时、计数器。
M1M0 = 01,方式1。16位定时、计数器。
M1M0 = 10,方式2。 8位自动重装定时、计数器。
M1M0 = 11,方式3。 T0分成两个独立的8位定时、计数器;T1停止计数。

TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。

TCON的图在上面已经出现过了,低4位用于控制外部中断。
TF1:T1溢出中断请求标志位。
T1计数溢出时,由硬件自动设置TF1为1 。
CPU响应中断后TF1由硬件自动清零。
T1工作时,CPU可随时查询TF1的状态。
T1可作为查询测试的标志,也可以被设置为1或清零。

TR1:T1运行控制位。
TR1设置为1时,T1开始工作。
TR1设置为0时,T1停止工作。

同样的
TF0:T0溢出中断请求标志位。
TR0:T0运行控制位。

定时、计数器的工作方式寄存器TMOD和控制寄存器TCON信息总览:



定时、计数器控制寄存器TCON
	TCON(SFR特殊寄存器中的位置,88H)
TCON.7	TF1 	定时、计数器1的中断标志位 T1中断结束时,CPU设定TF1=0,T1中断时,CPU设定TF1=1
TCON.6	TR1 	T1的启动开关
TCON.5	TF0 	定时、计数器0的中断标志位 T0中断结束时,CPU设定TF0=0,T0中断时,CPU设定TF0=1
TCON.4	TR0		T2的启动开关
TCON.3	IE1		INT1的中断标志位 IT1中断结束时,CPU设定IE1=0,IT1中断时,CPU设定IE1=1
TCON.2	IT1 	INT1的信号种类 IT1=1,负边沿触发。IT1=0,低电平触发。
TCON.1	IE0		INT0的中断标志位 IT0中断结束时,CPU设定IE0=0,IT0中断时,CPU设定IE0=1
TCON.0	IT0		INT0的信号种类 IT0=1,负边沿触发。IT0=0,低电平触发。



定时、计数器工作模式寄存器TMOD
	TMOD(SFR特殊寄存器中的位置,89H)
TMOD.7	GATE	T1门控位 
TMOD.6	C/T#	T1定时、计数模式选择位 C/T#=0,定时模式;C/T#=1,计数模式
TMOD.5	M1		T1工作模式选择位之一(高位)
TMOD.4	M0		T1工作模式选择位之一(低位)
TMOD.3	GATE	T0门控位
TMOD.2	C/T#	T0定时、计数模式选择位	C/T#=0,定时模式;C/T#=1,计数模式
TMOD.1	M1		T0工作模式选择位之一(高位)
TMOD.0	M0		T0工作模式选择位之一(低位)



LED定时闪烁

如果使用定时器?

  1. 对TMOD赋值,以确定T0和T1的工作方式
  2. 计算初值,将其写入TH0、TL0或TH1、TL1。--------------------如果像开头假设那样,CPU机器周期是1us的话,那么初值应该是 1 m s 1 u s = 1000 \frac{1 ms}{1 us} = 1000 。意思是要计数1000个数,初值 = 65535 - 1000 + 1= FC18H = 2 16 N + 1 2^{16} - N + 1 。等计数了N个时就会出现溢出。
  3. 中断方式,则对EA赋值,开放定时器中断。
  4. 使TR0或TR1设置位,启动定时、计数器定时或计数。
#include<reg52.h>
typedef unsigned int u16;
typedef unsigned char u8;


sbit led=P2^0;

void timer0_init(){

	TMOD |= 0x01;//只用低四位(T0)
	
	//设置还差1000 ms就溢出了
	TH0=0xfc;//高位
	TL0=0x18;//低位
	
	EA=1;//总中断
	ET0=1;//IE上的TF0中断开关
	TR0=1;//T0运行控制
	
}
void timer1_init(){

	TMOD |= 0x10;//只用高四位(T1)
	
	//定时一毫秒
	TH1=0xfc;//高位
	TL1=0x18;//低位
	
	ET1=1;
	EA=1;//总中断
	TR1=1;
	
	
}
void main(){

	timer1_init();
	while(1){
	}

}
	
void time0() interrupt 1{

	static u16 my_num;
	
		TH0=0xfc;//高位
		TL0=0x18;//低位
		
	my_num++;//一毫秒过去了
	if(my_num==1000){//1s
		my_num=0;
		led=~led;
	
	}
}

void time1() interrupt 3{

	static u16 my_num;
	
		TH1=0xfc;//高位
		TL1=0x18;//低位
		
	my_num++;//一毫秒过去了
	if(my_num==1000){//1s
		my_num=0;
		led=~led;
	
	}
}

**

串口通信

**

知识总是会被遗忘的。 所以在学习过程中,自己总是有意地忘记一些东西,一些很容易记也很容易忘的东西。这篇我写了好久了,而我自己也还在学习。
每次面对难题就想去逃避,对于我这样没有意志力的人来说,只能拖延了。
拖延也是没有结果的,像无意义的争吵一样。为了满足自上而下的学习习惯,我想早点上手这部分的实践内容。

所以我选择压缩,必须压缩才能把人从繁杂的记忆、理解和弄乱中解救出来。

串口通信的对立面是并口通信,并口通信是多条线路一起传输数据。

串口通信中信号传输方向的三个种类:

  • 1- 单工 ------- 单向通道, A B A\rightarrow B
  • 2- 半双工 ------- 双向通道,分时进行 A B &ThickSpace; o r A B A\rightarrow B\; or A\leftarrow B
  • 3- 双工 -------- 双向通道,同时进行 A B &ThickSpace; a n d A B A\rightarrow B\; and A\leftarrow B

在51单片机中提供了全双工的万能异步串行端口(Universal Asynchronous Receiver-Transmitter,UART)。其有4种工作模式:

  1. Mode0 > 由51单片机主导,其比特率为8051系统时钟脉冲频率(FOSC)的十二分之一
  2. Mode1 > 此模式为可变比特率的串行口,此方式的比特率是 ( 2 S M O D 32 ) ( T 1 ) (\frac{2^{SMOD}} {32})*(T1溢出率)
  3. Mode2 > 此方式的比特率是 ( 2 S M O D 64 ) F O S C (\frac{2^{SMOD}} {64})*FOSC
  4. Mode3 > 和Mode1一样。

下面关于比特率和串行口工作模式再解释两件事:

  1. SMOD是SFR特殊寄存器上的PCON(87H)的第7位。
功率控制寄存器PCON
	PCON(SFR特殊寄存器中的位置,87H)
PCON.7	SMOD	比特率翻倍标志位 SMOD=1,bps翻倍。复位时,SMOD=0

2.T1溢出率------定时、计数器溢出率是时钟频率与达到溢出所需的周期数之比。
比如下面这个式子
T 1 = F O S C 12 ( 256 T H 1 ) T1溢出率 = \frac{FOSC}{12 * ( 256 - TH1)}
由于定时、计数器只用了高位寄存器TH1(8位模式),所以终点设置256。TH1为设定的8位初值----------当然,这些设置全依靠时钟,所以工作模式和初值可以自己设定。

另外,异步通信和同步通信的概念:异步是两个计算机有不同的时钟,而且发送的字节数据间有间隙,效率低但操作简单。而同步通信是相同的时钟,让接受方时钟和发送方时钟一致(位同步或字节同步),字节间没有间隙,效率高但复杂。

1.串行通信中的错误校验

串行传输中如果某个字节或某段数据出错,这个我需要知道。
了解方法有:奇偶校验、循环冗余校验等

  • PSW中有一位奇偶校验位,如果累加器A输出值中1的个数是奇数,那么这一位为1,否则为0

程序状态寄存器PSW
	PSW(SFR特殊寄存器中的位置,D0H)
PSW.0	P 	奇偶校验位

2.串行口控制寄存器SCON

在这里插入图片描述
前面讲定时、计数器有提到这个。

SM0、SM1
首先是左边两位,设定串行口的工作模式:
在这里插入图片描述

SCON.5(SM2)
多重处理器通信启用位

  1. 处于Mode0时,SM2=0
  2. 处于Mode1时,如果SM2=1,且收到了有效的停止位,则RI = 1(引发RI中断),否则RI=0
  3. 处于Mode2或Mode3时,如果SM2=1,且收到的第9位是1,则RI=1(引发RI中断);如果若第9位是0,则RI=0 (停止位不一定是第9位)

SCON.4(REN)
串行数据接收允许位
如果REN=1,允许接收,否则停止。

SCON.3(TB8)
Mode2或Mode3传送数据时,本位是第9传送位。

SCON.2(RB8)

  1. 处于Mode0时,无作用
  2. 处于Mode1时,如果SM2=0,则本位为停止位。
  3. 处于Mode2或Mode3时,本位是第9个接收位。

SCON.1(TI)
传送中断标志位,当中断结束时,本位不会恢复为0.

  1. 处于Mode0时,如果完成传送第8位,则本位自动设定为1,并产生TI中断
  2. 其他模式下,如果完成传送停止位,则本位自动设定为1,并产生TI中断

SCON.0(RI)
接收中断标志位,当中断结束时,本位不会恢复为0.

  1. 处于Mode0时,如果完成传送第8位,则本位自动设定为1,并产生RI中断
  2. 其他模式下,如果完成传送停止位,则本位自动设定为1,并产生RI中断

串行口控制寄存器SCON部分信息:



串行口控制寄存器SCON
	SCON(SFR特殊寄存器中的位置,98H)
SCON.7	SM0		通信模式/工作方式选择位之一(高位)
SCON.6	SM1		通信模式/工作方式选择位之一(低位)
SCON.5	SM2		多机通信控制位
SCON.4	REN		串行数据接收运行位 REN=1,启动串行口接收。REN=0,禁止接收
SCON.3	TB8		Mode0/Mode3下,是发送数据的第9位  可以用作数据的奇偶校验位;也可以在多机通信中,作为地址帧、数据帧的标志位
SCON.2	RB8		Mode0/Mode3下,是接收数据的第9位  Mode1下,若SM2=0,RB8是接收到的停止位;作为奇偶校验位或地址帧、数据帧的标志位
SCON.1	TI		发送中断标志位
SCON.0	RI		接收中断标志位


3.4个模式?

Mode0
此模式下,串行口为同步移位寄存器的输入输出方式。
用于扩展并行输入或输出口。CPU的RXD引脚连接串行数据线,TXD连接移位脉冲线。

执行数据接收时,由TXD送出移位脉冲,而由RXD收下串行数据;如左图
执行数据发送时,由TXD送出移位脉冲,由RXD送出串行数据;如右图

在这里插入图片描述

Mode1
这是10位数据的异步通信口。
一位起始位,一位停止位,中间8位数据位。
在这里插入图片描述

TXD为数据发送引脚,RXD为数据接收引脚。

在这里插入图片描述

Mode2 & Mode3
这两个模式是11位数据异步通信口。
TXD为数据发送引脚,RXD为数据接收引脚。

  • 数据传出时,第9位,TB8为奇偶位,可取PSW寄存器中的P(奇偶校验位)标志位。
  • 数据接收时,第9位,直接移入SCON寄存器中的RB8,不必理睬停止位。

在这里插入图片描述

4.串口使用?

串口工作前,先初始化。主要是设置产生比特率的定时器1、串行口控制和中断控制。
1> 确定 T1工作方式(对TMOD进行编程)
2> 计算 T1的初值,装载TH1,TL1
3> 启动T1 (对TCON中的TR1位进行编程)
4> 确定串行口控制(对SCON寄存器进行编程)
串行口在中断方式工作时,还要进行中断设置(编程IE、IP寄存器)

  • 现在回头看看,发现我们只是在进行几个寄存器的操作而已,这么一想,几天的烦恼都烟消云散了。

串口通信的内容中,除了SCON寄存器、PCON寄存器外,还有一个重要的寄存器SBUF:


串行口缓冲寄存器SBUF(有两个,物理上独立,但是地址相同。一个发送缓冲寄存器,一个输出接受寄存器)
	SBUF(SFR特殊寄存器中的位置,99H)
SBUF.7	--
SBUF.6	--
SBUF.5	--
SBUF.4	--
SBUF.3	--
SBUF.2	--
SBUF.1	--
SBUF.0	--

SBUF在串口中的地位:
在这里插入图片描述

下面是一自发自收的例子:

#include<reg52.h>
#include<intrins.h>
typedef unsigned char u8;
typedef unsigned int  u16;




//位选
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

//动态数码管显示的数据
u8 code smgduan[]={
	0x3f,0x06,0x5b,0x4f,
	0x66,0x6d,0x7d,0x07,
	0x7f,0x6f,0x77,0x7c,
	0x39,0x5e,0x79,0x71
};
void delay(u8 i){
	while(i--){}
}
void init(){
	
	PCON=0;//PCON.7 = SMOD =1
	/*
	SCON    SM0   SM1  SM2   REN  TB8  RB8  TI  BI
			 1     0    0	  1	   0	0	 0	 0		
	 Mode2
		REN=1  允许输入
	*/
	SCON=0x90;
	
}

void display(u8 num){
		switch(num){
			case(0):
				LSC=0,LSB=0,LSA=0;
				break;
			case(1):
				LSC=0,LSB=0,LSA=1;
				break;
			case(2):
				LSC=0,LSB=1,LSA=0;
				break;
			case(3):
				LSC=0,LSB=1,LSA=1;
				break;
			case(4):
				LSC=1,LSB=0,LSA=0;
				break;
			case(5):
				LSC=1,LSB=0,LSA=1;
				break;
			case(6):
				LSC=1,LSB=1,LSA=0;
				break;
			case(7):
				LSC=1,LSB=1,LSA=1;
				break;
		}

}
void main(){
	
	u8 num=0;
	init();
	
	while(1){
	
		P2=smgduan[num];//暂存
		SBUF =P2;  //将数据放入SBUF接收缓冲器

		while(RI==0){}//当SBUF接收缓冲器被填满时,会触发中断RI=1
			
			display(num);//位选
			
			RI=0;
			P0 = SBUF;//段选
			TI=0;
	
			delay(10000);//延迟
			P0=0x00;//消隐
			if(++num>=8)
					num=0;
			
	}
	

}

将P30与P31连上,即模式2下的自收自发的串口通信。
RXD接收端。
TXD发送端。
在这里插入图片描述

5.如何实现两块单片机间的通信

重要的数据:
共阳数码管显示:

u8  display[]={
	0x3f,0x06,0x5b,0x4f,//0123
	0x66,0x6d,0x7d,0x07,//4567
	0x7f,0x6f,0x77,0x7c,//89ab
	0x39,0x5e,0x79,0x71, //cdef
};

共阴数码管显示:

u8 code display[16]={
	0xc0,0xf9,0xa4,	0xb0,  //0  1    2    3
	0x99,0x92,0x82,0xf8,  //4   5  6   7
	0x80,0x90,0x88,0x83,  //8    9    A  B
	0xc6,0xa1,0x86,0x8e   //C  D   E   F
}; 

下面这个两片51单片机通信的案例需要用到两个稍微不同的程序。
发送端也会接收。
接收端也会发送。
原理是

  • 发送端发一个数据N给接收端,接收端显示对应的数字,再发送给发送端一个N+1,发送端再接着显示。接着循环往复。

发送端代码:

#include<reg52.h>
typedef     unsigned  char u8;
typedef unsigned int u16;

u8 index=0;



//位选
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;


//模拟输入的数据
u8  code info[16]={
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};


//共阳数码管显示
u8  display[]={
	0x3f,0x06,0x5b,0x4f,//0123
	0x66,0x6d,0x7d,0x07,//4567
	0x7f,0x6f,0x77,0x7c,//89ab
	0x39,0x5e,0x79,0x71, //cdef
};

void delay(u16 i){
	while(i--){}
}

void init(){
 	
	/*
	SCON  SM0  SM1   SM2  REN  TB8  RB8  TI  RI
	       0    1     0    1    0    0    0   0
	Mode1
	允许接收
	*/
  SCON = 0x50;
  PCON = 0x00;//SMOD=0
	
	/*
	TMOD   GATE  C/T#  M1  M0  GATE  C/T#  M1  M0  	
			0     0    1    0    0    0    0    0
	Mode2 
	8位重装定时器
	*/	
  TMOD = 0x20;
	
//设定初值
  TH1 = 0xf4;
  TL1 =0xf4;

  TR1 = 1;//启动Timer1
  
	/*
	IE     EA  --  ET2   ES   ET1   EX1  ET0  EX0
		   1   0    0    1     0     0    0    0
	
	*/
	IE=0x90;


//位选初始化
	LSC = 0,LSB=0,LSA=1;
	
}


void show(){
	
	LSA=1;
		if(index<10){
			P0=display[0];
			delay(1000);
			P0=0;
			LSA=0;
			P0=display[index];
		}
		else{
			P0=display[1];
			delay(1000);
			P0=0;
			LSA=0;
			P0=display[index-10];
		}
		delay(1000);
		P0=0;
	
}
void main() {
	
	u8 a=0;
		
  
	while(1){
			//发送端必须重复初始化
		 init();
		
		
		 index=info[a]&0x0f;// 0000 1111  去除高4位
		 SBUF=index;
		if(++a>=16)
					a=0;
		
		show();
		delay(10000);
	}
	
    
}
	
void MyInterruptFun() interrupt 4{
	
 if(TI)  {//发送完成
     TI=0;
    	index=SBUF;	
  }
 else if(RI){ //接收完成
			RI=0;		
 }
	
	show();
 
 }

接收端代码:

#include<reg52.h>
typedef unsigned  char u8;
typedef unsigned int u16;

u8 index;

//位选
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;



//¹共阳数码管显示
u8  display[]={
	0x3f,0x06,0x5b,0x4f,//0123
	0x66,0x6d,0x7d,0x07,//4567
	0x7f,0x6f,0x77,0x7c,//89ab
	0x39,0x5e,0x79,0x71, //cdef
};



void delay(u16 i){
	while(i--){}
}

void init(){
	
	/*
	SCON  SM0  SM1   SM2  REN  TB8  RB8   TI   RI
	       0    1     0    1    0    0    0    0
	Mode1
	运行接受
	*/
  SCON = 0x50;
  PCON = 0x00;//SMOD=0
	
	/*
	TMOD   GATE  C/T#  M1  M0  GATE  C/T#  M1  M0  	
		    0     0    1   0    0     0    0   0
	Mode2 
	8位重装定时器
	*/	
  TMOD = 0x20;
	
	//设定初值
  TH1 = 0xf4;
  TL1 =0xf4;

  TR1 = 1;//启动定时器1
	
	/*
	IE     EA  --  ET2   ES    ET1   EX1  ET0  EX0
		   1    0   0     1     0     0    0    0
	打开总中断 EA=1
	打开串口中断 ES=1
	*/
	IE=0x90;
	
	  LSC=0,LSB=0,LSA=1;
}

void show(){
		LSA=1;
		if(index<10){
			P0=display[0];
			delay(1000);
			P0=0x00;
			LSA=0;
			P0=display[index];
		}
		else{
			P0=display[1];
			delay(1000);
			P0=0x00;
			LSA=0;
			P0=display[index-10];
		}
			delay(1000);
			P0=0x00;

}
void main() {
	
	//接收端可以不重复初始化
   init();
	
	while(1){	//避免重复初始化,消耗时间
		show();
		delay(10000);
	
	}
    
 }

 //中断号4 串口中断TI=1 or RI=1
 void MyInterruptFun() interrupt 4{
	 
	
	if(RI){//接收完成
		RI=0;
			SBUF=index+1;

	}
	else if(TI){//发送完成
		TI=0;	
		index=SBUF;
	
	}
	
	show();
		
 }

分别把两个程序烧录入两个单片机后,还需要用排线把对应的TXD预RXD连起来--------一般P30 = RXD, P31 = TXD ,我的是相反的-----------还有电源和接地,也可以用两根数据线。

下面是显示结果。
在这里插入图片描述

参考:教程《单片机原理及应用》、 《普中单片机开发教程》(注:PPT错误太多了)

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/88083382
C51