QSerialPort::readLine doesn't work as expected on MS Windows

4.1k views Asked by At

I'm trying to connect a micro-controller with my desktop PC via USB-serial cable. The OS of my desktop PC is Windows 8.1, and USB-serial cable is TTL-232R-3V3. (FTDI) (Qt version: 5.2.0 beta1, QtCreator Version: 3.0, Compiler: MSVC2012)

Now I'm trying read/write loop-back tests, and that's why RX/TX pin of USB-serial cable are connected with each other.

Here is my code.

#include <QtCore/QCoreApplication>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QtCore/QDebug>

#define PORT_NAME "COM3"
#define BAUDRATE 19600
#define TIMEOUT_MS 1000

QT_USE_NAMESPACE
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QSerialPort pSerial(PORT_NAME);
    const char strMsg[] = "#1:Send data line \n #2:Send data line\n #3:Send data line end\n";
    char strBuf[256];
    qint64 nByte;


    if(pSerial.open(QIODevice::ReadWrite)){
        pSerial.setBaudRate(BAUDRATE);
        qDebug() << "OPEN PASS";

        pSerial.write(strMsg);
        pSerial.flush();
        if(pSerial.waitForBytesWritten(TIMEOUT_MS)){
            qDebug() << "WRITE PASS";
        }


        pSerial.waitForReadyRead(TIMEOUT_MS);
        while(true){
            if( pSerial.canReadLine()){
                qDebug() << "CAN READ LINE";
                nByte = pSerial.readLine(strBuf,sizeof(strBuf));
                qDebug() << "Length: " << nByte;
                qDebug() << "Read data: " << strBuf;

            }
        }
        pSerial.close();
    } else {
        qDebug() << "OPEN FAIL\n";
    }
    return a.exec();
}

When the program starts to run, the result is different than I expected. Only first line of sent data can be received. So, "Read data: #1 Send data line" is printed on console. But the rest of sent data will never be received. Does anyone know why?

Any help would be appreciated. Thanks in advance.

EDIT: I revised my code according to Papp's comment.Then it works as I expected. All sent message has been received.

Does it mean I misunderstand the usage about readLine() or canReadLine()?

//        while(true){
//            if( pSerial.canReadLine()){
//                qDebug() << "CAN READ LINE";
//                nByte = pSerial.readLine(strBuf,sizeof(strBuf));
//                qDebug() << "Length: " << nByte;
//                qDebug() << "Read data: " << strBuf;
//            }
//        }

        pSerial.waitForReadyRead(TIMEOUT_MS);
        QByteArray readData = pSerial.readAll();
        while (pSerial.waitForReadyRead(TIMEOUT_MS)) {
            readData.append(pSerial.readAll());
        }
        qDebug() << "Read data: " << readData;

EDIT 2nd time : Following code also works for me.

while(true){
    if( pSerial.waitForReadyRead(TIMEOUT_MS) && pSerial.canReadLine()){ // I revised this line
        qDebug() << "CAN READ LINE";
        nByte = pSerial.readLine(strBuf,sizeof(strBuf));
        qDebug() << "Length: " << nByte;
        qDebug() << "Read data: " << strBuf;
        qDebug() << "Error Message: " << pSerial.errorString();

    }
}
3

There are 3 answers

2
László Papp On BEST ANSWER

That is because you need to read in a loop like this:

QByteArray readData = serialPort.readAll();
while (serialPort.waitForReadyRead(5000))
    readData.append(serialPort.readAll());

Please see the creadersync example for the details what I added to 5.2. You can also check the creaderasync example for non-blocking operation.

To be fair, we have not tested readLine that much, but it works for me on Unix, so does it on Windows for someone else.

4
Kuba hasn't forgotten Monica On

The mistake that you've made is expecting to receive all the sent data when waitForReadyRead returns. When waitForReadyRead finishes, all you're guaranteed is some data being available to be read. It may be as little as one character, not necessarily a whole line.

The loop from your last modification is the almost correct way to do it. You should nest reading of the lines in a separate loop. The following code is how it should be done, and agrees with the semantics of QIODevice:

while (pSerial.waitForReadyRead(TIMEOUT_MS)) {
  while (pSerial.canReadLine()) {
    qDebug() << "NEW LINE";
    QByteArray line = pSerial.readLine();
    qDebug() << "Length: " << line.size();
    qDebug() << "Read data: " << line;
    qDebug() << "Error Message: " << pSerial.errorString();
  }
}
qDebug << "TIMED OUT";

Note that none of this code should even run in the GUI thread. Ideally you should move it to a QObject, use the signals emitted by QIODevice (and thus QSerialPort), and move that object to a separate thread.

The GUI thread can sometimes block for long periods of time, it's not normally desirable to have it disturb the timeliness of your device communication. Similarly, you don't want device timeouts to block the GUI thread. Both are equally bad and are a very common source of bad user experience. Qt makes multithreading very easy - leverage it for your user's sake, and do it properly.

0
jlso On

On Linux I have to do it this way to receive ASCII text ending with '\n'

QByteArray readData = pSerial.readAll();
while (readData[readData.length() - 1] != '\n') {
    pSerial.waitForReadyRead(5000);
    readData.append(pSerial.readAll());
}
QString result(readData);

QSerialPort::readLine() doesn't work for me either