Skip to main content

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.

buy industrial grade Ft232 usb to serial rs232 rs485 converter in India with FT232 breakout Board with Screw terminals

 

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.

Buy cheap DIN rail mountable Isolated USB to Serial (TTL) /RS232/RS485 converter from India Bangalore(Bengaluru)

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.

programming the serial port of a industrial PC running linux using C language and termios API

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
  • 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 

 

how to find serial port names in linux inside the /dev directory for programming

 

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.

identifying the serial ports of a industrial PC running linux using terminal command dmesg

 

identifying and programming hardware serialports on linux in C using termios api

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.

linux serial port communication program written in C talking with arduino uno
 
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 

 

checking for hardware serial ports on linux using setserialutility

 

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.

  1. /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.

  2. /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.

  3. /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 .

how to connect arduino to linux for serial communication using C

or you can use 

sudo dmesg | grep tty
connecting arduino to PC for serial port programming tutorial in C

 

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

arduino to pc serial communication using C language

 

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

serial port programming on ubuntu linux using C language and termios api for absolute beginner

 

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 ,

  1. name of the file to be opened (here serial port )
  2. 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
  1. 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.

  2. Second argument

    1.  O_RDWR means that the port is opened for both reading and writing.

    2. 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
compiling the linux serial port communication program written in c using gcc on linux mint xia

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.

how to solve failed to open serial port :permission denied error in  linux

 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

  1.    tty
  2.   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.

RS232 serial communication programming on Linux  in C

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,

  1. tcgetattr() 

  2.  tcsetattr().

 

tcgetattr() gets the current terminal settings (I/O configuration) for a device (often a serial port or terminal).

It takes two arguments, 

  1. the file descriptor fd corresponding to the serial port we have just opened

  2. 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.

how to set baud rate,number of stop/start bits,parity for serial communication in Linux using the termios structure in C language and GCC

 

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.