zoukankan      html  css  js  c++  java
  • (转)串行通信与单片机串行口

    原文:http://chenqp.hg.blog.163.com/blog/static/5402633820088493025120/

    随着多微机系统的广泛应用和计算机网络技术的普及,计算机的通信功能愈来愈显得重要。计算机通信是指计算机与外部设备或计算机与计算机之间的信息交换。通信有并行通信和串行通信两种方式。在多微机系统以及现代测控系统中信息的交换多采用串行通信方式。

    一    串行通信

    通信时数据是一位接一位顺序传送的称为串行通信。串行通信可以通过串行口来实现。根据信息传送的方向,串行通信可以分为单工、半双工和全双工三种方式。根据信息的格式又可以分为异步通信和同步通信两种方式。

    注解:单工方式只有一根数据线,信息只能单向传送;半双工方式也只有一根数据线,但是信息可以分时双向传送;全双工方式有两根数据线,在同一个时刻能够实现数据双向传送。异步通信应用最多。

    1.1  异步通信数据格式

    异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方的收发协调,要求发送和接收设备的时钟尽可能一致。异步通信的数据格式如图1描述:

     图1:异步通讯数据格式

    依据上图所示,传送前,线路处于空闲状态,此时约定线路为高电平。在传送时,加一个低电平的起始位,然后是数据位,数据位可以是5~8位,低位在前,高位在后,数据位可以带一个奇偶校验位,最后是停止位,停止位用高电平表示,它可以是1位、1位半或2位。异步通信有以下特点:

    • 以字符(构成的帧)为单位进行传输。
    • 字符与字符之间的间隙(时间间隔)是任意的,但每个字符中的各位是以固定的时间传送的。
    • 一次只传送一个字符,因而一次传送的位数比较少,对发送时钟和接收时钟的要求相对不高。
    •  每个字符要附加2~3位用于起止位,各帧之间还有间隔,因此传输效率不高。

     1.2  异步通信参数设置

        主要有波特率、数据位、停止位和奇偶校验。波特率是串行通信中的一个重要概念,它用于衡量串行通信速度快慢。波特率指每秒种传送的二进制位数。数据位表示传输过程中实际包含的传送的数据位数,数据位可以是5~8位。停止位用于信号传送的过程中确认每个数据包的结尾,通常是1个、1.5或者2个停止位。奇偶校验是串行通讯中使用的一种简单的错误检验手段。奇偶校验有时也不用设置。

     附:波特率计算

    波特率的单位是bps。在异步通信中,传输速度往往又可用每秒传送多少个字节来表示(Bps)。

    波特率(bps)=一个字符的二进制位数×字符/秒(Bps)例:每秒传送200个字符,每个字符1个起始位、八个数据位、1个校验位和1个停止位。则波特率为2200bps。在异步串行通信中,波特率一般为50~9600bps。

    在异步通信中,CPU与外设之间必须有两项规定,即字符格式和波特率。字符格式的规定是双方能够在对同一种0和1的串理解成同一种意义。原则上字符格式能由通信的双方自由制定,但从通用、方便的角度出发,一般还是使用一些标准为好,如采用ASCII标准。

    二  MCS—51单片机串行口

    MCS—51具有一个全双工的串行异步通信接口,可以同时发送、接收数据。发送、接收数据可通过查询或中断方式处理,使用十分灵活,能方便地与其他计算机或串行传送信息的外部设备(如串行打印机、CRT终端)实现双机、多机通信。

    2.1  特殊功能寄存器

    单片机对串行口的管理是通过内部相应的寄存器来实现的。从用户使用的角度,单片机串行口由三个特殊功能寄存器组成:发送数据寄存器和接收数据寄存器合起来用一个特殊功能寄存器SBUF(串行口数据寄存器),串行口控制寄存器SCON和电源控制寄存器PCON。

    2.11  SBUF

    包括发送寄存器和接收寄存器。它们有相同名字和地址空间,但不会出现冲突,因为它们两个一个只能被CPU读出数据,一个只能被CPU写入数据。

    2.12  SCON

    它用于定义串行口的工作方式及实施接收和发送控制,可以进行位寻址,位地址为98H~9FH。它的格式如下表2.1所示。

     SCON  D7  D6  D5  D4  D3  D2  D1  D0
     98H  SM0  SM1  SM2  REN  TB8  RB8  TI  RI
     初始赋值  0  1  0  1  0  0  0  0

    其中:SM0、SM1:串行口工作方式选择位。其定义如下表:表中fosc为晶振频率。

     SM0、SM1  工作方式  功能 波特率 
     00  方式0  移位寄存器方式  fosc/12
     01  方式1  8位异步通信方式  可变
     10  方式2  9位异步通信方式  fosc/32或fosc/64
     11  方式3  9位异步通信方式  可变

     1、SM2:多机通讯控制位。在方式0时,SM2一定要等于0。在方式1中,当(SM2)=1则只有接收到有效停止位时,RI才置1。在方式2或方式3当(SM2)=1且接收到的第九位数据RB8=0时,RI才置1。

    2、REN:接收允许控制位。由软件置位以允许接收,又由软件清0来禁止接收。

    3、TB8:是要发送数据的第9位。在方式2或方式3中,要发送的第9位数据,根据需要由软件置1或清0。例如,可约定作为奇偶校验位,或在多机通讯中作为区别地址帧或数据帧的标志位。

    4、RB8:接收到的数据的第9位。在方式0中不使用RB8。在方式1中,若(SM2)=0,RB8为接收到的停止位。在方式2或方式3中,RB8为接收到的第9位数据。

    5、TI:发送中断标志。在方式0中,第8位发送结束时,由硬件置位。在其它方式的发送停止位前,由硬件置位。TI置位既表示一帧信息发送结束,同时也是申请中断,可根据需要,用软件查询的方法获得数据已发送完毕的信息,或用中断的方式来发送下一个数据。TI必须用软件清0。

    6、RI:接收中断标志位。在方式0,当接收完第8位数据后,由硬件置位。在其它方式中,在接收到停止位的中间时刻由硬件置位(例外情况见于SM2的说明)。RI置位表示一帧数据接收完毕,可用查询的方法获知或者用中断的方法获知。RI也必须用软件清0。

    注解:在实际应用中,方式1应用最多。在接收数据前,必须置REN为1。TI、RI在发送数据、接收数据后硬件自动将它们置1,最后必须用软件将它们清0。归纳起来,对于方式1,串口初始化时,给串行口控制寄存器SCON赋初值,具体如表2.1所示。

    2.13  PCON

    PCON是为了在CHMOS的80C51单片机上实现电源控制而附加的。其中最高位是SMOD(D7),称为波特率加倍位。当SMOD位为1时,则串行口方式1、2、3的波特率加倍。PCON不能进行位寻址,只能按字节方式访问。

    2.2  串行口工作方式1

         方式1为波特率可变的10位异步通信接口方式。发送或接收一帧信息,包括1位起始位(0),8位数据位(低位在前)和1位停止位(1)。

    输出:当CPU执行一条指令将数据写入发送缓冲SBUF时,就启动发送。串行数据从TXD引脚输出,发送完一帧数据后,就由硬件置位TI。

    输入:在(REN=1)时,串行口采样RXD引脚,当采样到1~0的跳变时,确认时开始位0,就开始接收一帧数据。只有当(RI=0)且停止位为1或者(SM2=0)时,停止位才进入RB8,8位数据才能进入接收寄存器,并由硬件置位中断标志RI;否则信息丢失。所以在方式1接收时,应先用软件清0RI和SM2标志。

    2.2.1  方式1波特率选择

    波特率可变,由定时/计数器T1的溢出率和电源控制寄存器PCON中的SMOD位决定。

    即:波特率=2SMOD×(T1的溢出率)/32……………………①

    因此在方式1下,需要对定时/计数器T1进行初始化。

    由:T1溢出率=T1计数率/产生溢出所需的周期数

    式中T1计数率取决于它工作在定时器状态还是计数器状态。当工作于定时器状态时,T1计数率为fosc/12;当工作于计数器状态时,T1计数率为外部输入频率,此频率应小于fosc/24。产生溢出所需周期与定时器T1的工作方式、T1的预置值有关。

    定时器T1工作于方式0:溢出所需周期数=8192-x;

    定时器T1工作于方式1:溢出所需周期数=65536-x;

    定时器T1工作于方式2:溢出所需周期数=256-x。

    因为方式2为自动重装入初值的8位定时器/计数器模式,所以用它来做波特率发生器最恰当。

    综上所述,T1溢出率=fosc/(12×(256-初值))…………②

    结合①②可得:

    T1的初值=256-fosc×2SMOD/(12×波特率×32)。

    注解:当时钟频率选用11.0592MHZ时,易获得标准的波特率,所以很多单片机系统选用这个看起来“怪”的晶振就是这个道理。除了按上述式子手工计算外,也可以使用51串口通信计算器之类小工具。这就大大简化的定时器初值的运算。我使用了一个小工具down_11.exe,它的界面如下图所示。只要在晶振频率和波特率那两个栏中选择相应的参数,就能迅速获得定时器初值。

     附录:定时/计数器T1工作方式寄存器TMOD。其格式如下:

    其中,低四位用于T0,高四位用于T1。M1M0:工作方式设置位。M1M0=10,为工作方式2,即为8位自动重装定时/计数器。

     

    三  计算机与单片机通信

     RS-232是美国电子工业协会正式公布的串行总线标准,也是目前最常用的串行接口标准,用来实现计算机与计算机之间、计算机与外设之间的数据通信。单片机和电脑之间可以方便地通过串行接口进行通信。进行串行通信时一定要注意电平的转换。电脑的串口是RS232电平(高-12V 低+12V)的,而单片机的串口是TTL电平(高+5V 低0V)的,两者之间必须有一个电平转换电路。专用芯片MAX232可以实现此功能。虽然也可以用几个三极管进行模拟转换,但是还是用专用芯片更简单可靠。

    注解:RS-232串行接口总线适用于:设备之间的通信距离不大于15m,传输速率最大为20kBps。RS-232协议以-5V--15V表示逻辑1;以+5V-+15V表示逻辑0。一个完整的RS-232接口有22根线,采用标准的25芯插头座。目前常见简化的9芯插头座,其对应的串口数为9针。各针的定义如下表所示:

       9针串口  
     针号  功能说明  缩写
     1  数据载波检测  DCD
     2  接收数据  RXD
     3  发送数据  TXD
     4  数据终端准备  DTR
     5  信号地  GND
     6  数据设备准备好  DSR
     7  请求发送  RTS
     8  清除发送  CTS
     9  振铃指示  DELL

        实际应用中,常采用三线制连接串口,也就是说电脑的9针串口中只连接其中的3根线:第5脚的GND、第2脚的RXD、第3脚的TXD。这是最简单的连接方法,但是对我们来说已经足够使用了。

    在做实验时,需要在PC上进行信息传送。这个过程用户可以用VB6.0、Labview等语言设计一个人机界面,也可以使用Windows自带的超级终端,还有一种方法是上网下载一个小巧的串口调试助手软件。这里,笔者使用了一个串口调试助手软件,其软件界面如图2所示:先要在“通信设置”设置串口号、波特率、数据位等参数;打开串口(如果断开的话),然后在“发送区”里输入要发送的数据,单击手动发送就将数据发送出去了。注意,如果选中‘十六进制发送’那么发送的数据是十六进制的,必须输入两位数据(写入的十六进制符之间要间隔开,如aa间隔ff间隔bb)。如果没有选中,则发送的是ASCLL码。

      图2   串口调试助手软件界面

      

    四  串行口的编程以及应用

    4.1  串行口的初始化编程

    串行口工作之前,应对其进行初始化,主要是设置产生波特率的定时器1、串行口控制和中断控制。具体步骤如下:

    • 确定T1的工作方式(编程TMOD寄存器);
    • 计算T1的初值,装载TH1、TL1;
    • 启动T1(编程TCON中的TR1位);
    •  确定串行口控制(编程SCON寄存器);
    • 串行口在中断方式工作时,要进行中断设置(编程IEIP寄存器)。

    相应程序代码:

    void initial()    //串口初始化

    {   TMOD=0x20;    //定时器1方式2

        PCON=0x00;    //SMOD=0

        TH1=0xfd;

        TL1=0xfd;    //定时器1初值,波特率为9600bps

        TR1=1;       //    启动定时器1

        SCON=0x50;   //串口工作方式1并允许接收控制位

        EA=1;        //开cpu中断

    ES=1;   //开串行中断}

    4.2 应用实例

    1.       计算机通过串口发送字符控制单片机上的流水灯亮灭,同时单片机把收到的信息重新返回给计算机。假设流水灯与单片机的P0相连。

    程序:(查询方式)

    #include<reg52.h>

    #define uchar unsigned char

    uchar num;

    void initial()    //串口初始化

    {

        TMOD=0x20; //定时器1方式2

        PCON=0x00; //SMOD=0

        TH1=0xfd;

        TL1=0xfd;  // 定时器1初值,波特率为9600bps

        TR1=1;      // 启动定时器1

        SCON=0x50;    //串口工作方式1并允许接收控制位 

    }

    void main()

    {

        initial();

        while(1)

        {

            while(RI==0);

            RI=0;

            num=SBUF;

            P0=num;

            SBUF=num;

            while(TI==0);

            TI=0;

    }

    }

    (中断方式)

    #include<reg52.h>

    #define uchar unsigned char

    uchar num,a;

    void initial()    //串口初始化

    {

        TMOD=0x20; //定时器1方式2

        PCON=0x00; //SMOD=0

        TH1=0xfd;

        TL1=0xfd;  // 定时器1初值,波特率为9600bps

        TR1=1;      // 启动定时器1

        SCON=0x50;    //串口工作方式1并允许接收控制位

        EA=1;

        ES=1;  

    }

    void main()

    {

        while(1)   

        {

            if(a==1)

            {

                ES=0;

                a=0;

                SBUF=num;

                while(TI==0);

                TI=0;

                ES=1;  

            }

        }

    }

    //中断程序

    void serv() interrupt 4

    {

        RI=0;    //软件清0

        num=SBUF;

        P0=num;

        a=1;

    }

    2       由串口调试助手向单片机发送0FFH,表示上位机需要联机信号,单片机发送0FFH作为应答信号,如果接收到数字1~n,表示相应的功能;这里,如果收到1,则单片机向计算机发送字符'H';如果收到2,则单片机向计算机发送字符'e';如果收到其他的数据,则发送'J'。

    程序:

    #include<reg52.h>

    #define uint unsigned int

    unsigned char num,i;

    void initial()    //串口初始化

    {

        TMOD=0x20; //定时器1方式2

        PCON=0x00; //SMOD=0

        TH1=0xfd;

        TL1=0xfd;  // 定时器1初值,波特率为9600bps

        TR1=1;      // 启动定时器1

        SCON=0x50;    //串口工作方式1并允许接收控制位 

    }

    void main()

    {

        initial();

        do{

            while(RI==0);

            RI=0;

            }while(SBUF!=0XFF);//判断请求

        SBUF=0XFF;

        while(TI==0);

        TI=0;

        while(1)

        {

            while(RI==0);

            RI=0;

            num=SBUF;

            switch(num)

            {

            case '1':

            SBUF='H';

            break;

            case '2':

            SBUF='e';

            break;

            default:

            SBUF='J';

            }

            while(TI==0);

            TI=0;

        }  

    }

    3 PC机发送一个字符给单片机,单片机收到后在个位、十位数码管上进行显示,同时将其回发给PC机。(收到和回发均采用查询方式)

    #include<reg52.h>
    #define uchar  unsigned char
    #define uint   unsigned int
    #define CH451_DIG0 0x0800         /*数码管位0显示*/
    #define CH451_DIG1 0x0900         /*数码管位1显示*/
    sbit ch451_din  = P1^0;
    sbit ch451_clk  = P1^1;
    sbit ch451_load = P1^2;
    uchar code as[]="Receving Data:";
    uchar a=0x30,b,c,i,j;
    void ch451_init(void)
    {
      ch451_din  = 0;           /*先低后高,选择4线输入*/
      ch451_din  = 1;
      ch451_load = 1;
      ch451_clk  = 1;
    }
    void ch451_write(uint command)
    {
      ch451_load=0;                            /*命令开始*/    
      for(i=0;i<12;i++)
      {                                        /*送入12位数据,低位在前,&表按位与*/
        ch451_din=command&1;    
        ch451_clk=0;
        ch451_clk=1;
        command>>=1;                    /*上升沿有效*/
      }
       ch451_load=1;                     /*加载数据*/
    }
    void serial_init()    //串口初始化
    {
        TMOD=0x20; //定时器1方式2
        PCON=0x00; //SMOD=0
        TH1=0xfd;
        TL1=0xfd;  // 定时器1初值,波特率为9600bps
        TR1=1;      // 启动定时器1
        SCON=0x50;    //串口工作方式1并允许接收控制位
    }
    void main(void)
    {
     ch451_init();
     ch451_write(0x401);   //数码管系统设置
     ch451_write(0x580);   //数码管显示设置
     ch451_write(CH451_DIG0|0);
     ch451_write(CH451_DIG1|0);
     serial_init();
     while(1)
     {
      while(RI==0);
      RI=0;
      a=SBUF;
      b=(a-0x30)/10; // 把ASCII转化为(0-9字符),并求模
      c=(a-0x30)%10;  //把ASCII转化为(0-9字符),并求余
      ch451_write(CH451_DIG0|c);
      ch451_write(CH451_DIG1|b);
      j=0;
      while(as[j]!='')
       {
       SBUF=as[j];
       while(!TI);
       TI=0;j++; 
       }
      SBUF=a;while(!TI);TI=0;
     } 
    }

    4 PC机发送控制指令给单片机,单片机收到后即可控制D0-D7这8个发光管的亮、灭,同时收到的指令参数在各位、百位数码管上进行显示。说明:百位数码管显示发光管编号(1-8)个数数码管显示发光管的亮、灭(1代表亮、0代表灭)。

    注意:上位机界面中,需要用户输入控制下位机的指令,如发光管编号的选择、发光管的亮、灭选择等,转换成可以传递的ASCII码控制指令,通过串行数据的发送、接收和处理,实现控制动作。

    对控制指令作如下的规定:“#X(X)” 其中:#为指令起始符,X为发光管编号,(为发光管的亮、灭选择起始符,X为发光管的亮、灭选择,)为指令结束符;

    #include<reg52.h>
    #define uchar  unsigned char
    #define uint   unsigned int
    #define CH451_DIG0 0x0800         /*数码管位0显示*/
    #define CH451_DIG2 0x0a00         /*数码管位2显示*/
    #define ON 1
    #define OFF 0
    sbit ch451_din  = P1^0;
    sbit ch451_clk  = P1^1;
    sbit ch451_load = P1^2;
    sbit D0=P0^0;
    sbit D1=P0^1;
    sbit D2=P0^2;
    sbit D3=P0^3;
    sbit D4=P0^4;
    sbit D5=P0^5;
    sbit D6=P0^6;
    sbit D7=P0^7;
    uchar temp,i;
    uchar a[2];
    uchar cnt;
    bit outflag;
    void ch451_init(void)
    {
      ch451_din  = 0;           /*先低后高,选择4线输入*/
      ch451_din  = 1;
      ch451_load = 1;
      ch451_clk  = 1;
    }
    void ch451_write(uint command)
    {
      ch451_load=0;                            /*命令开始*/    
      for(i=0;i<12;i++)
      {                                        /*送入12位数据,低位在前*,&表按位与,如5&7=5,即:0101^0111=5*/
        ch451_din=command&1;    
        ch451_clk=0;
        ch451_clk=1;
     command>>=1;                    /*上升沿有效*/
      }
     ch451_load=1;                     /*加载数据*/
    }
    void serial_init()    //串口初始化
    {
        TMOD=0x20; //定时器1方式2
        PCON=0x00; //SMOD=0
        TH1=0xfd;
        TL1=0xfd;  // 定时器1初值,波特率为9600bps
        TR1=1;      // 启动定时器1
        SCON=0x50;    //串口工作方式1并允许接收控制位
    }
    void main(void)
    {
     ch451_init();
     ch451_write(0x401);   //数码管系统设置
     ch451_write(0x580);   //数码管显示设置
     ch451_write(CH451_DIG0|0);
     ch451_write(CH451_DIG2|0);
     serial_init();
     while(1)
     {
      while(RI==0);
      RI=0;
      P0=0xff;
      temp=SBUF;
      switch(cnt)
      {
       case 0:if(temp=='#') cnt=1;else outflag=0;break;
       case 1:if((temp>0x30)&&(temp<0x39)) {a[0]=temp-0x30;cnt=2;}else outflag=0;break;
       case 2:if(temp=='(') cnt=3;else outflag=0;break;
       case 3:if((temp>=0x30)&&(temp<=0x31)) {a[1]=temp-0x30;cnt=4;}else outflag=0;break;
       case 4:if(temp==')') {cnt=0;outflag=1;}else outflag=0;break;
       default:break;
      }
      ch451_write(CH451_DIG0|a[1]);
      ch451_write(CH451_DIG2|a[0]);
      if(outflag==1)
      {
       switch(a[0])
       {
        case 1:if(a[1]==1) D0=ON;else D0=OFF;break;
        case 2:if(a[1]==1) D1=ON;else D1=OFF;break;
        case 3:if(a[1]==1) D2=ON;else D2=OFF;break;
        case 4:if(a[1]==1) D3=ON;else D3=OFF;break;
        case 5:if(a[1]==1) D4=ON;else D4=OFF;break;
        case 6:if(a[1]==1) D5=ON;else D5=OFF;break;
        case 7:if(a[1]==1) D6=ON;else D6=OFF;break;
        case 8:if(a[1]==1) D7=ON;else D7=OFF;break;
        default:break;
       }
       outflag=0;
      }
     }
    }

    附录:通信通讯有什么区别?

    目前,这两个词的使用频率相当高,但词义范围如何界定,并未明确统一。传统意义上的“通讯”主要指电话、电报、电传。通讯的“讯”指消息(Message),媒体讯息通过通讯网络从一端传递到另外一端。媒体讯息的内容主要是话音、文字、图片和视频图像。其网络的构成主要由电子设备系统和无线电系统构成,传输和处理的信号是模拟的。所以,“通讯”一词应特指采用电报、电话等媒体传输系统实现上述媒体信息传输的过程。

    “通信”仅指数据通信,即通过计算机网络系统和数据通信系统实现数据的端到端传输。通信的“信”指的是信息(Information),信息的载体是二进制的数据。数据则是可以用来表达传统媒体形式的信息,如声音、图像、动画等。

    由于旧的“通讯”系统早已实现了数字化、计算机网络化改造,因此可以认为目前的数据通信系统已涵盖了过去的“通讯”系统的功能。按照这个结论,目前应多使用“通信”一词表达互联网间与局域网内的数据传输,尽量少用或不用“通讯”一词,以免引起概念上的误解。

  • 相关阅读:
    UI界面思想
    ARM汇编语言(3)(寄存器读写控制外设)
    ARM汇编(2)(指令)
    ARM汇编语言(1)(基本概念)
    QT软件初次使用中遇到的若干问题及思考
    webpack4打包html中img后src为“[object Module]”问题(已解决)
    gulp dev报错:ReferenceError:primordials is not defined(已解决)
    Ubuntu20.04安装yarn报错gpg: can't connect to the agent: IPC connect call failed(已解决)
    手摸手教程之ubuntu20.04更改国内镜像源(附其他版本ubuntu换源教程,最新详细教程)
    Alert提示框的vue组件编写
  • 原文地址:https://www.cnblogs.com/yinsua/p/3351550.html
Copyright © 2011-2022 走看看