5th Edition
Michael R. Sweet
Copyright 1994-1999, All Rights Reserved
原文:http://digilander.libero.it/robang/rubrica/serial.htm
第一次翻译文档,不当之处请指正。
《POSIX操作系统的串口编程指南》将会教你如何成功、高效和可移植的在UNIX环境或PC上对串口进行编程。每一章提供的例程都使用POSIX(Portable Standard for UNIX)终端控制函数,只需极少的修改就可运行在IRIX 、HP-UX、 SunOS、 Solaris、 Digital UNIX、 Linux等大多数类UNIX操作系统。操作系统间最大的不同是串口文件名和文件锁。
本指南包括以下章节和附录:
第一章:串口通信基础
第二章:配置串口
第三章:与MODEM通信
第四章:高级串口编程
附录A:RS-232引脚
附录B:ASCII编码
本章介绍串口通信,RS-232和其他多数计算机使用的标准,还有如何用C语言访问串口。
计算机传送信息(数据)时,每次传送一个或多个位。串口每次只传送一个位。串口通信包括多数网络设备、键盘、鼠标、MODEM和终端。
进行串口通信时,每次只发送一位,不是0就是1。你有时会听到一些专业术语,mark表示on状态,space表示off状态。
串行数据的速度通常表示为位每秒(bps)或波特率(baud),这只是表示一秒内能发送多少个数字0或1。在计算机诞生的初期,300baud已经被认为很快了,但是今天的计算机可以将RS-232的速度提高至430800baud!当波特率超过1000时,你通常会看到速率显示为kilo baud或kbps。当波特率超过1000000时,会显示为mega baud或Mbps。
当提到串行设备或串行接口时,他们通常称为数据通信设备(DCE)或数据终端设备(DTE),两者之间的区别很简单——每一对信号,例如发送和接收,它们是交叉的。如果要把两个DCE设备或两个DTE设备连接到一起,需要用串行交叉线或转接器将两个信号交换。
RS-232是 Electronic Industries Association ("EIA")定义的一个标准串行通信电气接口。RS-232事实上有三种(A、B和C),它们分别使用不同的电平定义on和off。使用最多的是RS-232C,它定义-3V到-12V的电压作为mark位(on),定义+3V到+12V的电压作为space位(off)。RS-232C标准最远可以传输8米。你通常可以使信号传的更远,只要波特率设置的够低。
除了数据输入和输出线,还有其他信号用来提供计时、状态和握手:
Table 1 - RS-232 Pin Assignments
Pin |
Description |
Pin |
Description |
1 |
Earth Ground |
14 |
Secondary TXD |
2 |
TXD- Transmitted Data |
15 |
Transmit Clock |
3 |
RXD- Received Data |
16 |
Secondary RXD |
4 |
RTS- Request To Send |
17 |
Receiver Clock |
5 |
CTS- Clear To Send |
18 |
Unassigned |
6 |
DSR- Data Set Ready |
19 |
Secondary RTS |
7 |
GND- Logic Ground |
20 |
DTR- Data Terminal Ready |
8 |
DCD- Data Carrier Detect |
21 |
Signal Quality Detect |
9 |
Reserved |
22 |
Ring Detect |
10 |
Reserved |
23 |
Data Rate Select |
11 |
Unassigned |
24 |
Transmit Clock |
12 |
Secondary DCD |
25 |
Unassigned |
13 |
Secondary CTS |
|
|
你还可以看到另外两个串行标准——RS-422和RS-573。RS-422使用更低的电平和差分信号,从而将传输距离提高到300m。RS-573定义了通常PC上使用的9针串口和电平。
RS-232为串口通信定义了18个不同的信号。在UNIX环境中通常只使用其中的6个。
严格的说,逻辑地不是一个信号,但是没有它其他信号就不能用了。基本上,逻辑地是一个参考电平,通过它来判断电压的正负。
TXD信号将数据从你的工作站中发送到另一端的计算机或设备(例如MODEM)。mark电平被当做1,space电平被当做0.
RXD信号将数据从其他计算机或设备上发送到你的工作站中。信号解析与TXD相同。
DCD信号是从位于串口线另一端的计算机或设备接收到的。该信号上的space电平意味着计算机或设备已经处于连接状态。DCD不一定有效,有些设备是没有DCD信号的。
DTR信号是由你的工作站产生的,它用来告诉另一端的计算机或设备,你已经准备好(space电平)或没有准备好(mark电平)。一般情况下,在你打开工作站的串口时,DTR就被自动使能了。
CTS信号也是从另一端的计算机接收到的。该信号上的space电平意味着你可以发送更多的数据。CTS通常被用来管理从你的工作站到另一端的数据流。
如果RTS信号被你的工作站设置为space电平,表示有很多数据已经准备好发送了。
就像CTS,RTS一样,它也用来管理工作站与另一端计算机之间的数据流。多数工作站会一直把它设为space电平。
为了让计算机理解传给它的串行数据,要用些方法来确定字符的开始和结束位置。这专门有异步串行数据来处理。
在异步模式下,串行数据线一直保持mark(1)状态,直到接收到一个字符。每个字符都有一个开始(start)位,在它之后紧跟着字符的每一位,然后是一个可选的parity(奇偶)位,还有一个或多个停止(stop)位。开始(start)位总是一个space(0),它告诉计算机新的串行数据就要到了。数据可以在任何时候发生和接收。因此叫做异步。
Figure 1 - Asynchronous Data Transmission
可选的parity位是简单的数据位的和,它表示数据中的mark(1)位的个数是奇数还是偶数。奇校验(even parity)时,如果字符中包含奇数个1,parity位就是0。偶校验(odd parity)时,如果数据中有偶数个1,parity为就是0。你可能还听说过space parity,mark parity和noparity。Space parity就是parity位一直是0,mark parity就是parity位一直是1,no parity就是没有parity位。
剩余的位叫做停止位(stop bits),可以是1位,1.5位或2位,位于字符之间,停止位的值总是1。过去,停止位用来让计算机有时间处理已经接收到的字符,但是现在,它只是用来同步接收计算机和接收的字符。
异步数据格式通常设为“8N1”,“7E1”,诸如此类。它们的意思分别是“8位数据位,没有奇偶校验,1位停止位”和“7位数据位,奇校验,1位停止位”。
全双工(Full duplex)的意思是,计算机可以同时接收和发送数据,这意味着有两个分开的数据通道(一个输入,一个输出)。
半双工的意思是,计算机不能在同一时间发生或接收数据。通常这意味着只有一个信号数据通道用来通信。这并不是有些RS-232信号没有使用,而是,通信连接使用了与RS-232不同的不支持全双工的标准。
经常有必要对两个串口间传送的数据流进行控制。这是由串行通信连接中的某个串口或存储介质的限制造成的。在异步通信中,通常有两种方法。
第一种方法叫做“软件”流控制,使用特殊的字符来开始(XON或DC1,021 octal)或停止(XOFF或DC3,023 octal)数据流。这些字符在美国信息互换标准代码(ASCII)中定义。当传输文本信息时,这些代码是有用的,它们不能用来传输没有经过特殊编程的其他类型的信息。
正常情况下,发生或接受信号会一直保持mark电平,直到一个新的字符被传送。如果一个信号降为space电平,并保持一段时间,通常是1/4到1/2秒,这时就存在break的情况了。
Break有事被用来重启通信连接或改变通信设备(如MODEM)的操作模式,第三章会深入讨论这些应用。
与异步数据不同,同步数据是一个恒定的比特流。为了读取链接中的数据,计算机必须提供或接收一个公共的位时钟,这样发送方和接收放就同步了。
即使是同步通信,计算机也必须以某种方式标记数据的开始。常见的做法是使用数据包协议,例如 Serial Data Link Control ("SDLC")或High-Speed Data Link Control ("HDLC")。
每个协议都定义了明确的比特序列来表示一个数据包的开始和结束。协议还定义了一个用来表示没有数据的比特序列。这些比特序列可以使计算机确定数据包的开端。
由于同步协议不需要在每个字符都使用同步位,它在性能上比异步通信提高了至少25%,更适合远程网络,可以配置多个串行接口。
尽管同步通信有速度优势,多数RS-232硬件还是不支持它,以免增加额外的软件和硬件。
像所有的文件一样,UNIX提供通过设备文件访问串口。要使用一个串口你只要打开相应的设备文件。
每个UNIX系统下的串口都有一个或多个设备文件(在/dev文件夹下)与它关联:
Table 2 - Serial Port Device Files
System |
Port 1 |
Port 2 |
IRIX® |
/dev/ttyf1 |
/dev/ttyf2 |
HP-UX |
/dev/tty1p0 |
/dev/tty2p0 |
Solaris®/SunOS® |
/dev/ttya |
/dev/ttyb |
Linux® |
/dev/ttyS0 |
/dev/ttyS1 |
Digital UNIX® |
/dev/tty01 |
/dev/tty02 |
串口也是一个文件,可以用open(2)函数打开。有一个问题是,UNIX系统的设备文件通常是不允许普通用户使用的。解决这个问题的办法包括:改变文件的使用权限,在root用户下运行程序,或者改变你的程序的USERID,使它可以作为设备文件的所有者运行。
现在假设设备文件可以被所有用户使用。在IRIX系统下打开串口设备的代码如下:
Listing 1 - Opening a serial port.
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
/*
* 'open_port()' - Open serial port 1.
*
* Returns the file descriptor on success or -1 on error.
*/
int
open_port(void)
{
int fd; /* File descriptor for the port */
fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
/*
* Could not open the port.
*/
perror("open_port: Unable to open /dev/ttyf1 - ");
}
else
fcntl(fd, F_SETFL, 0);
return (fd);
}
在其他操作系统上需要使用相应的设备文件名。
Open选项
你应该留言到了,我们打开设备文件的时候,在读写模式后面使用了两个标志:
fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);
O_NOCTTY标志告诉UNIX,这个程序不想成为“控制终端”。如果你不指定这个标志,任何输入(例如键盘中断信号)将会影响你的进程。getty(1M/8)程序会在启动登录进程的时候使用这个特性,正常的用户程序是不需要这个特性的。
O_NDELAY标志告诉UNIX,程序不关注DCD信号线的状态——串口的另一端是否已经打开并运行。如果不指定这个标志,你的进程将会进入睡眠状态,直到DCD信号线为space电平。
向串口写数据很容易——只需要用write(2)系统调用发生数据即可:
n = write(fd, "ATZ/r", 4);
if (n < 0)
fputs("write() of 4 bytes failed!/n", stderr);
write函数返回发生的字节数,如果调用失败返回-1。这种情况一直持续到关闭串口。
常见的错误是EIO,当MODEM或数据连接将Data Carrier Detect (DCD)信号线弄掉时会发生这个错误。
从串口读取数据有一点麻烦。当你在raw data模式下操作串口时,每个read(2)调用都会返回串口输入缓冲区里实际接收到的字符的个数。如果没有字符是有效的,函数会阻塞(等待),直到有字符输入、定时器过期、或发生错误。可以用下面的代码使read函数立即返回:
fcntl(fd, F_SETFL, FNDELAY);
FNDELAY选项会使read函数在串口没有有效字符时返回0。要恢复正常(阻塞)属性,调用没有FNDELAY选项的fcntl函数:
fcntl(fd, F_SETFL, 0);
这个函数也可以在用O_NDELAY 选项打开串口后调用。
要关闭串口,只需要使用close系统调用:
close(fd);
关闭串口后通常会将DTR信号设为低电平,这会使多数MODEM挂起。