Linux串口编程


什么是串行通信

串行传输 指每次只传输1位(bit)数据,当需要发送1字节(8bit)的数据时,就需要发送8次,每个位可能是0(off)或1(on),有些术语用mark表示on,space表示off。
串行数据的速度通常用每秒传输的字节数bps(bits-per-second)或者波特率(baud)表示。这个值表示每秒钟发送的0和1的个数。


串行信号定义

Unix下有6个定义

GND - Logic Ground
参考电压。

TXD - Transmitted Data
TXD信号:将数据发送出去,给RXD接收。

RXD - Received Data
RXDTXD相反,用于接收从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电压。


全双工与半双工

全双工(Full duplex)指 计算机或者设备 可以同时接受和发送数据。
半双工(half duplex)指 计算机或者设备 不能同时接受和发送数据,某一时刻,只能发送或者接受。


什么是控制流

两个串口之间的传输数据流通常要一致才能收到正确的数据。一般有两种方法来实现:
一种是“软件”流控制,这种方法采用特殊的字符表示数据的开始(XON,DC1,八进制021)或者结束(XOFF,DC3,八进制023)数据流。这些字符都在ASCII码中有定义,此方法一般对于传输文本有用,但是不能用于特殊程序中的其他类型信息(该信息可能含有这些特殊字符)。
另一种方法是“硬件”流控制,这种方法使用RS232标准的CTS和RTS信号来取代之前提到的特殊字符,发送方准备就绪时,会将RTS设置为space电压,接受方准备就绪时,会将CTS设置为space电压。此种方法传输数据比软件流控制速度要快,但并不是所有的硬件都支持硬件流控制。


用户看到的串口和用户空间的串口编程

Linux系统通过设备文件来访问串口功能。


linux串口的设备文件

操作系统 串口1 串口2 USB/RS232转换器
windows COM1 COM2
-
Linux /dev/ttyS0 /dev/ttyS1 /dev/ttyUSB0

打开串口

打开串口用open(2)系统调用/函数来访问。linux系统下,普通用户需要以sudo方式执行程序,才能打开串口。

#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd == -1) {
        printf("open port fail\n");
        exit(-1);
    }

    printf("open port ok\n");

    return 0;
}

打开文件的选项

选项O_RDWR 表示 读写 串口,其他参数:O_RDONLY只读,O_WRONLY只写。

O_NOCTTY 表示这个程序不会成为这个端口的控制终端,即ctrl+c不会终止该串口程序。

O_NDELAYO_NOBLOCK类似,非阻塞模式,表示程序不关心DCD信号状态,即程序不关心另一端是否已经连接。

如果不设置这个标志,除非DCD信号线上有space电压,否则这个程序会一直睡眠,即阻塞模式。


往串口上写数据

给串口写入数据(发送数据),调用write(2)系统调用/函数就可以了。

代码衔接上文。

#include <unistd.h>

write(fd, "11111", 5);

write函数会返回发送的字节数,或者返回错误-1.


阻塞与非阻塞模式从串口上读数据

如果在原始数据模式(raw data mode)下,read()函数会返回从串口输入缓冲区中实际得到的数据个数,如果不能得到数据,read()函数会一直阻塞,直到端口上有新数据可读取或者 发生读取超时、错误等情况才会返回。
如果需要read()函数立即返回,可使用如下方式:

fcntl(fd, F_SETFL, FNDELAY);

标志FNDELAY可以保证read()函数不会阻塞,若需要阻塞模式,可再次使用

fcntl(fd, F_SETFL, 0);

关闭串口

使用close()系统调用/函数来关闭打开的串口。

close(fd);

关闭串口会将DTR信号设置为space电压,这会导致很多调制解调器挂起。


配置串口

POSIX终端接口

串口需要设置 波特率,数据位,奇偶校验,停止位等。
<termios.h>头文件中定义了终端控制结构体和POSIX控制函数。

termios结构体成员:

成员 描述
c_iflag 输入模式
c_oflag 输出模式
c_cflag 控制模式
c_lflag 本地模式
c_cc[NCCS] 特殊(控制)字符
c_ispeed 输入波特率(New)
c_ospeed 输出波特率(New)

操作的函数:

int tcgetattr(int fd, struct termios *termios_p);  // 获取终端属性
int tcsetattr(int fd, int optional_actions, struct termios *termios_p);  // 设置终端属性
int tcflush(int fd, int queue_selector);  // 刷新缓存
speed_t cfgetispeed(struct termios *termios_p);  // 获取输入波特率
speed_t cfgetospeed(struct termios *termios_p);  // 获取输出波特率
int cfsetispeed(struct termios *termios_p, speed_t speed);  // 设置输入波特率
int cfsetospeed(struct termios *termios_p, speed_t speed);  // 设置输出波特率

对串口的所有操作都是通过结构体struct termios和以上操作函数实现。其中最常用的函数是tcgetattr()tcsetattr()
通常用tcgetattr()函数获取设备当前的设置,然后修改设置,最后用tcsetattr()函数使这些设置生效。

读取端口当前(旧)设置

int tcgetattr(int fd, struct termios *termios_p); // 获取终端属性
用来获取文件描述符fd所表示的设备当前设置,并写入指针termios_p所指向的成员中。
例如:

struct termios oldTio, oldStdTio;

tcgetattr(0, &oldStdTio);  // 标准输入
tcgetattr(fd, &oldTio);    // fd指向的设备


设置串口属性

例如:

struct termios newTio, newStdTio;

newTio.c_cflag = CLOCAL | CREAD;

newTio.c_cc[VMIN] = 1;
newTio.c_cc[VTIME] = 0;


写入串口属性

int tcsetattr(int fd, int optional_actions, struct termios *termios_p); // 设置终端属性
用来将termios结构体指针termios_p里的设置值赋给当前文件描述符 fd 所表示的端口。
optional_actions 变量决定设置什么时候生效,其值有:
TCSANOW ------- 修改立即生效
TCSADRAIN ------ 所有已经发送的输出写入fd后生效
TCSAFLUSH ------ 输出队列为空时生效


清空输入/输出缓存

int tcflush(int fd, int queue_selector); // 刷新缓存

tcflush()函数清空输入缓存(终端驱动程序已经收到,但用户还没有读取)或者输出缓存(用户程序已经写入数据,但还没有发送出去)。
queue_selector 的值是下列三个之一:
TCIFLUSH ------ 丢弃驱动器上已经接收到,但还没读取的所有数据。
TCOFLUSH ------ 丢弃所有已经写入驱动器,但还没发送出去的数据。
TCIOFLUSH ------ 丢弃所有输入输出队列上还没有读取或发送的数据。


控制选项

通过termios结构体的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)

通常有两个选项应该一直打开,CLOCALCREAD,它将保证你的程序不会成为端口的“所有者”,
受到挂起信号控制,如:ctrl+c。

波特率常量(CBAUD,B9600等)用于缺少c_ispeed和c_ospeed成员的旧接口。

永远不要直接初始化c_cflag(或任何其他标志)成员;您应该始终使用按位AND,OR和NOT运算符来设置或清除成员中的位。不同的操作系统版本(甚至补丁)可以并且确实以不同的方式使用这些位,因此使用按位运算符将阻止您破坏较新的串行驱动程序中所需的位标志。


设置波特率

根据操作系统的不同,波特率存储在不同的位置。较旧的接口使用表4中的一个波特率常量将波特率存储在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)函数使用当前串行端口配置填充您提供的termios结构。设置波特率并启用本地模式和串行数据接收后,我们使用tcsetattr(3)选择新配置。 TCSANOW常量指定所有更改应立即发生,而不等待输出数据完成发送或输入数据以完成接收。还有其他常量等待输入和输出完成或刷新输入和输出缓冲区。

大多数系统不支持不同的输入和输出速度,因此请务必将两者设置为相同的值以获得最大的可移植性。


设置字符大小

与波特率不同,没有便利功能来设置字符大小。相反,你必须做一些掩码来设置。字符大小以位为单位指定:

options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= CS8;    /* Select 8 data bits */

设置奇偶校验

与字符大小一样,您必须手动设置奇偶校验启用和奇偶校验类型位。 UNIX串行驱动程序支持偶数,奇数和无奇偶校验位生成。可以通过巧妙的编码来模拟空间奇偶校验。

  • 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(清除发送)和RTS(请求发送)信号线进行硬件流控制。如果在系统上定义了CNEW_RTSCTS或CRTSCTS常量,则可能支持硬件流控制。执行以下操作以启用硬件流控制:

options.c_cflag |= CNEW_RTSCTS;    /* Also called CRTSCTS */

同样,禁用硬件流控制:

options.c_cflag &= ~CNEW_RTSCTS;

本地设置

本地模式成员c_lflag控制串行驱动程序如何管理输入字符。通常,您将为规范或原始输入配置c_lflag成员。

Table 6 - Constants for the c_lflag Member
Constant Description
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(回车)或LF(换行)字符。
选择此模式时,通常选择ICANON,ECHO和ECHOE选项:

options.c_lflag |= (ICANON | ECHO | ECHOE);

选择原始输入

原始输入未经处理。输入字符在接收时完全按照接收方式传递。通常,在使用原始输入时,您将取消选择ICANON,ECHO,ECHOE和ISIG选项:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

关于输入回显的注记

在向MODEM或其他回显字符的计算机发送命令时,切勿启用输入回显(ECHO,ECHOE),因为您将在两个串行接口之间生成反馈回路!


输入选项

输入模式成员c_iflag控制对端口上接收的字符执行的任何输入处理。与c_cflag字段一样,存储在c_iflag中的最终值是所需选项的按位OR。

Table 7 - Constants for the c_iflag Member
Constant Description
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);

要禁用软件流控制,只需屏蔽这些位:

options.c_iflag&=〜(IXON | IXOFF | IXANY);

XON(起始数据)和XOFF(停止数据)字符在下面描述的c_cc数组中定义。


输出选项

c_oflag成员包含输出筛选选项。与输入模式一样,您可以选择已处理或原始数据输出。

Table 8 - Constants for the c_oflag Member
Constant Description
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;

在所有不同的选项中,您只能使用将新行映射到CR-LF对的ONLCR选项。其余的输出选项主要是历史性的,可以追溯到行式打印机和终端无法跟上串行数据流的时间!


选择原始输出

通过重置c_oflag成员中的OPOST选项来选择原始输出:

options.c_oflag&= ~OPOST;

禁用OPOST选项时,将忽略c_oflag中的所有其他选项位。


控制字符

c_cc字符数组包含控制字符定义以及超时参数。为此数组的每个元素定义常量。

Table 9 - Control Characters in the c_cc Member
Constant Description Key
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
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或fcntl在文件上设置NDELAY选项时,将忽略超时。

VMIN指定要读取的最小字符数。如果设置为0,则VTIME值指定等待每个字符读取的时间。请注意,这并不意味着对N个字节的读取调用将等待N个字符进入。而是,超时将应用于第一个字符,并且读取调用将返回立即可用的字符数(最多为您的数字)请求)。

如果VMIN不为零,则VTIME指定等待第一个字符读取的时间。如果在给定的时间内读取字符,则任何读取都将阻塞(等待),直到读取所有VMIN字符。也就是说,一旦读取了第一个字符,串行接口驱动程序就会收到整个字符包(总共VMIN字节)。如果在允许的时间内没有读取字符,则读取调用返回0.此方法允许您告诉串行驱动程序您需要正好N个字节,任何读取调用将返回0或N个字节。但是,超时仅适用于第一个字符读取,因此如果由于某种原因驱动程序错过N字节数据包中的一个字符,则读取调用可能会永远阻止等待其他输入字符。

VTIME指定在十分之几秒内等待传入字符的时间量。如果VTIME设置为0(默认值),则读取将无限期阻塞(等待),除非在打开或fcntl的端口上设置了NDELAY选项。

(没有设置NDELAY选项)
VMIN >0, VTIME = 0; read函数读到大于等于VMIN个字节,才会返回,否则阻塞,直到有大于等于VMIN个字节可读。
VMIN > 0, VTIME > 0 ; read函数若读到大于等于VMIN个字节,则会返回。
若read函数没有读到大于等于VMIN个字节,则会等待VTIME超时,若VTIME超时,并且read函数读到至少一个字节,就会返回,若没有字节可读,则会阻塞。
VMIN = 0, VTIME > 0; read函数若读到至少一个字节,就会返回,若没有字节可读,则会等到VTIME超时,若VTIME超时,则返回0。
VMIN = 0,VTIME = 0; read函数不管读没读到数据,都会马上返回0。

(设置NDELAY选项),VTIME的作用失效:
VMIN >0, VTIME = 0;
VMIN > 0, VTIME > 0 ;
VMIN = 0, VTIME > 0;
read函数不会阻塞,当有数据可读,就返回读到的数据,当没有数据可读,就返回-1。

VMIN = 0,VTIME = 0;
read函数不会阻塞,当有数据可读,就返回读到的数据,当没有数据可读,就返回0。

非阻塞模式: read没有读到数据立即返回-1

超时0秒时:  read没有读到数据立即返回 0  (设置了超时的阻塞模式)

查看linux头文件可发现:
# define FNONBLOCK O_NONBLOCK
# define FNDELAY O_NDELAY

# define O_NONBLOCK 04000
# define O_NDELAY O_NONBLOCK

由此可看出:
O_NDELAY == O_NONBLOCK == 0x4000
FNONBLOCK == O_NONBLOCK
FNDELAY   == O_NDELAY
所以 FNDELAY == FNONBLOCK

高级串口编程

介绍使用ioctl(2)和select(2)系统调用的高级串行编程技术。


串口的ioctl

上面我们使用tcgetattr和tcsetattr函数来配置串行端口。在UNIX下,这些函数使用ioctl(2)系统调用来实现它们的魔力。

ioctl系统调用有三个参数:

int ioctl(int fd,int request,...);

fd参数指定串行端口文件描述符。 request参数是<termios.h>头文件中定义的常量,通常是以下之一:

Table 10 - IOCTL Requests for Serial Ports
Request Description POSIX Function
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, TCSANOW, &options)
TCSETSW Sets the serial port settings after allowing the input and output buffers to drain/empty. tcsetattr(fd, TCSANOW, &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获得当前“MODEM”状态位,包括除RXD和TXD之外的所有RS-232信号线:

Table 11 - Control Signal Constants
Constant Description
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以保存位:

获取MODEM状态位。

#include <unistd.h>
#include <termios.h>

int fd;
int status;

ioctl(fd, TIOCMGET, &status);

设置控制信号

TIOCMSET ioctl设置上面定义的“MODEM”状态位。要删除DTR信号,您可以执行以下操作:
使用TIOCMSET ioctl删除DTR。

#include <unistd.h>
#include <termios.h>

int fd;
int status;

ioctl(fd, TIOCMGET, &status);

status &= ~TIOCM_DTR;

ioctl(fd, TIOCMSET, status);

可以设置的位取决于操作系统,驱动程序和使用的模式。有关更多信息,请参阅操作系统文档.


获取可用的字节数

FIONREAD ioctl获取串行端口输入缓冲区中的字节数。与TIOCMGET一样,您传入一个指向整数的指针来保存字节数:
获取输入缓冲区中的字节数。

#include <unistd.h>
#include <termios.h>

int fd;
int bytes;

ioctl(fd, FIONREAD, &bytes);

这在轮询串行端口以获取数据时非常有用,因为程序可以在尝试读取之前确定输入缓冲区中的字节数。


从串行端口选择输入

虽然简单的应用程序可以轮询或等待来自串行端口的数据,但大多数应用程序并不简单,需要处理来自多个源的输入。

UNIX通过select(2)系统调用提供此功能。此系统调用允许程序检查一个或多个文件描述符上的输入,输出或错误条件。文件描述符可以指向串行端口,常规文件,其他设备,管道或套接字。您可以轮询以检查挂起的输入,无限期地等待输入,或在特定时间后超时,从而使select系统调用非常灵活。

大多数GUI工具包都提供了一个选择界面;我们将在本章后面讨论X Intrinsics(“Xt”)库。


SELECT系统调用

select系统调用接受5个参数:

int select(int max_fd, fd_set *input, fd_set *output, fd_set *error,
           struct timeval *timeout);

max_fd参数指定输入,输出和错误集中编号最大的文件描述符。输入,输出和错误参数指定待处理输入,输出或错误条件的文件描述符集;指定NULL以禁用对相应条件的监视。这些集使用三个宏进行初始化:

FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);

FD_ZERO宏完全清除该集。 FD_SET和FD_CLR宏分别从集合中添加和删除文件描述符。

timeout参数指定超时值,该值包括秒(timeout.tv_sec)和微秒(timeout.tv_usec)。要轮询一个或多个文件描述符,请将秒和微秒设置为零。要无限期地等待,请为超时指针指定NULL。

select系统调用返回具有挂起条件的文件描述符数,如果有错误则返回-1。


使用SELECT系统调用

假设我们正在从串行端口和套接字读取数据。我们想要检查来自任一文件描述符的输入,但是如果在10秒内没有看到数据,则想要通知用户。为此,我们需要使用select系统调用:

使用SELECT处理来自多个源的输入。

#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>

int            n;
int            socket;
int            fd;
int            max_fd;
fd_set         input;
struct timeval timeout;

/* Initialize the input set */
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(socket, input);

max_fd = (socket > fd ? socket : fd) + 1;

/* Initialize the timeout structure */
timeout.tv_sec  = 10;
timeout.tv_usec = 0;

/* Do the select */
n = select(max_fd,  NULL, NULL, ;

/* See if there was an error */
if (n 0)
  perror("select failed");
else if (n == 0)
  puts("TIMEOUT");
else
{
  /* We have input */
  if (FD_ISSET(fd, input))
    process_fd();
  if (FD_ISSET(socket, input))
    process_socket();
}

您会注意到我们首先检查select系统调用的返回值。值0和-1会产生相应的警告和错误消息。大于0的值意味着我们在一个或多个文件描述符上有待处理的数据。

要确定哪些文件描述符具有挂起的输入,我们使用FD_ISSET宏来测试每个文件描述符的输入集。如果设置了文件描述符标志,则条件存在(在这种情况下输入挂起),我们需要做一些事情。


将SELECT与X内在函数库一起使用

X Intrinsics库通过XtAppAddInput(3x)和XtAppRemoveInput(3x)函数为select系统调用提供了一个接口:

int XtAppAddInput(XtAppContext context, int fd, int mask,
                  XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);

select系统调用在内部用于实现超时,工作过程以及检查来自X服务器的输入。这些函数可以与任何基于Xt的工具包一起使用,包括Xaw,Lesstif和Motif。

XtAppAddInput的proc参数指定当文件描述符上存在所选条件(例如,可用输入)时要调用的函数。在前面的示例中,您可以指定process_fd或process_socket函数。

因为Xt限制了对select系统调用的访问,所以你需要通过另一种机制实现超时,可能是通过XtAppAddTimeout(3x)。


ASCII控制代码

列出了ASCII控制代码及其名称。

ASCII Control Codes
Name Binary Octal Decimal Hexadecimal
NUL 00000000 000 0 00
SOH 00000001 001 1 01
STX 00000010 002 2 02
ETX 00000011 003 3 03
EOT 00000100 004 4 04
ENQ 00000101 005 5 05
ACK 00000110 006 6 06
BEL 00000111 007 7 07
BS 00001000 010 8 08
HT 00001001 011 9 09
NL 00001010 012 10 0A
VT 00001011 013 11 0B
NP, FF 00001100 014 12 0C
CR 00001101 015 13 0D
SO 00001110 016 14 0E
SI 00001111 017 15 0F
DLE 00010000 020 16 10
XON, DC1 00010001 021 17 11
DC2 00010010 022 18 12
XOFF, DC3 00010011 023 19 13
DC4 00010100 024 20 14
NAK 00010101 025 21 15
SYN 00010110 026 22 16
ETB 00010111 027 23 17
CAN 00011000 030 24 18
EM 00011001 031 25 19
SUB 00011010 032 26 1A
ESC 00011011 033 27 1B
FS 00011100 034 28 1C
GS 00011101 035 29 1D
RS 00011110 036 30 1E
US 00011111 037 31 1F


==end==

posted on 2019-08-25 11:16  wybliw  阅读(1326)  评论(0)    收藏  举报