serial_port_web_logo

SerialPort C++ library

v3.2.1

Table of contents

Overview

The SerialPort C++ library provides a simple interface for working with serial ports. The SerialPort.h file contains the declaration of the SerialPort C++ class. SerialPort includes functions to open serial ports, write data to them, and read data from them. The SerialPort library also provides applications to test communication with any equipment (send data and check responses). It requires the C++17 standard. The library has no third-party dependencies and is compatible with Linux and Windows operating systems.

SerialPort is general-purpose software: it is not tied to any sector or application, and the developer of a program decides what the program does with it.

Versions

Table 1 - Library versions.

Version Release date What’s new
1.0.0 14.10.2021 First version
2.0.0 20.05.2022 - Interface changed.
- Added method for RTS/CTS hardware flow control.
2.1.0 10.07.2022 - Code optimized.
2.2.0 22.03.2023 - Code optimized.
- Documentation updated.
2.3.0 24.04.2023 - Added new test application.
2.4.0 19.06.2023 - Added new test application (SerialPortStringTester).
2.5.0 26.06.2023 - Updated test applications (SerialPortStringTester and SerialPortTester combined into one application).
3.0.0 27.06.2023 - Changed interface (added new methods read(…) and write(…) instead of readData(…) and sendData(…)).
3.0.1 19.07.2023 - Fixed compilation error in methods read(…) and write(…) for Linux.
3.0.2 17.12.2023 - Methods description updated.
- Documentation updated.
- Default branch name changed from “main” to “master”.
3.0.3 26.03.2024 - Test application updated.
- Documentation updated.
3.0.4 22.05.2024 - Documentation updated.
3.0.5 30.05.2024 - SBUS protocol baudrate (100000) added for Linux.
- Wait data timeout mechanism changed for Linux.
3.1.0 12.07.2024 - Synchronized behavior on Linux and Windows.
- CMake updated.
3.2.0 16.10.2025 - Added custom baudrate support for Linux and Windows.
3.2.1 16.10.2026 - Fixed leak of the port handle and of the port lock after a failed open(…).
- Fixed memory corruption in read(…) on Linux for a port with the descriptor number >= 1024 (select(…) replaced by poll(…)).
- Fixed parity checking (INPCK) on Linux: it is enabled for the modes with parity.
- open(…) returns FALSE for nullptr mode, zero baudrate, timeout > INT_MAX and device name with the NUL character.
- Copy and move of SerialPort are deleted (a copy caused double free).
- read(…) returns -1 instead of 0 (“no data”) if the device is lost on Linux, for example a USB adapter is removed.
- read(…) returns 0 instead of -1 if the wait is interrupted by a signal (Linux).
- read(…) and write(…) transfer no more than INT_MAX bytes per call.
- Port descriptor is not inherited by child processes (Linux).
- Examples and SerialPortTester check the input data (fixed buffer overflow in SerialPortTester and read of invalid memory in SerialPortDataSender).
- Compiler warning options and security hardening options added to the CMake files.
- Documentation updated.

Library files

The library is supplied only by source code. The user is given a set of files in the form of a CMake project (repository). The repository structure is shown below:

CMakeLists.txt -------------- Main CMake file of the library.
src ------------------------- Folder with library source code.
    Impl -------------------- Folder with serial port implementation files.
        SerialPortImpl.h ---- Header file of serial port implementation.
        SerialPortImpl.cpp -- C++ implementation file.
        CustomBaudrate.h ---- Header file of custom baudrate initialization.
        CustomBaudrate.cpp -- C++ implementation file (Linux only).
    CMakeLists.txt ---------- CMake file of the library.
    SerialPort.h ------------ Main library header file.
    SerialPortVersion.h ----- Header file with library version.
    SerialPortVersion.h.in -- Service CMake file to generate version header.
    SerialPort.cpp ---------- C++ implementation file.
examples -------------------- Folder with examples.
    CMakeLists.txt ---------- CMake file to include examples.
    SerialPortDataReceiver -- Folder with data receiver example.
        CMakeLists.txt ------ CMake file of data receiver example.
        main.cpp ------------ Source code of data receiver example.
    SerialPortDataSender ---- Folder with data sender example.
        CMakeLists.txt ------ CMake file of data sender example.
        main.cpp ------------ Source code of data sender example.
    SerialPortTester -------- Folder with serial port tester application.
        CMakeLists.txt ------ CMake file of serial port tester application.
        main.cpp ------------ Source code of serial port tester application.
static ---------------------- Folder with the image used in this documentation.
README.md ------------------- This documentation.

SerialPort class description

SerialPort class declaration

SerialPort class declared in SerialPort.h file. Class declaration:

namespace rapidpixel
{
/// Serial port class.
class SerialPort
{
public:

    /// Get string of current library version.
    static std::string getVersion();

    /// Class constructor.
    SerialPort();

    /// Class destructor.
    ~SerialPort();

    /// Copy constructor (deleted).
    SerialPort(const SerialPort&) = delete;

    /// Move constructor (deleted).
    SerialPort(SerialPort&&) = delete;

    /// Copy assignment (deleted).
    SerialPort& operator=(const SerialPort&) = delete;

    /// Move assignment (deleted).
    SerialPort& operator=(SerialPort&&) = delete;

    /// Open serial port.
    bool open(std::string file, unsigned int baudrate,
              unsigned int timeoutMsec = 100, const char *mode = "8N1");

    /// Read data from serial port.
    int read(uint8_t *buf, uint32_t size);

    /// Write data to serial port.
    int write(uint8_t *buf, uint32_t size);

    /// Get open status.
    bool isOpen();

    /// Close serial port.
    void close();

    /// Set RTS/CTS hardware flow control.
    bool setFlowControl(bool enable);
};
}

The class is not thread-safe: methods of one object must not be called from several threads at the same time without external synchronization. The only exception is isOpen(). An object owns a serial port, therefore it can not be copied or moved.

getVersion method

The getVersion() method returns string of current library version. Method declaration:

static std::string getVersion();

Method can be used without SerialPort class instance:

cout << "Serial port version: " << SerialPort::getVersion() << endl;

Console output:

Serial port version: 3.2.1

open method

The open(…) method opens a serial port. If a serial port is already open, the method will first close the serial port and then attempt to open it again according to the new parameters. If the method fails, the serial port stays closed. The serial port is opened for exclusive use: another object or process can not open the same device until the port is closed. The method does not throw exceptions. Method declaration:

bool open(std::string file, unsigned int baudrate, unsigned int timeoutMsec = 100, const char *mode = "8N1");
Parameter Value
file Serial port device name. Format depends on OS. For Linux OS format: “/dev/serial/by-id/…”, “/dev/ttyS0”, “/dev/ttyUSB0” etc. For Windows OS format “\\.\COM0” etc. The name must not contain the NUL character.
baudrate Baudrate, not 0. For example 115200. A value that is not a standard baudrate of the operating system is set as a custom baudrate (the serial port driver must support it).
timeoutMsec Wait data timeout, milliseconds, from 0 to 2147483647. 0 - the read(…) method does not wait for data (on Windows it waits 1 millisecond).
mode Mode, not nullptr. Always 3 symbols: 1 - Number of bits (8, 7, 6, 5), 2 - parity (N, E, O; the symbol is case insensitive), 3 - number of stop bits (1 or 2). Example: “8N1”.

Returns: TRUE if the serial port opened successfully or FALSE if not (a parameter is not valid, the device does not exist, is busy or is not a serial port, memory allocation failed).

read method

The read(…) method reads data from the serial port. When a user calls the read(…) method, it will check if there is any data in the serial port buffer and return the data if it exists. If no data exists in the serial port, the read(…) method will wait for a maximum of timeoutMsec milliseconds (set by the user in the open(…) method) until data arrives, and return 0 if no data is received within the timeout period. If you don’t want to wait and just want to check for data in the serial port, set timeoutMsec = 0 in the open(…) method. On Linux a signal can interrupt the wait: the method then returns 0 before the timeout is over. No more than 2147483647 bytes are read by one call. Method declaration:

int read(uint8_t *buf, uint32_t size);
Parameter Value
buf Pointer to buffer. Must not be nullptr and must be valid for size bytes.
size Size of buffer, bytes, not 0. Size of data that you want to read from the serial port. The method will return <= size bytes from the input serial port buffer.

Returns: Number of received bytes <= size, or 0 if no data was received within the timeout, or -1 if the serial port is not open, buf is nullptr, size is 0 or in case of an error (including the loss of the device, for example a removed USB adapter).

write method

The write(…) method is intended to send data to serial port. No more than 2147483647 bytes are written by one call. Method declaration:

int write(uint8_t *buf, uint32_t size);
Parameter Value
buf Pointer to buffer with data to send. Must not be nullptr and must be valid for size bytes.
size Number of bytes to send, not 0.

Returns: Number of bytes sent (<= size, it can be less than size), or -1 if the serial port is not open, buf is nullptr, size is 0 or in case of an error.

isOpen method

The isOpen() method is intended to obtain the serial port connection status. The method can be called from any thread. Method declaration:

bool isOpen();

Returns: TRUE if the serial port is open or FALSE if not.

close method

The close() method is intended to close the serial port. This method will close the serial port if it is open. Method declaration:

void close();

setFlowControl method

The setFlowControl(…) method is intended to set RTS/CTS hardware flow control. RTS / CTS Flow Control is another flow control mechanism that is part of the RS232 standard. It makes use of two further pins on the RS232 connector, RTS (Request to Send) and CTS (Clear to Send). These two lines allow the receiver and the transmitter to alert each other to their state. On Windows the method asserts the RTS line (enable = true) or clears it (enable = false). Method declaration:

bool setFlowControl(bool enable);
Parameter Value
enable Enable RTS/CTS hardware flow control flag: true - enable, false - disable.

Returns: TRUE if the flow control mode is set or FALSE if the serial port is not open or the operating system call failed.

Examples

Data sender example

#include <chrono>
#include <cstdint>
#include <iostream>
#include <string>
#include <thread>
#include "SerialPort.h"



namespace
{
/// Maximum number of bytes that the example sends at a time.
constexpr int MAX_BYTES_TO_SEND{1024};

/// Maximum period of sending data accepted by the example, milliseconds.
constexpr int MAX_PERIOD_MS{60000};



/// Get the next pseudo random byte. The data is sent only to check the
/// communication, so a simple generator is enough.
uint8_t nextRandomByte(uint32_t& state)
{
    state = state * 1664525U + 1013904223U;
    return static_cast<uint8_t>(state >> 24);
}
}



int main()
{
    std::cout << "Data sender example v" <<
                 rapidpixel::SerialPort::getVersion() << std::endl;

    std::string portName;
#if defined(__linux__)
    std::cout << "Enter serial port name (e.g. /dev/ttyS0): ";
    if (!(std::cin >> portName))
    {
        std::cout << "ERROR: Invalid serial port name" << std::endl;
        return -1;
    }
#elif defined(_WIN32)
    std::cout << "Enter serial port num (1, 2 ..): ";
    if (!(std::cin >> portName))
    {
        std::cout << "ERROR: Invalid serial port num" << std::endl;
        return -1;
    }
    portName = "\\\\.\\COM" + portName;
#endif

    int portBaudrate{115200};
    std::cout << "Enter serial port baudrate: ";
    if (!(std::cin >> portBaudrate) || portBaudrate <= 0)
    {
        std::cout << "ERROR: Invalid baudrate" << std::endl;
        return -1;
    }

    int numBytesToSend{10};
    std::cout << "Enter num bytes to send (1 - " << MAX_BYTES_TO_SEND << "): ";
    if (!(std::cin >> numBytesToSend) || numBytesToSend < 1 ||
        numBytesToSend > MAX_BYTES_TO_SEND)
    {
        std::cout << "ERROR: Invalid number of bytes" << std::endl;
        return -1;
    }

    int cyclePeriodMs{0};
    std::cout << "Enter sending data period ms (0 - " << MAX_PERIOD_MS << "): ";
    if (!(std::cin >> cyclePeriodMs) || cyclePeriodMs < 0 ||
        cyclePeriodMs > MAX_PERIOD_MS)
    {
        std::cout << "ERROR: Invalid period" << std::endl;
        return -1;
    }

    // Open serial port.
    rapidpixel::SerialPort serialPort;
    if (!serialPort.open(portName, static_cast<unsigned int>(portBaudrate)))
    {
        std::cout << "ERROR: Serial port not open" << std::endl;
        return -1;
    }

    // Main loop. The application works until it is stopped by the user
    // (Ctrl+C) or until the serial port fails.
    uint8_t outputData[MAX_BYTES_TO_SEND];
    uint32_t randomState{1};
    std::chrono::steady_clock::time_point start =
            std::chrono::steady_clock::now();
    while (true)
    {
        // Wait according to parameters.
        std::this_thread::sleep_until(
                    start + std::chrono::milliseconds(cyclePeriodMs));
        start = std::chrono::steady_clock::now();

        // Prepare random data.
        for (int i = 0; i < numBytesToSend; ++i)
        {
            outputData[i] = nextRandomByte(randomState);
        }

        // Send data.
        const int bytes = serialPort.write(
                    outputData, static_cast<uint32_t>(numBytesToSend));
        if (bytes < 0)
        {
            std::cout << "ERROR: Can't send data" << std::endl;
            return -1;
        }
        std::cout << bytes << " bytes sent" << std::endl;
    }
}

Data receiver example

#include <cstdint>
#include <iostream>
#include <string>
#include "SerialPort.h"



namespace
{
/// Maximum wait data timeout accepted by the example, milliseconds.
constexpr int MAX_TIMEOUT_MS{60000};

/// Size of the input data buffer, bytes.
constexpr uint32_t INPUT_BUFFER_SIZE{1024};
}



int main()
{
    std::cout << "Data receiver example v" <<
                 rapidpixel::SerialPort::getVersion() << std::endl;

    std::string portName;
#if defined(__linux__)
    std::cout << "Enter serial port name (e.g. /dev/ttyS0): ";
    if (!(std::cin >> portName))
    {
        std::cout << "ERROR: Invalid serial port name" << std::endl;
        return -1;
    }
#elif defined(_WIN32)
    std::cout << "Enter serial port num (1, 2 ..): ";
    if (!(std::cin >> portName))
    {
        std::cout << "ERROR: Invalid serial port num" << std::endl;
        return -1;
    }
    portName = "\\\\.\\COM" + portName;
#endif

    int portBaudrate{115200};
    std::cout << "Enter serial port baudrate: ";
    if (!(std::cin >> portBaudrate) || portBaudrate <= 0)
    {
        std::cout << "ERROR: Invalid baudrate" << std::endl;
        return -1;
    }

    int waitDataTimeoutMs{0};
    std::cout << "Enter wait data timeout, msec (1 - " << MAX_TIMEOUT_MS <<
                 "): ";
    if (!(std::cin >> waitDataTimeoutMs) || waitDataTimeoutMs < 1 ||
        waitDataTimeoutMs > MAX_TIMEOUT_MS)
    {
        std::cout << "ERROR: Invalid timeout" << std::endl;
        return -1;
    }

    // Open serial port.
    rapidpixel::SerialPort serialPort;
    if (!serialPort.open(portName, static_cast<unsigned int>(portBaudrate),
                         static_cast<unsigned int>(waitDataTimeoutMs)))
    {
        std::cout << "ERROR: Serial port not open" << std::endl;
        return -1;
    }

    // Main loop. The application works until it is stopped by the user
    // (Ctrl+C) or until the serial port fails.
    uint8_t inputData[INPUT_BUFFER_SIZE];
    while (true)
    {
        // Read data.
        const int bytes = serialPort.read(inputData, INPUT_BUFFER_SIZE);
        if (bytes < 0)
        {
            std::cout << "ERROR: Can't read data" << std::endl;
            return -1;
        }

        if (bytes == 0)
        {
            std::cout << "No input data" << std::endl;
        }
        else
        {
            std::cout << bytes << " bytes read." << std::endl;
        }
    }
}

Build and connect to your project

Build stand alone

The library has no dependencies on other libraries and the repository does not contain sub-repositories. Typical commands to build the library and the examples (on Linux OS):

git clone <SerialPort repository URL>
cd SerialPort
mkdir build
cd build
cmake ..
make

On Windows use cmake --build . --config Release instead of make. The executable files of the examples are placed to the bin folder of the build folder.

CMake options

Table 2 - CMake options of the repository. In the options below ${PARENT} is the prefix defined by the top-level project (empty if SerialPort is the top-level project).

Option Default Description
${PARENT}_SERIAL_PORT ON Build the library.
${PARENT}_SERIAL_PORT_EXAMPLES ON (stand alone), OFF (included in other project) Build the examples (SerialPortDataReceiver, SerialPortDataSender and SerialPortTester).
${PARENT}_SUBMODULE_CACHE_OVERWRITE ON Forced rewriting of the options above in the CMake cache. The top-level project sets it to OFF when it includes SerialPort as a sub-repository.
CMAKE_BUILD_TYPE Release (if not specified) Build type.

Connect to your project

To connect the SerialPort library to your CMake project as source code, do the following. The library has no dependencies, so no other library has to be added to your project. For example, if your repository has the structure:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp

Create the 3rdparty folder in your repository folder and copy the SerialPort repository folder to the 3rdparty folder of your repository. The structure of your repository becomes:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    SerialPort

Create the CMakeLists.txt file in the 3rdparty folder. CMakeLists.txt should contain:

cmake_minimum_required(VERSION 3.13)

################################################################################
## 3RD-PARTY
## dependencies for the project
################################################################################
project(3rdparty LANGUAGES CXX)

################################################################################
## SETTINGS
## basic 3rd-party settings before use
################################################################################
# To inherit the top-level architecture when the project is used as a submodule.
SET(PARENT ${PARENT}_YOUR_PROJECT_3RDPARTY)
# Disable self-overwriting of parameters inside included subdirectories.
SET(${PARENT}_SUBMODULE_CACHE_OVERWRITE OFF CACHE BOOL "" FORCE)

################################################################################
## CONFIGURATION
## 3rd-party submodules configuration
################################################################################
SET(${PARENT}_SUBMODULE_SERIAL_PORT                     ON  CACHE BOOL "" FORCE)
if (${PARENT}_SUBMODULE_SERIAL_PORT)
    SET(${PARENT}_SERIAL_PORT                           ON  CACHE BOOL "" FORCE)
    SET(${PARENT}_SERIAL_PORT_EXAMPLES                  OFF CACHE BOOL "" FORCE)
endif()

################################################################################
## INCLUDING SUBDIRECTORIES
## Adding subdirectories according to the 3rd-party configuration
################################################################################
if (${PARENT}_SUBMODULE_SERIAL_PORT)
    add_subdirectory(SerialPort)
endif()

File 3rdparty/CMakeLists.txt adds folder SerialPort to your project and excludes examples (SerialPort examples) from compiling (by default examples are excluded from compiling if SerialPort is included as sub-repository). The structure of your repository becomes:

CMakeLists.txt
src
    CMakeList.txt
    yourLib.h
    yourLib.cpp
3rdparty
    CMakeLists.txt
    SerialPort

Next you need to include the 3rdparty folder in the main CMakeLists.txt file of your repository. Add the string at the end of your main CMakeLists.txt:

add_subdirectory(3rdparty)

Next you have to link the SerialPort library in your src/CMakeLists.txt file:

target_link_libraries(${PROJECT_NAME} SerialPort)

Done!

Portability

The library supports Linux and Windows: the compilation for another operating system stops with an error message. The library requires a compiler with C++17 support, does not serialize data and does not depend on the byte order of the processor. On Linux the library was built with GCC 9, GCC 11 and GCC 12 for x86-64 and with GCC 11 for AArch64. A custom baudrate (a baudrate that is not a standard baudrate of the operating system) is set on Linux with the TCSETS2 ioctl, therefore it requires the support of the serial port driver. On Windows any baudrate is passed to the serial port driver.

SerialPortTester application

SerialPortTester is an application designed to test communication with any equipment via a serial port. The application allows users to send any data (manual input) to the serial port and check the response from the equipment. The application supports data entry in both HEX and string formats (for ASCII protocols). The application supports Windows and Linux operating systems. How to use:

Copy the executable file SerialPortTester and make it executable (on Linux):

sudo chmod +x SerialPortTester

Run the application with sudo (the application doesn’t have parameters):

sudo ./SerialPortTester

You will see a dialog to enter the serial port name. On Windows OS you should set the COM port number (the application will create the serial port name according to the Windows format "\\\\.\\COM" + portNum):

================================================
Serial port tester v3.2.1
================================================

Set COM port num (1,2,3,...): 2

On Linux OS you have to type the full serial port name: /dev/ttyUSB0,1(N) (for USB to serial adapters), /dev/ttyS0,1(N) (for built-in serial ports), /dev/serial/by-id/(port name) (for USB to serial adapters) and press “Enter” on the keyboard:

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/ttyUSB0

Next, you have to set the baudrate and press “Enter” on the keyboard:

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/ttyUSB0
Set baudrate: 115200

Next, you have to set the wait data timeout (after sending data, the application will wait for a response) and press “Enter” on the keyboard:

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/ttyUSB0
Set baudrate: 115200
Set wait data timeout (msec): 2000

After that, you have to set and choose the mode: string mode (you will be able to input text for ASCII protocols) or HEX mode (you will be able to input HEX values):

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/ttyUSB0
Set baudrate: 115200
Set wait data timeout (msec): 2000
Chose mode (1 - string, 0 - HEX): 0

After that, you will be able to enter a message to send. In HEX mode, you have to input a string in HEX format and press “Enter” on the keyboard. The application will send your message to the serial port and wait for the timeout period. After the timeout, the application will check for a response and display it, or show the “ERROR: No response from serial port” message:

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/ttyUSB0
Set baudrate: 115200
Set wait data timeout (msec): 2000
Chose mode (1 - string, 0 - HEX): 0
Enter HEX string (e.g. AAF00A): AA040170001FEBAA
[TX]: aa 4 1 70 0 1f eb aa 
[RX]: 55 17 70 33 46 54 49 49 36 34 30 30 30 31 30 30 30 30 30 58 45 50 4e 0 91 eb aa 
Enter HEX string (e.g. AAF00A): AA0401710020EBAA
[TX]: aa 4 1 71 0 20 eb aa 
[RX]: 55 17 71 33 42 31 31 36 30 31 34 31 0 0 0 0 0 0 0 0 0 0 0 0 b0 eb aa 
Enter HEX string (e.g. AAF00A): 

In string mode, the application will show both the HEX data and the string representation of the response.

================================================
Serial port tester v3.2.1
================================================

Set serial port name: /dev/serial/by-id/usb-FTDI_USB-RS232_Cable_FT5MJ4PE-if00-port0
Set baudrate: 115200
Set wait data timeout (msec): 2000
Chose mode (1 - string, 0 - HEX): 1
Enter string: TestMessage
[TX]: 54 65 73 74 4d 65 73 73 61 67 65 d a 
ERROR: No response from serial port
Enter string: ZR
[TX]: 5a 52 d a
[RX]: 5a 52 3d 31 d a  :  ZR=1

In string mode, the application automatically adds the symbols ‘\r’ and ‘\n’ at the end of a string.


Table of contents