MCU 51-6 Serial Communication

串行通信基础知识

计算机与外界的信息交换称为通信。通信的基本方式可分为并行通信和串行通信两种。

并行通信是指数据的各位同时在多根数据线上发送或接收。

并行通信控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难。

串行通信是指 使用一条数据线,将数据一位一位地依次传输,每 一位数据占据一个固定的时间长度。其只需要少数几条线就可以 在系统间交换信息,特别适用于计算机与计算机、计算机与外设 之间的远距离通信。

串行通信的特点:传输线少,长距离传送时成本低,但数据的传送控制比并行通信复杂。

异步通信与同步通信

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

异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的时间间隔是任意的,但每个字符中的各位是以固定的时间传送的,即字符之间不一定有“位间隔”的整数倍的关系,但同一字符内的各位之间的距离均为“位间隔”的整数倍。

同步通信 同步通信时要建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为“位间隔”的整数倍,同时传送的字符间不留间隙,即保持位同步关系,也保持字符同步关系。

串行通信的制式

在串行通信中,数据是在两个站之间传送的。按照数据传送方向,串行通信可分为三种制式。

传输速率

比特率是每秒钟传输二进制代码的位数, 单位是:位/秒(bps)。如每秒钟传送 240个字符,而每个字符格式包含10位(1 个起始位、1个停止位、8个数据位),这 时的比特率为: 10位×240个/秒 = 2400 bps

串行接口的结构

在逻辑上,SBUF只有一个,它既表示发送寄存器,又表示接收寄存器,具有同一个单元地址99H。

但在物理结构上,则有两个完全独立的SBUF,一个是发送缓冲寄存器SBUF,另一个是接收缓冲寄存器SBUF。

如果CPU写SBUF,数据就会被送入发送寄存器准备发送;如果CPU读SBUF,则读入的数据一定来自接收缓冲器。即CPU对SBUF的读写,实际上是分别访问上述两个不同的寄存器。 a = SBUF; SBUF = a;

串行控制寄存器SCON 地址98H

用于设置串行口的工作方式、监视串行口的工作状态、控制发送与接收的状态等。它是一个既可以字节寻址又可以位寻址的8位特殊功能寄存器。其格式如下图。

REN:串行接受允许控制位。该位由软件置位或复位。当REN=1,允许接收;当REN=0,禁止接收。

RI:接收中断标志位。RI=1,表示一帧数据接收结束。可由软件查询RI位标志,也可以向CPU申请中断。 注意:RI在任何工作方式下也都必须由软件清0。

TI:发送中断标志位。TI=1,表示已结束一帧数据发送,可由软件查询TI位标志,也可以向CPU申请中断。 注意:TI在任何工作方式下都必须由软件清0。

串行发送中断TI和接收中断RI的中断入口地址是同是0023H,因此在中断程序中必须由软件查询TI和RI的状态才能确定究竟是接收还是发送中断,进而作出相应的处理。单片机复位时,SCON所有位均清0。

SM0和SM1为工作方式选择位,可选择四种工作方式:

电源控制寄存器PCON

SMOD:在串行口工作方式 1、2、3 中, 是波特率加倍位 (产生高波特率时启用平时不用,比如用11.0592晶振产生57600波特率时就要设置成SMOD=1

=1 时,波特率加倍(PCON=0x80;)

=0 时,波特率不加倍。(PCON=0x00;) (在PCON中只有这一个位与串口有关)

中断允许控制寄存器IE(A8H)

ES,串行口中断允许位;=0 时禁止中断; =1 时允许中断。

波特率的计算

在串行通信中,收发双方对发送或接收数据的速率要有约定。通过软件可对单片机串行口编程为四种工作方式,其中方式0和方式2的波特率是固定的,而方式1和方式3的波特率是可变的,由定时器T1的溢出率来决定。

方式0的波特率 = fosc/12

方式2的波特率 =(2^SMOD/64)· fosc

方式1的波特率 =(2^SMOD/32)·(T1溢出率)

方式3的波特率 =(2^SMOD/32)·(T1溢出率)

当T1作为波特率发生器时,最典型的用法是使T1工作在自动再装入的8位定时器方式(即方式2,且TCON的TR1=1,以启动定时器)。这时溢出率取决于TH1中的计数值。

T1 溢出率 = fosc /{12×[256 -(TH1)]}

常用串口波特率: 300、600、1200、2400、4800、9600、19200 ……115200;

串行口工作之前需对相关寄存器进行配置,设定其工作模式。

(1)设置T1的工作方式(编程TMOD寄存器);

(2)计算T1的初值,装载TH1、TL1;

(3)启动T1(编程TCON中的TR1位);

(4)确定串行口控制(编程SCON寄存器);

如需串行口在中断方式工作时,要进行中断设置编程IE寄存器。

串行通信接口标准

一、RS-232C接口 RS-232C是EIA(美国电子工业协会)1969年修订RS-232C标准。RS-232C定义了数据终端设备(DTE)与数据通信设备(DCE)之间的物理接口标准。

1、机械特性 RS-232C接口规定使用25针连接器,连接器的尺寸及每个插针的排列位置都有明确的定义。(阳头)

2、功能特性

 

典型的RS-232C通信电路   解决电平不兼容的问题

将计算机的232电平(逻辑‘1’ 是+3V to +15V,逻辑‘0’是 -3V to -15V)转换为单片机TTL电平(0V to 0.8V,逻辑‘1’ 是+3V to +5V,逻辑‘0’是 0V to 0.8V )

串口的发送(给计算机发送数据):

#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

uchar num;

void delay(uint z)
{
    uint x,y;
    for(x = z; x > 0; x--)
        for(y = 114; y > 0 ; y--);
}    
void UART_init()
{
    TMOD = 0x20;      //T1工作模式2  8位自动重装
//波特率:9600 TH1=256- 11.0592*10^6/(9600*32*12)=253 = 0xFD TH1 = 0xfd; TL1 = 0xfd; //比特率9600 TR1 = 1; //启动T1定时器 SM0 = 0; SM1 = 1; //串口工作方式1 10位异步 REN = 1; //串口允许接收 } void main() { UART_init(); //串口初始化 while(1)
{
SBUF = num; //发送数据给计算机
while(!TI);//发送成功
TI= 0; //软件清中断
num++;
delay(500);
} }
单片机不停地给计算机发送数据:

串口的接收(计算机发送数据给单片机 并点亮第一个灯):

#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

uchar num;

void delay(uint z)
{
    uint x,y;
    for(x = z; x > 0; x--)
        for(y = 114; y > 0 ; y--);
}    
void UART_init()
{
    TMOD = 0x20;      //T1工作模式2  8位自动重装
    TH1 = 0xfd;
    TL1 = 0xfd;     //比特率9600
    TR1 = 1;        //启动T1定时器
    SM0 = 0;
    SM1 = 1;         //串口工作方式1 10位异步
    REN = 1;        //串口允许接收
}
void main()
{
    UART_init(); //串口初始化
    while(1)
    {
      while(!RI);//接收成功
      P1 = SBUF; //接收计算机发的数据,并给P1 IO
      RI= 0; //软件清中断
    }   
}

利用串口中断进行计算机与串口通信:

#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

uchar num;

void UART_init()
{
    TMOD = 0x20;      //T1工作模式2  8位自动重装
    TH1 = 0xfd;
    TL1 = 0xfd;     //比特率9600
    TR1 = 1;        //启动T1定时器
    SM0 = 0;
    SM1 = 1;         //串口工作方式1 10位异步
    REN = 1;        //串口允许接收
    EA = 1; //开总中断
    ES =1; //开串口中断
}

void main()
{
    UART_init(); //串口初始化
    while(1);
}

void UART() interrupt 4
{
  if(RI)//如果计算机发送给电片机数据成功
  {
    num = SBUF;//将数据取出来
    P1 = num;
    num ++;
    RI =0;
    SBUF = num;//将数据在单片机端送到Buff,给计算机
    while(!TI);  //发送完成
    TI = 0//清中断
  }
}

串口接收一字节数码管以十进制显示

/*
以4800bps从计算机发任意一字节数据,通过数码管以十进制显示
的形式显示出来。
*/
#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

sbit we = P2^7;    //数码管位选
sbit du = P2^6;    //数码管段选

/*数码管段码*/
uchar code leddata[]={ 
 
                0x3F,  //"0"
                0x06,  //"1"
                0x5B,  //"2"
                0x4F,  //"3"
                0x66,  //"4"
                0x6D,  //"5"
                0x7D,  //"6"
                0x07,  //"7"
                0x7F,  //"8"
                0x6F,  //"9"
                0x77,  //"A"
                0x7C,  //"B"
                0x39,  //"C"
                0x5E,  //"D"
                0x79,  //"E"
                0x71,  //"F"
                0x76,  //"H"
                0x38,  //"L"
                0x37,  //"n"
                0x3E,  //"u"
                0x73,  //"P"
                0x5C,  //"o"
                0x40,  //"-"
                0x00,  //熄灭
                0x00  //自定义
 
                         };

/*1毫秒延时函数*/
void delay(uint z)    
{
    uint x,y;
    for(x = z; x > 0; x--)
        for(y = 114; y > 0 ; y--);
}

/*
串口初始化函数
工作模式1 10位异步收发 发送速率4800bps
*/
void UART_init()  
{
    TMOD = 0x20;      //T1工作模式2  8位自动重装
    TH1 = 0xfa;
    TL1 = 0xfa;     //比特率4800
    TR1 = 1;        //启动T1定时器
    SM0 = 0;
    SM1 = 1;         //串口工作方式1 10位异步
    REN = 1;        //串口允许接收
}

/*3位数码管显示函数*/
void display(uchar num)
{
    uchar bai,shi,ge;
    bai = num / 100; //求模
    shi = num % 100 / 10; //求余100后求出有多少个10 
    ge  = num % 10; //求余
    
    P0 = 0xff;    //清除断码
    we = 1;
    P0 = 0xfe;     //点亮第一位数码管
    we = 0;

    du = 1;
    P0 = leddata[bai];    //显示百位
    du = 0;
    delay(1);

    P0 = 0xff;    //清除断码
    we = 1;
    P0 = 0xfd;//点亮第二位数码管
    we = 0;

    du = 1;
    P0 = leddata[shi];    //显示十位
    du = 0;
    delay(1);

    P0 = 0xff;    //清除断码
    we = 1;
    P0 = 0xfb;//点亮第三位数码管
    we = 0;

    du = 1;
    P0 = leddata[ge];  //显示各位
    du = 0;
    delay(1);    
}

void main()
{
    UART_init();//串口配置初始化
    while(1)
    {
        if (RI)    //检测是否接收完成
        {
            RI = 0;    //清除接收标志位,以便于下次接收
        }
        display(SBUF); //取出接收SBUF的值赋给数码管显示
    }
}

发送矩阵键盘的按键值到计算机

/*
把矩阵键盘的键值以2400bps上传到计算机串口助手
*/
#include <reg52.h>

#define uchar unsigned char
#define uint  unsigned int

/*1毫秒延时函数*/
void delay(uint z)    
{
    uint x,y;
    for(x = z; x > 0; x--)
        for(y = 114; y > 0 ; y--);
}

/*
串口初始化函数
工作模式1 10位异步收发 发送速率2400bps
*/
void UART_init()  
{
    TMOD = 0x20;      //T1工作模式2  8位自动重装
    TH1 = 0xf4;
    TL1 = 0xf4;     //比特率2400,计算公式256-11059200/2400/32/12
    TR1 = 1;        //启动T1定时器
    SM0 = 0;
    SM1 = 1;         //串口工作方式1 10位异步
//    REN = 1;        //串口允许接收
}

/*
    4*4矩阵键盘扫描函数
    带返回值,返回键值码
*/
uchar KeyScan()
{
    uchar cord_l,cord_h;//声明列线和行线的值的储存变量
    P3 = 0xf0;//1111 0000
    if( (P3 & 0xf0) != 0xf0)//判断是否有按键按下
    {
        delay(5);//软件消抖
        if( (P3 & 0xf0) != 0xf0)//判断是否有按键按下
        {
              cord_l = P3 & 0xf0;// 储存列线值
              P3 = cord_l | 0x0f;
              cord_h = P3 & 0x0f;// 储存行线值
              while( (P3 & 0x0f) != 0x0f );//松手检测
              return (cord_l + cord_h);//返回键值码
        }    
    }
        
}

/*
    4*4矩阵键盘键值码处理函数
    返回转换后的键值码
*/
uchar KeyPro()
{
    uchar key_value; //存放转换后的按键值
    switch( KeyScan() )
    {
         //第一行键值码
        case 0xee: key_value = 0x01;        break;
        case 0xde: key_value = 0x02;        break;
        case 0xbe: key_value = 0x03;        break;
        case 0x7e: key_value = 0x04;        break;
        
        //第二行键值码
        case 0xed: key_value = 0x05;        break;
        case 0xdd: key_value = 0x06;        break;
        case 0xbd: key_value = 0x07;        break;
        case 0x7d: key_value = 0x08;        break;

        //第三行键值码
        case 0xeb: key_value = 0x09;        break;
        case 0xdb: key_value = 0x0a;        break;
        case 0xbb: key_value = 0x0b;        break;
        case 0x7b: key_value = 0x0c;        break;

        //第四行键值码
        case 0xe7: key_value = 0x0d;        break;
        case 0xd7: key_value = 0x0e;        break;
        case 0xb7: key_value = 0x0f;        break;
    case 0x77: key_value = 0x10;        break;
    }
    return (key_value);//返回转换后的键值码    
}


void main()
{
    UART_init();//串口初始化
    while(1)
    {
        SBUF = KeyPro();//调用带返回值的键值码转换函数,把转换后的键值码送入发送SBUF
        while(!TI);        //检测是否发送完毕
        TI = 0;            //清楚发送完毕标志位,已便于下次发送
    }
}

#include <reg52.h>
#include <stdio.h>

typedef unsigned int  INT16U;
typedef unsigned char INT8U;

void Delay_Ms(INT16U z)
{
    INT16U x,y;
    for(x = z; x > 0; x--)
        for(y = 114; y > 0 ; y--);
}    

void UART_Init()
{
    TMOD |= 0x20;      //T1工作模式2  8位自动重装
    TH1 = 0xfd;
    TL1 = 0xfd;     //比特率9600
    TR1 = 1;        //启动T1定时器
    SM0 = 0;
    SM1 = 1;         //串口工作方式1 10位异步
}

void main()
{
    UART_Init(); //串口初始化
    while(1)
    {
        TI = 1;
        puts("大家好!欢迎学习单片机");
        while(!TI);
        TI = 0;
        Delay_Ms(1000);
    }    
}
/*************************************
备注:
使用printf()puts()前都应软件置位TI = 1;
printf()puts()  使用putchar函数发送字节

void putchar(uchar sbyte )
{
    while(!TI);   //等待发送完

    SBUF=sbyte;

}

所第调用putchar前没TI=1 永远等待
*************************************/


51单片机的晶振选取为什么是11.0592MHz,而不直接选取12MHz?
由波特率计算公式计算时,12MHz计算时除不尽,有较大误差,所以选用11.0592MHz的晶振对于串口通信的通信速率很重要。
如:
11.0592MHz晶振:256-11059200/2400/32/12 = 256-12=244
12MHz晶振:256-12000000/2400/32/12 =256-13.020833333333...

通信双方波特率如果不同,通信传输数据就会错乱



原文地址:https://www.cnblogs.com/darren-pty/p/13285994.html