4

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.

Matt
  • 330
  • 1
  • 2
  • 11
  • 2
    Could it be an access rights issue? (user on the Pi not allowed to change /dev/serial0 settings?). Can you run the program as root? – docmarvin Sep 27 '22 at 16:24
  • 2
    @docmarvin I did not look at that, good thought. I made sure my user was in the dialout group and still encountered the issue, I also ran the program as root but the same problem persists. – Matt Sep 27 '22 at 16:52

2 Answers2

3

Very solid debugging and analysis info.

I don't immediately see something wrong with the code. My intuition was to separate construction from open(), so the options could be set prior to opening, but it turns out that is just not working.

So maybe you can verify that the set_option calls had their desired effect. I can imagine hardware limitations that don't allow certain configuration?

This should definitely uncover any unexpected behavior:

Live On Coliru

//#undef NDEBUG
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
namespace asio = boost::asio;
using asio::serial_port;
using boost::system::error_code;

#include <iostream>

int main() {
    asio::io_service  ioService;
    asio::serial_port sp(ioService);
    sp.open("/dev/serial0");

    serial_port::baud_rate      br{57600};
    serial_port::character_size cs{8};
    serial_port::stop_bits      sb{serial_port::stop_bits::two};
    serial_port::flow_control   fc{serial_port::flow_control::none};
    serial_port::parity         pb{serial_port::parity::none};

    error_code ec;
    if (!ec) { sp.set_option(br, ec); std::cout << "baud_rate:      " << ec.message() << std::endl; }
    if (!ec) { sp.set_option(cs, ec); std::cout << "character_size: " << ec.message() << std::endl; }
    if (!ec) { sp.set_option(sb, ec); std::cout << "stop_bits:      " << ec.message() << std::endl; }
    if (!ec) { sp.set_option(fc, ec); std::cout << "flow_control:   " << ec.message() << std::endl; }
    if (!ec) { sp.set_option(pb, ec); std::cout << "parity:         " << ec.message() << std::endl; }

    sp.get_option(pb, ec); std::cout << "parity:         " << pb.value() << " (" << ec.message() << ")" << std::endl;
    sp.get_option(fc, ec); std::cout << "flow_control:   " << fc.value() << " (" << ec.message() << ")" << std::endl;
    sp.get_option(sb, ec); std::cout << "stop_bits:      " << sb.value() << " (" << ec.message() << ")" << std::endl;
    sp.get_option(cs, ec); std::cout << "character_size: " << cs.value() << " (" << ec.message() << ")" << std::endl;
    sp.get_option(br, ec); std::cout << "baud_rate:      " << br.value() << " (" << ec.message() << ")" << std::endl;

    assert(br.value() == 57600);
    assert(cs.value() == 8);
    assert(sb.value() == serial_port::stop_bits::two);
    assert(fc.value() == serial_port::flow_control::none);
    assert(pb.value() == serial_port::parity::none);

    std::string test("Test@");
    write(sp, asio::buffer(test));

    std::array<char, 5> buf;
    auto n = read(sp, asio::buffer(buf));

    std::cout << "Received: " << std::string(buf.data(), n) << std::endl;
}

Which on my system (Ubuntu host, using /dev/ttyS0) prints e.g.

baud_rate:      Success
character_size: Success
stop_bits:      Success
flow_control:   Success
parity:         Success
parity:         0 (Success)
flow_control:   0 (Success)
stop_bits:      2 (Success)
character_size: 8 (Success)
baud_rate:      57600 (Success)

As expected

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you for the advice! I ran the test program and included the results in an update to my original question. It appears that the stop bits failed to apply. Any other thoughts as to why that may be? – Matt Sep 28 '22 at 13:56
3

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.

Sunderam Dubey
  • 1
  • 11
  • 20
  • 40
Matt
  • 330
  • 1
  • 2
  • 11