串行通讯
目录
第1章硬件连接
1.1 RS232接口
串行通讯中,RS232的应用最为广泛。其通讯接口一般为DB9或DB25,其中的9、25表示通讯线的个数。
1.1.1 DB9
DB9有9条通讯线,每条通讯线的编号请见下图。注意:公头和母头的编号有所区别。
图1.1
各条通讯线的含义:
编号 |
含义 |
输入/输出 |
1 |
DCD (Data Carrier Detect) CD (Carrier Detect) RLSD (Receive Line Signal Detect) 载波检测 |
in |
2 |
RXD 接收数据 |
in |
3 |
TXD 发送数据 |
out |
4 |
DTR (data-terminal-ready) 设备就绪就设置为高电平,这样对方可以发送数据了 |
out |
5 |
SG 信号地线 |
|
6 |
DSR (data-set-ready) 高电平说明对方已准备就绪,可以给对方发送数据了 |
in |
7 |
RTS (request-to-send) 半双工:发送状态置为高电平,接收状态置为低电平 全双工:用于硬件流控制 |
out |
8 |
CTS (clear-to-send) 半双工:高/低电平说明对方处于发送/接收状态 全双工:用于硬件流控制 |
in |
9 |
RI 振铃指示,通知对方通信线路已接通 |
in |
1.1.2 DB25
DB25有25条通讯线。每条通讯线的编号与图1.1的编号类似:第一行的编号从1至13,第二行的编号从14至25。
各条通讯线的含义:
编号 |
含义 |
输入/输出 |
1 |
GND 设备地线 |
|
2 |
TXD 发送数据 |
out |
3 |
RXD 接收数据 |
in |
4 |
RTS (request-to-send) 半双工:发送状态置为高电平,接收状态置为低电平 全双工:用于硬件流控制 |
out |
5 |
CTS (clear-to-send) 半双工:高/低电平说明对方处于发送/接收状态 全双工:用于硬件流控制 |
in |
6 |
DSR (data-set-ready) 高电平说明对方已准备就绪,可以给对方发送数据了 |
in |
7 |
SG 信号地线 |
|
8 |
DCD (Data Carrier Detect) CD (Carrier Detect) RLSD (Receive Line Signal Detect) 载波检测 |
in |
9 |
发送电流+ |
|
11 |
发送电流- |
|
18 |
接收电流+ |
|
20 |
DTR (data-terminal-ready) 设备就绪就设置为高电平,这样对方可以发送数据了 |
out |
22 |
RI 振铃指示,通知对方通信线路已接通 |
in |
25 |
接收电流- |
|
其它 |
未用,具体有十一个:10、12-17、19、21、23、24 |
1.2 连线及说明
如果只是延长通讯线,就采用顺接的方法,即:DCD接DCD、DTR接DTR、CTS接CTS……
如果是两个串口设备相互通讯,就必须采用交叉的方式连线,如下图所示:
图1.2
说明如下:
1、RXD、TXD、SG这三条线是必需的,用来传输数据;
2、设备A在给设备B发送数据前,需要知道设备B是否已经开机。因此,它会检查DSR或DCD或RI是否为高电平,如果是高电平说明设备B已经准备就绪,可以通讯了。串口设备在开机后或PC的串口程序打开串口后一般都会设置自己的DTR为高电平,这样对方的DSR、DCD、RI都变成了高电平,就可以正常通讯了。DTR除了通知对方准备就绪外,还可用于硬件流控制,其原理与RTS硬件流控制相同;
3、RTS和CTS的设计初衷是用来协调半双工通讯的,在全双工通讯普及的现在它们的主要作用是用来进行硬件流控制。
啥叫半双工、全双工?对讲机就是半双工——按下按钮,只能讲不能听;松开按钮,只能听不能讲。电话、手机就是全双工——接通电话后,可以同时讲、听。
半双工通讯时,设备A给设备B发送数据前,需要查询CTS的状态。如果是高电平说明设备B正处于发送状态(设备B的RTS为高电平)。为此,设备A将自己的RTS设置为高电平,设备B的CTS也随之变为高电平,设备B察觉到CTS由低电平变为高电平后会将状态切换为接收数据,然后设置自己的RTS为低电平。这样,设备A的CTS就由高电平变成了低电平。好了,现在设备A可以给设备B发送数据了。发送完数据,设备A会切换状态为接收并设置RTS为低电压,这样设备B就可以给它发送数据了。
全双工通讯时代,不再需要切换发送或接收状态,RTS/CTS的作用也发生了改变——那就是用于硬件流控制。如:设备A给设备B发送数据,其中设备B的反应速度比较慢。短时间内,在设备A给设备B发送大量数据的前提下,设备B可能会丢失一些数据,甚至导致通讯中断。为此,可以使用RTS硬件流控制。其步骤为:设备A、B的RTS初始状态均为高电平,表明大家现在都很闲,可以发送数据过来。设备A给设备B发送的数据被存放在设备B的串口数据缓冲区内,当设备B发现缓冲区内的数据太多,如超过75%时,会设置RTS为低电压,告诉设备A我忙不过来了。此时,设备A的CTS就由高电压变成了低电压它就知道设备B反应不过来了,那就等一会儿吧。设备B处理缓冲区内的数据,当缓冲区内的数据被处理得差不多的时候,如缓冲区内的数据不足25%的时候,会再次设置自身的RTS为高电平,用以表明我现在缓过劲来了,可以继续发送数据过来了。设备A的CTS由低电平变为高电平后,就会继续发送数据给设备B。
这里有一个关键点:那就是设备B正忙的时候,设备A需要等待多长时间?在Windows下,这个时间可以进行设置。具体的就是使用SetCommTimeouts函数。其声明如下:
BOOL SetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS pTimeout);
第2个参数是一个结构,其定义如下
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
最关键的就是 WriteTotalTimeoutMultiplier 和 WriteTotalTimeoutConstant。它们两确定了等待时间,具体公式如下:
上式中的Bytes是将要发送的字节数。
1.3 代码控制
1.3.1 输出信号
DTR和RTS属于输出信号,它们的状态(高电平、低电平)可以使用代码(VC++)进行控制。
方法一:调用函数SetCommState
第2个参数是一个DCB结构。DCB重要的成员如下:
fOutxDsrFlow
是否启用DTR/DSR硬件流控制。
启用后,发送数据前会检查DSR是否为高电平
fDtrControl
DTR_CONTROL_DISABLE 设置DTR为低电平
DTR_CONTROL_ENABLE 设置DTR为高电平
DTR_CONTROL_HANDSHAKE 根据接收缓冲区的数据量设置DTR
fOutxCtsFlow
是否启用RTS/CTS硬件流控制
启用后,发送数据前会检查CTS是否为高电平
fRtsControl
RTS_CONTROL_DISABLE 设置RTS为低电平
RTS_CONTROL_ENABLE 设置RTS为高电平
RTS_CONTROL_HANDSHAKE 根据接收缓冲区的数据量设置RTS
RTS_CONTROL_TOGGLE 发送数据时高电平,发送完低电平。
用于半双工通讯
方法二:调用函数EscapeCommFunction
给第2个参数传递如下宏
SETDTR 设置DTR为高电平
CLRDTR 设置DTR为低电平
SETRTS 设置RTS为高电平
CLRRTS 设置RTS为低电平
显然,如果SetCommState的fDtrControl为DTR_CONTROL_HANDSHAKE,那么DTR的状态由Windows来维护。此时就不能使用EscapeCommFunction修改DTR的状态。同样要注意RTS_CONTROL_HANDSHAKE和RTS_CONTROL_TOGGLE。
1.3.2 输入信号
DSR 、CTS、DCD(也叫RLSD)、RI属于输入信号,它们的状态(高电平、低电平)可以使用代码查询到。
请使用函数GetCommModemStatus,获得的第2个参数就是查询到的状态dwStat。
dwStat & MS_DSR_ON DSR 是否为高电压
dwStat & MS_CTS_ON CTS 是否为高电压
dwStat & MS_RLSD_ON DCD是否为高电压
dwStat & MS_RING_ON RI是否为高电压
1.4 连线实例
1.4.1 PC-E500
PC-E500 |
含义 |
PC(DB9) |
1 |
GND设备地线 |
壳(可选) |
2 |
TXD发送 |
2 RXD |
3 |
RXD接收 |
3 TXD |
4 |
RTS |
|
5 |
CTS |
4 DTR |
8 |
DCD |
|
7 |
SG信号地线 |
5 SG |
10,13 |
与SG之间的电压应为+6V,实际与电池电压有关。用来给外部供电 |
|
11 |
Receive Ready |
8 CTS |
14 |
DTR |
6 DSR 1 DCD(可选) 9 RI(可选) |
说明:
1、PC-E500的针脚编号是从上到下的,即由DEL键到INS键;
2、PC-E500的4号针为RTS。但是打开串口后,它不是高电平,因此无法进行RTS硬件流控制。反而是11号针,打开串口后,它是高电平,而且在接收串口数据忙不过来时它会设置为低电平。因此,11号针可以充当RTS的角色进行硬件流控制,所以它被接到电脑的CTS上;
3、PC-E500的5号针(CTS)按理应该接到PC的7号针(RTS),不过连接到4号针(DTR)也是可以的。因为PC上打开串口后DTR一般都是高电平,而RTS不一定高电平。这种连法通用性更强。
1.4.2 DSNP GPS接收机(5000系列)
接收机A口是RS232接口,共有7根针,如下图所示:
图1.3
各个针头的含义,及与PC上DB9公头的连接请参考下表
针号 |
含义 |
输入/输出 |
PC(DB9) |
1 |
+12V |
9 RI |
|
2 |
TXD |
out |
2 RXD |
3 |
RXD |
in |
3 TXD |
4 |
DSR |
4 DTR |
|
5 |
SG |
5 SG |
|
6 |
CTS |
out |
7 RTS |
7 |
RTS |
in |
8 CTS |
1.4.3 麦哲伦SP24手持机
SP24手持机背面有5根针,如下图所示:
图1. 4
各个针头的含义,及与PC上DB9公头的连接请参考下表
针号 |
含义 |
线色 |
PC(DB9) |
1 |
向天线供电 |
棕 |
|
2 |
输入电压(10~20V) |
红 |
|
3 |
TXD |
橙 |
2 RXD |
4 |
RXD |
黄 |
3 TXD |
5 |
SG |
蓝 |
5 SG |
第2章流控制
啥叫流控制?串行通讯的双方,如果有一方反应较对方慢,就可能来不及处理对方发来的串口数据,导致串口数据丢失甚至通讯失败。因此,需要对串口数据流进行控制,使其准确无误的相互传输。
流控制也被称之为握手,分为软件控制(XON/XOFF)和硬件控制(DTR或RTS)。本章重点讲述如何编码实现RTS硬件流控制。
2.1 Quick Basic
1998年,笔者就使用Quick Basic编写了PC与PC-E500的串行通讯程序。当时对RTS硬件流控制理解不够深刻,编写出来的程序在向PC-E500发送数据时,经常发生通讯错误。后来,采用了PC发一行数据就等待1秒的笨方法缓解了这一错误。但这一问题始终没有根除。理解了RTS硬件流控制,这一问题就迎刃而解了。
关键的代码在于打开串口,以前的代码是这样的:
OPEN "COM1:1200,N,8,1,DS,RS" FOR OUTPUT AS 1
改成这样就行了:
OPEN "COM1:1200,N,8,1,DS,CS60000" FOR OUTPUT AS 1
重要参数的说明:
1、DS
使用OPEN "COM1:1200,N,8,1"打开串口时,QB会检查串口的DSR是否为高电平,即对方的DTR是否为高电平。如果不是高电平就会提示错误。如果PC-E500已经开机,并且已经打开了串口(运行LOAD"COM:"),那么它的DTR就是高电平,OPEN语句就能正常运行,否则就会出错。为了在打开串口时不检查DSR,请使用DS0或DS。
2、RS
使用OPEN "COM1:1200,N,8,1"打开串口时,RTS硬件流控制即被启用,即:打开串口时会设置RTS为高电平,同时发送数据时(如:PRINT#1,"123";)会检查CTS是否为高电平,不是高电平表明对方较忙我得等一会儿。最多等待1秒,如果CTS还是低电平,发送数据的PRINT#1语句就会出错。
OPEN "COM1:1200,N,8,1,RS"语句里增加了RS参数,其含义为:禁用RTS硬件流控制,即:打开串口时设置RTS为低电平,同时发送数据时不再检查CTS是否为高电平。以前增加RS参数应该是为了避免PRINT#1时出错,其实设置好等待时间问题就解决了。
3、CS60000
CS60000表示等待CTS信号为高电平时最多等待60000毫秒(即60秒),这个数值最大只能为65535。
CS0或CS表示不等待CTS信号为高电平,亦即禁用RTS硬件流控制。
总结:
1、QB在OPEN语句里通过参数RS来控制是否使用RTS硬件流控制;
2、使用RTS硬件流控制时,应该在OPEN语句里设置等待时间,即CS参数。
2.2 Visual Basic 6.0
VB6.0一般使用MSCOMM控件进行串行通讯。控制MSCOMM是否启用RTS硬件流控制太简单了:只要设置它的Handshaking为comRTS即可。
那么MSCOMM控件等待CTS为高电平的时间是多少呢?请参考代码:
Private Type COMMTIMEOUTS ReadIntervalTimeout As Long ReadTotalTimeoutMultiplier As Long ReadTotalTimeoutConstant As Long WriteTotalTimeoutMultiplier As Long WriteTotalTimeoutConstant As Long End Type
Private Declare Function GetCommTimeouts Lib "kernel32" (ByVal hFile As Long, lpCommTimeouts As COMMTIMEOUTS) As Long
Private Sub Command1_Click() MSComm1.CommPort = 1 MSComm1.PortOpen = True '打开COM1 Dim tm As COMMTIMEOUTS GetCommTimeouts MSComm1.CommID, tm '获得等待时间的设置 End Sub |
关键代码就一行:GetCommTimeouts MSComm1.CommID, tm。它调用了API函数GetCommTimeouts。第一个参数是打开串口的句柄,传递MSCOMM控件的CommID即可;第二个参数用来获得等待时间信息,结果如下:
图2.1
也就是说MSCOMM控件在等待CTS信号时,最多等待5秒。修改等待时间可调用SetCommTimeouts函数。
2.3 读取超时
上图中的ReadIntervalTimeout、ReadTotalTimeoutMultiplier、ReadTotalTimeoutConstant用来控制读取串口数据时的等待时间。
ReadIntervalTimeout 用来设置两个字符之间的等待时间。如:设置ReadIntervalTimeout为10,则ReadFile在读取下一个字符时最多等待10ms。超过这个时间ReadFile将返回。
ReadTotalTimeoutConstant和ReadTotalTimeoutMultiplier用来设置总的等待时间。总等待时间首先被设置为ReadTotalTimeoutConstant,读取到n个字符后总等待时间将加上n×ReadTotalTimeoutMultiplier
最常见的设置方法为(上图也是这么设置的):
ReadIntervalTimeout = MAXDWORD;
ReadTotalTimeoutMultiplier = 0;
ReadTotalTimeoutConstant = 0;
它的含义为:ReadFile只会读取串口缓冲区内的数据,不会做任何等待。当串口缓冲区内没有数据时,ReadFile也会立即返回。
2.4 串口驱动缓存
有些串口的驱动程序没有提供缓存,而有些串口的驱动程序提供了缓存。发送数据时,这两种串口的表现是不一致的。
笔者的电脑上有两种串口:COM1是PC自带的,其驱动为微软的标准串口驱动;COM17是USB转串口,其驱动是生产厂家开发的。用这两种串口发送数据时,等待CTS信号的流程有所不同。
对于COM1而言,它会立即检查CTS信号是否为高电平。如果是低电平就会等待,等待时间按下式计算:
上式中的Bytes就是WriteFile要写入的字节数。
对于COM17而言,它会首先检查驱动提供的缓冲区是否已满?如果未满就把待写入的数据直接复制到缓冲区内。如果缓冲区已满,WriteFile就会检查CTS是否为高电平,其后的处理流程与COM1的一致。
同样的代码,COM1和COM17的表现是不一致的:
DWORD dwWrite = 0; WriteFile(hComm,"123...",4096,&dwWrite,NULL); WriteFile(hComm,"123...",2,&dwWrite,NULL); |
第一个WriteFile写入4096字节。对于COM1而言会检查CTS信号,可能会等待较长时间;对于COM17而言会立即返回,同时4096字节的数据将填满驱动提供的发送缓冲区。
第二个WriteFile写入2字节。COM17的发送缓冲区已满,因此它与COM1的表现就没有区别的,两者等待的时间相同。