Qt中串口数据处理机制

在嵌入式项目或者工控项目中经常会用到串口通讯,用到串口通讯可能就涉及到串口私有协议(类似 包头 + 帧类型 + 帧长度 + 帧数据 + 校验和 的形式)的解析。在Qt中经常用到QSerialPort类来进行串口数据收发,QSerialPort在串口数据可读时会释放readyRead()信号,接到这个信号再调用readAll()将缓冲区的数据全部读出来(串口数据量比较大,这个过程一般都是在一个独立的接收线程中进行处理)。但是这个readyRead()信号释放时,缓冲区的数据长度是不固定的(一般是几十个字节会释放一次读信号),不过串口私有协议各个帧的长度基本都是固定的,这就导致处理串口数据时需要对readAll()中读出来的数据进行链接,然后进行数据解析。
记录一下最近调试Qt串口通讯时用到的方法,这个方法也适用于任何系统的串口数据接收,可以达到不错的处理速度,保证不丢帧的接收。该方法的处理机制比较简单,在槽函数中接收到串口数据后,释放信号将读到的数据传递出去,接收信号方逐字节的按照协议对这些数据进行处理,这样只要QSerialPort将串口上的数据都完整的读出来了,应该是不会出现丢帧的情况。

串口初始化及数据接收

  • 串口类进行初始化后,转移到thread线程中进行处理,在readSerialData()中将数据读取后将数据传递出去。
    serialport.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class SerialPort : public QObject
    {
    Q_OBJECT
    public:
    explicit SerialPort(QObject *parent = nullptr);
    ~SerialPort();

    public slots:
    void startThread(const QString &port);
    void closeSerialPort();
    void writeSerialData(const QByteArray &data);

    private slots:
    void openSerialPort();
    void readSerialData();

    signals:
    void serialStateChanged(bool state);
    void dataReaded(const QByteArray &data);

    private:
    QString port;
    qint32 baudrate;
    QSerialPort *serial;
    QThread *thread;
    };

    serialport.cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    #include "serialport.h"

    /**
    * @brief SerialPort::SerialPort
    * @param parent
    */
    SerialPort::SerialPort(QObject *parent) : QObject(parent)
    {
    serial = new QSerialPort;
    thread = new QThread;
    this->moveToThread(thread);
    serial->moveToThread(thread);
    connect(thread, &QThread::started, this, &SerialPort::openSerialPort);
    }

    /**
    * @brief SerialPort::~SerialPort
    */
    SerialPort::~SerialPort()
    {
    if (serial->isOpen())
    {
    serial->close();
    }
    serial->deleteLater();
    thread->quit();
    thread->wait();
    thread->deleteLater();
    }

    /**
    * @brief SerialPort::startThread
    * @param port serial port name(QSerialPortInfo::name)
    */
    void SerialPort::startThread(const QString &port)
    {
    this->port = port;
    qDebug() << "current port is " << port;
    if (!thread->isRunning())
    {
    thread->start();
    qDebug() << "serialport thread start: " << QThread::currentThread();
    }
    }

    /**
    * @brief SerialPort::openSerialPort
    */
    void SerialPort::openSerialPort()
    {
    if (serial->isOpen())
    {
    qDebug() << "serial is opened, return.";
    return;
    }
    serial->setPortName(port);
    serial->setBaudRate(115200);
    serial->setDataBits(QSerialPort::Data8);
    serial->setStopBits(QSerialPort::OneStop);
    serial->setParity(QSerialPort::NoParity);
    serial->setFlowControl(QSerialPort::NoFlowControl);
    if (serial->open(QIODevice::ReadWrite))
    {
    qDebug() << "serial open success." << QThread::currentThread();
    connect(serial, &QSerialPort::readyRead,
    this, &SerialPort::readSerialData, Qt::QueuedConnection);
    }
    else
    {
    qDebug() << "serial open failed.";
    }
    }

    /**
    * @brief SerialPort::readSerialData
    * read 16 bytes each time
    */
    void SerialPort::readSerialData()
    {
    QByteArray buf = serial->readAll();
    emit dataReaded(buf);
    }

    /**
    * @brief SerialPort::writeSerialData
    * @param data buff to write
    */
    void SerialPort::writeSerialData(const QByteArray &data)
    {
    serial->write(data);
    }

    /**
    * @brief SerialPort::closeSerialPort
    */
    void SerialPort::closeSerialPort()
    {
    qDebug() << "close serialport.";
    serial->clear();
    serial->close();
    if (thread->isRunning())
    {
    thread->quit();
    thread->wait();
    }
    }

串口数据处理

  • 数据处理类接收到串口类传递的数据后,按照协议逐字节的进行处理,帧头的处理比较麻烦,因为帧头一般是两个字节,可能被缓冲到前后两个数据缓冲区,所以需要对前后两个帧头的位置关系进行判断。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    void uartRecvProcess(const QByteArray& data)
    {
    int index = 0;
    static int firstHeadPos = -1;
    static int step = 0;
    static int frameLen = 0;
    static int frameDataIndex = 0;
    static int lastLen = 0;
    static uint8_t frameData[MAX_FRAME_LEN] = {0};

    #ifdef PRINT_UART_DEBUG
    for (int i = 0; i < data.size(); i++)
    {
    printf("0x%02x ", (uint8_t)data.at(i));
    }
    printf("\n");
    #endif

    while (index < data.size())
    {
    uint8_t temp = data.at(index);

    switch (step)
    {
    // 查找第一个帧头
    case 0:
    if (temp == 0x55)
    {
    step = 1;
    firstHeadPos = index;
    frameData[frameDataIndex++] = temp;
    lastLen = data.size();
    }
    break;

    // 查找第二个帧头
    case 1:
    if ((temp == 0xAA) &&
    ((index == firstHeadPos + 1) ||
    ((firstHeadPos == lastLen -1) && index == 0)))
    {
    step = 2;
    frameData[frameDataIndex++] = temp;
    }
    else
    {
    step = 0;
    firstHeadPos = -1;
    frameDataIndex = 0;
    }
    break;

    // 帧类型判断
    case 2:
    if(temp == CMD_AD)
    {
    frameLen = FRAME_SIZE_AD;
    frameData[frameDataIndex++] = temp;
    step = 3;
    }
    else if(temp == CMD_IO)
    {
    frameLen = FRAME_SIZE_IO;
    step = 3;
    frameData[frameDataIndex++] = temp;
    }
    else
    {
    //去掉帧头,重新查找
    printf("unknow uart frame type\n");
    step = 0;
    frameLen = 0;
    firstHeadPos = -1;
    frameDataIndex = 0;
    }
    break;

    // 帧数据接收及解析
    case 3:
    if (frameDataIndex < frameLen)
    frameData[frameDataIndex++] = temp;

    if (frameDataIndex == frameLen)
    {
    if(frameData[frameLen - 1] != checkSum(frameData, frameLen - 1))
    {
    printf("type: %d, checksum error! received is 0x%02x, calculated is 0x%02x\n",
    frameLen, frameData[frameLen - 1], checkSum(frameData, frameLen - 1));
    step = 0;
    frameLen = 0;
    firstHeadPos = -1;
    frameDataIndex = 0;
    }
    else
    {
    printf("type: %d, uart data handler\n", frameLen);
    uartDataHandler(frameData, frameLen);
    step = 0;
    frameLen = 0;
    firstHeadPos = -1;
    frameDataIndex = 0;
    }
    }
    break;

    default:
    break;
    }

    index ++;
    }
    }