c++linuxserial-portvirtual-machinecom-port

Error 11 reading from Com port: Resource temporarily unavailable


I am studying interaction with Com ports in C++ on a virtual machine with Ubuntu 24.04.2.

Port mode is set to Raw fille in VM. So I see that I can write to the Com port.

But reading from the port each time with error -11.

The code of my program:

#include <iostream>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>

void configurePort(int fd) 
{
   struct termios options;

   // Get current port parameters
   tcgetattr(fd, &options);

   // Set data rate
   cfsetispeed(&options, B115200);
   cfsetospeed(&options, B115200);

   // Set 8 data bits, no parity, 1 stop bit
   options.c_cflag &= ~PARENB; // No parity
   options.c_cflag &= ~CSTOPB; // 1 stop bit
   options.c_cflag &= ~CSIZE; // Reset data size
   options.c_cflag |= CS8; // 8 bits of data

   // Set non-canonical input mode and disable flow control
   options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
   options.c_iflag &= ~(IXON | IXOFF | IXANY);

   // Apply settings
   tcsetattr(fd, TCSANOW, &options);
}

void sendData(int fd, const char* message) 
{
   ssize_t bytesWritten = write(fd, message, strlen(message));

   if (bytesWritten < 0) 
   {
      std::cerr << "Error sending data" << std::endl;
      return;
   }

   std::cout << "Bytes sent: " << bytesWritten << std::endl;
}

void readData(int fd) 
{
   char buffer[256];

   // Read data from the port
   ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);

   if (bytesRead < 0) 
   {
      printf("Error %i reading from port: %s\n", errno, strerror(errno));
      std::cerr << "Error reading data" << std::endl;
      return;
   }

   buffer[bytesRead] = '\0'; // Terminate the string with a null character
   std::cout << "Bytes read: " << bytesRead << ", data: " << buffer << std::endl;
}

int main() 
{
   const char* portName = "/dev/ttyS0"; // Port name (can be changed to /dev/ttyS1)

   //Open the port in non-blocking mode
   int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY);

   if (fd == -1) 
   {
      std::cerr << "Failed to open port " << portName << std::endl;
      return 1;
   }

   // Configure the port
   configurePort(fd);

   const char* message = "123456789!sdas!!\n";

   // Send data
   sendData(fd, message);

   // Read data
   sleep(1); // Delay to wait for a response 

   readData(fd);

   // Close the port
   close(fd);

   return 0;
}

Initially I tried to do this in a more complex program, sending and reading data in different streams. But when I encountered a error, I decided to simplify the program as much as possible, so that it would be easier to track down the error.

I added the current user to the dialout group.

While googling the error code, I saw that this happens with Arduino, zigbee, etc. And there the error is solved by stopping the port, for example:

sudo systemctl stop zigbee2mqtt

But I didn't find whether it is possible and necessary to make such a stop for Com ports. As well as I didn't find how to do it.

Console output about Com ports


Solution

  • To make reading possible, it was necessary to correct the port opening parameters:

    int fd = open(portName, O_RDWR | O_NOCTTY);

    So I created two virtual ports on the host using com0com, connected them, linked them to VirtualBox. After that I sent a message to ttyS0 and read from ttyS1. The final code looks like this:

    int main()
    {
        const char* portName = "/dev/ttyS0";
        const char* portName2 = "/dev/ttyS1";
    
        // open port
        int fd = open(portName, O_RDWR | O_NOCTTY);
        if (fd == -1) {
            std::cerr << "Can`t open port " << portName << std::endl;
            return 1;
        }
    
        int fd2 = open(portName2, O_RDWR | O_NOCTTY);
        if (fd2 == -1) {
            std::cerr << "Can`t open port " << portName2 << std::endl;
            return 1;
        }
    
        configurePort(fd);
        configurePort(fd2);
    
        const char* message = getCurrentTime();
    
        // Sending data
        std::thread senderThread(sendData, fd, message);
        // sendData(fd, message);
    
        
        usleep ((7 + 25) * 100);
        // Read data
        std::thread readerThread(readData, fd2);
        // readData(fd2);
    
        senderThread.join();
        readerThread.join();
    
        // Close ports
        close(fd);
        close(fd2);
    
        return 0;
    }
    

    In addition, I made changes to configurePort, but to be honest, I still don’t understand whether they were really needed in my case:

    void configurePort(int fd)
    {
        ...
        //Disabling special byte handling when receiving
        options.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
    
        //We wait up to 1 s (10 deciseconds) and return as soon as any data is received
        options.c_cc[VTIME] = 10;
        options.c_cc[VMIN] = 0;
    
        //Apply the settings
        if (tcsetattr(fd, TCSANOW, &options) != 0) {
            printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
        }
    }
    

    After I wrote the code, I was told that in order to use only one port on a virtual machine, you can use socat. But I haven't figured out how to use it yet.