简体   繁体   English

串行数据包丢失-QTSerialPort

[英]Serial Packet Loss - QTSerialPort

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). 我正在编写一个程序,以每秒250个样本(115200波特)的速度读取来自串行RN42蓝牙连接的数据流。 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. 这是一个EEG设备,因此每个数据点都至关重要。

Thanks in advance! 提前致谢!

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. 如果您可以控制设备上的微控制器,则不错的选择是实施chk机制以恢复丢失的数据。 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. 注意:您应该在打开之后而不是之前设置串行参数。

  1. You can try check it with bytesAvailable/readAll() instead of (can)readLine() . 您可以尝试使用bytesAvailable/readAll()而不是(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). 驱动程序可能无法及时从FIFO读取数据,从而导致数据丢失(可能是驱动程序问题)。

I had the same problem. 我有同样的问题。 QSerialPort sometimes lose some blocks of bytes. QSerialPort有时会丢失一些字节块。 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: .hpp文件:

#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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM