Chapter 4, Advanced Serial Programming
第四章,高级串口编程
Carol: 已获原作者简体中文翻译授权。
为避免重复劳动,愿意参与翻译的朋友请直接与我联系 puccacarol AT hotmail DOT com
原文名称:Serial Programming Guide for POSIX Operating Systems
<http://www.easysw.com/~mike/serial/>
此翻译为初稿,语言较粗糙,不断完善中。。。 欢迎大家提出宝贵意见。
This chapter covers advanced serial programming techniques using the ioctl(2) and select(2) system calls.
Serial Port IOCTLs
In Chapter 2, Configuring the Serial Port we used the tcgetattr and tcsetattr functions to configure the serial port. Under UNIX these functions use the ioctl(2) system call to do their magic. The ioctl system call takes three arguments:
int ioctl(int fd, int request, ...);
The fd argument specifies the serial port file descriptor. The request argument is a constant defined in the
<termios.h> header file and is typically one of the constants listed in Table 10.
本章主要谈论使用ioctl(2)和select(2)系统调用的高级串口编程技术。
串行端口 IOCTLs
在第二章,配置串口中我们使用了 tcgetattr 和 tcsetattr 函数来做串口的设置。在 UNIX 下,这些函数都是使用 ioctl(2) 系统调用来完成他们的任务的。ioctl 系统调用有如下三个参数:
int ioctl(int fd, int request, ...);
参数 fd 指定了串行端口的文件描述符。
参数 request 是一个定义在 <termios.h> 头文件里的常量,它的一些常用值都在 表10 里面定义了。
Table 10 - IOCTL Requests for Serial Ports
表10 - IOCTL 的串口调用
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, 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.
在指定时间后发送
break
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.
返回 “MODEM” 位的状态
None
TIOCMSET
Sets the state of the "MODEM" bits.
设置“MODEM”位的状态
None
FIONREAD
Returns the number of bytes in the input buffer.
返回输入缓冲区内的字节数
None
Getting the Control Signals
The TIOCMGET ioctl gets the current "MODEM" status bits, which consist of all of the RS-232 signal lines except RXD and TXD, listed in Table 11.
To get the status bits, call ioctl with a pointer to an integer to hold the bits, as shown in Listing 5.
获得控制信号
TIOCMGET - ioctl 获得当前“MODEM”的状态位,其中包括了除 RXD 和 TXD 之外,所有的RS-232 信号线,见列表 11。
为了获得状态位,使用一个包含比特位的整数的指针来调用 ioctl,见清单5。
Listing 5 - Getting the MODEM status bits.
清单 5 - 读取 DODEM 的状态位.
#include <unistd.h>
#include <termios.h>
int fd;
int status;
ioctl(fd, TIOCMGET, &status);
Table 11 - Control Signal Constants
表 11 - 控制信号常量
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)
Setting the Control Signals
The TIOCMSET ioctl sets the "MODEM" status bits defined above. To drop the DTR signal you can use the code in Listing 6.
设置控制信号
TIOCMSET - ioctl 设置“MODEM”上述定义的状态位。可以使用 清单6 的代码来给DTR信号置低。
Listing 6 - Dropping DTR with the TIOCMSET ioctl.
清单 6 - 使用 TIOCMSET ioctl 置低 DTR 信号
#include <unistd.h>
#include <termios.h>
int fd; int status;
ioctl(fd, TIOCMGET, &status);
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
The bits that can be set depend on the operating system, driver, and modes in use. Consult your operating system documentation for more information.
能进行设置的比特位有操作系统,驱动以及使用的模式决定。查询你的操作系统的档案可以获取更多的信息。
Getting the Number of Bytes Available
The FIONREAD ioctl gets the number of bytes in the serial port input buffer. As with TIOCMGET you pass in a pointer to an integer to hold the number of bytes, as shown in Listing 7.
获取可供读取的字节数
FIONREAD - ioctl 读取串行端口输入缓冲区中的字节数。与 TIOCMGET 一起传递一个包含字节数的整数的指针,如 清单7 所示。
Listing 7 - Getting the number of bytes in the input buffer.
清单7 - 读取串行端口输入缓冲区中的字节数
#include <unistd.h>
#include <termios.h>
int fd;
int bytes;
ioctl(fd, FIONREAD, &bytes);
This can be useful when polling a serial port for data, as your program can determine the number of bytes in the input buffer before attempting a read.
在查询串口是否有数据到来的时候这一段是很有用的,可以让程序在准备读取之前用来确定输入缓冲区里面的可读字节数。
Selecting Input from a Serial Port
While simple applications can poll or wait on data coming from the serial port, most applications are not simple and need to handle input from multiple sources.
UNIX provides this capability through the select(2) system call. This system call allows your program to check for input, output, or error conditions on one or more file descriptors. The file descriptors can point to serial ports, regular files, other devices, pipes, or sockets. You can poll to check for pending input, wait for input indefinitely, or timeout after a specific amount of time, making the select system call extremely flexible.
Most GUI Toolkits provide an interface to select; we will discuss the X Intrinsics ("Xt") library later in this chapter.
从串行端口选择输入
虽然简单的程序可以通过poll串口或者等待串口数据到来读取串口,大多数的程序却并不这么简单,有时候需要处理来自多个源的输入。
UNIX 通过 select(2) 系统调用提供了这种能力。这个系统调用允许你的程序从一个或多个文件描述符获取输入/输出或者错误信息。文件描述符可以指向串口,普通文件,其他设备,管道,或者socket 。你可以查询待处理的输入,等待不确定的输入,或者在一指定的超时到达后超时退出,这些都使得 select 系统调用非常的灵活。
大多数的 GUI 工具提供一个借口指向 select,我们会在本章后面的部分讨论 X Intrinsics 库(“Xt”)。
The SELECT System Call
The select system call accepts 5 arguments:
int select(int max_fd, fd_set *input, fd_set *output, fd_set *error, struct timeval *timeout);
The max_fd argument specifies the highest numbered file descriptor in the input, output, and error sets. The input, output, and error arguments specify sets of file descriptors for pending input, output, or error conditions; specify NULL to disable monitoring for the corresponding condition. These sets are initialized using three macros:
FD_ZERO(fd_set);
FD_SET(fd, fd_set);
FD_CLR(fd, fd_set);
The FD_ZERO macro clears the set entirely. The FD_SET and FD_CLR macros add and remove a file descriptor from the set, respectively.
The timeout argument specifies a timeout value which consists of seconds (timeout.tv_sec) and microseconds (timeout.tv_usec). To poll one or more file descriptors, set the seconds and microseconds to zero. To wait indefinitely specify NULL for the timeout pointer.
The select system call returns the number of file descriptors that have a pending condition, or -1 if there was an error.
SELECT 系统调用
Select 系统调用可以接受 5 个参数:
int select(int max_fd, fd_set *input, fd_set *output, fd_set *error, struct timeval *timeout);
参数 max_fd 定义了所有用到的文件描述符(input, output, error集合)中的最大值。
参数 input, output, error 定义了待处理的输入,输出,错误情况;置 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)组成的一个超时值。要查询一个或多个文件描述符,把秒和毫秒置为 0 。要无限等待的话就把 timeout 指针置 NULL 。
select 系统调用返回那个有待处理条件的文件描述符的值,或者,如果有错误发生的话就返回 -1。
Using the SELECT System Call
Suppose we are reading data from a serial port and a socket. We want to check for input from either file descriptor, but want to notify the user if no data is seen within 10 seconds. To do this we'll need to use the select system call, as shown in Listing 8.
使用 select 系统调用
假设我们正在从一个串口和一个socket 读取数据。我们想确认来自各文件描述符的输入,又希望如果10秒钟内都没有数据的话通知用户。要完成这些工作,我们可以像 清单8 这样使用select系统调用:
Listing 8 - Using SELECT to process input from more than one source.
清单8 - 使用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 结构*/
timeout.tv_sec = 10;
timeout.tv_usec = 0;
/* Do the select */
n = select(max_fd, &input, NULL, NULL, &timeout);
/* 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();
}
You'll notice that we first check the return value of the select system call. Values of 0 and -1 yield the appropriate warning and error messages. Values greater than 0 mean that we have data pending on one or more file descriptors.
To determine which file descriptor(s) have pending input, we use the FD_ISSET macro to test the input set for each file descriptor. If the file descriptor flag is set then the condition exists (input pending in this case) and we need to do something.
你会发现,我们首先检查select系统调用的返回值。如果是 0 和 -1 则表示相应的警告和错误信息。大于 0 的值代表我们在一个或多个文件描述符上有待处理的数据。
要知道是哪个文件描述符有待处理的输入,我们要使用 FDISSET 宏来测试每个文件描述符的输入集合。如果文件描述符标志被设置了,则符合条件(这时候就有待处理的输入了)我们需要进行一些操作。
Using SELECT with the X Intrinsics Library
The X Intrinsics library provides an interface to the select system call via the XtAppAddInput(3x) and XtAppRemoveInput(3x) functions:
int XtAppAddInput(XtAppContext context, int fd, int mask, XtInputProc proc, XtPointer data);
void XtAppRemoveInput(XtAppContext context, int input);
The select system call is used internally to implement timeouts, work procedures, and check for input from the X server. These functions can be used with any Xt-based toolkit including Xaw, Lesstif, and Motif.
The proc argument to XtAppAddInput specifies the function to call when the selected condition (e.g. input available) exists on the file descriptor. In the previous example you could specify the process_fd or process_socket functions.
Because Xt limits your access to the select system call, you'll need to implement timeouts through another mechanism, probably via XtAppAddTimeout(3x).
在X Intrinsics library 中使用select
X Intrinsics library 提供了一个接口,通过 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 Server 的输入。这些函数可以在任何 基于Xt 的工具上使用,包括Xaw, Lesstif, 和 Motif。
XtAppAddInput 的参数 proc 定义了当某一个文件描述符上的select条件满足时函数的调用(比如有输入进来)。在之前的例子里,你可以定义process_fd 或 processs_socket 函数。
由于 Xt 限制了你对 select 系统调用的访问,你需要通过其他机制实现超时,可以通过 XtAppAddTimeout(3x)。