**
前言
**
这一部分也是相当重要的基础内容。
虽然目前的学习过程中,每个程序的大小都不大,但是每个程序都让自己明白了一点知识,这已经足够了。
上一篇单片机,我想用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
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
- 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个振荡周期
指令周期 >>> 完成一条指令所占用的全部时间,以机器周期为单位
关于频率与时间的换算:
对于12MHZ的晶振的单片机来说,其机器周期为 1us (上一篇的延时函数_nop_()就是延时一个机器周期)
需要注意的一些有:
- 51单片机有两组定时、计数器----------- 一组定时、计数器两个字节
- 定时、计数器和单片机CPU是相互独立的
- 有了定时、计数器后,可以增加单片机效率,一些简单的重复加一的工作可以交给定时、计数器来完成,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定时闪烁
如果使用定时器?
- 对TMOD赋值,以确定T0和T1的工作方式
- 计算初值,将其写入TH0、TL0或TH1、TL1。--------------------如果像开头假设那样,CPU机器周期是1us的话,那么初值应该是 。意思是要计数1000个数,初值 = 65535 - 1000 + 1= FC18H = 。等计数了N个时就会出现溢出。
- 中断方式,则对EA赋值,开放定时器中断。
- 使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- 单工 ------- 单向通道,
- 2- 半双工 ------- 双向通道,分时进行
- 3- 双工 -------- 双向通道,同时进行 。
在51单片机中提供了全双工的万能异步串行端口(Universal Asynchronous Receiver-Transmitter,UART)。其有4种工作模式:
- Mode0 > 由51单片机主导,其比特率为8051系统时钟脉冲频率(FOSC)的十二分之一
- Mode1 > 此模式为可变比特率的串行口,此方式的比特率是
- Mode2 > 此方式的比特率是
- Mode3 > 和Mode1一样。
下面关于比特率和串行口工作模式再解释两件事:
- SMOD是SFR特殊寄存器上的PCON(87H)的第7位。
功率控制寄存器PCON
PCON(SFR特殊寄存器中的位置,87H)
PCON.7 SMOD 比特率翻倍标志位 SMOD=1,bps翻倍。复位时,SMOD=0
2.T1溢出率------定时、计数器溢出率是时钟频率与达到溢出所需的周期数之比。
比如下面这个式子
由于定时、计数器只用了高位寄存器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)
多重处理器通信启用位
- 处于Mode0时,SM2=0
- 处于Mode1时,如果SM2=1,且收到了有效的停止位,则RI = 1(引发RI中断),否则RI=0
- 处于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)
- 处于Mode0时,无作用
- 处于Mode1时,如果SM2=0,则本位为停止位。
- 处于Mode2或Mode3时,本位是第9个接收位。
SCON.1(TI)
传送中断标志位,当中断结束时,本位不会恢复为0.
- 处于Mode0时,如果完成传送第8位,则本位自动设定为1,并产生TI中断
- 其他模式下,如果完成传送停止位,则本位自动设定为1,并产生TI中断
SCON.0(RI)
接收中断标志位,当中断结束时,本位不会恢复为0.
- 处于Mode0时,如果完成传送第8位,则本位自动设定为1,并产生RI中断
- 其他模式下,如果完成传送停止位,则本位自动设定为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错误太多了)