Chapter 4, Advanced Serial Programming - JohnHau/mis GitHub Wiki
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 following:
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:
To get the status bits, call ioctl with a pointer to an integer to hold the bits:
Listing 5 - Getting the MODEM status bits.
#include <unistd.h> #include <termios.h>
int fd; int status;
ioctl(fd, TIOCMGET, &status); Setting the Control Signals The TIOCMSET ioctl sets the "MODEM" status bits defined above. To drop the DTR signal you can do:
Listing 6 - Dropping DTR with the TIOCMSET ioctl.
#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:
Listing 7 - Getting the number of bytes in the input buffer.
#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.
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.
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: Listing 8 - Using SELECT to process input from more than one source.
#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(); } 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.
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).
This appendix provides pinout information for many of the common serial ports you will find.
RS-232 Pinouts RS-232 comes in three flavors (A, B, C) and uses a 25-pin D-Sub connector:
RS-422 Pinouts RS-422 also uses a 25-pin D-Sub connector, but with differential signals:
RS-574 (IBM PC/AT) Pinouts The RS-574 interface is used exclusively by PC manufacturers and uses a 9-pin male D-Sub connector:
SGI Pinouts Older SGI equipment uses a 9-pin female D-Sub connector. Unlike RS-574, the SGI pinouts nearly match those of RS-232:
The SGI Indigo, Indigo2, and Indy workstations use the Apple 8-pin MiniDIN connector for their serial ports:
This chapter lists the ASCII control codes and their names.
Control Codes The following ASCII characters are used for control purposes: