Serial Programming/termios

Introduction
termios is the newer (now already a few decades old) Unix API for terminal I/O. The anatomy of a program performing serial I/O with the help of termios is as follows:


 * Open serial device with standard Unix system call open(2)


 * Configure communication parameters and other interface properties (line discipline, etc.) with the help of specific termios functions and data structures.


 * Use standard Unix system calls read(2) and write(2) for reading from, and writing to the serial interface. Related system calls like readv(2) and writev(2) can be used, too. Multiple I/O techniques, like blocking, non-blocking, asynchronous I/O (select(2) or poll(2), or signal-driven I/O (  signal)) are also possible. The selection of the I/O technique is an important part of the application's design. The serial I/O needs to work well with other kinds of I/O performed by the application, like networking, and must not waste CPU cycles.


 * Close device with the standard Unix system call close(2) when done.

An important part when starting a program for serial I/I is to decide on the I/O technique to deploy.

The necessary declarations and constants for termios can be found in the header file. So code for serial or terminal I/O will usually start with

Some additional functions and declarations can also be found in the,  , and   header files.

The termios I/O API supports two different modes: ''doesn't old termio do this too? if yes, move paragraphs up to the general section about serial and terminal I/O in Unix).''

1. Canonical mode. This is most useful when dealing with real terminals, or devices that provide line-by-line communication. The terminal driver returns data line-by-line.

2. Non-canonical mode. In this mode, no special processing is done, and the terminal driver returns individual characters.

On BSD-like systems, there are three modes:

1. Cooked Mode. Input is assembled into lines and special characters are processed.

2. Raw mode. Input is not assembled into lines and special characters are not processed.

3. Cbreak mode. Input is not assembled into lines but some special characters are processed.

Unless set otherwise, canonical (or cooked mode under BSD) is the default. The special characters processed in the corresponding modes are control characters, such as end-of-line or backspace. The full list for a particular Unix flavor can be found in the corresponding termios man page. For serial communication it is often advisable to use non-canonical, (raw or cbreak mode under BSD) to ensure that transmitted data is not interpreted by the terminal driver. Therefore, when setting up the communication parameters, the device should also be configured for raw/non-canonical mode by setting/clearing the corresponding termios flags. It is also possible to enable or disable the processing of the special characters on an individual basis.

This configuration is done by using the  data structure, defined in the   header. This structure is central to both the configuration of a serial device and querying its setup. It contains a minimum of the following fields:

It should be noted that real  declarations are often much more complicated. This stems from the fact that Unix vendors implement termios so that it is backward compatible with termio and integrate termio and termios behavior in the same data structure so they can avoid to have to implement the same code twice. In such a case, an application programmer may be able to intermix termio and termios code.

There are more than 45 different flags that can be set (via ) or got (via  ) with the help of the. The large number of flags, and their sometimes esoteric and pathologic meaning and behavior, is one of the reasons why serial programming under Unix can be hard. In the device configuration, one must be careful not to make a mistake.

open(2)
A few decisions have to be made when opening a serial device. Should the device be opened for reading only, writing only, or both reading and writing? Should the device be opened for blocking or non-blocking I/O (non-blocking is recommended)? And should the device be opened in exclusive mode so other programs cannot access the device once opened?

While open(2) can be called with quite a number of different flags controlling these and other properties, the following as a typical example:

Where:


 * device:The path to the serial port (e.g. /dev/ttyS0)
 * fd:The returned file handle for the device. -1 if an error occurred
 * O_RDWR:Opens the port for reading and writing
 * O_NOCTTY:The port never becomes the controlling terminal of the process.
 * O_NDELAY:Use non-blocking I/O. On some systems this also means the RS232 DCD signal line is ignored.

Note well: if present, the  flag is silently ignored by the kernel when opening a serial device like a modem.

On modern Linux systems programs like ModemManager will sometimes read and write to your device and possibly corrupt your program state. To avoid problems with programs like ModemManager you should set  on the terminal after associating a terminal with the device. You cannot open with  because it is silently ignored.

close(2)
Given an open file handle fd you can close it with the following system call

Serial Device Configuration
After a serial device has been opened you often perform two or three tasks to configure the device. First, you verify the device is indeed a serial device. Second, you configure terminal settings for the specific hardware. This step includes settings like baud rate or line discipline. And third, you optionally set exclusive mode on the terminal. Configuration can be a challenging task because the interface supports many hardware devices and there are over 60 termios flags. The example code below demonstrates the most important flags.

TTY Device
The first step in configuration is verifying a device is a. You can verify a device is a  using   as shown below.

Terminal Configuration
The second step in configuration is settings terminal attributes like baud rate or line discipline. This is done with a rather complex data structure using the tcgetattr(3) and tcsetattr(3) functions.

Exclusive Access
If you wish ensure exclusive access to the serial device then use  to set. If your system includes programs like ModemManager then you should set this attribute.

Note well: once you set  other programs will not be able to open the serial device. If your program architecture includes a separate reader and writer, then you should /  unique instances that inherit the file descriptor.

Line-Control Functions
termios contains a number of line-control functions. These allow a more fine-grained control over the serial line in certain special situations. They all work on a file descriptor fildes, returned by an open(2) call to open the serial device. In the case of an error, the detailed cause can be found in the global  variable (see errno(2)).

tcdrain
Wait until all data previously written to the serial line indicated by  has been sent. This means, the function will return when the UART's send buffer has cleared. If successful, the function returns 0. Otherwise it returns -1, and the global variable  contains the exact reason for the error.

Today's computer are fast, have more cores and code is subject to a lot of optimizations. Together they can cause strange results. In the example below:

You would expect a signal going up, the write and then a signal going down. But that did not happen though the programmer intended it. Perhaps optimization causes the kernel to report success to write before the data are really written.

Now the same code using tcdrain:

Now the code behaves as expected, because the clr_rts; is executed only when data really is written. Several programmers solve the problem by using sleep/usleep what may be not exactly what you want.

tcflow
This function suspends/restarts transmission and/or reception of data on the serial device indicated by fildes. The exact function is controlled by the action argument. action should be one of the following constants:
 * TCOOFF:Suspend output.
 * TCOON:Restarts previously suspended output.
 * TCIOFF:Transmit a   character. Remote devices are supposed to stop transmitting data if they receive this character. This requires the remote device on the other end of the serial line to support this software flow-control.
 * TCION:Transmit a   character. Remote devices should restart transmitting data if they receive this character. This requires the remote device on the other end of the serial line to support this software flow-control.

If successful, the function returns 0. Otherwise it returns -1, and the global variable  contains the exact reason for the error.

tcflush
Flushes (discards) not-sent data (data still in the UART send buffer) and/or flushes (discards) received data (data already in the UART receive buffer). The exact operation is defined by the queue_selector argument. The possible constants for queue_selector are:
 * TCIFLUSH:Flush received, but unread data.
 * TCOFLUSH:Flush written, but not send data.
 * TCIOFLUSH:Flush both.

If successful, the function returns 0. Otherwise it returns -1, and the global variable  contains the exact reason for the error.

tcsendbreak
Sends a break for a certain duration. The duration_flag controls the duration of the break signal:
 * 0:Send a break of at least 0.25 seconds, and not more than 0.5 seconds.
 * any other value:For other values than 0, the behavior is implementation defined. Some implementations interpret the value as some time specifications, others just let the function behave like tcdrain.

A break is a deliberately generated framing (timing) error of the serial data – the signal's timing is violated by sending a series of zero bits, which also encompasses the start/stop bits, so the framing is explicitly gone.

If successful, the function returns 0. Otherwise it returns -1, and the global variable  contains the exact reason for the error.

Reading and Setting Parameters
The Unix and Linux serial interface have more than 60 parameters due to the different hardware supported by the interface. This plethora of parameters and the resulting different interface configuration is what make serial programming in Unix and Linux challenging. Not only are there so many parameters, but their meanings are often rather unknown to contemporary hackers, because they originated at the dawn of computing, where things were done differently and are no longer known or taught in Little-Hacker School.

Nevertheless, most of the parameters of a serial interface in Unix are controlled via just two functions:

and
 * tcgetattr:For reading the current attributes.
 * tcsetattr:For setting serial interface attributes.

All information about the configuration of a serial interface is stored in an instance of the  data type. tcgetattr requires a pointer to a pre-allocated  where it will write to. tcsetattr requires a pointer to a pre-allocated and initialized  where it reads the values from.

Further, speed parameters are set via a separate set of functions:


 * cfgetispeed:Get line-in speed.
 * cfgetospeed:Get line-out speed.
 * cfsetispeed:Set line-in speed.
 * cfsetospeed:Set line-out speed.

The following sub-section explain the mentioned functions in more detail.

Attribute Changes
50+ attributes of a serial interface in Unix can be read with a single function: tcgetattr. Among these parameters are all the option flags and, for example, information about which special character handling is applied. The signature of that function is as it follows:

Where the arguments are:
 * fd:A file handle pointing to an opened terminal device. The device has typically be opened via the open(2) system call. However, there are several more mechanisms in Unix to obtain a legitimate file handle (e.g. by inheriting it over a fork(2)/exec(2) combo). As long as the handle points to an opened terminal device things are fine.
 * *attribs :A pointer to a pre-allocated, where tcgetattr will write to.

tcgetattr returns an integer that either indicates success or failure in the way typical for Unix system calls:


 * 0:Indicates successful completion
 * -1:Indicates failure. Further information about the problem can be found in the global (or thread-local)  variable. See the errno(2), intro(2), and/or perror(3C) man page for information about the meaning of the   values.


 * Note; it is a typical beginner and hacker error to not check the return value and assume everything will always work.

The following is a simple example demonstrating the use of tcgetattr. It assumes that standard input has been redirected to a terminal device:

Once the above program is compiled and linked, let's say under the name, it can be run as it follows:

./example < /dev/ttya

Assuming,  is a valid serial device. One can run stty to verify of the output is correct.

tcsetattr

Sets the termios struct of the file handle fd from the options defined in options. optional_actions specifies when the change will happen:
 * TCSANOW: the configuration is changed immediately.
 * TCSADRAIN: the configuration is changed after all the output written to fd has been transmitted. This prevents the change from corrupting in-transmission data.
 * TCSAFLUSH: same as above but any data received and not read will be discarded.

Baud-Rate Setting
Reading and setting the baud rates (the line speeds) can be done via the tcgetattr and tcsetattr function. This can be done by reading or writing the necessary data into the. The previous example for tcgetattr show how messy it can be when directly accessing the structure members.

Instead of directly accessing the structure members, it is recommended you use one of the following functions:

Which have the following signatures:
 * cfgetispeed:Get line-in speed.
 * cfgetospeed:Get line-out speed.
 * cfsetispeed:Set line-in speed.
 * cfsetospeed:Set line-out speed.


 * speed:The input baud rate.
 * attribs :The  from which to extract the speed.


 * speed:The output baud rate.
 * attribs :The  from which to extract the speed.


 * attribs :The  in which the input baud rate  should be set.
 * speed:The input baud rate that should be set.

The  parameter should be one of the predefined values, like ,  , or.

The function returns
 * 0:If the speed could be set (encoded).
 * -1:If the speed could not be set (e.g. if it is not a valid or supported speed value).


 * attribs :The  in which the output baud rate  should be set.
 * speed:The output baud rate that should be set.

The function returns
 * 0:If the speed could be set (encoded).
 * -1:If the speed could not be set (e.g. if it is not a valid or supported speed value).

Here is a simple example for cfgetispeed. cfgetospeed works very similar:

cfsetispeed and cfsetospeed work straight-forward, too. The following example sets the input speed of stdin to 9600 baud. Note, the setting will not be permanent, since the device might be reset at the end of the program:

Canonical Mode
Everything is stored into a buffer and can be edited until a carriage return or line feed is entered. After the carriage return or line feed is pressed, the buffer is sent.

where:


 * ICANON:Enables canonical input mode

Non-Canonical Mode
This mode will handle a fixed number of characters and allows for a character timer. In this mode input is not assembled into lines and input processing does not occur.Here we have to set two parameters time and minimum number of characters to be received before read is satisfied and these are set by setting VTIME and VMIN characters for example if we have to set Minimum number of characters as 4 and we don't want to use any timer then we can do so as follows-:

Misc.
There are a few C functions that can be useful for terminal and serial I/O programming and are not part of the terminal I/O API. These are

This function returns the device name of the current controlling terminal of the process as a string (e.g. "/dev/tty01"). This is useful for programs who want to open that terminal device directly in order to communicate with it, even if the controlling terminal association is removed later (because, for example, the process forks/execs to become a daemon process). *s can either be  or should point to a character array of at least L_ctermid bytes (the constant is also defined in stdio.h). If *s is, then some internal static char array is used, otherwise the provided array is used. In both cases, a pointer to the first element of the char array is returned

Checks if the provided file descriptor represents a terminal device. This can e.g. be used to figure out if a device will understand the commands send via the terminal I/O API.

This function returns the device name of a terminal device represented by a file descriptor as a string.

These I/O controls allow to get and set the window size of a terminal emulation, e.g. an xterm in pixel and character sizes. Typically the get variant (TIOCGWINSZ) is used in combination with a SIGWINCH signal handler. The signal handler gets called when the size has changed (e.g. because the user has resized the terminal emulation window), and the application uses the I/O control to get the new size.

Modems
Modems are still common for many users, like those people who use dialup connections in Central and South America and the Africas. Additionally, users who integrate with telephone company services will use modems for faxing and call blocking. Modems are also common in more modern applications that manage communications over cellular and other wireless networks as there are several wireless module vendors that offer system developers solutions that allow them to easily add wireless access to their product offerings. Those modules are often controlled via modem APIs. This section will provide configuration and operational information on modems.

Modem Configuration
There are usually two ways to configure a modem, give or take. First, you can modify the existing file descriptor. Second, you can use  and apply fresh configuration flags.

Modify an existing file descriptor would use code similar to below. The example is based on Mike Sweet's Serial Programming Guide for POSIX Operating Systems.

The second way to configure a modem is use  as shown below.

In both cases you should tune the  to your particular modem. You will have to consult the modem's documentation for settings like line discipline, start and stop bits, UART speed, etc.

Ring Count
Software that operates a modem connected to the telephone network often performs tasks like detect ring, collect Caller ID information, answer a call and play a message. The software will often maintain a state machine that depends upon the ring count.

The ring count can be retrieved from the  register by writing   and then reading the response. The response will be similar to the following with.

ATS1? 001 OK

A typical workflow sequence using the ring count is shown below.

RING # unsolicited message from the modem count = read S1 # program reads ring count NAME = JOHN DOE NMBR = 4105551212 DATE = 0101 # month and date TIME = 1345 # hour and minute RING count = read S1 RING count = read S1 RING count = read S1 ...

Some versions US Robotics modems, like the USR5637, have a firmware bug when reading the. The bug is, reading the  register destroys the Caller ID message. If you are using an affected USR modem your program will observe the following.

RING # unsolicited message from the modem count = read S1 # program reads ring count RING # No Caller ID message count = read S1 ...

If you use an affected modem you should not use the  register. Rather, maintain an internal timestamp that resets after 8 seconds. The code would look similar to the following.

Common Problems
Mike Sweet provides the following advice in Serial Programming Guide for POSIX Operating Systems:


 * don't forget to disable input echoing. Input echoing will cause a feedback loop between the MODEM and computer.
 * you must terminate modem commands with a carriage return (CR) and not a newline (NL). The C character constant for CR is.
 * make sure you use a baud that the MODEM supports. While many MODEMs do auto-baud detection, some have limits (19.2kbps is common) that you must observe.

Example terminal program
A simple terminal program with termios.h can look like this:

Warning: In this program the VMIN and VTIME flags are ignored because the O_NONBLOCK flag is set.