Search code examples
c++boostraspberry-piboost-asioasio

Why does ASIO not apply serial port settings when sending data (only receiving)?


I have a program that uses the modbus protocol to send chunks of data between a 64-bit Raspberry Pi 4 (running Raspberry Pi OS 64) and a receiving computer. My intended setup for the serial port is baud rate of 57600, 8 data bits, two stop bits, no flow control, and no parity. I have noticed that the data is only properly interpreted when the receiving computer is set to view one stop bit and no parity, regardless of the settings on the Raspberry Pi.

What is interesting is this program works as expected when run on Windows, only the Pi has caused problems at the moment. This was originally seen in ASIO 1.20 and can still be reproduced in 1.24 on the Pi.

I wrote a minimal example that reproduces the issue for me on the Pi:

#include <asio.hpp>
#include <asio/serial_port.hpp>

#include <iostream>

int main(void) {
    asio::io_service ioService;
    asio::serial_port serialPort(ioService, "/dev/serial0");

    serialPort.set_option(asio::serial_port_base::baud_rate(57600));
    serialPort.set_option(asio::serial_port_base::character_size(8));
    serialPort.set_option(asio::serial_port_base::stop_bits(asio::serial_port_base::stop_bits::two));
    serialPort.set_option(asio::serial_port_base::flow_control(asio::serial_port_base::flow_control::none));
    serialPort.set_option(asio::serial_port_base::parity(asio::serial_port_base::parity::none));

    std::string test("Test@");

    asio::write(serialPort, asio::buffer(test.data(), test.size()));

    std::array<char, 5> buf;

    asio::read(serialPort, asio::buffer(buf.data(), buf.size()));

    std::cout << "Received: " << std::string(std::begin(buf), std::end(buf)) << std::endl;

    serialPort.close();

    return 0;
}

I looked closer at the issue and used a Saleae Logic Analyzer to see what data is being sent between the machines. Below you can see the expected behavior for a successful run, this is when the test is run on Windows. Expected behavior of serial transmission (successful using Windows)

Here you can see the behavior that occurs on the Raspberry Pi when it runs the test code. The analyzer fails to interpret the data using the parameters set in the code. Actual behavior on Raspberry Pi, using the same test

Below you can see that when the analyzer is set with one stop bit rather than two, it interprets the hex without an issue. Setting the analyzer to read serial with 1 stop bit produces the expected hex

Overall you can see that the issue takes place on the Pi's end because of the responses seen in the logic analyzer. The program running on the Pi can interpret messages sent to it using the given parameters without any issue, however when it tries to reply to those messages it seems that the ASIO port settings are not being applied.

Any insight that can be provided would be very helpful. Let me know if you need more information. Thanks for the help!

UPDATE: Ran @sehe's test code as they recommended and results are as follows:

baud_rate:      Success
character_size: Success
stop_bits:      Success
flow_control:   Success
parity:         Success
parity:         0 (Success)
flow_control:   0 (Success)
stop_bits:      0 (Success)
character_size: 8 (Success)
baud_rate:      57600 (Success)
ModbusTest: Main.cpp:37: int main(): Assertion `sb.value() == serial_port::stop_bits::two' failed.

It appears that the setting for stop bits did not successfully apply and rather failed silently. Any ideas on how to proceed with further debugging?

UPDATE 2: Also wanted to mention that I ran minicom with the same hardware setup and was able to communicate without issue using two stop bits.


Solution

  • I was able to discover the cause and fix the problem!

    I am using a Raspberry Pi 4 for this project and interfacing with GPIO pins 14/15 to use /dev/serial0. With the default configuration /dev/serial0 maps to /dev/ttyS0 which is a mini UART and is not capable of using multiple stop bits, etc.

    Disabling Bluetooth sets the symlink to map to /dev/ttyAMA0 which is a full UART and is capable of parity and multiple stop bits.

    In /boot/config.txt I added the following lines:

    [all]
    dtoverlay=disable-bt
    

    If you are experiencing a similar problem with /dev/serial0, this may be worth a shot.