Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes
QtWS25 Last Chance

QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes

Scheduled Pinned Locked Moved Solved Mobile and Embedded
10 Posts 4 Posters 1.2k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • H Offline
    H Offline
    Hu Zhang
    wrote on last edited by
    #1

    Recently, I use QModbus on the Raspberry Pi, and a problem often occurs,if a long modbus rtu request sent to raspberry pi, error message show, and no response for this request.

    qt.modbus: (RTU server) ADU does not match expected size, ignoring
    

    After looking at the source code, I found if a serial port data longer than 16bytes, QSerialPort::readyRead will trigger twice to receive this data, that resulting in a interval which is longer than 3.5 char,so QModbusRtuSerialSlave consider it as two requests

    I've test Qt 5.12.8 on raspberry pi 4b, and 5.12.4 on Desktop PC, Desktop is fine.

        static int cnt = 0;
        QObject::connect(&serialPort, &QSerialPort::readyRead, [this]() {
    
            const int size = serialPort.size();
            qDebug() << cnt++ << ":" << QTime::currentTime() << serialPort.read(size);
        });
    
        //serialPort.setPortName("/dev/serial0");  // raspberry pi
        serialPort.setPortName("COM3"); //Desktop PC
        serialPort.setParity(QSerialPort::NoParity);
        serialPort.setBaudRate(QSerialPort::Baud19200);
        serialPort.setDataBits(QSerialPort::Data8);
        serialPort.setStopBits(QSerialPort::OneStop);
    
        serialPort.open(QIODevice::ReadWrite);
    

    send data from a desktop pc

    02 10 00 80 00 06 0C 37 BD 35 86 CC CD 3D 4C 00 01 FF FF F8 C5 
    

    on raspberry pi 4b, output is

    0 : QTime("01:24:54.913") "\x02\x10\x00\x80\x00\x06\f7\xBD""5\x86\xCC\xCD=L\x00"
    1 : QTime("01:24:54.917") "\x01\xFF\xFF\xF8\xC5"
    

    on Desktop PC

    0 : QTime("08:39:10.893") "\x02\x10\x00\x80\x00\x06\f7\xBD""5\x86\xCC\xCD=L\x00\x01\xFF\xFF\xF8\xC5"
    
    KroMignonK 1 Reply Last reply
    0
    • H Hu Zhang

      Recently, I use QModbus on the Raspberry Pi, and a problem often occurs,if a long modbus rtu request sent to raspberry pi, error message show, and no response for this request.

      qt.modbus: (RTU server) ADU does not match expected size, ignoring
      

      After looking at the source code, I found if a serial port data longer than 16bytes, QSerialPort::readyRead will trigger twice to receive this data, that resulting in a interval which is longer than 3.5 char,so QModbusRtuSerialSlave consider it as two requests

      I've test Qt 5.12.8 on raspberry pi 4b, and 5.12.4 on Desktop PC, Desktop is fine.

          static int cnt = 0;
          QObject::connect(&serialPort, &QSerialPort::readyRead, [this]() {
      
              const int size = serialPort.size();
              qDebug() << cnt++ << ":" << QTime::currentTime() << serialPort.read(size);
          });
      
          //serialPort.setPortName("/dev/serial0");  // raspberry pi
          serialPort.setPortName("COM3"); //Desktop PC
          serialPort.setParity(QSerialPort::NoParity);
          serialPort.setBaudRate(QSerialPort::Baud19200);
          serialPort.setDataBits(QSerialPort::Data8);
          serialPort.setStopBits(QSerialPort::OneStop);
      
          serialPort.open(QIODevice::ReadWrite);
      

      send data from a desktop pc

      02 10 00 80 00 06 0C 37 BD 35 86 CC CD 3D 4C 00 01 FF FF F8 C5 
      

      on raspberry pi 4b, output is

      0 : QTime("01:24:54.913") "\x02\x10\x00\x80\x00\x06\f7\xBD""5\x86\xCC\xCD=L\x00"
      1 : QTime("01:24:54.917") "\x01\xFF\xFF\xF8\xC5"
      

      on Desktop PC

      0 : QTime("08:39:10.893") "\x02\x10\x00\x80\x00\x06\f7\xBD""5\x86\xCC\xCD=L\x00\x01\xFF\xFF\xF8\xC5"
      
      KroMignonK Offline
      KroMignonK Offline
      KroMignon
      wrote on last edited by
      #2

      @Hu-Zhang Serial port is a stream, like TCP socket. When you read data from it, you get what currently arrived, which does not mean it is a complete message. It is up to you to detect frame/message begin and end.
      Depending on PC CPU charge, I am quite sure will get same behavior on your PC.

      So this is not a bug in QSerialPort, this normal behavior.

      It is an old maxim of mine that when you have excluded the impossible, whatever remains, however improbable, must be the truth. (Sherlock Holmes)

      J.HilkJ 1 Reply Last reply
      0
      • KroMignonK KroMignon

        @Hu-Zhang Serial port is a stream, like TCP socket. When you read data from it, you get what currently arrived, which does not mean it is a complete message. It is up to you to detect frame/message begin and end.
        Depending on PC CPU charge, I am quite sure will get same behavior on your PC.

        So this is not a bug in QSerialPort, this normal behavior.

        J.HilkJ Offline
        J.HilkJ Offline
        J.Hilk
        Moderators
        wrote on last edited by
        #3

        @KroMignon Usually, this holds true!

        But @Hu-Zhang is saying he uses QModbus, which actually should take care of the data accumulation and the (Qt)user layer is a step higher.

        That said, I'm not sure why the op is operating on Serialport level, maybe to investigate 🤷‍♂️

        @Hu-Zhang I would consider upgrading! QSerialPort and especially QModbus were super buggy in 5.12! So buggy in fact that I dropped QModbus from our project and wrote my own version, because company wise we're stuck on 5.12


        Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


        Q: What's that?
        A: It's blue light.
        Q: What does it do?
        A: It turns blue.

        KroMignonK 1 Reply Last reply
        0
        • J.HilkJ J.Hilk

          @KroMignon Usually, this holds true!

          But @Hu-Zhang is saying he uses QModbus, which actually should take care of the data accumulation and the (Qt)user layer is a step higher.

          That said, I'm not sure why the op is operating on Serialport level, maybe to investigate 🤷‍♂️

          @Hu-Zhang I would consider upgrading! QSerialPort and especially QModbus were super buggy in 5.12! So buggy in fact that I dropped QModbus from our project and wrote my own version, because company wise we're stuck on 5.12

          KroMignonK Offline
          KroMignonK Offline
          KroMignon
          wrote on last edited by KroMignon
          #4

          @J-Hilk said in QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes:

          But @Hu-Zhang is saying he uses QModbus, which actually should take care of the data accumulation and the (Qt)user layer is a step higher.

          On his example, I can only see QSerialPort output, nothing about QModbus usage.

          It is an old maxim of mine that when you have excluded the impossible, whatever remains, however improbable, must be the truth. (Sherlock Holmes)

          J.HilkJ H 2 Replies Last reply
          0
          • KroMignonK KroMignon

            @J-Hilk said in QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes:

            But @Hu-Zhang is saying he uses QModbus, which actually should take care of the data accumulation and the (Qt)user layer is a step higher.

            On his example, I can only see QSerialPort output, nothing about QModbus usage.

            J.HilkJ Offline
            J.HilkJ Offline
            J.Hilk
            Moderators
            wrote on last edited by
            #5

            @KroMignon A agree, in his example, no QModbus! the actual question:

            Recently, I use QModbus on the Raspberry Pi, and a problem often occurs,if a long modbus rtu request sent to raspberry pi, error message show, and no response for this request.

            qt.modbus: (RTU server) ADU does not match expected size, ignoring


            Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


            Q: What's that?
            A: It's blue light.
            Q: What does it do?
            A: It turns blue.

            1 Reply Last reply
            0
            • KroMignonK KroMignon

              @J-Hilk said in QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes:

              But @Hu-Zhang is saying he uses QModbus, which actually should take care of the data accumulation and the (Qt)user layer is a step higher.

              On his example, I can only see QSerialPort output, nothing about QModbus usage.

              H Offline
              H Offline
              Hu Zhang
              wrote on last edited by Hu Zhang
              #6

              Thank you for reply

              I wrote the simple QSerialPort code for test, the behavior is same as QModbusRtuSerialSlave.

              and here is the full qmodbus output

              qt.modbus.lowlevel: (RTU server) Received ADU: "0210008000060c37bd3586cccd3d4c00"
              qt.modbus: (RTU server) ADU does not match expected size, ignoring
              qt.modbus.lowlevel: (RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected: 3 , max: 4)
              qt.modbus.lowlevel: (RTU server) Received ADU: "01fffff8c5"
              qt.modbus: (RTU server) Discarding request with wrong CRC, received: 63685 , calculated CRC: 8624
              

              below is qmodbus code, Under my test conditions, baudrate is 19200, The calculation result of m_interFrameDelayMilliseconds is 3 ms, and m_interFrameTimer.elapsed() is bigger than 3 ms (Normally 4, occasionally bigger), I guess it's because serial data is received in two parts, so the first part is dropped, and the second part CRC error.

              File: qmodbusrtuserialslave_p.h

              class QModbusRtuSerialSlavePrivate : public QModbusServerPrivate
              {
                  Q_DECLARE_PUBLIC(QModbusRtuSerialSlave)
              
              public:
                  void setupSerialPort()
                  {
                      Q_Q(QModbusRtuSerialSlave);
              
                      m_serialPort = new QSerialPort(q);
                      QObject::connect(m_serialPort, &QSerialPort::readyRead, [this]() {
              
                          if (m_interFrameTimer.isValid()
                                  && m_interFrameTimer.elapsed() > m_interFrameDelayMilliseconds   <<=============
                                  && !m_requestBuffer.isEmpty()) {
                              // This permits response buffer clearing if it contains garbage
                              // but still permits cases where very slow baud rates can cause
                              // chunked and delayed packets
                              qCDebug(QT_MODBUS_LOW) << "(RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected:"
                                                     << m_interFrameDelayMilliseconds << ", max:"
                                                     << m_interFrameTimer.elapsed() << ")";
                              m_requestBuffer.clear();
                          }
              
                          m_interFrameTimer.start();
              
                          const int size = m_serialPort->size();
                          m_requestBuffer += m_serialPort->read(size);
              
                          const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_requestBuffer);
                          qCDebug(QT_MODBUS_LOW) << "(RTU server) Received ADU:" << adu.rawData().toHex();
              
                          // Index                         -> description
                          // Server address                -> 1 byte
                          // FunctionCode                  -> 1 byte
                          // FunctionCode specific content -> 0-252 bytes
                          // CRC                           -> 2 bytes
                          Q_Q(QModbusRtuSerialSlave);
                          QModbusCommEvent event = QModbusCommEvent::ReceiveEvent;
                          if (q->value(QModbusServer::ListenOnlyMode).toBool())
                              event |= QModbusCommEvent::ReceiveFlag::CurrentlyInListenOnlyMode;
              
                          // We expect at least the server address, function code and CRC.
                          if (adu.rawSize() < 4) { // TODO: LRC should be 3 bytes.
                              qCWarning(QT_MODBUS) << "(RTU server) Incomplete ADU received, ignoring";
              
                              // The quantity of CRC errors encountered by the remote device since its last
                              // restart, clear counters operation, or power-up. In case of a message
                              // length < 4 bytes, the receiving device is not able to calculate the CRC.
                              incrementCounter(QModbusServerPrivate::Counter::BusCommunicationError);
                              storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CommunicationError);
                              return;
                          }
              
                          // Server address is set to 0, this is a broadcast.
                          m_processesBroadcast = (adu.serverAddress() == 0);
                          if (q->processesBroadcast())
                              event |= QModbusCommEvent::ReceiveFlag::BroadcastReceived;
              
                          const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(adu.pdu());
              
                          // server address byte + function code byte + PDU size + 2 bytes CRC
                          if ((pduSizeWithoutFcode < 0) || ((2 + pduSizeWithoutFcode + 2) != adu.rawSize())) {
                              qCWarning(QT_MODBUS) << "(RTU server) ADU does not match expected size, ignoring";   <<=============
                              // The quantity of messages addressed to the remote device that it could not
                              // handle due to a character overrun condition, since its last restart, clear
                              // counters operation, or power-up. A character overrun is caused by data
                              // characters arriving at the port faster than they can be stored, or by the loss
                              // of a character due to a hardware malfunction.
                              incrementCounter(QModbusServerPrivate::Counter::BusCharacterOverrun);
                              storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CharacterOverrun);
                              return;
                          }
              
                          // We received the full message, including checksum. We do not expect more bytes to
                          // arrive, so clear the buffer. All new bytes are considered part of the next message.
                          m_requestBuffer.resize(0);
              
                          if (!adu.matchingChecksum()) {
                              qCWarning(QT_MODBUS) << "(RTU server) Discarding request with wrong CRC, received:"
                                                   << adu.checksum<quint16>() << ", calculated CRC:"
                                                   << QModbusSerialAdu::calculateCRC(adu.data(), adu.size());   <<=============
                              // The quantity of CRC errors encountered by the remote device since its last
                              // restart, clear counters operation, or power-up.
                              incrementCounter(QModbusServerPrivate::Counter::BusCommunicationError);
                              storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CommunicationError);
                              return;
                          }
              
              KroMignonK 1 Reply Last reply
              0
              • H Hu Zhang

                Thank you for reply

                I wrote the simple QSerialPort code for test, the behavior is same as QModbusRtuSerialSlave.

                and here is the full qmodbus output

                qt.modbus.lowlevel: (RTU server) Received ADU: "0210008000060c37bd3586cccd3d4c00"
                qt.modbus: (RTU server) ADU does not match expected size, ignoring
                qt.modbus.lowlevel: (RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected: 3 , max: 4)
                qt.modbus.lowlevel: (RTU server) Received ADU: "01fffff8c5"
                qt.modbus: (RTU server) Discarding request with wrong CRC, received: 63685 , calculated CRC: 8624
                

                below is qmodbus code, Under my test conditions, baudrate is 19200, The calculation result of m_interFrameDelayMilliseconds is 3 ms, and m_interFrameTimer.elapsed() is bigger than 3 ms (Normally 4, occasionally bigger), I guess it's because serial data is received in two parts, so the first part is dropped, and the second part CRC error.

                File: qmodbusrtuserialslave_p.h

                class QModbusRtuSerialSlavePrivate : public QModbusServerPrivate
                {
                    Q_DECLARE_PUBLIC(QModbusRtuSerialSlave)
                
                public:
                    void setupSerialPort()
                    {
                        Q_Q(QModbusRtuSerialSlave);
                
                        m_serialPort = new QSerialPort(q);
                        QObject::connect(m_serialPort, &QSerialPort::readyRead, [this]() {
                
                            if (m_interFrameTimer.isValid()
                                    && m_interFrameTimer.elapsed() > m_interFrameDelayMilliseconds   <<=============
                                    && !m_requestBuffer.isEmpty()) {
                                // This permits response buffer clearing if it contains garbage
                                // but still permits cases where very slow baud rates can cause
                                // chunked and delayed packets
                                qCDebug(QT_MODBUS_LOW) << "(RTU server) Dropping older ADU fragments due to larger than 3.5 char delay (expected:"
                                                       << m_interFrameDelayMilliseconds << ", max:"
                                                       << m_interFrameTimer.elapsed() << ")";
                                m_requestBuffer.clear();
                            }
                
                            m_interFrameTimer.start();
                
                            const int size = m_serialPort->size();
                            m_requestBuffer += m_serialPort->read(size);
                
                            const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_requestBuffer);
                            qCDebug(QT_MODBUS_LOW) << "(RTU server) Received ADU:" << adu.rawData().toHex();
                
                            // Index                         -> description
                            // Server address                -> 1 byte
                            // FunctionCode                  -> 1 byte
                            // FunctionCode specific content -> 0-252 bytes
                            // CRC                           -> 2 bytes
                            Q_Q(QModbusRtuSerialSlave);
                            QModbusCommEvent event = QModbusCommEvent::ReceiveEvent;
                            if (q->value(QModbusServer::ListenOnlyMode).toBool())
                                event |= QModbusCommEvent::ReceiveFlag::CurrentlyInListenOnlyMode;
                
                            // We expect at least the server address, function code and CRC.
                            if (adu.rawSize() < 4) { // TODO: LRC should be 3 bytes.
                                qCWarning(QT_MODBUS) << "(RTU server) Incomplete ADU received, ignoring";
                
                                // The quantity of CRC errors encountered by the remote device since its last
                                // restart, clear counters operation, or power-up. In case of a message
                                // length < 4 bytes, the receiving device is not able to calculate the CRC.
                                incrementCounter(QModbusServerPrivate::Counter::BusCommunicationError);
                                storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CommunicationError);
                                return;
                            }
                
                            // Server address is set to 0, this is a broadcast.
                            m_processesBroadcast = (adu.serverAddress() == 0);
                            if (q->processesBroadcast())
                                event |= QModbusCommEvent::ReceiveFlag::BroadcastReceived;
                
                            const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(adu.pdu());
                
                            // server address byte + function code byte + PDU size + 2 bytes CRC
                            if ((pduSizeWithoutFcode < 0) || ((2 + pduSizeWithoutFcode + 2) != adu.rawSize())) {
                                qCWarning(QT_MODBUS) << "(RTU server) ADU does not match expected size, ignoring";   <<=============
                                // The quantity of messages addressed to the remote device that it could not
                                // handle due to a character overrun condition, since its last restart, clear
                                // counters operation, or power-up. A character overrun is caused by data
                                // characters arriving at the port faster than they can be stored, or by the loss
                                // of a character due to a hardware malfunction.
                                incrementCounter(QModbusServerPrivate::Counter::BusCharacterOverrun);
                                storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CharacterOverrun);
                                return;
                            }
                
                            // We received the full message, including checksum. We do not expect more bytes to
                            // arrive, so clear the buffer. All new bytes are considered part of the next message.
                            m_requestBuffer.resize(0);
                
                            if (!adu.matchingChecksum()) {
                                qCWarning(QT_MODBUS) << "(RTU server) Discarding request with wrong CRC, received:"
                                                     << adu.checksum<quint16>() << ", calculated CRC:"
                                                     << QModbusSerialAdu::calculateCRC(adu.data(), adu.size());   <<=============
                                // The quantity of CRC errors encountered by the remote device since its last
                                // restart, clear counters operation, or power-up.
                                incrementCounter(QModbusServerPrivate::Counter::BusCommunicationError);
                                storeModbusCommEvent(event | QModbusCommEvent::ReceiveFlag::CommunicationError);
                                return;
                            }
                
                KroMignonK Offline
                KroMignonK Offline
                KroMignon
                wrote on last edited by
                #7

                @Hu-Zhang According to qmodbus output, there are more than 3.5 char delay:

                • 19200 bits/sec
                • 10 bits per byte (1 start, 8 data and 1 stop)

                ==> delay is about 1.8 ms

                And your previous test with QSerialPort you have about 4ms (01:24:54.917 - 01:24:54.913) between each reception event.
                I don't know if you are using RPi hardware serial port or an USB serial dongle, but you have to "tune" your serial driver to improve reception performances.
                Perhaps changing reception buffer size?

                It is an old maxim of mine that when you have excluded the impossible, whatever remains, however improbable, must be the truth. (Sherlock Holmes)

                H 1 Reply Last reply
                1
                • KroMignonK KroMignon

                  @Hu-Zhang According to qmodbus output, there are more than 3.5 char delay:

                  • 19200 bits/sec
                  • 10 bits per byte (1 start, 8 data and 1 stop)

                  ==> delay is about 1.8 ms

                  And your previous test with QSerialPort you have about 4ms (01:24:54.917 - 01:24:54.913) between each reception event.
                  I don't know if you are using RPi hardware serial port or an USB serial dongle, but you have to "tune" your serial driver to improve reception performances.
                  Perhaps changing reception buffer size?

                  H Offline
                  H Offline
                  Hu Zhang
                  wrote on last edited by
                  #8

                  @KroMignon

                  I'm using hardware serial port, now I change m_interFrameDelayMilliseconds to 5ms for temporary use, working much better.

                  not sure if it’s the problem of Raspberry Pi or Qt, need to go deeper.

                  KroMignonK 1 Reply Last reply
                  1
                  • H Hu Zhang

                    @KroMignon

                    I'm using hardware serial port, now I change m_interFrameDelayMilliseconds to 5ms for temporary use, working much better.

                    not sure if it’s the problem of Raspberry Pi or Qt, need to go deeper.

                    KroMignonK Offline
                    KroMignonK Offline
                    KroMignon
                    wrote on last edited by
                    #9

                    @Hu-Zhang said in QSerialPort::readyRead triggered twice on raspberry pi 4B if data larger than 16bytes:

                    not sure if it’s the problem of Raspberry Pi or Qt, need to go deep

                    I think it is a driver issue, because your message is splitted in 16 byte blocks.
                    This looks to me as the serial port driver reception buffer size.

                    I don't have a RPi to check this, but 16 bytes is too "perfect" value ;)

                    It is an old maxim of mine that when you have excluded the impossible, whatever remains, however improbable, must be the truth. (Sherlock Holmes)

                    1 Reply Last reply
                    0
                    • K Offline
                      K Offline
                      kuzulis
                      Qt Champions 2020
                      wrote on last edited by
                      #10

                      Yes, also, you can't rely on m_interFrameDelayMilliseconds interval for the non-real-time OS. So, even 3, 3.5, 4, or 5 msecs will be interpreted as same value. I'm not sure, that QTimer e.g. with the 3 ms interval will trigger exacly every 3 ms for all use-cases (for all CPU loads and so on)...

                      To be honest, I'm don't know why the QtModbus module calculates and relies on this m_interFrameDelayMilliseconds parameter. As I remember, I suggested to introduce an additional "timeout" parameter for a whole request/response frame, but I'm don't know is it implemented now or not.

                      As a workaround, you can try to play with the QSerialPort handle, and to set the VTIME && VMIN values into the termios structure (currently there are zeros).

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved