Serial read/write



  • Hello,

    I'm trying to do a very simple thing using QSerialPort. This is a task that is already working (in windows legacy software) and I'm trying to implement it using Qt.

    I have to send a short (6-byte) binary command to a device which should respond with some status info (according to a well defined protocol that we use for all of our devices). I can use our legacy software to verify the device is functioning and the command I'm sending using Qt is identical to the one the legacy software is sending and I'm using the same baud rate and serial port settings.

    Yet when I send the 6 byte command using Qt I get no response (ie No readyRead signal). I'm using a serial/usb converter (RS232/DB9 to USB) with led's so I can see there is actually data going out, but nothing coming back. The same thing happens in Linux and Windows 10.

    I reproduced the problem in this simplified example. Can anyone spot anything I'm doing wrong in this code?

    #include <QDebug>
    #include <QThread>
    #include "testtask.h"
    #include "iccontrol.hpp"
    #include <iomanip>
    
    
    TestTask::TestTask(QObject *parent) : 
        QObject(parent),
        _serialPort(),
        _timeout()
    {
    #if __linux
        _serialPort.setPortName(QString("/dev/ttyUSB0"));
    #else
        _serialPort.setPortName(QString("//./com5"));
    #endif
        qDebug() << "PORT NAME=" << _serialPort.portName();
    
    
        connect(&_serialPort, SIGNAL(bytesWritten(qint64)),
                this, SLOT(handleBytesWritten(qint64)));
    
        connect(&_serialPort, &QSerialPort::readyRead,
                this, &TestTask::handleReadyRead);
    
        connect(&_serialPort, SIGNAL(error(QSerialPort::SerialPortError)),
                this, SLOT(handleSerialError(QSerialPort::SerialPortError)));
    
        connect(&_timeout, &QTimer::timeout,
                this, &TestTask::finished);
    }
    
    void TestTask::run()
    {
    
        int baud = QSerialPort::Baud19200;
        _serialPort.setBaudRate(baud);
        if (_serialPort.open(QIODevice::ReadWrite))
        {
            if (_serialPort.isOpen())
            {
                qDebug() << "The port is OPEN";
            }
            qDebug() << "Serial connection open... Setting properties";
    
    // tried doing this here, doesn't make a difference		
    //        if  (!_serialPort.setBaudRate(baud))
    //        {
    //            qDebug() << "Warning: QSerialPort failed to set its baud rate. ERROR"
    //                     << _serialPort.error() << ": " << _serialPort.errorString();
    //        }
    //        else
    //        {
    //            qDebug() << QString::number(_serialPort.baudRate());
    //        }
    
            if (!_serialPort.setDataBits(QSerialPort::Data8))
            {
                qDebug() << "Serial port (setDataBits):" << _serialPort.errorString();
            }
            if (!_serialPort.setParity(QSerialPort::NoParity))
            {
                qDebug() << "Serial port (setParity):" << _serialPort.errorString();
            }
            if (!_serialPort.setStopBits(QSerialPort::OneStop))
            {
                qDebug() << "Serial port (setStopBits):" << _serialPort.errorString();
            }
            if (!_serialPort.setFlowControl(QSerialPort::NoFlowControl))
            {
                qDebug() << "Serial port (setFlowControl):" << _serialPort.errorString();
            }
        }
        else
        {
            qDebug() << "Failed to open port. Error:"
                     << _serialPort.errorString();
        }
    
        // create the 6-byte message
        EnquireCmd enq;
        uint8_t cmdBuf[10];
        size_t cmdSz = 0;
    
        ControlMessage cmd(&enq);
        cmd.serialize((size_t*)&cmdSz, (uint8_t*)cmdBuf);
    
    
    	// print command as hex
        for (int i = 0; i < cmdSz; i++)
        {
            std::cout << std::hex << std::setw(2) << std::setfill('0')
                      << (int) cmdBuf[i] << ":";
        }
        std::cout << std::endl;
    
        _serialPort.write((char*)cmdBuf, cmdSz);
        //_serialPort.flush(); // doesn't seem to do anything
        _timeout.start(5000);
    
    }
    
    void TestTask::handleReadyRead()
    {
        qDebug() << "Ready Read";
        emit finished();
    }
    
    
    void TestTask::handleBytesWritten(qint64 count)
    {
        qDebug() << "Wrote "<< count << " bytes: ";
    
    }
    
    void TestTask::handleSerialError(QSerialPort::SerialPortError)
    {
        qDebug() << "An error has occurred on serial port " << _serialPort.portName() << endl
                 << "Error: " << _serialPort.errorString();
    }
    
    

    Output:
    PORT NAME= "com5"
    An error has occurred on serial port "com5"
    Error: "No error"
    The port is OPEN
    2a:45:00:00:19:cd:
    Serial connection open... Setting properties
    Wrote 6 bytes:
    D:\workspace-qt\build\debug\QtTest.exe exited with code 0

    This is how it gets executed... for this example anyway. This is example is pulled from a larger application:

    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);
    
        TestTask *task = new TestTask(&app);
    
        // terminate when task emits finished
        QObject::connect(task, &TestTask::finished,
                         &app, &QApplication::quit);
    
        // run the task from the application event loop.
        QTimer::singleShot(0, task, SLOT(run()));
    
        
        
        return app.exec();
    }
    

  • Qt Champions 2016

    @CentralScrutinizer

    Hi

    The code look ok from a quick view.

    Could you try with this sample ?
    http://doc.qt.io/qt-5/qtserialport-terminal-example.html
    (its available directly in Creator)

    it can read & send and have dialog for comport setup so its good to test with.



  • Thanks for the suggestion. I ran that example and it does work (no surprise there). The only thing it does differently is that it configures all the port settings before calling open. So I tried that and it seens to work in Linux. I haven't tested it in Windows.

    The documentation for QSerialPort has a note on each of these the serial port properties that says:

    "Note: If the setting is set before opening the port, the actual serial port setting is done automatically in the QSerialPort::open() method right after that the opening of the port succeeds."

    Given that I'm not sure why setting them after opening would make a difference but I guess it does. Anyone have any theories about why that might might be?


  • Qt Champions 2016

    Hi
    Considering QSerial have signals when the setup changes, it seems unlikely that that is not supported/makes a difference.
    however, i never tried it due to old habit of setting up then open.
    I noticed that you include #include <QThread>
    Just to be sure. You are not running it in another thread?



  • @mrjj Oh, no the QThread was copy/pasted from the app this stuff came from. Only because I have a QThread::msleep in another spot.



  • @mrjj And I agree. From the docs it doesn't seem like it should matter what order you call it in. It could have started working for some other reason, I haven't have a chance to really test it thoroughly. Actually it was working for a while, though failing intermittently. It's one of these odd problems that shouldn't exist and won't go away.



  • Ok So after a little more testing that did NOT work. Changing the order of the config and open doesn't make a difference in windows or linux. It must have been a fluke that it worked once.

    Looking at lower level termios examples, they do the config after the port is opened. It sounds like Qt does the same thing under the hood even if you set the properties first.

    So what the heck is going on?

    To find out I plugged my serial output into a null modem cable (serial crossover) and plugged that into my linux machine. Then to see my raw output I did this:

    cat /dev/ttyUSB0 > enquire.log
    

    And in another terminal, this:

    stty -F /dev/ttyUSB0 speed 19200 cs8 -cstopb -parenb
    

    Then I ran both the new software and the Legacy software, and they both send exactly the same thing to the file enquire.log (looking at a hexdump of the contents). What does that tell me? probably that my code works and the device has some quirk I don't know about yet.


  • Lifetime Qt Champion

    Hi,

    Out of curiosity, what are the rights on the serial device ?



  • the device itself has the following perms:

    crw-rw---- 1 root dialout 188, 0 May  7 12:41 /dev/ttyUSB0
    

    To access it you can either add youself to the dialout group or use sudo. I did the dialout group thing.

    Just to close off this thread, I discovered the quirk was actually in the Legacy software. It was showing me the wrong baud rate for the device so it was in 115200, not 19200 as I thought. So I hope the code I posted is a useful example for someone!


Log in to reply