0

Short version:

  • I had a code A, allowing me to read/write bytes over the serial port (UART), which passed unit testing
  • I wrote some code B, to read/write bytes over TCP/IP using boost asio (well, technically, standalone asio), which also passed unit testing
  • When I try to put A and B together, A starts receiving messages when none are sent
  • What I want to know is why, and what I can do about it

More details about code A:

Code A is mostly C code, using ioctl, fnctl, and unistd.

First I open the serial:

// Flag explanation:
//  - O_RDWR
//     = read & write
//     (O_RDONLY = read-only)
//     (O_WRONLY = write-only)
//  - O_NOCTTY
//     = if the path points to a terminal, that terminal will
//       not become the controlling terminal for the process
//  - O_NONBLOCK
//     = read/write requests are non-blocking, and return failure status instead of waiting
_filestream = ::open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NONBLOCK);

After checking that the serial was successfully opened, I configure the UART

// Get options currently used by the USART
termios options;
tcgetattr(_filestream, &options);

// Set control options
{
    // Set baudrate
    cfsetispeed(&options, static_cast<speed_t>(baudrate));
    cfsetospeed(&options, static_cast<speed_t>(baudrate));

    // Enable receiver (those flags must always be set)
    //  CLOCAL  = ignore modem status lines
    //  CREAD   = enable receiver
    options.c_cflag |= (CLOCAL | CREAD);

    // Set character size
    //  CSIZE   = bitmask for all sizes
    //  CS8     = specific flag for 8-bit
    options.c_cflag &= ~static_cast<unsigned int>(CSIZE);
    options.c_cflag |= CS8;

    // Disable parity bit
    //  PARENB = parity enable flag
    options.c_cflag &= ~static_cast<unsigned int>(PARENB);

    // Set number of stop bits
    //  CSTOPB = use 2 stop bits insteqd of 1
    options.c_cflag &= ~static_cast<unsigned int>(CSTOPB);
}


// Set local options
{
    // Make sure to use raw input (pass character exactly as they are received)
    // and not canonical input (which is line-oriented)
    //  ICANON  = enable canonical input
    //  ECHO    = enable echoing input characters
    //  ECHOE   = make echo erases some characters
    //  ISIG    = enable some signals
    options.c_lflag &= ~static_cast<unsigned int>(ICANON | ECHO | ECHOE | ISIG);
}


// Set input options
{
    // Enable software flow control
    //  IXON    = Enable software flow control (outgoing)
    //  IXOFF   = Enable software flow control (incoming)
    //  IXOFF   = Allow any character to start flow again
    options.c_iflag |= (IXON | IXOFF | IXANY);
}


// Set output options
{
    // Choose raw output
    //  OPOST = choose postprocess output
    options.c_oflag &= ~static_cast<unsigned int>(OPOST);
}


// Set options to use for the USART
//  TCSANOW = change attributes immediately
tcsetattr(_filestream, TCSANOW, &options);

To write stuff on the serial, I do this:

int status = write(_filestream, &byte_array, array_size);

and look at the status to check that everything went well.

To know how many bytes are available for reading, I do this:

int nbAvailable = 0;
ioctl(_filestream, FIONREAD, &nbAvailable);

To actually read those bytes, I do this:

uint8_t c;
auto status = ::read(_filestream, &c, 1);

and look at the status to check that everything went well.

When I'm done, I simply close the filestream:

close(_filestream);

More details about code B:

There's not much to say, first I do this:

asio::io_context _ioContext;
asio::ip::tcp::resolver::query query(remoteIP, remotePort);
asio::ip::tcp::resolver resolver (_ioContext);

asio::error_code errCode;
asio::ip::tcp::resolver::results_type _endPoints = resolver.resolve(query, errCode);

if (errCode)
{
    std::cerr << "Cannot resolve IP/port for the connection" << std::endl;
        exit(0);
}

Then I connect like this

asio::error_code errCode;
asio::connect(_socket, _endPoints, errCode);
if (!errCode)
{
        launch_thread();
}

The thread itself is a loop in which bytes are read using _socket.read_some() then moved to a mutex-protected array, or moved from another mutex-protected array then sent using _socket.write_some()


What happens when I put the two codes together:

The serial-port device is currently unplugged, I'm expecting nbAvailable to be 0 every time I check how many bytes are available from the serial port, but it isn't the case and many bytes are received when they should not be.

To the best I can tell, this seems to be the result of asio doing things to the serial I'm using. It's not that far-fetched, since asio also features serial-port i/o facilities in addition to their TCP/IP facilities, and all the evidence points to it:

  • the problems only arise when the TCP/IP communication starts
  • commenting the _socket.write_some() calls removes the issue

What I want to know:

Why is asio interfering with my serial port communication, when I'm only using its TCP/IP facilities? Is there any way to solve this issue?

Eternal
  • 2,648
  • 2
  • 15
  • 21
  • *"Why is `asio` interfering ..."* -- Your analysis of the situation is probably incorrect because there should be no *"interference"* as you allege. ["Correlation is not causation."](https://blog.oup.com/2013/11/correlation-is-not-causation/) Based on the snippets of code, one obvious flaw in your code is your improper and incomplete initialization of the serial terminal. See [Setting Terminal Modes Properly](http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_12.html#SEC237) and [Serial Programming Guide for POSIX Operating Systems](http://www.cmrr.umn.edu/~strupp/serial.html) – sawdust Oct 21 '19 at 22:00
  • @sawdust Thanks for the feedback, and for the links (which were much clearer than the online resources I had originally used). I went through them, and I think I fixed the initialization (see above). With that out of the way, unfortunately my problem still persists, and I'm still convinced there is an interference here. I've added more details above as to why I think so. – Eternal Oct 22 '19 at 12:08
  • 1
    Noncanonical mode also requires definition of the VMIN and VTIME members, as well as clearing a few more attributes. See https://stackoverflow.com/questions/58404561/how-to-open-a-tty-device-in-noncanonical-mode-on-linux-using-net-core/58421803#58421803. Your code snippets do not constitute a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). You could also provide details as to the runtime environment and SW versions. Does your code validate the return value from every syscall to detect errors/problems? – sawdust Oct 22 '19 at 20:53

0 Answers0