Serial Packet Loss - QTSerialPort

1.6k views Asked by At

I'm making a program that reads a stream of data coming from a serial RN42 bluetooth connection at 250 samples per second (115200 baud). When running the code, I've noticed that some of the data were being dropped not and read, thus getting out of sync.

SerialMonitor::SerialMonitor(QObject *parent) :
    QObject(parent)
{

    // Initialization here

     DAQ = new QSerialPort(this);
     DAQ->setPortName("/dev/tty.BIOEXG-SPP");
     DAQ->setBaudRate(QSerialPort::Baud115200);
     DAQ->setDataBits(QSerialPort::Data8);
     DAQ->setParity(QSerialPort::NoParity);
     DAQ->setStopBits(QSerialPort::OneStop);
     DAQ->setFlowControl(QSerialPort::NoFlowControl);

     if (DAQ->open(QIODevice::ReadOnly)) printf("Success!\n");
     else printf("FAILED...\n");

     connect(DAQ, SIGNAL(readyRead()), this, SLOT(WriteToText()));
}

void SerialMonitor::WriteToText()
{
    while (DAQ->canReadLine()) {
        QString IncomingData = DAQ->readLine();

        // More processing here

    }
}

}

Is there a problem with my code? If not, is there a way around this problem? This is an EEG device thus every single data point is crucial.

Thanks in advance!

3

There are 3 answers

0
Elia On

You have disabled all error check and sync mechanism:

  • Parity bit disabled.
  • Flow control disabled.

If you have control of microcontroller on devices a good choice is implementing a chk mechanism for recover de lost data. If your devices is a black box you must use the HW mechanism for improve stability. When a wireless communication is used the possibility of lost some data exist and was considered on the project.

Note: You should set-up the serial parameter after the opening not before.

0
Vttooo On

I had the same problem. QSerialPort sometimes lose some blocks of bytes. I adapted an old piece of code that I found on the web and this doesn't lose anything.

#include "QVSerialPort.hpp"
#include <QtDebug>

//////////////////////////////////////////
// Set header file for documentation    /
////////////////////////////////////////

//////////////////////////////////////////////////////
// Windows Version of the serial port driver Code
/////////////////////////////////////////////////////

#ifdef Q_OS_WIN32

#include <windows.h>

QVSerialPort::QVSerialPort(QObject *parent) :
    QThread(parent)
{
    // make everything in this thread, run in this thread. (Including signals/slots)
    QObject::moveToThread(this);
    // make our data buffer
    dataBuffer = new QByteArray();
    hSerial = INVALID_HANDLE_VALUE;
    running = true;
    deviceName=NULL;
    bufferSem = new QSemaphore(1); // control access to buffer
}

QVSerialPort::~QVSerialPort() {
    running = false;
    CloseHandle(hSerial);
}

//write data to serial port
int QVSerialPort::writeBuffer(QByteArray *buffer) {
        int dwBytesRead = 0;
    if (hSerial != INVALID_HANDLE_VALUE) {
        // have a valid file discriptor
        WriteFile(hSerial, buffer->constData(), buffer->size(), (DWORD *)&dwBytesRead, NULL);
        return dwBytesRead;
    } else {
        return -1;
    }
}

// setup what device we should use
void QVSerialPort::usePort(QString *device_Name, int _buad, int _byteSize, int _stopBits, int _parity) {
    deviceName = new QString(device_Name->toLatin1());
    // serial port settings
    Buad = _buad;
    ByteSize = _byteSize;
    StopBits = _stopBits;
    Parity = _parity;
}

// data fetcher, get next byte from buffer
uint8_t QVSerialPort::getNextByte() {
    // mutex needed to make thread safe
    bufferSem->acquire(1); // lock access to resource, or wait untill lock is avaliable
    uint8_t byte = (uint8_t)dataBuffer->at(0); // get the top most byte
    dataBuffer->remove(0, 1); // remove top most byte
    bufferSem->release(1);
    return byte; // return top most byte
}

// return number of bytes in receive buffer
uint32_t QVSerialPort::bytesAvailable() {
    // this is thread safe, read only operation
    bufferSem->acquire(1); // lock access to resource, or wait untill lock is avaliable
    uint32_t res = (uint32_t)dataBuffer->size();
    bufferSem->release(1);
    return res;
}

// our main code thread
void QVSerialPort::run() {
//    bufferSem->release(1);      // not in a locked state

    // thread procedure
    if (_SERIALTHREAD_DEBUG) {
        qDebug() << "QVSerialPort: QVSerialPort Started..";
        qDebug() << "QVSerialPort: Openning serial port " << deviceName->toLatin1();
    }

    // open selected device
    hSerial = CreateFile( (WCHAR *) deviceName->constData() , GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if ( hSerial == INVALID_HANDLE_VALUE ) {
        qDebug() << "QVSerialPort: Failed to open serial port " << deviceName->toLatin1();
        emit openPortFailed();
        return;  // exit thread
    }

    // Yay we are able to open device as read/write
    qDebug() << "QVSerialPort: Opened serial port " << deviceName->toLatin1() << " Sucessfully!";

    // now save current device/terminal settings
    dcbSerialParams.DCBlength=sizeof(dcbSerialParams);

    if (!GetCommState(hSerial, &dcbSerialParams)) {
                qDebug() << "QVSerialPort: Failed to get com port paramters";
                emit openPortFailed();
                return;
        }

    if (_SERIALTHREAD_DEBUG) {
        qDebug() << "QVSerialPort: Serial port setup and ready for use";
        qDebug() << "QVSerialPort: Starting QVSerialPort main loop";
    }
    dcbSerialParams.BaudRate=Buad;
    dcbSerialParams.ByteSize=ByteSize;
    dcbSerialParams.Parity=Parity;
    dcbSerialParams.StopBits=StopBits;

    if(!SetCommState(hSerial, &dcbSerialParams)) {
                qDebug() << "QVSerialPort: Failed to set new com port paramters";
                emit openPortFailed();
                return;
    }
    COMMTIMEOUTS timeouts;

    timeouts.ReadIntervalTimeout = 0;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 1;
    timeouts.WriteTotalTimeoutConstant = 1;
    if (!SetCommTimeouts(hSerial, &timeouts)){
        qDebug()<<" error setcommtimeout";
    }
    // signal we are opened and running
    emit openPortSuccess();

    static uint8_t byte123[1023]; // temp storage byte
    int dwBytesRead;
    int state=0;    // state machine state

    // start polling loop
    while(running) {
        int ret = ReadFile(hSerial, (void *)byte123, 128, (DWORD *)&dwBytesRead, NULL); // reading 1 byte at a time..  only 2400 baud.
        // print what we received
        if (ret != 0 && dwBytesRead > 0){
            if (_SERIALTHREAD_DEBUG) {
                qDebug() << "QVSerialPort: Received byte with value: " << byte123[0];
            }
            if (dataBuffer->size() > 1023*1024) {
                if ( state == 0 ) {
                    qDebug() << "Local buffer overflow, dropping input serial port data";
                    state = 1;  // over-flowed state
                    emit bufferOverflow();
                }
            } else {
                if ( state == 1 ) {
                    qDebug() << "Local buffer no-longer overflowing, back to normal";
                    state = 0;;
                }
                // stick byte read from device into buffer
                // Mutex needed to make thread safe from buffer read operation
                bufferSem->acquire(1);
                for (int i=0;i<dwBytesRead;i++)
                    dataBuffer->append(byte123[i]);
                bufferSem->release(1);
                emit hasData(); // signal our user that there is data to receive
            }
        }
    }
    CloseHandle(hSerial);
}

#else

//////////////////////////////////////////////////////////
// Linux/Mac/BSD Version of the serial port driver Code
////////////////////////////////////////////////////////

// POSIX C stuff for accessing the serial port
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

// Constructor
QVSerialPort::QVSerialPort(QObject *parent) :
    QThread(parent)
{
    // make everything in this thread, run in this thread. (Including signals/slots)
    QObject::moveToThread(this);
    // make our data buffer
    dataBuffer = new QByteArray();
    running = true;
    deviceName=NULL;
    bufferMutex = new QMutex(); // control access to buffer
    bufferMutex->unlock();  // not in a locked state
}

QVSerialPort::~QVSerialPort() {
                running = false;
                close(sfd);
}

//write data to serial port
int QVSerialPort::writeBuffer(QByteArray *buffer) {
    if (sfd != 0) {
        // have a valid file discriptor
        return write(sfd, buffer->constData(), buffer->size());
    } else {
        return -1;
    }
}

// setup what device we should use
void QVSerialPort::usePort(QString *device_Name) {
    deviceName = new QString(device_Name->toLatin1());
    if (_SERIALTHREAD_DEBUG) {
        qDebug() << "QVSerialPort: Using device: " << deviceName->toLatin1();
    }
}

// data fetcher, get next byte from buffer
uint8_t QVSerialPort::getNextByte() {
    // mutex needed to make thread safe
    bufferMutex->lock(); // lock access to resource, or wait untill lock is avaliable
    uint8_t byte = (uint8_t)dataBuffer->at(0); // get the top most byte
    dataBuffer->remove(0, 1); // remove top most byte
    bufferMutex->unlock();
    return byte; // return top most byte
}

// return number of bytes in receive buffer
uint32_t QVSerialPort::bytesAvailable() {
    // this is thread safe, read only operation
    return (uint32_t)dataBuffer->size();
}

// our main code thread
void QVSerialPort::run() {
    // thread procedure
    if (_SERIALTHREAD_DEBUG) {
        qDebug() << "QVSerialPort: QVSerialPort Started..";
        qDebug() << "QVSerialPort: Openning serial port " << deviceName->toLatin1();
    }

    // open selected device
    sfd = open( deviceName->toLatin1(), O_RDWR | O_NOCTTY );
    if ( sfd < 0 ) {
        qDebug() << "QVSerialPort: Failed to open serial port " << deviceName->toLatin1();
        emit openPortFailed();
        return;  // exit thread
    }

    // Yay we are able to open device as read/write
    qDebug() << "QVSerialPort: Opened serial port " << deviceName->toLatin1() << " Sucessfully!";
    // now save current device/terminal settings
    tcgetattr(sfd,&oldtio);
    // setup new terminal settings
    bzero(&newtio, sizeof(newtio));
    newtio.c_cflag = Buad | ByteSize | StopBits | Parity | CREAD | CLOCAL; // enable rx, ignore flowcontrol
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;
    newtio.c_lflag = 0;
    newtio.c_cc[VTIME]    = 0;   /* inter-character timer unused */
    newtio.c_cc[VMIN]     = 1;   /* blocking read until atleast 1 charactors received */
    // flush device buffer
    tcflush(sfd, TCIFLUSH);
    // set new terminal settings to the device
    tcsetattr(sfd,TCSANOW,&newtio);
    // ok serial port setup and ready for use

    if (_SERIALTHREAD_DEBUG) {
        qDebug() << "QVSerialPort: Serial port setup and ready for use";
        qDebug() << "QVSerialPort: Starting QVSerialPort main loop";
    }

    // signal we are opened and running
    emit openPortSuccess();

    uint8_t byte; // temp storage byte
    int state=0;    // state machine state

    // start polling loop
    while(running) {
        read(sfd, (void *)&byte, 1); // reading 1 byte at a time..  only 2400 baud.
        // print what we received
        if (_SERIALTHREAD_DEBUG) {
            qDebug() << "QVSerialPort: Received byte with value: " << byte;
        }
        if (dataBuffer->size() > 1023) {
            if ( state == 0 ) {
                qDebug() << "Local buffer overflow, dropping input serial port data";
                state = 1;  // over-flowed state
                emit bufferOverflow();
            }
        } else {
            if ( state == 1 ) {
                qDebug() << "Local buffer no-longer overflowing, back to normal";
                state = 0;;
            }
            // stick byte read from device into buffer
            // Mutex needed to make thread safe from buffer read operation
            bufferMutex->lock();
            dataBuffer->append(byte);
            bufferMutex->unlock();
            emit hasData(); // signal our user that there is data to receive
        }
    }
    close(sfd);
}

#endif // OS Selection

.hpp file:

#ifndef QVSerialPort_HPP
#define QVSerialPort_HPP

// library linking info
#include "QVSerialPort_Global.hpp"

// Common Stuff
#include <QThread>
#include <QMutex>
#include <QSemaphore>

// enables verbose qDebug messages
#define _SERIALTHREAD_DEBUG 0

#ifdef Q_OS_WIN32

///////////////////////////////////////////////////////
//  IF BUILDING ON WINDOWS, IMPLEMENT WINDOWS VERSION
//  THE SERIAL PORT CLASS.
///////////////////////////////////////////////////////

#include <windows.h>
#include <stdint.h>

// default defined baud rates
// custom ones could be set.  These are just clock dividers from some base serial clock.
#ifdef Q_OS_WIN32
// Use windows definitions
#define Baud300        CBR_300
#define Baud600        CBR_600
#define Baud1200       CBR_1200
#define Baud2400       CBR_2400
#define Baud4800       CBR_4800
#define Baud9600       CBR_9600
#define Baud19200      CBR_19200
#define Baud38400      CBR_38400
#define Baud57600      CBR_57600
#define Baud115200     CBR_115200
#else
// Use Posix definitions
#define Baud300        B300
#define Baud600        B600
#define Baud1200       B1200
#define Baud2400       B2400
#define Baud4800       B4800
#define Baud9600       B9600
#define Baud19200      B19200
#define Baud38400      B38400
#define Baud57600      B57600
#define Baud115200     B115200
#endif

// bytes sizes
#ifdef Q_OS_WIN32
// windows byte defines
#define CS8            8
#define CS7            7
#define CS6            6
#define CS5            5
#else
// posix is already CS8 CS7 CS6 CS5 defined
#endif

// parity
#ifdef Q_OS_WIN32
#define ParityEven      EVENPARITY
#define ParityOdd       ODDPARITY
#define ParityNone      NOPARITY
#else
#define ParityEven      PARENB
#define ParityOdd       PARENB | PARODD
#define ParityNone      0
#endif

// stop bit
#ifdef Q_OS_WIN32
#define SB1             0
#define SB2             CSTOPB
#else
#define SB1             ONESTOPBIT
#define SB2             TWOSTOPBIT
#endif

class QVSerialPort : public QThread
{
    Q_OBJECT
public:
    explicit QVSerialPort(QObject *parent = 0);
    ~QVSerialPort();
    void usePort(QString *device_Name, int _buad, int _byteSize, int _stopBits, int _parity);
    void closePort();

    // data fetcher, get next byte from buffer
    uint8_t getNextByte();

    // return number of bytes in receive buffer
    uint32_t bytesAvailable();

    // write buffer
    int writeBuffer(QByteArray *buffer);

protected:
    // thread process, called with a start() defined in the base class type
    // This is our hardware receive buffer polling thread
    // when data is received, the hasData() signal is emitted.
    virtual void run();

signals:
    // asynchronous signal to notify there is receive data to process
    void hasData();
    // signal that we couldn't open the serial port
    void openPortFailed();
    // signal that we openned the port correct and are running
    void openPortSuccess();
    // RX buffer overflow signal
    void bufferOverflow();

public slots:
    // we don't need no sinking slots

private:
    // serial port settings
    int Buad;
    int ByteSize;
    int StopBits;
    int Parity;

    HANDLE hSerial;
    bool running;
    QByteArray *dataBuffer;
    QSemaphore *bufferSem;
    QString *deviceName;
    // windows uses a struct called DCB to hold serial port configuration information
    DCB dcbSerialParams;
};

#else // not Q_OS_WIN32

////////////////////////////////////////////////////////////////
//  IF USING A POSIX OS, ONE THAT UNDSTANDS THE NOTION        /
//  OF A TERMINAL DEVICE (Linux,BSD,Mac OSX, Solaris, etc)   /
/////////////////////////////////////////////////////////////

// Assuming posix compliant OS  (Tested on Linux, might work on Mac / BSD etc )

#include <inttypes.h>
#include <termios.h>


class QVSerialPort : public QThread
{
    Q_OBJECT
public:
    explicit QVSerialPort(QObject *parent = 0);
    ~QVSerialPort();
    void usePort(QString *device_Name);
    void closePort();

    // data fetcher, get next byte from buffer
    uint8_t getNextByte();

    // return number of bytes in receive buffer
    uint32_t bytesAvailable();

    // write buffer
    int writeBuffer(QByteArray *buffer);

protected:
    // thread process, called with a start() defined in the base class type
    virtual void run();

signals:
    // asynchronous signal to notify there is receive data to process
    void hasData();
    // signal that we couldn't open the serial port
    void openPortFailed();
    // signal that we openned the port correct and are running
    void openPortSuccess();
    // RX buffer overflow signal
    void bufferOverflow();

public slots:
    // we don't need no sinking slots

private:
    // serial port settings
    int Buad;
    int ByteSize;
    int StopBits;
    int Parity;

    int sfd;
    bool running;
    QByteArray *dataBuffer;
    QMutex *bufferMutex;
    QString *deviceName;
    // termio structs
    struct termios oldtio, newtio;
};

#endif // OS Selection

#endif // QVSerialPort_HPP
0
kuzulis On
  1. You can try check it with bytesAvailable/readAll() instead of (can)readLine().

  2. There are probability that a driver is not in time to read data from the FIFO, that lead to data loss (probably, it is a driver problem).