串行数据包丢失 - QTSerialPort

Posted

技术标签:

【中文标题】串行数据包丢失 - QTSerialPort【英文标题】:Serial Packet Loss - QTSerialPort 【发布时间】:2014-11-13 19:39:16 【问题描述】:

我正在编写一个程序,它以每秒 250 个样本(115200 波特)的速度读取来自串行 RN42 蓝牙连接的数据流。运行代码时,我注意到一些数据没有被删除和读取,因此不同步。

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

    

我的代码有问题吗?如果没有,有没有办法解决这个问题?这是一个脑电图设备,因此每个数据点都至关重要。

提前致谢!

【问题讨论】:

蓝牙是否有 API 可以读取某种 RX FIFO 溢出? 【参考方案1】:

您已禁用所有错误检查和同步机制:

奇偶校验位已禁用。 流控制已禁用。

如果您可以控制设备上的微控制器,一个不错的选择是实施 chk 机制来恢复丢失的数据。如果您的设备是黑匣子,您必须使用硬件机制来提高稳定性。当使用无线通信时,存在丢失某些数据的可能性,并在项目中进行了考虑。

注意:串口参数设置在打开后而不是之前。

【讨论】:

【参考方案2】:

    您可以尝试使用bytesAvailable/readAll() 而不是(can)readLine() 进行检查。

    有可能驱动程序没有及时从 FIFO 中读取数据,从而导致数据丢失(可能是驱动程序问题)。

【讨论】:

【参考方案3】:

我遇到了同样的问题。 QSerialPort 有时会丢失一些字节块。 我改编了我在网上找到的一段旧代码,这并没有丢失任何东西。

#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 文件:

#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

【讨论】:

以上是关于串行数据包丢失 - QTSerialPort的主要内容,如果未能解决你的问题,请参考以下文章

Hyper-V:通过命名管道连接 VM 会丢失数据

PyQt5 数据在字节类型的信号发射期间丢失

TCP队头阻塞和HTTP队头阻塞

RFC1055:在串行线路上传输ip数据包的非标准协议

STC89C52RC单片机额外篇 | 02 - 认识串行通信波特率以及数据包

检索用户空间 Linux C 代码中 USB 串行写入传输的缓冲区/数据包/有效负载大小