LINUX SERIAL PORTS USING C/C++
Linux Serial Ports Using C/C++
Unluckily, using serial ports in Linux is not the easiest thing in the world. When dealing with the
termios.h header, there are many finicky settings buried within multiple bytes worth of bitfields. This page is an attempt to help explain these settings and show you how to configure a serial port in Linux correctly.
Everything Is A File
In typical UNIX style, serial ports are represented by files within the operating system. These files usually pop-up in
/dev/, and begin with the name
Common names are:
/dev/ttyACM0- ACM stands for the ACM modem on the USB bus. Arduino UNOs (and similar) will appear using this name.
/dev/ttyPS0- Xilinx Zynq FPGAs running a Yocto-based Linux build will use this name for the default serial port that Getty connects to.
/dev/ttyS0- Standard COM ports will have this name. These are less common these days with newer desktops and laptops not having actual COM ports.
/dev/ttyUSB0- Most USB-to-serial cables will show up using a file named like this.
/dev/pts/0- A pseudo terminal. These can be generated with
To write to a serial port, you write to the file. To read from a serial port, you read from the file. Of course, this allows you to send/receive data, but how do you set the serial port parameters such as baud rate, parity, e.t.c? This is set by a special
Basic Setup In C
First we want to include a few things:
Then we want to open the serial port device (which appears as a file under
/dev/), saving the file descriptor that is returned by
One of the common errors you might see here is
errno = 2, and
No such file or directory. Make sure you have the right path to the device and that the device exists!
Another common error you might get here is
errno = 13, which is
Permission denied. This usually happens because the current user is not part of the dialout group. Add the current user to the dialout group with:
You must log out and back in before these group changes come into effect.
At this point we could technically read and write to the serial port, but it will likely not work, because the default configuration settings are not designed for serial port use. So now we will set the configuration correctly.
When modifying any configuration value, it is best practice to only modify the bit you are interested in, and leave all other bits of the field untouched. This is why you will see below the use of
|=, and never
= when setting bits.
We need access to the
termios struct in order to configure the serial port. We will create a new
termios struct, and then write the existing configuration of the serial port to it using
tcgetattr(), before modifying the parameters as needed and saving the settings with
We can now change
tty’s settings as needed, as shown in the following sections. Before we get onto that, here is the definition of the
termios struct if you’re interested (pulled from
Control Modes (c_cflags)
c_cflag member of the
termios struct contains control parameter fields.
If this bit is set, generation and detection of the parity bit is enabled. Most serial communications do not use a parity bit, so if you are unsure, clear this bit.
CSTOPB (Num. Stop Bits)
If this bit is set, two stop bits are used. If this is cleared, only one stop bit is used. Most serial communications only use one stop bit.
Number Of Bits Per Byte
CS<number> fields set how many data bits are transmitted per byte across the serial port. The most common setting here is 8 (
CS8). Definitely use this if you are unsure, I have never used a serial port before which didn’t use 8 (but they do exist). You must clear all of the size bits before setting any of them with
Hardware Flow Control (CRTSCTS)
CRTSCTS field is set, hardware RTS/CTS flow control is enabled. This is when there are two extra wires between the end points, used to signal when data is ready to be sent/received. The most common setting here is to disable it. Enabling this when it should be disabled can result in your serial port receiving no data, as the sender will buffer it indefinitely, waiting for you to be “ready”.
See the Software Flow Control section below on other settings relating to flow control.
CREAD and CLOCAL
CLOCAL disables modem-specific signal lines such as carrier detect. It also prevents the controlling process from getting sent a
SIGHUP signal when a modem disconnect is detected, which is usually a good thing here. Setting
CREAD allows us to read data (we definitely want that!).
Local Modes (c_lflag)
Disabling Canonical Mode
UNIX systems provide two basic modes of input, canonical and non-canonical mode. In canonical mode, input is processed when a new line character is received. The receiving application receives that data line-by-line. This is usually undesirable when dealing with a serial port, and so we normally want to disable canonical mode.
Canonical mode is disabled with:
Also, in canonical mode, some characters such as backspace are treated specially, and are used to edit the current line of text (erase). Again, we don’t want this feature if processing raw serial data, as it will cause particular bytes to go missing!
If this bit is set, sent characters will be echoed back. Because we disabled canonical mode, I don’t think these bits actually do anything, but it doesn’t harm to disable them just in case!
Disable Signal Chars
ISIG bit is set,
SUSP characters are interpreted. We don’t want this with a serial port, so clear this bit:
Input Modes (c_iflag)
c_iflag member of the
termios struct contains low-level settings for input processing. The
c_iflag member is an
Software Flow Control (IXOFF, IXON, IXANY)
IXANY disables software flow control, which we don’t want:
Disabling Special Handling Of Bytes On Receive
Clearing all of the following bits disables any special handling of the bytes as they are received by the serial port, before they are passed to the application. We just want the raw data thanks!
Output Modes (c_oflag)
c_oflag member of the
termios struct contains low-level settings for output processing. When configuring a serial port, we want to disable any special handling of output chars/bytes, so do the following:
ONOEOT are not defined in Linux. Linux however does have the
XTABS field which seems to be related. When compiling for Linux, I just exclude these two fields and the serial port still works fine.
VMIN and VTIME (c_cc)
VTIME are a source of confusion for many programmers when trying to configure a serial port in Linux.
An important point to note is that
VTIME means slightly different things depending on what
VMIN is. When
VMIN is 0,
VTIME specifies a time-out from the start of the read() call. But when
VMIN is > 0,
VTIME specifies the time-out from the start of the first received character.
Let’s explore the different combinations:
VMIN = 0, VTIME = 0: No blocking, return immediately with what is available
VMIN > 0, VTIME = 0: This will make
read() always wait for bytes (exactly how many is determined by
read() could block indefinitely.
VMIN = 0, VTIME > 0: This is a blocking read of any number of chars with a maximum timeout (given by
read() will block until either any amount of data is available, or the timeout occurs. This happens to be my favourite mode (and the one I use the most).
VMIN > 0, VTIME > 0: Block until either
VMIN characters have been received, or
VTIME after first character has elapsed. Note that the timeout for
VTIME does not begin until the first character is received.
VTIME are both defined as the type
cc_t, which I have always seen be an alias for unsigned char (1 byte). This puts an upper limit on the number of
VMIN characters to be 255 and the maximum timeout of 25.5 seconds (255 deciseconds).
“Returning as soon as any data is received” does not mean you will only get 1 byte at a time. Depending on the OS latency, serial port speed, hardware buffers and many other things you have no direct control over, you may receive any number of bytes.
For example, if we wanted to wait for up to 1s, returning as soon as any data was received, we could use:
Rather than use bit fields as with all the other settings, the serial port baud rate is set by calling the functions
cfsetospeed(), passing in a pointer to your
tty struct and a
If you want to remain UNIX compliant, the baud rate must be chosen from one of the following:
Some implementation of Linux provide a helper function
cfsetspeed() which sets both the input and output speeds at the same time:
Custom Baud Rates
As you are now fully aware that configuring a Linux serial port is no trivial matter, you’re probably unfazed to learn that setting custom baud rates is just as difficult. There is no portable way of doing this, so be prepared to experiment with the following code examples to find out what works on your target system.
If you are compiling with the GNU C library, you can forgo the standard enumerations above just specify an integer baud rate directly to
This method relied on using a
termios2 struct, which is like a
termios struct but with sightly more functionality. I’m unsure on exactly what UNIX systems
termios2 is defined on, but if it is, it is usually defined in
termbits.h (it was on the Xubuntu 18.04 with GCC system I was doing these tests on):
Which is very similar to plain old
termios, except with the addition of the
c_ospeed. We can use these to directly set a custom baud rate! We can pretty much set everything other than the baud rate in exactly the same manner as we could for
termios, except for the reading/writing of the terminal attributes to and from the file descriptor — instead of using
tcsetattr() we have to use
Let’s first update our includes, we have to remove
termios.h and add the following:
Please read the comment above about
BOTHER. Perhaps on your system this method will work!
BXXXrates above if you have the option to do so. If you have no idea what the baud rate is and you are trying to communicate with a 3rd party system, try
B115200as they are the most common rates.
After changing these settings, we can save the
tty termios struct with
Reading And Writing
Now that we have opened and configured the serial port, we can read and write to it!
Writing to the Linux serial port is done through the
write() function. We use the
serial_port file descriptor which was returned from the call to
Reading is done through the
read() function. You have to provide a buffer for Linux to write the data into.
This is a simple as:
Full Example (Standard Baud Rates)
For Linux serial port code examples see https://github.com/gbmhunter/CppLinuxSerial.
Issues With Getty
Getty can cause issues with serial communication if it is trying to manage the same
tty device that you are attempting to perform serial communications with.
To Stop Getty:
Getty can be hard to stop, as by default if you try and kill the process, a new process will start up immediately.
These instructions apply to older versions of Linux, and/or embedded Linux.
/etc/inittabin your favourite text editor.
- Comment out any lines involving
- Save and close the file.
- Run the command
~$ init qto reload the
- Kill any running
gettyprocesses attached to your
ttydevice. They should now stay dead!
It can be prudent to try and prevent other processes from reading/writing to the serial port at the same time you are.
One way to accomplish this is with the
flock() system call (note this example is in C++, easy to change to C if needed!):
Getting The Number Of RX Bytes Available
You can use
FIONREAD along with
ioctl() to see if there are any bytes available in the OS input (receive) buffer for the serial port1. This can be useful in a polling-style method in where the application regularly checks for bytes before trying to read them.
The provided pointer to integer
bytes gets written by the
ioctl() function with the number of bytes available to be read from the serial port.
Changing Terminal Settings Are System Wide
Although getting and setting terminal settings are done with a file descriptor, the settings apply to the terminal device itself and will effect all other system applications that are using or going to use the terminal. This also means that terminal setting changes are persistent after the file descriptor is closed, and even after the application that changed the settings is terminated2.
For Linux serial port code examples see https://github.com/gbmhunter/CppLinuxSerial (note that this library is written in C++, not C).
See http://www.gnu.org/software/libc/manual/html_node/Terminal-Modes.html for the official specifications of the
termios struct configuration parameters.
Michael R. Sweet (1999). Serial Programming Guide for POSIX Operating Systems. Retrieved 2022-02-12, from https://www.cmrr.umn.edu/~strupp/serial.html. ↩︎
This work is licensed under a Creative Commons Attribution 4.0 International License .
- serial ports
- flow control