串口是计算机上的串行通讯的物理接口。串口在计算机历史上被广泛应用于连接计算机、终端设备和各种外部设备。尽管以太网接口和USB接口也通过串行流传输数据,但串行连接通常是指那些与RS-232标准兼容硬件或调制解调器接口。虽然现在在很多个人电脑上,连接外部设备的串口已经广泛使用了USB和Firewire替换;用于连接网络的串口被以太网取代,用于连接终端的串口设备被以太网取代MDA或者VGA取而代之。但一方面,由于串口本身成本低,技术成熟,另一方面,由于串口控制台的功能RS-232标准高度标准化,非常普及,至今仍广泛应用于各种设备。一些计算机使用一个叫做UART串口设备采用集成电路。集成电路可以转换字符和异步串行通信序列,并自动处理数据时序。而且一些低端设备会让CPU该技术称为输出针直接传输数据bit-banging。因为串口,RS-232和UARTs基本上总是出现在同一个语境中,所以这些名词通常是混淆的。下面逐一解释以下一些重要的名词和术语。
串行通信是什么??
计算机每次可以传输一个或多个位置(bit)数据。串行是指每次只传输一个(1bit)数据。当需要通过串行通信传输单词时。(word)每次只能接收或发送数据。每个位置可能是on(1)或者off(0)。常用于许多技术术语。mark表示on,而space表示off。
串行数据的速度通常是每秒传输的字节数bits-per-second(bps)或者波特率(baud)表示。这个值表示每秒送出的0和1 的个数。很久以前,3000bps速度很快,现在电脑可以处理430,800RS-232速率。表示波特率的单位也有kpbs和 Mbps,1kps=1000bps而1Mbps=1000kbps。一般来说,当有人提到串行设备时,它可能是某种数据通信设备-DCE(Data Communications Equipment)或数据终端设备-DTE(Data Terminal Equipment)。它们之间的区别很简单,每个信号都是对的,比如传输和接收,它们恰恰相反。假如需要两个DTE或者DCE如果设备连接,需要适配器或交叉电缆交换信号。
什么是RS-232?
RS-232是EIA(Electronic Industries Association)串行通信的电气接口定义。RS-实际上有三种232(A,B和C),它们用不同的电压表示on和off。最广泛使用的是RS-232C,它将mark(on)比特的电压定义为-3V到-12V之间,而将space(off)定义电压 3V到 12V之间。虽然 RS-232C标准信号最远传输8m,但事实上,你可以用它来传输更长的距离,直到信号波特率太小。 RS-除了232连接线中用于传输数据的电线外,还有一些用于提供时序、状态和握手的电线:
RS-232 针脚定义
DB-25
针脚 | 描述 | 针脚 | 描述 | 针脚 | 描述 | 针脚 | 描述 | 针脚 | 描述 |
1 | Earth Ground | 6 | DSR - Data Set Ready | 11 | Unassigned | 16 | Secondary RXD | 21 | Signal Quality Detect |
2 | TXD - Transmitted Data | 7 | GND - Logic Ground | 12 | Secondary DCD | 17 | Receiver Clock | 22 | Ring Detect |
3 | RXD - Received Data | 8 | DCD - ata Carrier Detecter | 13 | Secondary CTS | 18 | Unassigned | 23 | Data Rate Select |
4 | RTS - Request To Send | 9 | Reserved | 14 | Secondary TXD | 19 | Secondary RTS | 24 | Transmit Clock |
5 | CTS - Clear To Send | 10 | Reserved | 15 | Transmit Clock | 20 | DTR - Data Terminal Ready | 25 | Unassigned |
DB-9
针脚 | 名称 | 全名 | 方向(主机 外设) |
3 | TD | Transmit Data | -> |
2 | RD | Receive Data | <- |
7 | RTS | Request To Send | -> |
8 | CTS | Clear To Send | <- |
6 | DSR | Data Set Ready | <- |
4 | DTR | Data Terminal Ready | -> |
1 | CD | Data Carrier Detect | <- |
9 | RI | Ring Indicator | <- |
5 | - | Signal Ground |
另外两个比较常见的串行接口的标准式RS-422和RS-574。RS-422使用更低的电压和差分信号,这样可以将传输距离扩张到300m。而RS-574定义了通常可以见到的用在电脑上的9针连接器和电压。
信号定义 ?
RS-232标准定义了18个不同的串行通信的信号。而这些之中,仅仅有如下6个可以在UNIX环境中使用。
- GND - Logic Ground
从技术角度讲,GND不能算是信号。但是没有它其他信号都不能用了。基本上,logic ground有点像一个参考电压,通过它来判断哪个电压表示正哪个电压表示负。
- TXD - Transmitted Data
TXD信号负载着从你的电脑或者设备到另一端(比如调制解调器)的数据。Mark范围的电压被解析成1,而space范围电压被解析成0。
- RXD - Received Data
RXD于TXD正好相反。它负载着从另一端的电脑或者设备上传到你的工作站的数据。Mark和space的解析方法于TXD一致。
- DCD - Data Carrier Detect
DCD信号通常来自串口连结线的另一端。这条信号线上的space电压表示另一端的电脑或者设备现在已经连接。但是,DCD信号线却不是总可以得到的,有些设备上有这条信号线,而有的则没有。
- DTR - Data Terminal Ready
DTR信号是你的工作站产生的,用以告诉另一端的电脑或者设备你已经是否已经准备好了。Space电压表示准备好了,而mark电压表示没有准备好。当你在工作站上打开串行接口时,DTR通常自动被设置位有效。
- CTS - Clear To Send
CTS则通常来自连结线的另一端。Space电压表示你可以从工作站送出更多的数据。CTS通常用来协调你的工作站和另一端之间的串行数据流。
- RTS - Request To Send
如果RTS信号被设置成space电压,这表示你准备好了一些数据需要传送。和CTS一样,RTS也被用来协调工作站和另一端的电脑或者设备之间的数据流。有些工作站上会一直将这个信号设置位space。
异步通讯 ?
计算机为了弄懂传给它的串行数据,它需要确定每个字符开始和结束的位置。这通常是用异步串行数据来完成的。
在异步模式中,除非有字符被传输,否则串行数据线总是处于mark(1)状态。有一个start位会被加入传输字符的各个位之前,在字符本身的位之后会有一个可选的parity位和一个或者多个stop位。Start位总是space(0)并且它会告诉计算机新的串行数据过来了。数据可以随时被送出或者接收,这就是所谓的异步。
#ref(): File not found: "async.gif" at page "Linux串口编程详解"
那个可选的parity位仅仅是所有传输位的和,这个和用以表示传输字符中有奇数个1还是偶数个1。在偶数parity中,如果有传输字符中有偶数个1,那么parity位被设置成0,而传输字符中有奇数个1,那么parity位被设置成1。在奇数parity中,位设置与此相反。还有一些术语,比如space parity, mark parity和no parity。Space parity是指parity位会一直被设置位0,而mark parity正好与此相反,parity会一直是1。No parity的意思就是根本不会传输parity位。剩余的位叫做stop位。传输字符之间可以有1个,1.5个或者2个stop位,而且,它们的值总是1。传统上,Stop位式用给计算机一些时间处理前面的字符的,但是它只是被用来同步接收数据的计算机和接受的字符。异步数据通常被表示成"8N1","7E1",或者与此类似的形式。这表示“8数据位,no parity和1个stop bit”,还有相应得,“7数据位,even parity和1个stop bit”。
什么是全双工和半双工 ?
全双工(Full duplex)是说计算机可以同时接受和发送数据——也就是它有两个分开的数据传输通道(一个传入,一个传出)。
半双工(Half duplex)表示计算机不能同时接受和发送数据,而在某一时刻它只能单一的传送或者接收。这通常意味着,它只有一个数据通道。半双工并不是说RS-232的某些信号不能使用,而是,它通常是使用了有别于RS-232的其他不支持全双工的标准。
什么是流控制 ?
两个串行接口之间的传输数据流通常需要协调一致才行。这可能是由于用以通信的某个串行接口或者某些存储介质的中间串行通信链路的限制造成的。对于异步数据这里有两个方法做到这一点。
第一种方法通常被叫做“软件”流控制。这种方法采用特殊字符来开始(XON,DC1,八进制数021)或者结束(XOFF,DC3或者八进制数 023)数据流。而这些字符都在ASCII中定义好了。虽然这些编码对于传输文本信息非常有用,但是它们却不能被用于在特殊程序中的其他类型的信息。
第二种方法叫做“硬件”流控制。这种方法使用RS-232标准的CTS和RTS信号来取代之前提到的特殊字符。当准备就绪时,接受一方会将CTS信号设置成为space电压,而尚未准备就绪时它会被设置成为mark电压。相应得,发送方会在准备就绪的情况下将RTS设置成space电压。正因为硬件流控制使用了于数据分隔的信号,所以与需要传输特殊字符的软件流控制相比它的速度很快。但是,并不是所有的硬件和操作系统都支持CTS/RTS流控制。
什么是BREAK ?
通常,直到有数据传输时,接收和传输信号会保持在mark电压。如果一个信号掉到space电压并且持续了很长时间,一般来说是1/4到1/2秒,那么就说有一个break条件存在了。
BREAK经常被用来重置一条数据线或者用来改变像调制解调器这样的设备的通讯模式。
同步通讯 ?
与异步数据不同,同步数据是一个稳定的字节流。为了能够在线路上读取到数据,计算机必须提供或者接受一个时钟,这样才能保证发送端和接收端同步。尽管已经有同步时钟,计算机还是必须以某种方式标志数据流的开端。做这件事情最常见的办法就是使用像Serial Data Link Control("SDLC")或者High-Speed Data Link Control("HDLC")这样的数据包通讯协议。
这些协议每个都定义了一个确定的比特序列来表示数据包的开始和结束。当然,它们也定义了一个用来表示没有数据传输的比特序列。这些比特序列可以帮助计算机识别数据包的开端。
因为同步协议可以不使用每个字符的同步比特位,所以通常它们的性能比异步通讯快最少25%,而且一般比较适用于远距离的网络链接或者有两个串口接口的配置的情况。尽管同步通讯的速度有优势,大部分RS-232硬件却不支持它,因为同步通讯需要其他的硬件和软件。
用户看到的串口和用户空间的串口编程 ?
和其他设备一样,Linux也是通过设备文件来提供访问串口的功能。当需要访问串口的时候,你只需要open相应的文件。
串口的设备文件 ?
Linux系统上一般有一个或者多个串口,而这些串口设备文件名字比较奇怪,如比下面这样
串口设备文件名
操作系统 | 串口1 | 串口2 | USB/RS-232转换器 |
Windows | COM1 | COM2 | - |
Linux | /dev/ttyS0 | /dev/ttyS1 | /dev/ttyUSB0 |
打开串口 ?
因为串口和其他设备一样,在类Unix系统中都是以设备文件的形式存在的,所以,理所当然得你可以使用open(2)系统调用/函数来访问它。但 Linux系统中却有一个稍微不方便的地方,那就是普通用户一般不能直接访问设备文件。你可以选择以下方式做一些调整,以便你编写的程序可以访问串口。
- 改变设备文件的访问权限设置 [#cd9bd1e0]
- 以root超级用户的身份运行程序 [#kdd0e577]
- 将你的程序编写位setuid程序,以串口设备所有者的身份运行程序 [#s7b703ff]
OK.假如你已经准备好了让串口设备文件可以被所有用户访问,你可以在Linux系统中实验一下下面这个程序,它可以打开计算机的串口1。
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> /* File control definitions */ #include <errno.h> #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/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { /* * Could not open the port. */ perror("open_port: Unable to open /dev/ttyS0 -"); } else { fcntl(fd, F_SETFL, 0); return (fd); } }
打开文件的选项 ?
打开串口连接的时候,程序在open函数中除了Read+Write模式以外还指定了两个选项;
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
标志O_NOCTTY可以告诉UNIX这个程序不会成为这个端口上的“控制终端”。如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。而有些程序比如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,当调制解调器或者数据链路将Data Carrier Detect(DCD)信号线弄掉了,就会发生这个错误。而且,直至关闭端口这个情况会一直持续。
从端口上读取数据 ?
从串口上读取数据的时候就得耍花招了。因为,如果你在原数据模式(raw data mode)操作端口的话,每个read(2)系统调用都会返回从串口输入缓冲区中实际得到的字符的个数。在不能得到数据的情况下,read(2)系统调用就会一直等着,只到有端口上新的字符可以读取或者发生超时或者错误的情况发生。如果需要read(2)函数迅速返回的话,你可以使用下面这个方式:
fcntl(fd, F_SETFL, FNDELAY);
标志FNDELAY可以保证read(2)函数在端口上读不到字符的时候返回0。需要回到正常(阻塞)模式的时候,需要再次在不带FNDELAY标志的情况下调用fcntl(2)函数:
fcntl(fd, F_SETFL, 0);
当然,如果你最初就是以O_NDELAY标志打开串口的,你也可在之后使用这个方法改变读取的行为方式。
关闭串口 ?
可以使用close(2)系统调用关闭串口:
close(fd);
关闭串口会将DTR信号线设置成low,这会导致很多调制解调器挂起。
配置串口 ?
POSIX终端接口 ?
很多系统都支持POSIX终端(串口)接口。程序可以利用这个接口来改变终端的参数,比如,波特率,字符大小等等。要使用这个端口的话,你必须将<termios.h>头文件包含到你的程序中。这个头文件中定义了终端控制结构体和POSIX控制函数。
与串口操作相关的最重要的两个POSIX函数可能就是tcgetattr(3)和tcsetattr(3)。顾名思义,这两个函数分别用来取得设设置终端的属性。调用这两个函数的时候,你需要提供一个包含着所有串口选项的termios结构体:
termios结构体成员
成员 | 描述 |
c_cflag | 控制选项 |
c_lflag | 行选项 |
c_iflag | 输入选项 |
c_oflag | 输出选项 |
c_cc | 控制字符 |
c_ispeed | 输入波特率(NEW) |
c_ospeed | 输出波特率(NEW) |
控制选项 ?
通过termios结构体的c_cflag成员可以控制波特率,数据的比特数,parity,停止位和硬件流控制。下面这张表列出了所有可以使用的常数。
c_cflag常数
常量 | 描述 |
CBAUD | Bit mask for baud rate |
B0 | 0 baud (drop DTR) |
B50 | 50 baud |
B75 | 75 baud |
B110 | 110 baud |
B134 | 134.5 baud |
B150 | 150 baud |
B200 | 200 baud |
B300 | 300 baud |
B600 | 600 baud |
B1200 | 1200 baud |
B1800 | 1800 baud |
B2400 | 2400 baud |
B4800 | 4800 baud |
B9600 | 9600 baud |
B19200 | 19200 baud |
B38400 | 38400 baud |
B57600 | 57,600 baud |
B76800 | 76,800 baud |
B115200 | 115,200 baud |
EXTA | External rate clock |
EXTB | External rate clock |
CSIZE | Bit mask for data bits |
CS5 | 5 data bits |
CS6 | 6 data bits |
CS7 | 7 data bits |
CS8 | 8 data bits |
CSTOPB | 2 stop bits (1 otherwise) |
CREAD | Enable receiver |
PARENB | Enable parity bit |
PARODD | Use odd parity instead of even |
HUPCL | Hangup (drop DTR) on last close |
CLOCAL | Local line - do not change "owner" of port |
LOBLK | Block job control output |
CNEW_RTSCTS/CRTSCTS | Enable hardware flow control (not supported on all platforms) |
在传统的POSIX编程中,当连接一个本地的(不通过调制解调器)或者远程的终端(通过调制解调器)时,这里有两个选项应当一直打开,一个是 CLOCAL,另一个是CREAD。这两个选项可以保证你的程序不会变成端口的所有者,而端口所有者必须去处理发散性作业控制和挂断信号,同时还保证了串行接口驱动会读取过来的数据字节。
波特率常数(CBAUD,B9600等等)通常指用到那些不支持c_ispeed和c_ospeed成员的旧的接口上。后面文章将会提到如何使用其他POSIX函数来设置波特率。
千万不要直接用使用数字来初始化c_cflag(当然还有其他标志),最好的方法是使用位运算的与或非组合来设置或者清除这个标志。不同的操作系统版本会使用不同的位模式,使用常数定义和位运算组合来避免重复工作从而提高程序的可移植性。
设置波特率 ?
不同的操作系统会将波特率存储在不同的位置。旧的编程接口将波特率存储在上表所示的c_cflag成员中,而新的接口实装则提供了c_ispeed和c_ospeed成员来保存实际波特率的值。
程序中可是使用cfsetospeed(3)和cfsetispeed(3)函数在termios结构体中设置波特率而不用去管底层操作系统接口。下面的代码是个非常典型的设置波特率的例子。
struct termios options; /* * Get the current options for the port... */ tcgetattr(fd, &options);
/* * Set the baud rates to 19200... */ cfsetispeed(&options, B19200); cfsetospeed(&options, B19200); /* * Enable the receiver and set local mode... */ options.c_cflag |= (CLOCAL | CREAD); /* * Set the new options for the port... */ tcsetattr(fd, TCSANOW, &options);
函数tcgetattr(3)会将当前串口配置回填到termio结构体option中。然后,程序设置了输入输出的波特率并且将本地模式 (CLOCAL)和串行数据接收(CREAD)设置为有效,接着将新的配置作为参数传递给函数tcsetattr(3)。常量TCSANOW标志所有改变必须立刻生效而不用等到数据传输结束。其他另一些常数可以保证等待数据结束或者刷新输入输出之后再生效。
tcsetattr常量
常量 | 描述 |
TCSANOW | Make changes now without waiting for data to complete |
TCSADRAIN | Wait until everything has been transmitted |
TCSAFLUSH | Flush input and output buffers and make the change |
不同的系统上可能支持不同的输入输出速度,所以,通过串口连接两台机器或者设备的时候,应该将波特率设置成两者中较