51单片机学习笔记(郭天祥版)(7)——串行通信
上节课的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 }
作业: