Chinaunix首页 | 论坛 | 博客
  • 博客访问: 829199
  • 博文数量: 860
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-20 19:57
个人简介

对技术执着

文章分类

全部博文(860)

文章存档

2019年(16)

2018年(12)

2015年(732)

2013年(85)

2012年(15)

我的朋友

分类: LINUX

2015-03-14 16:37:26

原文地址:高级串口编程 作者:futter521

Chapter 4, Advanced Serial Programming
第四章,高级串口编程

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
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 是一个定义在 头文件里的常量,它的一些常用值都在 表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
#include
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
#include
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
#include
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
#include
#include
#include
int n;
int socket;
int fd;
int max_fd;
fd_set input;
struct timeval timeout;
FD_ZERO(input);
FD_SET(fd, input);
FD_SET(socket, input);
max_fd = (socket > fd ? socket : fd) + 1;
timeout.tv_sec = 10;
timeout.tv_usec = 0;

n = select(max_fd, &input, NULL, NULL, &timeout);

if (n < 0)
  perror("select failed");
else if (n == 0)
  puts("TIMEOUT");
else {
  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)。

 

————————————————————————————————————

关于fd_set:

select()函数主要是建立在fd_set类型的基础上的。fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作: 

fd_set set;

FD_ZERO(&set);      

FD_SET(fd, &set);   

FD_CLR(fd, &set);   

FD_ISSET(fd, &set);        

过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现 在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值 通常是1024(1位表示1个fd,该文件描述集合最多可表示1024个fd),这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏: 

fd_set set;

FD_ZERO(&set);     

FD_SET(0, &set);   

FD_CLR(4, &set);     

FD_ISSET(5, &set);   

―――――――――――――――――――――――――――――――――――――――

注意fd的最大值必须

――――――――――――――――――――――――――――――――――――――― 

select函数的接口比较简单:

int select(int nfds, fd_set *readset, fd_set *writeset,

fd_set* exceptset, struct tim *timeout); 

功能:

测试指定的fd可读?可写?有异常条件待处理?     

参数:

nfds: 需要检查的文件描述字个数(即检查到fd_set的第几位,因为打开一个文件是返回的文件句柄总是从0开始的,因此也可以说检查到fd_set的前几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)设这个值是为提高效率,使函数不必检查fd_set的所

       有1024位。

readset:  用来检查可读性的一组文件描述字。

writeset: 用来检查可写性的一组文件描述字。

exceptset:用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

timeout:有三种可能:

         1:timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回

         2:timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)

         3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回) 

返回值:     

返回对应位仍然为1的fd的总数。 

Remarks:

三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1

使用select函数的过程一般是:

先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set接着调用函数  

select测试fd_set中的所有fd最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。 

以下是一个测试单个文件描述字可读性(fd_set中相应位置1,就是在说对应的这个文件是可读的)的例子:

int isready(int fd)

{

    int rc;

    fd_set fds;

    struct tim tv;    

    FD_ZERO(&fds);

    FD_SET(fd,&fds);

    tv.tv_sec = tv.tv_usec = 0;    

    rc = select(fd+1, &fds, NULL, NULL, &tv); //第一个参数设为三组fd_set中最大的fd+1,这里只有一个文件,自然就是这个文件的fd+1

    if (rc < 0)   //error

        return -1;    

    return FD_ISSET(fd,&fds) ? 1 : 0;

 }

下面还有一个复杂一些的应用:

//这段代码将指定测试Socket的描述字的可读可写性,因为Socket使用的也是fd

uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    

{

     fd_set rfds,wfds;

 #ifdef _WIN32

     TIM tv;

 #else

     struct tim tv;

 #endif    

     FD_ZERO(&rfds);

     FD_ZERO(&wfds); 

     if (rd)   //TRUE

     FD_SET(*s,&rfds);   //添加要测试的描述字 

     if (wr)     //FALSE

       FD_SET(*s,&wfds); 

     tv.tv_sec=timems/1000;     //second

     tv.tv_usec=timems00;     //ms 

     for (;;) //如果errno==EINTR,反复测试缓冲区的可读性

          switch(select((*s)+1,&rfds,&wfds,NULL,

              (timems==TIME_INFINITE?NULL:&tv)))  //测试在规定的时间内套接口接收缓冲区中是否有数据可读

         {                                              //0--超时,-1--出错

         case 0:    

              return 0; 

         case (-1):   

              if (SocketError()==EINTR)

                   break;              

              return 0; //有错但不是EINTR 

          default:

              if (FD_ISSET(*s,&rfds)) //如果s是fds中的一员返回非0,否则返回0

                   return 1;

              if (FD_ISSET(*s,&wfds))

                   return 2;

              return 0;

         };

}

阅读(377) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册