单片机第一季:零基础10——串口通信和RS485详解

目录

1,串口通讯基础

1.1,同步和异步

1.2,并行和串行 

1.3,单工、半双工与全双工通信 

1.4,通信速率 

2,单片机串口通讯

2.1,接口标准 

2.2,通讯协议 

2.3,串口内部结构 

3,串口相关寄存器 

4,波特率计算 

5,串口初始化 

6,开发板硬件设计 

7,串口发送和接收程序

8,串口调试总结

9,RS485 



1,串口通讯基础

什么是通信:
(1)人和人之间的通信:说话、写信、狼烟、手势等;
(2)人和计算机之间的通信:按键、显示器、鼠标、触摸屏等;
(3)计算机和计算机之间的通信;

通信的关键:
(1)事先约定;
(2)基本信息单元;
(3)有效信息的编码、传输和解码 ;

通信的专业性概念:
(1)同步和异步;
(2)单工、半双工、全双工;
(3)并行和串行;
(4)电平信号和差分信号 ;

1.1,同步和异步

异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方的收发协调,要求发送和接收设备的时钟尽可能一致。
异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的间隙(时间间隔)是任意的,但每个字符中的各位是以固定的时间传送的,即字符之间不一定有“位间隔”的整数倍的关系,但同一字符内的各位之间的距离均为“ 位间隔”的整数倍。如下图所示:

异步通信的特点:不要求收发双方时钟的严格一致,实现容易,设备开销较小,但每个字符要附加2~3 位用于起止位,各帧之间还有间隔,因此传输效率不高。 

同步通信时要建立发送方时钟对接收方时钟的直接控制,使双方达到完全同步。此时,传输数据的位之间的距离均为“位间隔”的整数倍,同时传送的字符间不留间隙,即保持位同步关系,也保持字符同步关系。发送方对接收方的同步可以通过两种方法实现。如下图所示: 

1.2,并行和串行 

串行通讯:

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

串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。 

并行通讯: 

并行通信通常是将数据字节的各位用多条数据线同时进行传送,通常是8位、16 位、32 位等数据一起传输。如下图所示: 

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

串口通信基础:
(1)一种特定的通信协议;
(2)也叫串行通信、串口通信、UART、USART;
(3)异步、串行、全双工;

1.3,单工、半双工与全双工通信 

单工是指数据传输仅能沿一个方向,不能实现反向传输。如下图所示: 

半双工是指数据传输可以沿两个方向,但需要分时进行。如下图所示: 

全双工是指数据可以同时进行双向传输。如下图所示: 

串行通信的主要用途:
(1)早期:计算机之间短距离通信(15米),完备通信机制
(2)现在:CPU之间近距离通信、调试信息输入输出,非完备通信 

串行通信的工作方式:
(1)3根线(GND、RxD、TxD)或者9根线;
(2)发送方有发送移位寄存器,接收方有接收移位寄存器;
(3)数据在发送方和接收方的CPU中都以字节为单位整字节处理;
(4)数据在通信线上以位为单位逐个bit的传输; 

串行通信的主要概念:
(1)起始位、数据位、奇偶校验位、停止位(帧),一帧数据里各位的定义,例如数据位有几位;
(2)波特率:一秒钟传输多少个bit位,发送方和接收方必须波特率设置为一样,例如9600代表1秒钟发送了9600各bit位;
(3)流控:速率协商,现在一般都要禁用掉 ;

串口调试助手的串口设置里有对应的波特率,数据位长度,停止位长度的选择,RTS和DTR代表表速率协商的流控选择,不勾选。 

1.4,通信速率 

衡量通信性能的一个非常重要的参数就是通信速率,通常以比特率(Bitrate)来表示。比特率是每秒钟传输二进制代码的位数,单位是:位/秒( bps)。如每秒钟传送240 个字符,而每个字符格式包含10 位(1 个起始位、1 个停止位、8 个数据位),这时的比特率为:10 位×240 个/秒= 2400 bps 。

在后面会遇到一个“波特率”的概念,它表示每秒钟传输了多少个码元。而码元是通信信号调制的概念,通信中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如常见的通信传输中,用0V 表示数字0,5V 表示数字1,那么一个码元可以表示两种状态0 和1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通信传输中,有0V、2V、4V 以及6V 分别表示二进制数00、01、10、11,那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。由于很多常见的通信中一个码元都是表示两种状态,所以我们常常直接以波特率来表示比特率。 

先搞清楚以下问题:
(1)串行通信功能是SoC的一个(内部)外设提供的,与CPU本身无关;
(2)各种不同SoC的串行通信大同小异;
(3)串行通信经常作为主控SoC与其他外部芯片之间的通信接口;

2,单片机串口通讯

2.1,接口标准 

串口通信(Serial Communication),是指外设和计算机间通过数据信号线、地线等按位进行传输数据的一种通信方式,属于串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。 

串口通信的接口标准有很多,有RS-232C、RS-232、RS-422A、RS-485 等。常用的是RS-232 和RS-485。RS-232 其实是RS-232C 的改进,原理是一样的。这里我们就以RS-232C 接口进行解。

RS-232C 是EIA(美国电子工业协会)1969 年修订RS-232C 标准。RS-232C定义了数据终端设备(DTE)与数据通信设备(DCE)之间的物理接口标准。RS-232C 接口规定使用25 针连接器,简称DB25,连接器的尺寸及每个插针的排列位置都有明确的定义,如下图所示: 

RS-232C 还有一种9 针的非标准连接器接口,简称DB9。串口通信使用的大多都是DB9 接口。DB25 和DB9 接头有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。9 针串口线的外观图如下图所示: 

从上图中可以看到公头和母头的管脚定义顺序是不一样,这一点需要特别注意。这些管脚都有什么作用呢? 9 针串口和25 针串口常用管脚的功能说明如下图所示: 

在串口通信中,通常我们只使用2、3、5 三个管脚,即TXD、RXD、SGND,其他管脚功能大家看不明白也没关系。
RS-232C 对逻辑电平也做了规定,如下:
在TXD 和RXD 数据线上:
1.逻辑1 为-3~-15V 的电压 ;2.逻辑0 为3~15V 的电压
在RTS、CTS、DSR、DTR 和DCD 等控制线上:
1.信号有效( ON 状态) 为3~15V 的电压
2.信号无效( OFF 状态) 为-3~-15V 的电压

由此可见,RS-232C 是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。而我们51 单片机使用的就是TTL 电平,所以要实现51 单片机与计算机的串口通信,需要进行TTL与RS-232C 电平转换,通常使用的电平转换芯片是MAX232。 

在串口通信中通常PC 机的DB9 为公头,单片机上使用的串口DB9 为母头,通过一根直通串口线进行相连。在9 针串口线实物图即为直通型串口线,串口线(COM)母头连接计算机DB9 的公头,串口线公头连接单片机上使用的DB9 母头,这样就是将2、3、5 管脚直接相连。如果你要实现两台计算机串口通信,那么就需要一根交叉串口线,将2 对3、3 对2、5 对5 连接,交叉串口线一般两头都是母头。 

串口通信中还需要注意的是,串口数据收发线要交叉连接,计算机的TXD要对应单片机的RXD,计算机的RXD 要对应单片机的TXD,并且共GND,如下图: 

有的朋友就会问了,在计算机与单片机进行串口通信时,使用的不是直通线吗,这时候怎么让TXD 与RXD 交叉连接?前面我们说了单片机处理的是TTL电平,需要使用RS232 电平转换芯片,将RS232 电平转换芯片串行数据输出管脚交叉连接在DB9 母头上即可,本章后面硬件设计部分会介绍。 

2.2,通讯协议 

RS232 的通信协议比较简单,通常遵循96-N-8-1 格式。
“96”表示的是通信波特率为9600。串口通信中通常使用的是异步串口通信,即没有时钟线,所以两个设备要通信,必须要保持一致的波特率,当然,波特率常用值还有4800、115200 等。

“N”表示的是无校验位,由于串口通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、0 校验(space)、1 校验(mark)以及无校验(noparity)。具体的介绍,大家可以百度串口通信了解。 

“8”表示的是数据位数为8 位,其数据格式在前面介绍异步通信中已讲过。当然数据位数还可以为5、6、7 位长度。 

“1”表示的是1 位停止位,串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0 的数据位表示,而数据包的停止信号可由0.5、1、1.5 或2 个逻辑1 的数据位表示,只要双方约定一致即可。 

2.3,串口内部结构 

上图中右边的TXD 和RXD 为单片机IO 口,TXD 对应的是P3.1 管脚,RXD 对应的是P3.0 管脚。其内部工作方式在后面小节会介绍。 

3,串口相关寄存器 

实际串口例程中用的寄存器包括SCON/SBUF/PCON/IE的部分bit位,SCON中使用SM0和SM1选择工作方式,REN代表是否允许串口接收,实际例程是要接收上位机的数据所以要允许唇口接收,TI代表串口发送中断标志位,表示一帧数据是否发送完成,RI代表接收数据的中断标志位,TI/RI由硬件置位,由软件复位;

SBUF代表发送接收数据的寄存器;

PCON是使用到SMOD和SMOD0,SMOD代表波特率是否加倍,SMOD0的设置会影响到SCON的SM0/FE;

IE使用到EA/ES/ET1; 

串口控制寄存器SCON:

SM0 和SM1 为工作方式选择位: 

注意:溢出率使用的是定时器1的溢出率。

方式2和方式3中的9位UART的意思是发送的一帧数据中有9位数据位,一般使用方式1,数据位是8位。 

SM2:多机通信控制位,主要用于方式2 和方式3。当SM2=1 时可以利用收到的RB8 来控制是否激活RI(RB8=0 时不激活RI,收到的信息丢弃;RB8=1 时收到的数据进入SBUF,并激活RI,进而在中断服务中将数据从SBUF 读走)。当SM2=0 时,不论收到的RB8 为0 和1,均可以使收到的数据进入SBUF,并激活RI(即此时RB8 不具有控制RI 激活的功能)。通过控制SM2,可以实现多机通信。 

REN:允许串行接收位。由软件置REN=1,则启动串行口接收数据;若软件置REN=0,则禁止接收。 

TB8:在方式2 或方式3 中,是发送数据的第9 位,可以用软件规定其作用。可以用作数据的奇偶校验位,或在多机通信中,作为地址帧/数据帧的标志位。在方式0 和方式1 中,该位未用到。 

RB8:在方式2 或方式3 中,是接收到数据的第9 位,作为奇偶校验位或地址帧/数据帧的标志位。在方式1 时,若SM2=0,则RB8 是接收到的停止位。

TI:发送中断标志位。在方式0 时,当串行发送第8 位数据结束时,或在其它方式,串行发送停止位的开始时,由内部硬件使TI 置1,向CPU 发中断申请。在中断服务程序中,必须用软件将其清0,取消此中断申请。 代表是否将数据发送完成。

RI:接收中断标志位。在方式0 时,当串行接收第8 位数据结束时,或在其它方式,串行接收停止位的中间时,由内部硬件使RI 置1,向CPU 发中断申请。也必须在中断服务程序中,用软件将其清0,取消此中断申请。 

使用中断方式来发送/接收数据非常合适,提升CPU使用率,会使用到TI/RI标志位(TI/RI标志位代表硬件是否将数据发送/接收完成,发送/接收完成硬件将TI/RI标志位置1,需要使用软件复位为0)。会使用串行口允许中断ES。

电源控制寄存器PCON: 

SMOD:波特率倍增位。在串口方式1、方式2、方式3 时,波特率与SMOD 有关,当SMOD=1 时,波特率提高一倍。复位时,SMOD=0。 

波特率加倍主要目的是如果要达到一个很高的波特率,当前计算不能满足,就需要用到波特率加倍。

中断允许寄存器:

会用到串行口中断允许位ES(用于发送/接收数据),以及定时器中断(用于波特率计算)。

4,波特率计算 

以方式1为例,计算波特率:

方式1 的波特率=(2SMOD/32)·(T1 溢出率) ,其中T1 溢出率= fosc /{12×[256 -(TH1)]}。

可以使用小工具51波特率计算自动生成波特率 :

选择定时器工作方式,输入开发板上使用的晶振频率大小,选择所要使用的波特率,SMOD 为是否倍频,这个在前面介绍寄存器时说过,下面的误差大小可以反映出通信时是否出现乱码。在使用串口通信时,定时器1 工作方式为2,串口工作方式为1,以开发板晶振是11.0592Mh 为例,假如晶振频率是12M,那么在生成的波特率就会有误差而导致通信出错。为什么替换可以从误差值反映出来。在本章实验中波特率选择9600,使用SMOD,即值为1,点击确定后即会自动生成定时/计数器THx 的值。如下所示:

从上图可知,使用11.0592M 晶振时,误差为0。我们对比下当外部晶振使用12Mh 时,波特率误差多大,如下所示: 

从上图可知,当使用12M 晶振时,波特率误差有6.98%,是比较大的,会导致在通信过程中出现乱码等错误信息。这是我们不希望看到的,所以再次说明下,在做串口通信实验时,一定要确认外部晶振是否是11.0592M。 

5,串口初始化 

①确定T1 的工作方式(TMOD 寄存器);
②确定串口工作方式(SCON 寄存器);
③计算T1 的初值(设定波特率),装载TH1、TL1;
④启动T1(TCON 中的TR1 位);
⑤如果使用中断,需开启串口中断控制位(IE 寄存器)。 

例如:设置串口为工作方式1、波特率为9600、波特率加倍、使用中断。其配置程序如下: 

#include <reg51.h>

void uart_ini()
{
   TMOD = 0x20;   //定时器1,工作方式2
   TH1 = 0xFA;	 //设置波特率9600
   TL1 = 0xFA;

   SCON = 0x50;	 //工作方式1,允许串行接收
   PCON = 0x80;	 //波特率加倍

   EA = 1;	  //总中断
   ES = 1;	  //串口中断
   TR1 = 1;	 //定时器1工作,但不会使用定时器1的中断

}

思考:为什么要将定时器1的TR1置1? 

6,开发板硬件设计 

开发板上板载一个USB 转TTL 模块和一个RS232 模块,这两个模块都可进行串口通信。其硬件电路如下所示:

从上图中可以看出,通过CH340 芯片把51 单片机的串口与PC 机的USB 口进行连接,不仅可以实现程序的烧入,还可实现串口通信功能。根据前面介绍,串口通信需将数据收发管脚交叉连接,所以可以看到在CH340 芯片的2 和3 脚已做处理。电路中其他部分是自动下载电路部分,目的是控制单片机的电源,无需冷启动。使用USB 转串口芯片,免去了一根串口线,使用普通USB 数据线(支持安卓手机数据线)就可以进行串口通信。 

从上图中可以看到CH340 的2、3 脚串口并非直接连接到单片机串口,而是连接在J39 和J44 端子上,这样就把CH340 的串口与单片机串口独立出来,为什么不直接连接而要使用这个J39 和J44 端子呢?这是方便用户可以使用开发板上的USB 转TTL 模块(也就是CH340 转串口模块)做一些串口类模块的调试,比如:WIFI、蓝牙、GPS、GPRS 等,直接利用PC 上位机来调试模块。同时也方便用户使用板载USB 转TTL 模块给其它类型单片机下载程序。 

如果使用黄色跳线帽将J39 和J44 端子的2、3 短接,那么CH340 串口与单片机串口是连接一起的,此时即可实现程序的下载或串口通信。 

7,串口发送和接收程序

/**********************************************************************************
****
实验名称:串口通信实验
接线说明:
实验现象:下载程序后,当串口助手发送数据给单片机,单片机原封不动转发给串口助手显示
注意事项:使用黄色跳线帽将CH340 旁的J39 端子的UTX 和P30 短接,URX 和P31 短接,出厂默
认已短接好
***********************************************************************************

****/
#include "reg52.h"
typedef unsigned int u16;//对系统默认数据类型进行重定义
typedef unsigned char u8;
/*******************************************************************************
* 函数名: uart_init
* 函数功能: 串口通信中断配置函数,通过设置TH 和TL 即可确定定时时间
* 输入: baud:波特率对应的TH、TL 装载值
* 输出: 无
*******************************************************************************/
void uart_init(u8 baud)
{
	TMOD|=0X20; //设置计数器工作方式2
	SCON=0X50; //设置为工作方式1
	PCON=0X80; //波特率加倍
	TH1=baud; //计数器初始值设置
	TL1=baud;
	ES=1; //打开接收中断
	EA=1; //打开总中断
	TR1=1; //打开计数器
}
/*******************************************************************************
* 函数名: main
* 函数功能: 主函数
* 输入: 无
* 输出: 无
*******************************************************************************/
void main()
{
	uart_init(0XFA);//波特率为9600
	while(1)
	{
	}
}
void uart() interrupt 4 //串口通信中断函数
{
	u8 rec_data;
	RI = 0; //清除接收中断标志位
	rec_data=SBUF; //存储接收到的数据
	SBUF=rec_data; //将接收到的数据放入到发送寄存器
	while(!TI); //等待发送数据完成
	TI=0; //清除发送完成标志位
}

注意:要将晶振更换为11.0592MHz的,如果使用12MHz的收到的是乱码。

思考:当通过串口调试助手将一个字符串发给单片机后, 串口调试助手也会收到这个字符串,在串口调试助手中收到这个字符串时是很快速的就会显示的,如果在程序中将每次发送数据时延迟一段时间,串口调试助手会有怎样的显示?

实验代码比较简单,首先定义了串口通信中断配置函数uart_init,该函数有一个入口参数baud,该值可改变通信波特率。该函数的实现即是按照前面介绍的串口配置步骤。

最后进入while 循环,在循环体内不执行任何功能程序。如果发生接收中断,即会进入串口中断执行,在中断程序内将接收中断标志位RI置0,读取接收寄存器内的数据,并将读取的数据发给发送寄存器,直到发送完毕,硬件将发送完成中断标志位TI置1后,通过软件再将TI置0,执行完后回到主函数内继续运行,如此循环。 

上面的程序中,是通过串口调试助手给单片机发送数据后触发了中断执行函数,中断执行函数内从SBUF读取接收到的数据,然后在通过SBUF从单片机发送出去给串口调试助手,也就是发送的数据借用这个串口中断执行函数来发送的。

思考:如何通过中断实现发送数据?

思考程序:1,发送一个字符串;2,串口助手发送数据给单片机,单片机原封不动再发给串口助手。 

还有一种类似串口调试助手的软件SecureCRT也可实现收发数据。

串口实际调试中,直接发送一个字符串时,发现往字符串发送函数中不能直接传入一个字符串,需要将指向字符串的指针传入,也就是以下形式:

unsigned char *str = "abcdef";


void uart_send_string(unsigned char *str)
{
	while (*str != '\0')
	{
		uart_send_byte(*str);		// 发送1个字符
		str++;						// 指针指向下一个字符
	}
}

uart_send_string(str);

以下代码为PC发送数据给单片机,然后通过串口调试助手回读数据:

#include "reg51.h"
typedef unsigned int u16;//对系统默认数据类型进行重定义
typedef unsigned char u8;



void uart_send_byte(unsigned char c);
void delay(void);
void uart_send_string(unsigned char *str);

void uart_init(u8 baud)
{
TMOD|=0X20; //设置计数器工作方式2
SCON=0X50; //设置为工作方式1
PCON=0X80; //波特率加倍
TH1=baud; //计数器初始值设置
TL1=baud;
ES=1; //打开接收中断
EA=1; //打开总中断
TR1=1; //打开计数器
}

void main()
{
	unsigned char *str = "刘海龙";
	uart_init(0XFA);//波特率为9600

//	while(1)
//	{
//	   //send_byte('A');
//	   //uart_send_byte('B');
//	   uart_send_string(str);
//	   uart_send_byte('\n');
//	   delay();
//	}



while(1);


}

void uart_send_byte(unsigned char c)
{
   // 第1步,发送一个字节
   SBUF = c;

   // 第2步,先确认串口发送部分没有在忙
   while (!TI);

   // 第3步,软件复位TI标志位
   TI = 0;
}

void uart_send_string(unsigned char *str)
{
	while (*str != '\0')
	{
		uart_send_byte(*str);		// 发送1个字符
		str++;						// 指针指向下一个字符
	}
}


void delay(void)
{
	unsigned char i, j;

	for (i=0; i<200; i++)
		for (j=0; j<200; j++);
}

void uart_rec() interrupt  4
{
   unsigned char tmp;

   if(RI)
   {
   		tmp = SBUF;
		RI = 0;
   }
  uart_send_byte(tmp);
}

注意:中断函数中需要通过软件将RI置0,另外在中断函数内使用字节发送函数uart_send_byte() 发送数据,不使用那个字符串发送函数。

8,串口调试总结

串口编程主要以下几步:

第一步:找到相关寄存器;

第二部:模式选择,波特率设定以及对应定时器设定,打开串口中断;

第三步:单片机发出数据可以使用问询方式输出,也可使用中断方式输出,单片机接收数据只能通过中断方式接收;

第四步:如果需要使用中断,需要设定串口对应的中断函数,在中断函数中接收数据以及发送数据。

9,RS485 

待完善。 

物联沃分享整理
物联沃-IOTWORD物联网 » 单片机第一季:零基础10——串口通信和RS485详解

发表评论