上节课的AD和DA不属于单片机自身的知识,属于单片机的外围器件,不光单片机,DSPU、FPGA、嵌入式系统,AD和DA都是外围设备。掌握的不是很好也没事,用的时候在搞明白原理,要使用的AD、DA说明搞清楚,每一种AD和DA操作方法都是不一样的,并不是你写一个ADc0804的程序拿的别的芯片也能用。

接下来讲串口通信,其中计算机串口通信和单片机串口通信各涉及到一半,串口是单片机比较重要的知识。

并行通信就像操作DA芯片的时候,由单片机作为发送设备,DA芯片作为接受设备,单片机P0一下发送8位数据,DA接收,这就是并行通信,也就是说DA芯片时并行的,也有串行的。在发送设备发送前要先询问一下接收设备有没有准备好,接送设备准备好了会给发送设备一个应答信号,告诉准备好了,可以发送了,这时发送设备才能发。不然发送设备发送后,接收设备有没有接收到都不知道,所以每发一次要问一下。

 电话线就是两根线,电话上网,宽带上网:8根线并不是一下传8位数据,是2对差分信号传输(TXD1发送、RXD1接收、TXD0、RXD0,单片机引脚的P3.0是RXD,P3.1是TXD),还有电源线,地线,询问线,应答线,宽带一共这8根线。电话线上网,也是先有宽带接出来,通过调制解调器,解调完后发到电话线上,网络另一端又通过调制解调器(数字信号(计算机上为数字信号)调制为模拟信号),电话线不能传数字信号,只能传模拟信号(例如声音,根据声音大小,电话线上电流变化),电话的另一端用DTMT编码解调出来,电话线上网先把数字信号通过调制解调器(调制器)调制成电流信号,电流的强弱是发送的0或1,电话线上网很慢,最大在56k(mao),另一端通过解调器把模拟信号解调为数字信号传输给计算机。

发送设备和接收设备各有一个时钟,发一个数据,下一个就会变化,这个变化的过程就是由时钟控制的,单片机的时钟就是我们的晶振,进行了12个晶振周期(震荡周期),即1个机器周期,一个机器周期发一个数,每过一个周期,串行发送的时候IO口电平就会变化。

异步通信,发一帧数据(上图是10位,多少由自己定),只要发送设备和接收设备协调一致就可以,发送多少就接收多少,根据发送的数据再组装一下,变成你需要的数据。

空闲是指的每帧数据间的间隔,然后是起始位,先发送起始位,接收设备才知道下面开始传输数据了,否则直接发数据不知道是数据还是什么,所以每一帧先发起始位,起始位是低电平,然后是数据位,从低位(LSB最低有效位)到高位,最后跟一位校验位(表示你中间的数据有没有传错,根据校验位可以知道是否传错,传错了会告诉发送端重新发送这帧数据。如果没有校验位,那么8位可能都是数据位,不需要校验位。也有要校验位的,数据是6位,7位....随意。但是单片机有几种固定的格式。)然后跟一位停止位,以高电平结束,然后又是空闲,空闲完又发下一位数据,方法同上。

这就是异步通讯数据格式,适合所有的位控制器:单片机,DSP......


 

然后再看同步通信(单片机的串口是同步通信,单片机内有一个功能强的全双工异步串行通信

用的很少,了解就行。

接收和发送是由一个时钟控制的。

外同步:有一个时钟信号线,计算机甲每一个时钟发一位,计算机乙收到一个时钟收一位。


以前老式计算机都是串口上网,串口线,连接调制解调器,上网登录别人的服务器,服务器有解调器,解调器完后转换为rs232信号(计算机串口电平,第一节课讲的),转换成串口电平再进入计算机内部。

我们不用校验,但是要会。

单片机中可以设置是奇校验还是偶校验。

例如这里的奇校验,如果传输时第三位丢了,受到了干扰,那么就成了偶数,所以帧数据传错了,这帧数据就不能要,一帧一帧传输数据时,每收到一帧要给发送设备一个响应,没有错误再发送第二帧,如果这帧数据出错了,那么要告诉发送器,这帧有错误重发一次,发送器收到重发的信号要重发这一帧。丢一位的概率是最大的,丢两位三位甚至更多的概率很小很小。

代码和校验用的还是必须较多的。

校验后才把各个有用的信息分配到各个信号去。

研究生会讲到循环冗余校验。

以前的是25针的,现在是9针,25其中只用了几个。

阳头,阴头(公头,母头):针是公头,另一种孔的就是母头。

串口这里还有一种线,两端都是母头,线内部是交叉的,2和3是对调的,从单片机串口座(地线,发送,接收三根线)的第二口出来后,对调后连到计算机的第三口上。

平行线:2对应的2,3对应的3。

这25针不是电脑后面用的并口,那个是并行口,内部有八根数据线,18~25是并线,中间有一些信号询问、应答的。

我们现在用的串口通信只有TXD、RXD、SGND。

连mao要用RTS、CTS。

最早的时候用的是MC1488和MC1499,1488是把TTL电平转换为RS232电平,1499相反。现在用的是MC232,内部相当于这两个集成起来了

长的时候信息容易丢失,而且一打雷,所有板子全烧坏,静电进入MC232。解决方法可以把232转换成485电平。或者换成无线发射模块。

232改进后变成了422A,是差分信号。了解就好。

缓冲器SBUF也可以叫寄存器SBUF。

A表示一个任意的寄存器,SBUF是串行口寄存器TXD(translator)是P3.1,RXD(receive)是P3.0,这两个口是用来串口通信的。

T1(定时器1)的溢出率决定了发送控制器的发送速度,SMOD是用于发送速率倍频的,发送时T1每溢出一次,发送一位。每一个字节发送完后,有一个PI,PI是SCON寄存器中的一位,这一位会申请中断,每接收完一个字节,也要申请一次中断。接受完后通过移位寄存器接收到一个字节后送到SBUF然后产生中断,通知单片机从SBUF中把数据取走。发送和接收都是用的SBUF。RXD接收,接受完通过移位寄存器发送到SBUF,用一个变量把它取走,a=SBUF。发送时就a送给SBUF,SBUF=a,单片机在发送接收时就这一句话。串口通信非常简单,关键是把中断和波特率搞明白。

SCON是98H,也可以被8整除所以可以位操作。就像TMOD和TCON一样,那两个是定时器的,这个是串行口的。

方式0是移位寄存器,数据一位一位的出去。fosc(oscillation)是晶振频率。

方式1中8位数据,剩下的分别是起始位和结束为,波特率可变化,可由软件设置。

波特率就是传输的速率。

我们重点讲方式1。用的最多,其中0,2,3很少用(郭天祥写了2、3年还没用过)。

所以SM0=0,SM1=1。

RB8是用到校验的时候,RB8是收到的校验位,校验位放到这里。

所以我们SM2=0,REN=1。

TB8=0,不设置就行,单片机上电后,特殊功能寄存器默认为0,不用RB8设为0就可以。

这两位比较重要。发完8位数据位也可以不进入中断,检测到发送完直接TI=0就可以,然后让第二次进行发送。这两个是由硬件置1,所以一开始不用管。

是因为SCON设置为0101 0000就行。

PCON(power control)是单片机的电源控制位,里面有一位可以设置让单片机进入休眠状态(休眠时只能检测到中断和复位电路),进入掉电状态,低功耗状态......

暂时不用SMOD,所以为0就好。

下面0不讲不用看

SBUF是8位寄存器,只能存8位数据。

TI是在停止位开始时为1的。

D0、D1...是数据的高低电平。

RI在停止位中间置1。

位采样脉冲,是把机器周期16分频,因为采样需要采样很多次。

负跳变:高电平变为低电平,这里就是起始位。

16倍更加精准的判断出发送的0还是1,1次采样有可能出错。

下面2、3不用看

下面的波特率计算一定要掌握。

T1满了之后就溢出了。所以串行通信与定时器1有直接关系。

 什么是8位自动重装,意思是当溢出时,进入中断函数,不需要再自己设置初值,而是把TH的值赋值给Tl。

T1溢出率就是溢出速率,即1s溢出多少次。

以前为了方便都是用12MHz,实际是11.0592MHz,但是串口这里必须用11.0592。

计算机串口通信,打开下载软件,可以看到这里的波特率设置都是11.0592的整数倍。

用12MHz这里TH1也是0xFD,但是误差是8.51%,因为12M除完有小数,如果用12M的晶振串口通信时,传着传着数据就错了,晶振采样率就采错位置了,因为有误差。SMOD=1时就是倍频了,这时TH1=0xFA。

其实9600倍频和4800波特率正常情况下其实是一样的。4800倍频和9600不倍频是一样的。

波特率会设置了,接下来是串口工作之前进行的一些初始化:

下面的5张图仅需了解即可。


 接下来写程序控制单片机和计算机传输数据

首先要能让单片机收到数据,利用计算机发,用串口调试助手或者stc-isp,但是他们两个不能在同时运行时同时使用一个串口。

红灯亮了表示这个端口打开了,默认是串口1,如果此时下载程序也是用COM1口的话,是下载不进去的,它会提示串口不存在。

如果用的是同一个,下载程序时,你可以先把串口助手那里点击关闭串口,然后再下载就正常了,你们大部分机器用的COM2口,那么串口实验时,串口助手也要选择COM2。

上位机通过串口调试助手随便发送一个数,如果单片机收到就先点亮一个发光二极管。这就是调程序的过程,我们得先看他能不能收到数,然后再看收的什么数或对数做什么样的判断。所以我们收到数就先让发光二极管点亮,收不到就不亮。

main函数里首先要先检测有没有数据,不停地检测,所以要放在while(1)中,收到一帧数据,当到停止位的时候,SCON中的RI为置位,那么就有两种方法检测,1:查询RI是否置位,如果置位说明收到了数据,收到了就把数据存起来;2:RI置位后,会向单片机申请中断,那么就写一个中断函数,因为中断是自动进入的。第一种方法叫做查询法,第二种叫中断法。

先写一个查询法

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     while(1)
 9     {
10         while(!RI);//如果没收到数据,那么RI=0,就一直在这个循环,如果收到数据就执行下一句。
11         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
12         P1=0xfe;//收到数据就点亮第一个灯。
13         //现在我们波特率和...都没设置,我们就是看能不能收到东西
14     }
15 }

发送数据时,选择16进制就是发送的16进制数,如果不点就是以asc码形式发送

这个代码失败,未收到。

其实我们没有设置SCON寄存器

先设置一个REN=1看看

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     REN=1;
 9     while(1)
10     {
11         while(!RI);//如果没收到数据,那么RI=0,就一直在这个循环,如果收到数据就执行下一句。
12         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
13         P1=0xfe;//收到数据就点亮第一个灯。
14         //现在我们波特率和...都没设置,我们就是看能不能收到东西
15     }
16 }

会发现下载完后没发送就直接亮了。

是因为没有设置SM0、SM1,没设置时都是0,那就是方式0,同步移位寄存器,用于扩展IO口用的,这种状态下,会始终收1,因为数据口如果没法出去它就一直连着P3.0,因为是同步移位,一个时钟收一位。(好吧原因解释我也不懂,不懂可以回去前面看,前面介绍过方式0,不过无所谓了,反正不用)现在我们再设置上SM。

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     REN=1;
 9     SM0=0;
10     SM1=1;
11     while(1)
12     {
13         while(!RI);//如果没收到数据,那么RI=0,就一直在这个循环,如果收到数据就执行下一句。
14         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
15         P1=0xfe;//收到数据就点亮第一个灯。
16         //现在我们波特率和...都没设置,我们就是看能不能收到东西
17     }
18 }

发现不亮了,发送数据也不亮了....这郭天祥....原因是没设置波特率,没有波特率无法检测数,设置波特率时只有把定时器设置为方式2,打开定时器,然后波特率检测数据的信号才能产生。

接下里郭天祥又只打开了定时器,未设置波特率,看看情况

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     REN=1;
 9     SM0=0;
10     SM1=1;
11     TR1=1;
12     while(1)
13     {
14         while(!RI);//如果没收到数据,那么RI=0,就一直在这个循环,如果收到数据就执行下一句。
15         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
16         P1=0xfe;//收到数据就点亮第一个灯。
17         //现在我们波特率和...都没设置,我们就是看能不能收到东西
18     }
19 }

恩....没发送数据就亮了

再设置波特率

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     TMOD=0x20;//设置定时器1位工作方式2
 9     TH1=0xfd;//9600的波特率
10     TL1=0xfd;
11     TR1=1;
12     REN=1;
13     SM0=0;
14     SM1=1;
15     while(1)
16     {
17         while(!RI);//如果没收到数据,那么RI=0,就一直在这个循环,如果收到数据就执行下一句。
18         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
19         P1=0xfe;//收到数据就点亮第一个灯。
20     }
21 }

晕还是错的,还是亮....果然还是清翔的好,不过也学了点东西。

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     TMOD=0x20;//设置定时器1位工作方式2
 9     TH1=0xfd;//9600的波特率
10     TL1=0xfd;
11     TR1=1;
12     REN=1;
13     SM0=0;
14     SM1=1;
15     while(1)
16     {
17         if(RI==1)
18         {
19         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
20         P1=SBUF;
21         }
22     }
23 }

这样就对了,其实数据要从SBUF接收,郭天祥竟然还以为改成if就对了,其实while也是对的,看下面的代码就是了

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     TMOD=0x20;//设置定时器1位工作方式2
 9     TH1=0xfd;//9600的波特率
10     TL1=0xfd;
11     TR1=1;
12     REN=1;
13     SM0=0;
14     SM1=1;
15     while(1)
16     {
17         while(!RI);
18         {
19         RI=0;//收到数据必须把RI清零,RI必须由软件清零,不清零没法进行下一次接收数据。
20         P1=SBUF;
21         }
22     }
23 }

接下来学习中断方法:

串口和定时器很相似,串口和单片机是独立的两个部分,串口收到数通知单片机取数,让单片机给定时器重新装置,

 

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 void main()
 7 {
 8     TMOD=0x20;//设置定时器1位工作方式2
 9     TH1=0xfd;//9600的波特率
10     TL1=0xfd;
11     TR1=1;
12     REN=1;
13     SM0=0;
14     SM1=1;
15     EA=1;
16     ES=1;
17     while(1)
18     {
19     }
20 }
21 
22 void ser() interrupt 4
23 {
24     RI=0;
25     P1=SBUF;
26 }

刚下载完可能会亮几个led,因为刚下完程序有几个值,单片机和计算机在下载程序的时候有过通信

写一个把发到的数再发给电脑的程序

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 uchar flag=0;
 7 uchar a;
 8 void main()
 9 {
10     TMOD=0x20;//设置定时器1位工作方式2
11     TH1=0xfd;//9600的波特率
12     TL1=0xfd;
13     TR1=1;
14     REN=1;
15     SM0=0;
16     SM1=1;
17     EA=1;
18     ES=1;
19     while(1)
20     {
21         if(flag==1)
22         {
23             flag=0;
24             SBUF=a;//注意这里SBUF是两个,虽然地址一个。
25         }
26     }
27 }
28 
29 void ser() interrupt 4
30 {
31     RI=0;
32     P1=SBUF;
33     a=SBUF;
34     flag=1;
35 }

又是有错....开启串口一直接受,明明没发送。这郭天祥....后悔看了

如果进入一次串行中断,flag=1,然后进入if,SBUF=a,TI会被置1,然后又会触发中断,又进入中断函数,就陷入循环了。

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 uchar flag=0;
 7 uchar a;
 8 void main()
 9 {
10     TMOD=0x20;//设置定时器1位工作方式2
11     TH1=0xfd;//9600的波特率
12     TL1=0xfd;
13     TR1=1;
14     REN=1;
15     SM0=0;
16     SM1=1;
17     EA=1;
18     ES=1;
19     while(1)
20     {
21         if(flag==1)
22         {
23             ES=0;
24             flag=0;
25             SBUF=a;//注意这里SBUF是两个,虽然地址一个。
26             while(!TI);//如果没有给计算机发完数据,TI就是0,那么就一直循环while,发完TI=1,就执行下面了
27             TI=0;
28             ES=1;
29         }
30     }
31 }
32 
33 void ser() interrupt 4
34 {
35     RI=0;
36     P1=SBUF;
37     a=SBUF;
38     flag=1;
39 }

16进制发送数据要16进制看,文本发送要文本看。

也可以发送字符串给电脑,例如SBUF='b',当然要用文本格式看。

想要发送任意数字给计算机,就要分开发,比如123

 1 #include<reg51.h>
 2 
 3 #define uchar unsigned char
 4 #define uint unsigned int
 5 
 6 uchar flag=0;
 7 uchar a;
 8 void main()
 9 {
10     TMOD=0x20;//设置定时器1位工作方式2
11     TH1=0xfd;//9600的波特率
12     TL1=0xfd;
13     TR1=1;
14     REN=1;
15     SM0=0;
16     SM1=1;
17     EA=1;
18     ES=1;
19     while(1)
20     {
21         if(flag==1)
22         {
23             ES=0;
24             flag=0;
25             SBUF='1';//注意这里SBUF是两个,虽然地址一个。
26             while(!TI);//如果没有给计算机发完数据,TI就是0,那么就一直循环while,发完TI=1,就执行下面了
27             TI=0;
28             SBUF='2';
29             while(!TI);
30             TI=0;
31             SBUF='3';
32             while(!TI);
33             TI=0;
34             ES=1;
35         }
36     }
37 }
38 
39 void ser() interrupt 4
40 {
41     RI=0;
42     P1=SBUF;
43     a=SBUF;
44     flag=1;
45 }

 

作业: