Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Delayed readReady signal of QSerialPort



  • Hi everyone

    I developed a Qt GUI to control a microcontroller based device. The communication between the PC and the device is done over a serial port with a custom protocol. The communication itself works like a charm, but there is some kind of delay between the packages I send and therefore I do not reach the desired throughput with this approach.

    I debugged the physical connection with a scope and see that the response (yellow) is returned almost immediately after sending a request (blue):
    serCom.png

    There is chain of events triggered to send and receive data and the GUI is waiting in a eventloop to complete the data transfer:

    RegGetStatusTypes IBHandler::readRegister(const quint8 devId, const quint8 regId, QByteArray &readData)
    {
        QEventLoop eLoop;
        QTimer toTimer;
    
        if (m_serialport->isOpen())
        {
            // Hookup the local eventloop
            connect(this, SIGNAL(communicationFinished()), &eLoop, SLOT(quit()));
            connect(&toTimer, SIGNAL(timeout()), &eLoop, SLOT(quit()));
    
            // Send the message
            m_txMsgRetries = 3;   // Max 3 message retries
            m_txCRCRetries = 3;
            m_txBUSYRetries = 3;
            m_msgService->slotSendMessage(devId, regId, msgRead, QByteArray());
    
            toTimer.setSingleShot(true);
            toTimer.start(500);     // Emergency timeout 500mS
            eLoop.exec();           // Start the local eventloop - Waiting for the communication to complete
    
            readData.clear();
            readData.append(m_rxMsgData);
            return m_rxStatus;
        }
        return GetPortClosed;
    }
    

    The signal/slot event chain is simplified as follows:

    1. readRegister is called in GUI
    2. triggers slotSendMessage (-> writes data to serial port) and waits in an eventloop
    3. data is received -> readyRead signal triggers data parsing slot
    4. data parsing slot triggers communicationFinished signal
    5. eventloop quits and GUI will set new data and send next request.

    I used qDebug and a elapsed timer to print timings of the events:

    ========== START ==============
    init send message
    data written to port:  0.0147 ms
    readyRead signal triggered:  15.2787 ms
    payload parsing complete:  15.3484 ms
    read transfer complete:  15.4901 ms
    ========== STOP ==============
    

    It seems that the readyRead signal is triggered 15ms after sending a request.

    Is there a way to reduce this delay?

    Any help is much appreciated.


  • Lifetime Qt Champion

    @dazi Are you using a real hardware RS-232 or an USB-serial adapter like FTDI? (which one?)

    Because for the USB adapters there is a buffering involved which will delay the received data. For FTDI devices, you can reduce this delay in the advanced device settings down to one millisecond. I don't know if that's possible for other vendors.

    Regards



  • HI, the speed of the RS232 connection is the normal 115200 baud which means about 11520 bytes can be sent over the line per second as the best case, this translates to about 11 bytes per millisecond. So a delay of 15 milliseconds for the readyRead signal to fire is not unreasonable.

    I think you need to crank up the speed :-)



  • Hi, thanks for your answer.
    Unfortunately cranking up the speed isn't really helpful/applicable in this setup.

    The transfer takes only 2ms, but the readyRead signal is fired 15ms later. The ratio is a bit off in my opinion... Correct me if I'm wrong but, increasing the speed would probably just reduce the transfer time, but not the readyRead signal.


  • Moderators

    @dazi

    eLoop.exec(); // Start the local eventloop - Waiting for the communication to complete

    This is just wrong, I would rather you use the synchronous methods than creating and spinning your own separate event loop



  • Hi J.Hilk
    You are right, could have been done better. This way I prevent the GUI from freezing and return to the called line in my statemachine. However, could this eventloop cause the delay of the readyRead signal?



  • I removed the eventloop and used a signal instead_

    void IBHandler::slotRxComplete() {
        qDebug() << "Data received: " << m_rxMsgData.toHex('|');
        disconnect(this, SIGNAL(communicationFinished()), this, SLOT(slotRxComplete()));
        emit sigRxComplete(m_rxStatus,m_rxMsgData);
    }
    
    RegGetStatusTypes IBHandler::readRegister(const quint8 devId, const quint8 regId)
    {
        if (m_serialport->isOpen())
        {
            // Hookup the local eventloop
            connect(this, SIGNAL(communicationFinished()), this, SLOT(slotRxComplete()));
    
            // Send the message
            m_txMsgRetries = 3;   // Max 3 message retries
            m_txCRCRetries = 3;
            m_txBUSYRetries = 3;
            m_msgService->slotSendMessage(devId, regId, msgRead, QByteArray());
    
            return GetSuccess;
        }
        return GetPortClosed;
    }
    

    Unfortunately this did not have any impact on the readyRead delay...


  • Qt Champions 2019

    @dazi said in Delayed readReady signal of QSerialPort:

    I prevent the GUI from freezing

    If you want to prevent UI from freezing then do not use synchronous API, use the asynchronous one...



  • The code I posted previously is using the async API only. Everything is triggered by signals and processed by slots, but the delay of the readyRead signal remains.



  • I'm think, you can't improve the time. As it depends on a system scheduler, Qt-event loop and so on. Besides, the readyRead() signal triggering chain on windows is following:

    • system RX event -> system start read FIFO -> system FIFO read complete event -> qt readyRead() signal.

    PS: You can try to buy a more powerfull CPU. :)

    PS2: You can look e.g. this: https://docs.microsoft.com/en-us/windows/win32/procthread/multitasking for your info.

    PS3: Or, maybe, you can try to move your QSP instance to a separate thread and to give the 'highest' priority to that thread.



  • @kuzulis said in Delayed readReady signal of QSerialPort:

    PS: You can try to buy a more powerfull CPU. :)

    Well I hope an i7 core should be able to handle the serial port :)

    I know that the PC is able to fetch data in a much higher rate. This GUI should actually replace a Labview program, which retrieves data like this (timebase of the scope is the same as in the picture above):
    comSer2.png


  • Lifetime Qt Champion

    @dazi Are you using a real hardware RS-232 or an USB-serial adapter like FTDI? (which one?)

    Because for the USB adapters there is a buffering involved which will delay the received data. For FTDI devices, you can reduce this delay in the advanced device settings down to one millisecond. I don't know if that's possible for other vendors.

    Regards



  • @aha_1980 said in Delayed readReady signal of QSerialPort:

    @dazi Are you using a real hardware RS-232 or an USB-serial adapter like FTDI? (which one?)

    You are absolutly right! Thank you so much for pointing this out.

    I'm using the FT232R USB to RS232 converter from FTDI, reduced the buffering time to 1ms and this is the result:
    serCom3.png

    Do you know by any chance if this settings can be changed from the application?


  • Lifetime Qt Champion

    @dazi said in Delayed readReady signal of QSerialPort:

    Do you know by any chance if this settings can be changed from the application?

    I'm sorry, no; but I would be interested in this answer also :) So if you find a solution, feel free to share.

    Regards



  • but I would be interested in this answer also :)

    As I remember, we did some project where we do changes for FTDI latency parameter:

    #ifdef Q_OS_WIN
    // Возвращает указатель реестра для устройства с заданным именем,
    // если оно является последовательным портом FTDI, либо 0.
    HKEY openFtdiDeviceParameters(const QString &portName)
    {
        const wchar_t *keypath = L"SYSTEM\\CurrentControlSet\\Enum\\FTDIBUS";
        HKEY key;
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keypath, 0, KEY_READ, &key)) {
            // Порты FTDI отсутствуют.
            return 0;
        }
    
        // Просматриваем все порты FTDI.
        int idx = 0;
        HKEY readOnlyKey = 0;
        HKEY paramKey = 0;
        while (true) {
            if (paramKey) {
                RegCloseKey(paramKey);
                paramKey = 0;
            }
            DWORD subkeySize = 140;
            wchar_t subKey[140] = {0};
            // Последовательный порт не является устройством FTDI.
            if (RegEnumKeyEx(key, idx++, subKey, &subkeySize, 0, 0, 0, 0)) {
                break;
            }
            QString paramPath(QString(QLatin1String("%1\\0000\\Device Parameters")).arg(QString::fromWCharArray(subKey)));
            if (RegOpenKeyEx(key, paramPath.toStdWString().c_str(), 0, KEY_READ, &readOnlyKey)) {
                continue;
            }
            DWORD regPortNameSize = 20;
            wchar_t regPortName[20] = {0};
            DWORD type = 0;
            LONG result = RegQueryValueEx(readOnlyKey, L"PortName", 0, &type, (LPBYTE) &regPortName, &regPortNameSize);
            if (ERROR_SUCCESS == result) {
                QString name = QString::fromWCharArray(regPortName);
                if (portName == name) {
                    RegOpenKeyEx(readOnlyKey, 0, 0, KEY_READ | KEY_SET_VALUE, &paramKey);
                }
            }
            RegCloseKey(readOnlyKey);
            if (paramKey) {
                break;
            }
        }
        RegCloseKey(key);
        return paramKey;
    }
    #endif
    

    and then:

    #ifdef Q_OS_WIN
        // Если выбранный порт является FTDI, устанавливаем его latency в 1.
        DWORD latency = 0;
        HKEY latencyKey = openFtdiDeviceParameters(name);
        DWORD dwType = 0;
        DWORD dwSize = sizeof(DWORD);
        bool foundFtdiDevice;
        if (latencyKey && !RegQueryValueEx(latencyKey, L"LatencyTimer", 0, &dwType, (LPBYTE) &latency, &dwSize)
            && dwType == REG_DWORD) {
            foundFtdiDevice = true;
        } else {
            foundFtdiDevice = false;
        }
    
        if (foundFtdiDevice) {
            if (latency != 1) {
                DWORD dwLatency = 1;
                RegSetValueEx(latencyKey, L"LatencyTimer", 0, REG_DWORD, (LPBYTE) &dwLatency, sizeof(DWORD));
                RegCloseKey(latencyKey);
            }
        }
    
    #endif
    

    Here we did set the latency to 1 msec.

    PS: Sorry, all comments in russian (because it is copy-paste).


Log in to reply