串口本身。标准和硬件 †
串口是计算机上的串行通讯的物理接口。计算机历史上,串口以前被广泛用于连接计算机和终端设备和各种外部设备。尽管以太网接口和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的个数。非常久非常久曾经,300bps就是非常快的速度了,而如今的电脑能够处理高达430,800的RS-232速率。
表示波特率的单位还有kpbs和Mbps,1kps=1000bps而1Mbps=1000kbps。 一般有人提到串行设备的时候。它通常说可能是某种数据通讯设备-DCE(Data Communications Equipment)或者数据终端设备-DTE(Data Terminal Equipment)。它们之间的差别很easy。每一个信号对。比方传送和接收,它们俩正好是相反的。假设须要将两个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 - Data 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这个程序不会成为这个port上的“控制终端”。假设不这样做的话。全部的输入,比方键盘上过来的Ctrl+C中止信号等等。会影响到你的进程。而有些程序比方getty(1M/8)则会在打开登录进程的时候使用这个特性,可是通常情况下,用户程序不会使用这个行为。
O_NDELAY标志则是告诉UNIX,这个程序并不关心DCD信号线的状态——也就是不关心port还有一端是否已经连接。
假设不指定这个标志的话,除非DCD信号线上有space电压否则这个程序会一直睡眠。
给port上写数据 †
给port上写入数据也非常easy。使用write(2)系统调用就能够发送数据了:
n = write(fd, "ATZ ", 4); if (n < 0) fputs("write() of 4 bytes failed! ", stderr);
和写入其它设备文件的方式同样,write函数也会返回发送数据的字节数或者在错误发生的时候返回-1。
通常,发送数据最常见的错误就是EIO。当调制解调器或者数据链路将Data Carrier Detect(DCD)信号线弄掉了,就会发生这个错误。
并且。直至关闭port这个情况会一直持续。
从port上读取数据 †
从串口上读取数据的时候就得耍花招了。
由于,假设你在原数据模式(raw data mode)操作port的话。每一个read(2)系统调用都会返回从串口输入缓冲区中实际得到的字符的个数。
在不能得到数据的情况下,read(2)系统调用就会一直等着,仅仅到有port上新的字符能够读取或者发生超时或者错误的情况发生(这里跟打开方式有关。一般的打开方式应该是默认非堵塞的,这里有疑问)。假设须要read(2)函数迅速返回的话,你能够使用以下这个方式:
fcntl(fd, F_SETFL, FNDELAY);非堵塞能够使用while循环重复读取,配合超时设置
标志FNDELAY能够保证read(2)函数在port上读不到字符的时候返回0。须要回到正常(堵塞)模式的时候,须要再次在不带FNDELAY标志的情况下调用fcntl(2)函数:
fcntl(fd, F_SETFL, 0);堵塞模式能够跟时间參数配合设置超时
当然,假设你最初就是以O_NDELAY标志打开串口的,你也可在之后使用这种方法改变读取的行为方式。
关闭串口 †
能够使用close(2)系统调用关闭串口:
close(fd);
关闭串口会将DTR信号线设置成low。这会导致非常多调制解调器挂起。
配置串口 †
POSIX终端接口 †
非常多系统都支持POSIX终端(串口)接口。程序能够利用这个接口来改变终端的參数,比方。波特率,字符大小等等。要使用这个port的话。你必须将<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。
这两个选项能够保证你的程序不会变成port的全部者。而port全部者必须去处理发散性作业控制和挂断信号,同一时候还保证了串行接口驱动会读取过来的数据字节。
波特率常数(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 |
不同的系统上可能支持不同的输入输出速度。所以。通过串口连接两台机器或者设备的时候。应该将波特率设置成两者中较小的那个,即MIN(speed1, speed2)。
设置字符大小 †
设置字符大小的时候。这里却没有像设置波特率那么方便的函数。
所以,程序中须要一些位掩码运算来把事情搞定。
字符大小以比特为单位指定:
options.c_flag &= ~CSIZE; /* Mask the character size bits */ options.c_flag |= CS8; /* Select 8 data bits */
设置奇偶校验 †
与设置字符大小的方式差点儿相同。这里仍然须要组合一些位掩码来将奇偶校验设为有效和奇偶校验的类型。
UNIX串口驱动能够生成even,odd和no parity位码。设置space奇偶校验须要耍点小手段。
- No parity (8N1)
options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8;
- Even parity (7E1)
options.c_cflag |= PARENB options.c_cflag &= ~PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
- Odd parity (7O1)
options.c_cflag |= PARENB options.c_cflag |= PARODD options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS7;
- Space parity is setup the same as no parity (7S1)
options.c_cflag &= ~PARENB options.c_cflag &= ~CSTOPB options.c_cflag &= ~CSIZE; options.c_cflag |= CS8;
设置硬件流控制 †
某些版本号的UNIX系统支持通过CTS(Clear To Send)和RTS(Request To Send)信号线来设置硬件流控制。假设系统上定义了CNEW_RTSCTS和CRTSCTS常量,那么非常可能它会支持硬件流控制。
使用以下的方法将硬件流控制设置成有效:
options.c_cflag |= CNEW_RTSCTS; /* Also called CRTSCTS
将它设置成为无效的方法与此类似:
options.c_cflag &= ~CNEW_RTSCTS;
本地设置 †
本地模式成员变量c_lflag能够控制串口驱动如何控制输入字符。通常。你可能须要通过c_lflag成员来设置经典输入和原始输入模式。
成员变量c_lflag能够使用的常量
ISIG | Enable SIGINTR, SIGSUSP, SIGDSUSP, and SIGQUIT signals |
ICANON | Enable canonical input (else raw) |
XCASE | Map uppercase lowercase (obsolete) |
ECHO | Enable echoing of input characters |
ECHOE | Echo erase character as BS-SP-BS |
ECHOK | Echo NL after kill character |
ECHONL | Echo NL |
NOFLSH | Disable flushing of input buffers after interrupt or quit characters |
IEXTEN | Enable extended functions |
ECHOCTL | Echo control characters as ^char and delete as ~? |
ECHOPRT | Echo erased character as character erased |
ECHOKE | BS-SP-BS entire line on line kill |
FLUSHO | Output being flushed |
PENDIN | Retype pending input at next read or input char |
TOSTOP | Send SIGTTOU for background output |
选择经典输入 †
经典输入是以面向行设计的。在经典输入模式中输入字符会被放入一个缓冲之中。这样能够以与用户交互的方式编辑缓冲的内容。直到收到CR(carriage return)或者LF(line feed)字符。
选择使用经典输入模式的时候,你通常须要选择ICANON。ECHO和ECHOE选项:
options.c_lflag |= (ICANON | ECHO | ECHOE);
选择原始输入 †
原始输入根本不会被处理。输入字符仅仅是被原封不动的接收。
普通情况中。假设要使用原始输入模式。程序中须要去掉ICANON,ECHO。ECHOE和ISIG选项:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
输入选项 †
能够通过输入模式成员c_iflag来控制从port上收到的字符的输入过程。与c_cflag一样,c_iflag的终于值是想要使用的全部状态的位运算OR的组合。
c_iflag成员能够使用的常量
常量 | 描写叙述 |
INPCK | Enable parity check |
IGNPAR | Ignore parity errors |
PARMRK | Mark parity errors |
ISTRIP | Strip parity bits |
IXON | Enable software flow control (outgoing) |
IXOFF | Enable software flow control (incoming) |
IXANY | Allow any character to start flow again |
IGNBRK | Ignore break condition |
BRKINT | Send a SIGINT when a break condition is detected |
INLCR | Map NL to CR |
IGNCR | Ignore CR |
ICRNL | Map CR to NL |
IUCLC | Map uppercase to lowercase |
IMAXBEL | Echo BEL on input line too long |
设置输入奇偶校验选项 †
当程序在c_cflag中设置了奇偶校验成员(PARENB)的时候。程序就须要将输入奇偶校验设置成为有效。与奇偶校验相关的常量有INPCK,IGNPAR,PARMRK和ISTRIP。普通情况下,你可能须要选择INPCK和ISTRIP将奇偶校验设置为有效同一时候从接收字串中脱去奇偶校验位:
options.c_iflag |= (INPCK | ISTRIP);
IGNPAR是一个比較危急选项。即便有发生错误时。它也会告诉串口驱动直接忽略奇偶校验错误给数据放行。
这个选项在測试链接的通讯质量时比較实用而通常不会被用在实际程序中。
PARMRK会导致奇偶校验错误被标志成特殊字符增加到输入流之中。
假设IGNPAR选项也是有效的,那么一个NUL(八进制000)字符会被增加到发生奇偶校验错误的字符前面。否则,DEL(八进制177)和NUL字符会和出错的字符一起送出。
设置软件流控制 †
软件流控制能够通过IXON,IXOFF和IXANY常量设置成有效:
options.c_iflag |= (IXON | IXOFF | IXANY);
将其设置为无效的时候,非常easy,仅仅须要对这些位取反:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
XON(start data)和XOFF(stop data)字符却是在c_cc数组中定义的,以下会具体描写叙述这个数组。
输出选项 †
成员变量c_oflag之中包含了输出过滤选项。
和输入模式相似,程序能够选择使用经过加工的或者原始的数据输出。
c_oflag成员的常量
常量 | 描写叙述 |
OPOST | Postprocess output (not set = raw output) |
OLCUC | Map lowercase to uppercase |
ONLCR | Map NL to CR-NL |
OCRNL | Map CR to NL |
NOCR | No CR output at column 0 |
ONLRET | NL performs CR function |
OFILL | Use fill characters for delay |
OFDEL | Fill character is DEL |
NLDLY | Mask for delay time needed between lines |
NL0 | No delay for NLs |
NL1 | Delay further output after newline for 100 milliseconds |
CRDLY | Mask for delay time needed to return carriage to left column |
CR0 | No delay for CRs |
CR1 | Delay after CRs depending on current column position |
CR2 | Delay 100 milliseconds after sending CRs |
CR3 | Delay 150 milliseconds after sending CRs |
TABDLY | Mask for delay time needed after TABs |
TAB0 | No delay for TABs |
TAB1 | Delay after TABs depending on current column position |
TAB2 | Delay 100 milliseconds after sending TABs |
TAB3 | Expand TAB characters to spaces |
BSDLY | Mask for delay time needed after BSs |
BS0 | No delay for BSs |
BS1 | Delay 50 milliseconds after sending BSs |
VTDLY | Mask for delay time needed after VTs |
VT0 | No delay for VTs |
VT1 | Delay 2 seconds after sending VTs |
FFDLY | Mask for delay time needed after FFs |
FF0 | No delay for FFs |
FF1 | Delay 2 seconds after sending FFs |
选择加工过的输出 †
通过在c_oflag成员变量中设置OPOST选项的方法程序能够选择加工过的输入。
options.c_oflag |= OPOST;
在全部选项其中,你可能只须要使用ONLCR选项来将行分隔符映射到CR-LF组合对上。其它选项主要是历史遗留,只与行打印机和终端跟不上串行数据的年代有关。
选择原始输出 †
原始输出方式能够通过在c_oflag中重置OPOST选项来选择:
options.c_oflag &= ~OPOST;
假设OPOST选项被设置成无效的话,其它c_oflag中的选项都会失效。
控制字符 †
字符数组c_cc里面包含了控制字符的定义和超时參数。这个数组的每一个元素都是以常量定义的。
成员变量c_cc中的控制字符
常量 | 描写叙述 | 键 |
VINTR | Interrupt | CTRL-C |
VQUIT | Quit | CTRL-Z |
VERASE | Erase | Backspace (BS) |
VKILL | Kill-line | CTRL-U |
VEOF | End-of-file | CTRL-D |
VEOL | End-of-line | Carriage return (CR) |
VEOL2 | Second end-of-line | Line feed (LF) |
VMIN | Minimum number of characters to read | - |
VSTART | Start flow | CTRL-Q (XON) |
VSTOP | Stop flow | CTRL-S (XOFF) |
VTIME | Time to wait for data (tenths of seconds) | - |
设置软件流控制字符 †
用来做软件流控制的字符包括在数组c_cc的VSTART和VSTOP元素里面。通常情况下,它们应该被设置成DC1(八进制021)和DC3(八进制023)。它们在ASCII标准中代表着XON和XOFF字符。
设置读取超时 †
UNIX串口驱动提供了设置字符和包超时的能力。
数组c_cc中有两个元素能够用来设置超时:VMIN和VTIME。在经典输入模式或者通过open(2)和fcntl(2)函数传递NDELAY选项时,超时设置会被忽略。
VMIN能够指定读取的最小字符数。假设它被设置为0,那么VTIME值则会指定每一个字符读取的等待时间。
假设VMIN不为零,VTIME会指定等待第一个字符读取操作的时间。假设在这个指定时间中能够開始读取某个字符,直到VMIN个数的所有字符所有被读取,其它读取操作将会被堵塞(等待)。也就是说,一旦读取第一个字符,串口驱动的预期就是接收到整个字符包(一共VMIN字节)。假设在同意的时间内没有字符被读取。那么read(2)调用就会返回0。通过这种方法能够确切得告诉串口驱动程序须要读取N个字节,并且read(2)调用仅仅会返回N或者0。然而,超时设置仅仅对第一个字符的读取操作有效。所以,假设由于某些原因驱动程序在N字节的包中丢失某个字符的话,read(2)调用将会一直等下去。
VTIME能够以十分之中的一个秒为单位指定等待字符输入的时间。假设VTIME设置为0(默认情况)。除非open(2)或者fcntl(2)函数设置了NDELAY选项,否则read(2)将会永久得堵塞(等待)。
调制解调器通讯 †
说到串口通讯就不得不提一下通过调剂解调器通讯的方式。这里给出的程序样例都适用于支持“其实的”标准AT命令集的调制解调器。
什么是调制解调器 †
调制解调器是一种能够将数字信号的串行数据转化为模拟信号频率的设备。通过这样的转换,信息就能够通过像电话线或者有线电视线缆那样的模拟数据链路来传输了。
口语中,常常将调制解调器称作“猫”。标准的电话调制解调器能够将串行数据转化为能够通过电话线传输的音频;由于这样的转化很之快又很复杂,所以假设你去听一下的话。这些音频很像是大声尖叫时发出来的声音。
今天能够见到的调制解调器能够通过电话线每秒传输53000比特——5.3Kbps——的数据。还有就是,大多数调制解调器都使用数据压缩技术,这样就能够将某些类型数据的传输比特率提高到100kbps。
与调制解调器通讯 †
于调制解调器通讯的第一步就是要以原始输入模式打开和配置串口。
int fd; struct termios options;
/* open the port */ fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); fcntl(fd, F_SETFL, 0); /* get the current options */ tcgetattr(fd, &options); /* set raw input, 1 second timeout */ options.c_cflag |= (CLOCAL | CREAD); options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_oflag &= ~OPOST; options.c_cc[VMIN] = 0; options.c_cc[VTIME] = 10; /* set the options */ tcsetattr(fd, TCSANOW, &options);
接下来就须要和调制解调器建立通讯连接。
最好的办法就是给调制解调器发送“AT”命令。这也会让比較仅仅能的调制解调器探測到你正在使用的波特率。
假设正确地连接到调制解调器上,而且调制解调器开启电源。它会返回一个回应信号“OK”。
int /* O - 0 = MODEM ok, -1 = MODEM bad */ init_modem(int fd) /* I - Serial port file */ { char buffer[255]; /* Input buffer */ char *bufptr; /* Current char in buffer */ int nbytes; /* Number of bytes read */ int tries; /* Number of tries so far */ for (tries = 0; tries < 3; tries ++) { /* send an AT command followed by a CR */ if (write(fd, "AT ", 3) < 3) continue; /* read characters into our string buffer until we get a CR or NL */ bufptr = buffer; while ((nbytes = read(fd, bufptr, buffer + sizeof(buffer) - bufptr - 1)) > 0) { bufptr += nbytes; if (bufptr[-1] == ' ' | bufptr[-1] == ' ') break; } /* nul terminate the string and see if we got an OK response */ *bufptr = ' '; if (strncmp(buffer, "OK", 2) == 0) return (0); } return (-1); }
标准调制解调器命令 †
大多数调制解调器都支持“AT”命令集。之所以这样叫是由于这个命令集中的每一个命令都是以“AT”字符开头。每一个命令都是以第一列的AT开头字符后面跟上特殊命令參数和一个回车符CR(八进制015)。调制解调器处理完这条命令之后会依据命令回复一些文本消息。
- ATD 拨号 [#b39592a6]
通过ATD命令能够拨打一个指定号码。除过号码和分隔符(-)以外,你还能够指定以音频("T")或者脉冲("P")方式拨号,暂停一秒(",")和等待拨号音("W"):
ATDT 555-1212 ATDT 18008008008W1234,1,1234 ATD T555-1212WP1234
调制解调器可能回复以下列出的某个消息:
NO DIALTONE BUSY NO CARRIER CONNECT CONNECT baud
- ATH 挂断
通过ATH命令能够让调制解调器挂断。
由于,调制解调器假设在“命令”模式的话,你可能就不能打普通电话了。
假设DTR信号线掉了的话,大部分调制解调器也会挂断。
你能够将波特率设置成0而且持续至少1秒来做到这一点。再次让DTR掉落相同也能够把调制解调器又一次拉回命令模式。
调制解调器成功挂断以后。它会回复一个"NO CARRIER"回来。假设调制解调器仍然保持连接。它则会发送"CONNECT"或者"CONNECT baud"这种消息。
- ATZ 重置调制解调器
通过ATZ命令能够重置调制解调器。
重置之后它会回复字符串"OK"。
- 与调制解调器通讯的常见问题
首先,也是最重要的一点。千万不要使用回声输入(input echoing)。回声输入会导致调制解调器和计算机之间产生反馈循环。
其次,当发送调制解调器命令时,命令必须以回车(CR)而不是换行(NL)结束。C语言中回车的字符常量是" "。
最后,处理调制解调器通讯的时候,要一定保证你使用了调制解调器支持的波特率。尽管大多数调制解调器都支持自己主动探測波特率,但你也会注意到某些(一般是19.2kbps或者比較老的调制解调器)有局限性。
高级串口编程 †
所谓高级串口编程事实上说的就是使用更直接的底层的ioctl(2)和select(2)系统调用来操作串口。
串口的ioctl †
前文中以前提到使用tcgetattr和tcsetattr函数来配置串口。UNIX环境下,这些函数都是使用ioctl(2)系统调用来实现的。
系统调用ioctl能够带三个參数:
int ioctl(int fd, int request, ...);
显然,fd參数对于串口编程来说就是串口设备文件的文件描写叙述符咯。而request參数是在<termios.h>头文件里定义的常量,并且一般不会超出下表所列的范围。
串口的IOCTL请求
REQUEST | 描写叙述 | POSIX函数 |
TCGETS | Gets the current serial port settings. | tcgetattr |
TCSETS | Sets the serial port settings immediately. | tcsetattr(fd, TCSANOW, &options) |
TCSETSF | Sets the serial port settings after flushing the input and output buffers. | tcsetattr(fd, TCSAFLUSH, &options) |
TCSETSW | Sets the serial port settings after allowing the input and output buffers to drain/empty. | tcsetattr(fd, TCSADRAIN, &options) |
TCSBRK | Sends a break for the given time. | tcsendbreak, tcdrain |
TCXONC | Controls software flow control. | tcflow |
TCFLSH | Flushes the input and/or output queue. | tcflush |
TIOCMGET | Returns the state of the "MODEM" bits. | None |
TIOCMSET | Sets the state of the "MODEM" bits. | None |
FIONREAD | Returns the number of bytes in the input buffer. | None |
取得控制信号 †
TIOCMGET ioctl能够取得当前调制解调器的状态位。这个状态位囊括了除去RXD和TXD信号线的全部RS-232信号。这些都在下表中列出。
控制信号常量
常量 | 描写叙述 |
TIOCM_LE | DSR (data set ready/line enable) |
TIOCM_DTR | DTR (data terminal ready) |
TIOCM_RTS | RTS (request to send) |
TIOCM_ST | Secondary TXD (transmit) |
TIOCM_SR | Secondary RXD (receive) |
TIOCM_CTS | CTS (clear to send) |
TIOCM_CAR | DCD (data carrier detect) |
TIOCM_CD | Synonym for TIOCM_CAR |
TIOCM_RNG | RNG (ring) |
TIOCM_RI | Synonym for TIOCM_RNG |
TIOCM_DSR | DSR (data set ready) |
比如以下这个程序片段,你能够通过给ioctl带一个用来保存状态位的整形变量的指针来取得状态位。
#include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status);
设置控制信号 †
TIOCMSET ioctl能够设置上面定义的调制解调器状态位。
以下的样例展示怎样使用它来将DTR信号线设成掉线状态。
#include <unistd.h> #include <termios.h> int fd; int status; ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &status);
可能被设置的状态位取决于操作系统,驱动和正在使用的模式。关于更具体的信息应该去看下面你所使用的操作系统的文档。