使用QT实现DB9串口数据接收与发送

代码针对readAll()函数出现的数据读取不完整问题使用定时器进行了改进

首先,放上代码的UI设计图以及相应的变量名称

qt 串口 waitForBytesWritten Qt 串口中断接收发送数据_串口通信


下面是UI布局图的控件变量描述

qt 串口 waitForBytesWritten Qt 串口中断接收发送数据_QT_02


下面放上项目的结构图

qt 串口 waitForBytesWritten Qt 串口中断接收发送数据_qt_03


废话不多说,下面按照项目的目录直接放代码。

首先是demo3_serialTest.pro文件

QT       += core gui
QT       += serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

然后是mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QList>
#include <QDebug>
#include <QTimer>

#define cout qDebug() << "[" << __FILE__ << ":" << __LINE__ << "]"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    QStringList Data_analysis(QString data_received);
    void serialPort_readyRead();

    QTimer *timerSerial;
    QByteArray buffer;

private slots:
    void on_btn_openConsole_clicked();
    void on_btn_send_clicked();
    void on_btn_clearRecv_clicked();
    void on_btn_clearSend_clicked();
    void readData();
    void TimerUpdate();

private:
    Ui::MainWindow *ui;
    QSerialPort *serial;

};
#endif // MAINWINDOW_H

其次是main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

最后是mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
     ui->setupUi(this);
     // 添加定时器处理数据接受不完整问题
     QTimer *timeSerial;
     timerSerial = new QTimer(this);

     serial = new QSerialPort;
     // 端口描述
     QString description;
     // 制造商字符串
     QString manufacturer;
     // 端口序列号
     QString serialNumber;
     // 空串,表示不可用
     QString blankString = "";
     //获取可以用的串口
     QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
     //输出当前系统可以使用的串口个数
     // qDebug() << "Total numbers of ports: " << serialPortInfos.count();
     cout << "Total numbers of ports: " << serialPortInfos.count();
     //将所有可以使用的串口设备添加到ComboBox中
     for (const QSerialPortInfo &serialPortInfo : serialPortInfos)
     {
          QStringList list;
          // 获取端口信息
          description = serialPortInfo.description();
          manufacturer = serialPortInfo.manufacturer();
          serialNumber = serialPortInfo.serialNumber();
          // 端口信息列表
          list << serialPortInfo.portName()
            << (!description.isEmpty() ? description:blankString) //description : blankString
            << (!manufacturer.isEmpty() ? manufacturer:blankString)//manufacturer:blankString
            << (!serialNumber.isEmpty() ? serialNumber:blankString)//serialNumber:blankString
            << serialPortInfo.systemLocation()
            << (serialPortInfo.vendorIdentifier() ? QString::number(serialPortInfo.vendorIdentifier(), 16) : blankString)
            << (serialPortInfo.productIdentifier() ? QString::number(serialPortInfo.productIdentifier(), 16) : blankString);
          // 将端口信息放入下拉框,并以第一个进行显示
          ui->comboBox_serialPort->addItem(list.first(), list);
     }

     ui->comboBox_serialPort->addItem(tr("custom"));
     //设置波特率
     ui->comboBox_baudRate->addItem(QStringLiteral("9600"), QSerialPort::Baud9600);
     ui->comboBox_baudRate->addItem(QStringLiteral("19200"), QSerialPort::Baud19200);
     ui->comboBox_baudRate->addItem(QStringLiteral("38400"), QSerialPort::Baud38400);
     ui->comboBox_baudRate->addItem(QStringLiteral("115200"), QSerialPort::Baud115200);
     ui->comboBox_baudRate->addItem(tr("Custom"));
     //设置数据位
     ui->comboBox_dataBits->addItem(QStringLiteral("5"), QSerialPort::Data5);
     ui->comboBox_dataBits->addItem(QStringLiteral("6"), QSerialPort::Data6);
     ui->comboBox_dataBits->addItem(QStringLiteral("7"), QSerialPort::Data7);
     ui->comboBox_dataBits->addItem(QStringLiteral("8"), QSerialPort::Data8);
     ui->comboBox_dataBits->setCurrentIndex(3);
     //设置奇偶校验位
     ui->comboBox_parity->addItem(tr("None"), QSerialPort::NoParity);
     ui->comboBox_parity->addItem(tr("Even"), QSerialPort::EvenParity);
     ui->comboBox_parity->addItem(tr("Odd"), QSerialPort::OddParity);
     ui->comboBox_parity->addItem(tr("Mark"), QSerialPort::MarkParity);
     ui->comboBox_parity->addItem(tr("Space"), QSerialPort::SpaceParity);
     //设置停止位
     ui->comboBox_stopBit->addItem(QStringLiteral("1"), QSerialPort::OneStop);
     ui->comboBox_stopBit->addItem(QStringLiteral("2"), QSerialPort::TwoStop);
     //添加流控
     ui->comboBox_flowBit->addItem(tr("None"), QSerialPort::NoFlowControl);
     ui->comboBox_flowBit->addItem(tr("RTS/CTS"), QSerialPort::HardwareControl);
     ui->comboBox_flowBit->addItem(tr("XON/XOFF"), QSerialPort::SoftwareControl);
     //禁用发送按钮
     ui->btn_send->setEnabled(false);
}

MainWindow::~MainWindow()
{
    delete ui;
}
//打开串口按钮槽函数
void MainWindow::on_btn_openConsole_clicked()
{
 cout << ui->btn_openConsole->text();
 if (ui->btn_openConsole->text() == tr("打开串口"))
 {
  //设置串口名字
  serial->setPortName(ui->comboBox_serialPort->currentText());
  //设置波特率
  serial->setBaudRate(ui->comboBox_baudRate->currentText().toInt());
  //设置数据位
  serial->setDataBits(QSerialPort::Data8);
  //设置奇偶校验位
  serial->setParity(QSerialPort::NoParity);
  //设置停止位
  serial->setStopBits(QSerialPort::OneStop);
  //设置流控
  serial->setFlowControl(QSerialPort::NoFlowControl);
  //打开串口,关闭按钮使能
  if (serial->open(QIODevice::ReadWrite))
  {
   ui->comboBox_baudRate->setEnabled(false);
   ui->comboBox_dataBits->setEnabled(false);
   ui->comboBox_flowBit->setEnabled(false);
   ui->comboBox_parity->setEnabled(false);
   ui->comboBox_serialPort->setEnabled(false);
   ui->comboBox_stopBit->setEnabled(false);
   ui->btn_send->setEnabled(true);
   ui->btn_openConsole->setText(tr("关闭串口"));
   //信号与槽函数关联
   //connect(serial, &QSerialPort::readyRead, this, &MainWindow::readData);

   connect(serial, &QSerialPort::readyRead, this, &MainWindow::serialPort_readyRead);
   connect(timerSerial,SIGNAL(timeout()),this,SLOT(TimerUpdate()));

  }
 }
 else
 {
  //关闭串口
  serial->close();
  //恢复设置功能
  ui->comboBox_baudRate->setEnabled(true);
  ui->comboBox_dataBits->setEnabled(true);
  ui->comboBox_flowBit->setEnabled(true);
  ui->comboBox_parity->setEnabled(true);
  ui->comboBox_serialPort->setEnabled(true);
  ui->comboBox_stopBit->setEnabled(true);
  ui->btn_openConsole->setText(tr("打开串口"));
  ui->btn_send->setEnabled(false);
 }
}

// 读数据,利用定时器处理数据接受不完整问题
void MainWindow::serialPort_readyRead()
{
    timerSerial->start(100);
    buffer.append(serial->readAll());
}

void MainWindow::TimerUpdate()
{
    timerSerial->stop();
    if(buffer.length() !=0)
    {
        // 显示数据处理结果
        QStringList lists_data;
        // 调用数据处理函数,返回数据处理的QSTringList数据,逐条显示
        lists_data = Data_analysis(buffer);
        foreach(QString each_item,lists_data){
          ui->textEdit_recv->append(each_item);
        }
    }
    cout << buffer;
    buffer.clear();
}

//发送数据槽函数
void MainWindow::on_btn_send_clicked()
{
    //serial->write(ui->textEdit_send->toPlainText().toLatin1());
    // 可以发汉字
    serial->write(ui->textEdit_send->toPlainText().toUtf8());
}

//清空接收数据槽函数
void MainWindow::on_btn_clearRecv_clicked()
{
    ui->textEdit_recv->clear();
}

//清空发送区槽函数
void MainWindow::on_btn_clearSend_clicked()
{
    ui->textEdit_send->clear();
}

// 读数据
void MainWindow::readData()
{

 QByteArray buf;
 buf = serial->readAll();
 if (!buf.isEmpty()){
      QString str = ui->textEdit_recv->toPlainText();
      str += tr(buf);
      ui->textEdit_recv->clear();
      ui->textEdit_recv->append(str);
    }

}

QStringList MainWindow::Data_analysis(QString data_received){
    
    // 数据存储表
    QStringList data_save_list;

    // 接收数据,以参数进行传递
    // QString data_received = "$GPGGA,033547.000,3027.27207487,N,11425.22216796,E,1,8,1.151,32.686,M,0.000,M,,,1.678*66";
    // 获取参数列表
    QStringList data_list;

    // 卫星种类标识
    QString flag_bd = "$BD";
    QString flag_gps = "$GP";

    // 数据头
    QString head;
    // 数据UTC时间
    QString utc_time;
    // 维度
    QString dimension;
    // 维度标识
    QString dimenson_flag;
    // 经度
    QString longitude;
    // 经度标识
    QString longitude_flag;
    // 定位质量指示
    QString position_quality_indication;
    // 使用卫星数量
    QString satellite_number;
    // 水平精度
    QString horizontal_accuracy;
    // 天线距离海平面高度
    QString height_antenna_sea_level;
    // 大地水准面高度
    QString geoid_height;
    // 差分GPS数据期限
    QString data_period_GPS;
    // 差分参考基站标号
    QString reference_base_station_label;
    data_list = data_received.split(',');
    cout << data_list;
    cout << data_list[15].split("*")[0];
    cout << data_list[15].split("*")[1];
    head = data_list[0].left(3);
    // 解析头
    if(!head.isEmpty() && !(data_list[15].split("*")[1].isEmpty())){
        // 定位质量标识,0无效,1有效
        if(data_list[6] == "1"){
            if(head == flag_bd){
                data_save_list.append("北斗");
            }
            else if(head == flag_gps){
                data_save_list.append("GPS");
            }
            // 其它制式的处理
            else{
                data_save_list.append("暂无解析");
            }
            // 解析时间
            utc_time += "UTC:";
            utc_time += data_list[1];
            data_save_list.append(utc_time);

            // 解析纬度
            if(data_list[3] == "N"){
                 dimension += "北纬:";
             }
             else{
                 dimension += "南纬:";
             }
             dimension += data_list[2];
             data_save_list.append(dimension);

            // 解析经度
             if(data_list[5] == "E"){
                 longitude += "东经:";
             }
             else{
                 longitude += "西经:";
             }
             longitude += data_list[4];
             data_save_list.append(longitude);

            // 解析卫星数量
             satellite_number += "卫星数量:";
             satellite_number += data_list[7];
             data_save_list.append(satellite_number);

            // 解析水平经度
             horizontal_accuracy += "水平经度:";
             horizontal_accuracy += data_list[8];
             data_save_list.append(horizontal_accuracy);

            // 解析天线距离海平面高度
             height_antenna_sea_level += "天线距离海平面高度:";
             height_antenna_sea_level += data_list[9];
             height_antenna_sea_level += "M";
             data_save_list.append(height_antenna_sea_level);

            // 解析大地水准面高度
             geoid_height += "大地水平面高度:";
             geoid_height += data_list[11];
             geoid_height += "M";
             data_save_list.append(geoid_height);

            // 差分GPS数据期限
             data_period_GPS += "差分GPS数据期限:";
             data_period_GPS += data_list[13];
             data_save_list.append(data_period_GPS);

            // 差分参考基站标号
             reference_base_station_label += "差分参考基站标号";
             reference_base_station_label += data_list[15].split("*")[0];
             data_save_list.append(reference_base_station_label);
             cout << data_save_list;
             foreach(QString each, data_save_list){
                 qDebug() << each;
             }
             return data_save_list;

        }
        else{
            cout << "定位经度无效";
        }

     }
}

顺便放一下mainwindow.ui的设计代码吧,方便直接拿去用

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>698</width>
    <height>631</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QGroupBox" name="groupBox">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>0</y>
      <width>201</width>
      <height>581</height>
     </rect>
    </property>
    <property name="title">
     <string>串口参数配置</string>
    </property>
    <widget class="QWidget" name="widget" native="true">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>70</y>
       <width>164</width>
       <height>345</height>
      </rect>
     </property>
     <layout class="QGridLayout" name="gridLayout">
      <item row="0" column="0">
       <widget class="QLabel" name="label">
        <property name="text">
         <string>端口号:</string>
        </property>
       </widget>
      </item>
      <item row="0" column="1">
       <widget class="QComboBox" name="comboBox_serialPort"/>
      </item>
      <item row="1" column="0">
       <widget class="QLabel" name="label_2">
        <property name="text">
         <string>波特率:</string>
        </property>
       </widget>
      </item>
      <item row="1" column="1">
       <widget class="QComboBox" name="comboBox_baudRate"/>
      </item>
      <item row="2" column="0">
       <widget class="QLabel" name="label_3">
        <property name="text">
         <string>数据位:</string>
        </property>
       </widget>
      </item>
      <item row="2" column="1">
       <widget class="QComboBox" name="comboBox_dataBits"/>
      </item>
      <item row="3" column="0">
       <widget class="QLabel" name="label_4">
        <property name="text">
         <string>校验位:</string>
        </property>
       </widget>
      </item>
      <item row="3" column="1">
       <widget class="QComboBox" name="comboBox_parity"/>
      </item>
      <item row="4" column="0">
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>停止位:</string>
        </property>
       </widget>
      </item>
      <item row="4" column="1">
       <widget class="QComboBox" name="comboBox_stopBit"/>
      </item>
      <item row="5" column="0">
       <widget class="QLabel" name="label_6">
        <property name="text">
         <string>流控位:</string>
        </property>
       </widget>
      </item>
      <item row="5" column="1">
       <widget class="QComboBox" name="comboBox_flowBit"/>
      </item>
      <item row="6" column="1">
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>128</height>
         </size>
        </property>
       </spacer>
      </item>
      <item row="7" column="0" colspan="2">
       <widget class="QPushButton" name="btn_openConsole">
        <property name="text">
         <string>打开串口</string>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </widget>
   <widget class="QGroupBox" name="groupBox_2">
    <property name="geometry">
     <rect>
      <x>210</x>
      <y>0</y>
      <width>481</width>
      <height>581</height>
     </rect>
    </property>
    <property name="title">
     <string/>
    </property>
    <widget class="QLabel" name="label_7">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>30</y>
       <width>68</width>
       <height>15</height>
      </rect>
     </property>
     <property name="text">
      <string>接收数据</string>
     </property>
    </widget>
    <widget class="QLabel" name="label_8">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>260</y>
       <width>68</width>
       <height>15</height>
      </rect>
     </property>
     <property name="text">
      <string>发送数据</string>
     </property>
    </widget>
    <widget class="QTextEdit" name="textEdit_recv">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>50</y>
       <width>461</width>
       <height>201</height>
      </rect>
     </property>
     <property name="readOnly">
      <bool>true</bool>
     </property>
    </widget>
    <widget class="QTextEdit" name="textEdit_send">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>280</y>
       <width>461</width>
       <height>201</height>
      </rect>
     </property>
    </widget>
    <widget class="QWidget" name="horizontalLayoutWidget">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>500</y>
       <width>461</width>
       <height>61</height>
      </rect>
     </property>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>38</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="btn_send">
        <property name="text">
         <string>发送数据</string>
        </property>
       </widget>
      </item>
      <item>
       <spacer name="horizontalSpacer_3">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>28</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="btn_clearSend">
        <property name="text">
         <string>清空发送区</string>
        </property>
       </widget>
      </item>
      <item>
       <spacer name="horizontalSpacer_4">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="btn_clearRecv">
        <property name="text">
         <string>清空接收区</string>
        </property>
       </widget>
      </item>
      <item>
       <spacer name="horizontalSpacer_2">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>40</width>
          <height>20</height>
         </size>
        </property>
       </spacer>
      </item>
     </layout>
    </widget>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>698</width>
     <height>26</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

关于本文档的说明,文档主要针对GPS和北斗数据的协议解析,如果不需要解析协议只需要完成数据发送仅需要对函数QStringList MainWindow::Data_analysis(QString data_received)进行注释即可,同时将mainwindows.cpp文件中第147-152行代码QStringList lists_data; // 调用数据处理函数,返回数据处理的QSTringList数据,逐条显示 lists_data = Data_analysis(buffer); foreach(QString each_item,lists_data){ ui->textEdit_recv->append(each_item); } 替换为ui->textEdit_recv->append(buffer);即可。

最后附上几张结果图

qt 串口 waitForBytesWritten Qt 串口中断接收发送数据_串口通信_04