In this tutorial, You will learn How to program the Serial Ports of your Linux System using C Language and the native termios API .We will then send and receive data from an Microcontroller board like Arduino UNO (Mega) or MSP430 Launchpad connected to your Linux PC Virtual Serial Port.
Here we will be using C language to program the serial port on a Linux Operating system (Ubuntu/ LinuxMint).
For compiling our serial port C codes,We will use the native GCC compiler that is available by default on most Linux systems.
If you are looking for programming serial port using C++, Python, C#, VB.NET, or Java, be sure to explore our detailed tutorials
Serial Ports are nice little interfaces on the PC which helps you to interface your embedded system projects using a minimum number of wires,three to be precise (TX,RX and Ground). One problem with the traditional serial ports is that they are now legacy hardware and are being phased out by the PC manufacturers and most laptops have USB ports only,this problem can be easily solved by using a variety of USB to Serial Converters available in the market, eg USB2SERIAL.
We also make fully isolated DIN Rail Mountable USB to Serial /RS232/RS485 converters that provide full protection from random electrical spikes and Surges,which can be used to safely interface your PC's USB port with Other Serial ,RS232/RS485 devices.
Please note that in this tutorial I am using a USB to Serial Converter based on FT232 from FTDI. You can use any FT232 based board ( other USB converter boards based on different chip sets should work ) or you can buy the one I am using from here.
If you have traditional D sub miniature (DB9) Serial Ports like in the case of some Industrial PC's (shown below)), just identify the relevant pins (RXD, TXD, RTS, DTR and Ground) and continue with the tutorial.

Contents
- Source codes
- Serial Ports under Linux
- Identifying Hardware Serial Ports in Linux
- Checking Hardware Serial Ports using setserial utility
- Identifying Virtual Serial Ports in Linux
- Identifying Hardware Serial Ports in Linux
- Programming the Serial Port in Linux
Source codes
Please note that the source codes on the website show only the relevant sections to highlight the process of programming the serial port.
Serial Ports under Linux
Here we will first learn how Linux treats the serial port devices inside its file system.
Linux uses device files to represent hardware devices like serial ports. The /dev directory in Linux is a special virtual filesystem that contains device files, these files represent hardware devices and virtual devices that your system can interact with.All your Serial devices will be listed in the /dev directory
You can view them by typing ( shown in the below image).
# -p flag in the ls command is used to append a forward slash / to directory names in the listing output.
# This makes it easy to distinguish directories from regular files.
ls -p /dev

Identifying Hardware Serial Ports in Linux
Traditional hardware serial ports ( traditional 16550 UART style serial ports) under Linux are named as ttyS* where * can be 1,2,3... etc. for eg ttyS1,ttyS2,ttyS23 ...... etc.They are similar to COM1,COM2 etc under a Windows OS (Windows 10,11).
Most Computers and Laptops in the market no longer has a traditional DB9 Serial Port.If your motherboard supports hardware UART's they may be listed as ttyS* as in the case of some Industrial grade computers.


Now in the above screenshot ,you can see multiple hardware serial port files like ttyS1 to ttyS31 on a Laptop with no hardware based serial ports.
Most of the /dev/ttys* entries in the above screenshot are statically created device nodes or made available by dynamic device management like udev, anticipating possible serial or pseudo-serial devices. These are not actual hardware-backed files unless something opens or uses them.
Now to identify which of the ttyS* corrosponds to your hardware serial port run the following command on the terminal.
# use sudo in some systems
dmesg | grep ttyS
if you are getting an output like this
[ 37.531286] serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
it means that ttyS0 is your hardware serial port connected to your computer .
Now if you don't have any hardware serial ports ( like in my case ) the command will not print anything,as shown below.

Checking Hardware Serial Ports using setserial utility
You can also use the setserial utility to get the available serial ports on the Linux system.
Please note that setserial utility only works on traditional serial ports, devices that are compatible with the 8250/16550 UART chipset.
If you use setserial with other chipsets like FT232 it will generate an error "Cannot get serial info: Inappropriate ioctl for device"
You have to install the setserial utility first using apt.
You can get the UART settings of all the available hardware Serial devices on a Linux system using the below command.
sudo setserial -g /dev/ttyS* # -g "Get" current settings

The output shows UART : unknown which means no hardware UART found on the motherboard of my Laptop.All the ttyS* are just place holders.
Now if a real UART hardware device is present and detected by the system then we will get the following output.
/dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4
Identifying Virtual Serial Ports in Linux
Here we will learn how to identify and use Virtual Serial ports created by USB to Serial Converters and Embedded microcontroller boards like Arduino on a Linux System.
The virtual serial ports on Linux come in a variety of ways as shown below.
/dev/ttyUSB0 -these are for USB to Serial Converters based on FTDI ,CH340 or other chips that uses UART over USB Protocol.These are USB devices pretending to be serial ports, often used for RS-232, TTL, or debugging connections.They need to have drivers installed on the system.
/dev/ttyACM0 - these are common for microcontrollers that implement USB natively, like Arduino Leonardo, Micro, Due,MSP430 Launchpad, STM32 boards with native USB controllers.Appears when the device supports the CDC-ACM protocol.
/dev/ttyAMA0 - appears when you are using a Serial port on ARM-based boards (like Raspberry Pi UART).
using dmesg
Best way to get the name of the Virtual Serial port is to connect the device to your Linux installation and run a dmesg command as shown below.
sudo dmesg | tail
here we are running dmesg to get the messages from the kernel Ring Buffer and pipes to the tail command so the last 10 lines are displayed on the terminal.
Here is the output after connecting an Arduino .

or you can use
sudo dmesg | grep tty

Another place where you can see the attached serial devices is the /dev/serial/by-id folder.

Here is an example on Ubuntu Showing MSP430Launchpad and a USB to serial converter attached to Linux Serial Port.

Programming the Linux Serial Port in C
In this tutorial i am going to use C language to program the Serial port on a Linux System using GCC Compiler,
To perform serial I/O under Linux we are going to use the termios API.
What is termios API
The termios API in Unix-like systems (including Linux and macOS) provides an interface for controlling asynchronous communications ports, such as terminals and serial ports. It is commonly used in POSIX-compliant systems for low-level terminal I/O configuration.
termios API supports two modes of accessing the serial ports.
1. Cannonical Mode
2. Non-Cannonical Mode (Raw Mode)
Cannonical mode is the default mode and is used for accessing terminals and stuff.
For our tutorial we are going to use the second mode called the non cannonical mode.In non-canonical mode, input is processed immediately, character by character, or in chunks, rather than waiting for a newline (\n).
Opening and Closing the Serial Port
Opening a serial port in Linux is accomplished by using the open() system call and closing the serial port is done using the close() system call.
Here is a simple code to open a connection to the Linux Serial Port using C and close it after some time.
Replace /dev/ttyACM0 with the name of your Serial Port before running the code.
/*
Basic Code to Open a Serial port on Linux using C and termios API
(c) wwww.xanthium.in 2025
serial_open.c
*/
#include <stdio.h>
#include <fcntl.h> /* file open flags and open() */
#include <termios.h>
#include <unistd.h>
int main()
{
// Replace /dev/ttyACM0 with the name of your Serial Port
int fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY); //open a connection to serialport
if (fd == -1)
{
perror("Failed to open serial port"); /* to print system error messages */
return 1;
}
else
{
printf("Connection to Port Opened fd = %d \n",fd);
}
close(fd); /* Close the file descriptor*/
}
Here is the explanation of the Serial port Open code.
The open() system call takes two arguments ,
- name of the file to be opened (here serial port )
- and the various parameters associated with it.
open() system call returns a -1 on failure and sets the global variable errno to indicate the error.
open() system call returns a positive integer on success.
int fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY); //open a connection to serialport
The first argument to the open() system call is the name of the serial port to be opened,Here We are giving the name as "/dev/ttyACM0". Please note that the name of the serial port may be different on your system.Use the correct name while opening the port.
Second argument
O_RDWR means that the port is opened for both reading and writing.
- O_NOCTTY means that no terminal will control the process opening the serial port.
Here if there is an issue with opening the serial port open() returns a -1 which is used to check and print the error message using perror()
if (fd == -1)
{
perror("Failed to open serial port"); /* to print system error messages */
return 1;
}
After a serial port is opened it should be closed by using the close() system call. close() takes a single argument,the file descriptor fd which we have earlier used to open the serial port using open() system call.Like this
close(fd); /* Close the file descriptor*/
Save the above code to a text editor with file extension .c .You can compile the code using gcc .
gcc serial_open.c -o serial_open
and run the code using
./serial_open

Please make sure your hardware (Arduino or USB to Serial Converter) is connected before executing the binary.
Serial Port Access Permissions in Linux
If you run the above code and is getting the following error " Access to ttyUSB0 or ttyACM0 access denied" or " Failed to open serial port :permission denied" error as shown below.

You may need to add the user to groups that have permission to access to the Serial Port.
To access the serial port ,the user must be part of 2 groups
- tty
- dialout
You can use the usermod command to add yourself to these two groups.
Please note that you should have permission to run the sudo command (part of the Sudo group in Ubuntu) or part of the wheel group in Centos/RHEL/Rocky Linux.
Adding a user to tty and dialout groups for serial port access in Linux .
sudo usermod -a -G tty $USER
sudo usermod -a -G dialout $USER
Here $USER variable is a standard environment variable in Unix-like operating systems (such as Linux and macOS) which contains the username of the currently logged-in user.

The -a makes sure that you are appending the user to the groups .
if -a is missing you will get unsubscribed from all other groups except tty and dialout.
After the commands are run,
Logoff from your account and then log back in so that changes are saved.
Configuring the termios structure
Now that we’ve successfully opened the serial port in Linux using the open() system call, the next step is to configure its settings.
This includes setting the
- baud rate,
- parity,
- number of start and stop bits,
- and the number of data bits
- using the termios structure.
These configurations are essential to ensure proper serial communication with the connected Arduino UNO from our Linux System (Linux Mint/Ubuntu/Fedora).
In Linux, the termios structure is part of the POSIX API used to configure terminal I/O settings. When working with serial ports (like communicating with an Arduino), termios allows you to control how data is transmitted and received by setting parameters such as baud rate, character size, parity, stop bits, and more.
It’s defined in the <termios.h> header and looks something like this
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
};
Now first thing to do is to declare a structure of type termios,make sure that you have included the header file <termios.h>
struct termios serial_port_settings;
Now to configure the termios structure according to our needs we use two functions,
tcgetattr()
tcsetattr().
tcgetattr() gets the current terminal settings (I/O configuration) for a device (often a serial port or terminal).
It takes two arguments,
the file descriptor fd corresponding to the serial port we have just opened
the address of the structure we just declared.
int tcgetattr(int fd, struct termios *termios_p);
So in our case it will be
tcgetattr(fd, &serial_port_settings);
After getting the current serial port settings we are going to change it suit our needs ,
to do that we will use the tcsetattr() function to update the structure with modified values.

termios members needed for Serial Port Configuration
The termios structure controls many I/O settings. However, for controlling the serial port on Linux, we only need to know two members of the structure; the rest can be ignored.
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[NCCS]; /* control characters */
};
Here tcflag_t c_iflag is used to control software based flow control during Serial Communication.
Software flow control uses in-band special characters like XON (0x11, Ctrl-Q) and XOFF (0x13, Ctrl-S) to pause/resume transmission.
Disabling these flags prevents the terminal driver from interpreting Ctrl-S, Ctrl-Q, or any other character as flow control.
The Flags are named IXON,IXOFF, IXANY which we will clear in order to disable software based flow control
- IXON Disables transmit flow control
- IXOFF Disables receive flow control
- IXANY Disables the feature that allows any character to restart output
c_iflag is also used to select between canonical or non canonical mode by clearing the ICANON Flag
The tcflag_t c_cflag field in the termios structure controls hardware-related serial port behavior, such as:
- Character size (how many bits per character)
- Parity checking (used for error detection)
- Number of stop bits
- Whether the receiver is enabled
- Whether modem control lines are used
This field is especially important when configuring serial ports or working with raw terminal I/O.
- CSIZE Bitmask for character size (use with CS5, CS6, CS7, CS8)
- CS5 5-bit characters
- CS6 6-bit characters
- CS7 7-bit characters
- CS8 8-bit characters (most common)
- CSTOPB Use 2 stop bits instead of 1
- CREAD Enable receiver (needed to receive input)
- PARENB Enable parity generation and checking
- PARODD Use odd parity (if PARENB is set); otherwise even parity
Our plan is to configure the serial port in the 8N1 format forcommunicating with the connected Arduino UNO which means
Data bits = 8,
Parity = None and
No of Stop Bits = 1
To SET and Clear the bits we will be using C Bit wise operators.
For example
//Bit set/clear demo in C using BIT wise operators
serial_port_settings.c_cflag &= ~(CRTSCTS); //Clear Flag CRTSCTS,
serial_port_settings.c_cflag |= (CRTSCTS); //SET Flag CRTSCTS,
Configuring other Options
Turn off hardware based flow control (RTS/CTS).
SerialPortSettings.c_cflag &= ~CRTSCTS;
Turn on the receiver of the serial port (CREAD),other wise reading from the serial port will not work.
SerialPortSettings.c_cflag |= CREAD | CLOCAL;
Turn off software based flow control (XON/XOFF).
SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);
Setting the mode of operation,the default mode of operation of serial port in Linux is the Cannonical mode.For Serial communications with outside devices like serial modems,mice etc NON Cannonical mode is recommended.
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
More information about the functions and constants discussed above can be found in the termios manual page.You can easly access them by typing
man termios on your terminal.
The man pages are also available online at www.man7.org.
After you have configured all of the required bitfields of the terminos structure.You can use the tcsetattr() function to set them.tcsetattr() takes three arguments,the first and last arguments are same as in the tcgetattr()function.The second one TCSANOW tells to make the changes now without waiting.
tcsetattr(fd,TCSANOW,&SerialPortSettings)
Setting the Baudrate
In Linux ,there is an option to set different read and write speeds but it is recommended to set the same speed for both read and write.
The Baudrate is set using two functions,
cfsetispeed() for setting the input speed or read speed and
cfsetospeed() for setting the output speed or write speed.
Now let's set the speed for both reading and writing at 9600 bps
cfsetispeed(&SerialPortSettings,B9600);
cfsetospeed(&SerialPortSettings,B9600);
you can use other standard baudrates like 4800,19200,38400 etc all Baudrate constants have to be prefixed with a 'B' like B4800 , B19200 etc .
Please note that the MSP430 microcontroller code provided (microcontroller side) uses 9600bps 8N1 format for communicating with the PC.
Configuring data format,Start/Stop bits ,Parity
The control flags ( c_cflag ) of the termios structure configures the data format (8bit or 7 bits for data),Parity(Even,Odd,None) and the number of start and stop bits to use while communicating.
Configuring these information involves setting and clearing individual bits of the control flags.It is advisable to check out “Bit manipulation in C language” in google/wikipedia,if you are not familiar.
Hardware Connections
Now we will create a small serial link between a microcontroller board and a PC running Linux OS to test out reading and writing from a PC serial port .
Microcontroller used here is MSP430G2553 from Texas instruments on Launchpad development board.Please note that you can use any microcontroller like 8051 or Atmel AVR on the embedded system side.Since this is an article about setting up and configuring the serial port on Linux,the microcontroller side code is explained here.
The PC is connected to the microcontroller board using a null modem cable.The RX of the PC (serial port) is connected to the TX of the Microcontroller and vice versa.The Grounds of PC and microcontroller are connected together.
Here my PC do not have a hardware DB9 serial port(9 pin),So i am using a FTDI based USB to Serial converter called USB2SERIAL which converts the USB signals to TTL compatible serial outputs (RXD and TXD). you can directly connect the outputs of USB2SERIAL to MSP430 microcontroller as shown in the below block diagram.USB2SERIAL also offers selectable 3V/5V TTL outputs for interfacing to 3.3V logic microcontrollers like MSP430.
Null modem connection between a linux PC and an MSP430 micro controller for communicating using serial port
MSP430 connected to a USB to serial converter for serial communication with a linux operating system
Please noter that if you are using a DB9 RS232 SerialPort of your PC ,you will have to build a RS232 signal level converter at the microcontroller side to decode the RS232 signal.Connecting the RS232 Lines from the PC directly to microcontroller UART directly will damage the chip.
Writing data into Serial Port
Writing data to serial port is accomplished using the write() system call.
Sucessfull Serial port communication with Linux PC.Writing data to serial port using the write() system call on linux
Reading Data from the Serial Port
Reading data from the serial port is accomplished by using the read() system call.
In the zip file you can find “SerialPort_read.c” which contains the complete program.Compile the program using gcc and run it.Remember to use sudo so that your executable runs as root.
After providing the root password,the program will execute and wait for a character to be send by the microcontroller.
serial program waiting for a character to be received
You can now reset the MSP430 microcontroller connected to the USB2SERIAL converter to transmit the string .
Program receiving the data send by MSP430
Program receives the “Hello from MSP430” string send by the microcontroller and displays it on the terminal.
If you want to know how to control the RTS and DTR pins of the serial port you can check out our next section.The section outlines ioctl() calls used to control the RTS and DTR pins and uses them to build a small RS485 network controlled by a Linux Box.
- Log in to post comments