zoukankan      html  css  js  c++  java
  • 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 }

    作业:

  • 相关阅读:
    875. 家的范围
    Codeforces 1260D A Game with Traps(二分查找)
    Codeforces 1260D A Game with Traps(二分查找)
    Codeforces 1260C Infinite Fence(扩展欧几里得有解的条件)
    Codeforces 1260C Infinite Fence(扩展欧几里得有解的条件)
    Codeforces 1260B Obtain Two Zeroes
    Codeforces 1260B Obtain Two Zeroes
    Codeforces1260A Heating
    Codeforces1260A Heating
    HDU 2795 Billboard(线段树查询区间最大值)
  • 原文地址:https://www.cnblogs.com/IceHowe/p/10684322.html
Copyright © 2011-2022 走看看