基于Qt串口Serial Port配置纯代码实现(桌面和嵌入式平台)

568 阅读13分钟

image.png

Serial Port

Qt 提供了串口类,可以直接对串口访问。我们可以直接使用 Qt 的串口类编程即可,十分方便。Qt 串口类不仅在 Windows 能用,还能在 Linux 下用,虽然串口编程不是什么新鲜事儿,既然 Qt 提供了这方面的接口,我们就充分利用起来,这将会使我们的开发十分方便!其实 Qt也提供了相关的 Qt 串口的例子,我们也可以直接参考来编程,笔者根据实际情况,化繁为易,直接写了个简单的例子给大家参考

串口调试工具:

虚拟串口+串口助手+UDP和TCP调试助手[编程人员必备]

资源简介

在 STM32 开发板的出厂系统里,默认已经配置了三路串口可用。一路是调试串口 UART4(对应系统里的节点/dev/ttySTM0),一路是 UART3(对应系统里的节点/dev/ttySTM1),另一路是 UART5(对应系统里的节点/dev/ ttySTM2),由于 UART4 已经作为调试串口被使用。所以我们只能对 UART5/UART3 编程,(如需要使用多路串口,请自行设计底板与系统)。


应用实例

项目简介:Qt 串口的使用示例,应用到 STM32开发板上。Qt 串口编程(难度:一般)。在 .pro 里,我们需要使用串口,需要在 pro 项目文件中添加串口模块的支持,如下。

QT += serialport

需要的头文件

#include <QSerialPort>     // 串口配置 
#include <QSerialPortInfo// 串口信息 
#include <QPushButton>     // 按钮 
#include <QTextBrowser>    // 文本浏览框 
#include <QTextEdit>       // 文本编辑框 
#include <QVBoxLayout>     // 垂直布局 
#include <QLabel>          // 标签 
#include <QComboBox>       // 下拉选择盒子 
#include <QGridLayout>     // 网格布局 
#include <QMessageBox>     // 弹窗信息 
#include <QDebug>          // 调试信息  
#include <QGuiApplication// 纯代码界面实现 
#include <QScreen>         // 屏幕尺寸 
#include <QRect>           // 矩形

串口分析

QSerialPort是一个在Qt框架中使用的类,用于在计算机和串行设备之间进行通信。串行设备包括像传感器、打印机、调制解调器等等。它们通过串行通信协议来传输数据,而QSerialPort就是帮助我们在程序中与这些设备进行交互的工具。


UART(通用异步收发传输器)是一种通信接口,用于在计算机和外部设备之间传输数据。它描述了数据的传输方式和电气特性。而RS232是一种串行通信标准,它规定了UART通信中使用的电气特性和通信协议。

简单来说,UART是一种通信接口的概念,而RS232是UART所使用的一种具体的串行通信标准。在实际应用中,UART可以使用不同的串行通信标准,如RS232、RS485等。

RS232是一种传统的串行通信标准,通常用于较短的通信距离,比如计算机和外部设备之间的连接。它使用两根信号线(一个用于发送数据,一个用于接收数据)来传输数据,采用了特定的电压表示不同的逻辑状态。RS232定义了数据的传输格式、波特率和其他参数。

所以,当我们提到UART时,它是一个更广义的概念,表示通信接口;而RS232是UART使用的一种具体的串行通信标准,描述了UART通信的电气特性和通信规范。


RS232和RS485是两种常见的串行通信标准,它们定义了数据的传输方式和电气特性。让我们先来看看RS232。

RS232是一种传统的串行通信标准,通常用于较短的通信距离,比如计算机和外部设备之间的连接。它使用两根信号线(一个用于发送数据,一个用于接收数据)来传输数据。RS232使用电压的正负值来表示不同的逻辑状态,比如高电平表示逻辑1,低电平表示逻辑0。这种方式适用于短距离通信,但在长距离通信中可能会受到电压衰减的影响。

接下来是RS485,它是一种更现代化和适用于长距离通信的串行通信标准。RS485支持多点通信,这意味着你可以通过一个总线连接多个设备。它使用差分信号传输数据,其中一根信号线传输正向信号,另一根信号线传输反向信号。RS485还使用了特定的电气特性和通信协议,以提供可靠的通信和抗干扰能力。

总结一下,RS232适用于较短的通信距离,而RS485适用于较长的通信距离和多设备通信。在选择哪种标准时,你需要考虑通信距离、设备数量以及系统的可靠性和抗干扰能力要求。

## 项目源码

头文件:mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
​
#include <QMainWindow>
​
 #include <QSerialPort>
 #include <QSerialPortInfo>
 #include <QPushButton>
 #include <QTextBrowser>
 #include <QTextEdit>
 #include <QVBoxLayout>
 #include <QLabel>
 #include <QComboBox>
 #include <QGridLayout>
 #include <QMessageBox>
 #include <QDebug>
​
class MainWindow : public QMainWindow
{
    Q_OBJECTpublic:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
​
private slots:
void sendPushButtonClicked();
void openSerialPortPushButtonClicked();
void serialPortReadyRead();
​
private:
    QSerialPort *serialPort;
​
     /* 用作接收数据 */
     QTextBrowser *textBrowser;
​
     /* 用作发送数据 */
      QTextEdit *textEdit;
​
      /* 按钮 */
      QPushButton *pushButton[2];
​
      /* 下拉选择盒子 */
      QComboBox *comboBox[5];
​
      /* 标签 */
      QLabel *label[5];
​
      /* 垂直布局 */
      QVBoxLayout *vboxLayout;
​
      /* 网络布局 */
      QGridLayout *gridLayout;
​
      /* 主布局 */
      QWidget *mainWidget;
​
      /* 设置功能区域 */
      QWidget *funcWidget;
​
      /* 布局初始化 */
      void layoutInit();
​
      /* 扫描系统可用串口 */
      void scanSerialPort();
​
      /* 波特率项初始化 */
      void baudRateItemInit();
​
      /* 数据位项初始化 */
      void dataBitsItemInit();
​
      /* 检验位项初始化 */
      void parityItemInit();
​
      /* 停止位项初始化 */
      void stopBitsItemInit();
​
};
#endif // MAINWINDOW_H

源文件:mainwindow.cpp

#include "mainwindow.h"
​
#include <QGuiApplication>
#include <QScreen>
#include <QRect>
​
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
​
    this->setWindowTitle("wx公众号search:Qt历险记");
​
    /* 布局初始化 */
    layoutInit();
​
     /* 扫描系统的串口 */
     scanSerialPort();
​
     /* 波特率项初始化 */
     baudRateItemInit();
​
     /* 数据位项初始化 */
     dataBitsItemInit();
​
     /* 检验位项初始化 */
     parityItemInit();
​
     /* 停止位项初始化 */
     stopBitsItemInit();
​
}
​
MainWindow::~MainWindow()
{
}
​
/* 布局初始化 */
void MainWindow::layoutInit()
{
    /* 获取屏幕的分辨率,Qt 官方建议使用这
     * 种方法获取屏幕分辨率,防上多屏设备导致对应不上
     * 注意,这是获取整个桌面系统的分辨率
     */
     QList <QScreen *> list_screen = QGuiApplication::screens();
​
     /* 如果是 ARM 平台,直接设置大小为屏幕的大小 */
     #if __arm__
         /* 重设大小 */
         this->resize(list_screen.at(0)->geometry().width(),
         list_screen.at(0)->geometry().height());
     #else
     /* 否则则设置主窗体大小为 800x480 */
     this->resize(800, 480);
     #endif
     /* 初始化 */
     serialPort = new QSerialPort(this);
     textBrowser = new QTextBrowser();
     textEdit = new QTextEdit();
     vboxLayout = new QVBoxLayout();
     funcWidget = new QWidget();
     mainWidget = new QWidget();
     gridLayout = new QGridLayout();
​
     /* QList 链表,字符串类型 */
     QList <QString> list1;
     list1<<"Serial port number:"<<"baudrate:"<<"Data bits:"<<"Parity bit:"<<"stop bit:";
​
     for (int i = 0; i < 5; i++) {
         label[i] = new QLabel(list1[i]);
         /* 设置最小宽度与高度 */
         label[i]->setMinimumSize(80, 30);
         /* 自动调整 label 的大小 */
         label[i]->setSizePolicy(
             QSizePolicy::Expanding,
             QSizePolicy::Expanding
         );
         /* 将 label[i]添加至网格的坐标(0, i) */
             gridLayout->addWidget(label[i], 0, i); // 0行 ;列++
     }
​
     for (int i = 0; i < 5; i++) {
          comboBox[i] = new QComboBox();
          comboBox[i]->setMinimumSize(80, 30);
          /* 自动调整 label 的大小 */
          comboBox[i]->setSizePolicy(
              QSizePolicy::Expanding,
              QSizePolicy::Expanding
          );
          /* 将 comboBox[i]添加至网格的坐标(1, i) */
          gridLayout->addWidget(comboBox[i], 1, i); // 1行 ;列++
      }
​
     /* QList 链表,字符串类型 */
      QList <QString> list2;
      list2<<"send"<<"Open serial port";
​
      for (int i = 0; i < 2; i++) {
          pushButton[i] = new QPushButton(list2[i]);
          pushButton[i]->setMinimumSize(80, 30);
          /* 自动调整 label 的大小 */
          pushButton[i]->setSizePolicy(
              QSizePolicy::Expanding,
              QSizePolicy::Expanding
          );
          /* 将 pushButton[0]添加至网格的坐标(i, 5) */
          gridLayout->addWidget(pushButton[i], i, 5); // 0行 5列 1行 5列
      }
      pushButton[0]->setEnabled(false);
​
      /* 布局 */
      vboxLayout->addWidget(textBrowser);
      vboxLayout->addWidget(textEdit);
​
      funcWidget->setLayout(gridLayout);
​
      vboxLayout->addWidget(funcWidget);
      mainWidget->setLayout(vboxLayout);
      this->setCentralWidget(mainWidget);
​
       /* 占位文本 */
       textBrowser->setPlaceholderText("Received messages");
       textEdit->setText(QString::fromUtf8("wx search Qt历险记"));
​
       /* 信号槽连接 */
        connect(pushButton[0], SIGNAL(clicked()),this, SLOT(sendPushButtonClicked()));
        connect(pushButton[1], SIGNAL(clicked()),this, SLOT(openSerialPortPushButtonClicked()));
​
        connect(serialPort, SIGNAL(readyRead()),this, SLOT(serialPortReadyRead()));
}
​
​
/* 扫描系统可用串口 */
void MainWindow::scanSerialPort()
{
    /* 查找可用串口 */
     foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()) {
         comboBox[0]->addItem(info.portName());
     }
}
​
/* 波特率项初始化 */
void MainWindow::baudRateItemInit()
{
    /* QList 链表,字符串类型 */
     QList <QString> list;
         list<<"1200"<<"2400"<<"4800"<<"9600"
         <<"19200"<<"38400"<<"57600"
         <<"115200"<<"230400"<<"460800"
         <<"921600";
         for (int i = 0; i < 11; i++)
         {
            comboBox[1]->addItem(list[i]);
         }
     comboBox[1]->setCurrentIndex(7); // 115200
}
​
/* 数据位项初始化 */
void MainWindow::dataBitsItemInit()
{
    /* QList 链表,字符串类型 */
     QList <QString> list;
     list<<"5"<<"6"<<"7"<<"8";
     for (int i = 0; i < 4; i++) {
        comboBox[2]->addItem(list[i]);
     }
     comboBox[2]->setCurrentIndex(3); // 8
}
​
/* 检验位项初始化 */
void MainWindow::parityItemInit()
{
    /* QList 链表,字符串类型 */
     QList <QString> list;
     list<<"None"<<"Even"<<"Odd"<<"Space"<<"Mark";
     for (int i = 0; i < 5; i++) {
        comboBox[3]->addItem(list[i]);
     }
     comboBox[3]->setCurrentIndex(0); // None
}
​
/* 停止位项初始化 */
void MainWindow::stopBitsItemInit()
{
    /* QList 链表,字符串类型 */
     QList <QString> list;
     list<<"1"<<"2";
     for (int i = 0; i < 2; i++) {
        comboBox[4]->addItem(list[i]);
     }
     comboBox[4]->setCurrentIndex(0); // 1
}
​
// 发送到串口
void MainWindow::sendPushButtonClicked()
{
     /* 获取 textEdit 数据,转换成 utf8 格式的字节流 */
     QByteArray data = textEdit->toPlainText().toUtf8();
     serialPort->write(data);
}
​
void MainWindow::openSerialPortPushButtonClicked()
 {
     if (pushButton[1]->text() == "Open serial port") {
         /* 设置串口名 */
         serialPort->setPortName(comboBox[0]->currentText());
         /* 设置波特率 */
         serialPort->setBaudRate(comboBox[1]->currentText().toInt());
         /* 设置数据位数 */
         switch (comboBox[2]->currentText().toInt()) {
         case 5:
             serialPort->setDataBits(QSerialPort::Data5);
             break;
         case 6:
             serialPort->setDataBits(QSerialPort::Data6);
              break;
          case 7:
              serialPort->setDataBits(QSerialPort::Data7);
              break;
          case 8:
              serialPort->setDataBits(QSerialPort::Data8);
              break;
          default: break;
          }
          /* 设置奇偶校验 */
          switch (comboBox[3]->currentIndex()) {
          case 0:
              serialPort->setParity(QSerialPort::NoParity);
              break;
          case 1: // 每个字符中的1位(包括奇偶校验位)的数量总是偶数。
              serialPort->setParity(QSerialPort::EvenParity);
              break;
          case 2: // 每个字符中的1位(包括奇偶校验位)的数量总是奇数
              serialPort->setParity(QSerialPort::OddParity);
              break;
          case 3: // 空间奇偶性。奇偶校验位是在空间信号条件下发送的。它不提供错误检测信息。
              serialPort->setParity(QSerialPort::SpaceParity);
              break;
          case 4: // 标记奇偶性。奇偶校验位总是被设置为标记信号条件(逻辑1)。它不提供错误检测信息。
              serialPort->setParity(QSerialPort::MarkParity);
              break;
          default: break;
          }
          /* 设置停止位 */
          switch (comboBox[4]->currentText().toInt()) {
          case 1:
              serialPort->setStopBits(QSerialPort::OneStop);
              break;
          case 2:
              serialPort->setStopBits(QSerialPort::TwoStop);
              break;
          default: break;
          }
​
          /* 设置流控制 */
           serialPort->setFlowControl(QSerialPort::NoFlowControl);
           if (!serialPort->open(QIODevice::ReadWrite)) {
               QMessageBox::about(NULL, "error",
               "Serial port cannot be opened! Perhaps the serial port is already occupied!");
           }
           else { // 打开串口
            for (int i = 0; i < 5; i++) {
                comboBox[i]->setEnabled(false);
            }
                pushButton[1]->setText("close port");
                pushButton[0]->setEnabled(true);
            }
     }
     else { // 关闭串口
        serialPort->close();
        for (int i = 0; i < 5; i++) {
            comboBox[i]->setEnabled(true);
        }
            pushButton[1]->setText("Open serial port");
            pushButton[0]->setEnabled(false);
        }
}
​
// 串口读取
void MainWindow::serialPortReadyRead()
{
     /* 接收缓冲区中读取数据 */
     QByteArray buf = serialPort->readAll();
     textBrowser->insertPlainText(QString(buf));
}
​

效果演示

使用Launch Virtual Serial Port Driver创建虚拟端口

**如何模拟通信? **

【1】将当前项目创建两个窗口进行通信,一个COM6,一个COM7

【2】使用串口助手,这里我编写了一个简单的调试助手。

调试助手源码如下

#ifndef WIDGET_H
#define WIDGET_H
​
#include <QWidget>
#include <QSerialPort>          //提供访问串口得到功能
#include <QSerialPortInfo>   //提供系统中存在的串口信息
#include <QMessageBox>    //信息弹出框
#include <QDebug>
​
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE//使用枚举保存波特率
typedef enum
{
    B1200 =1200,
    B9600 = 9600,
    B115200 = 115200,
    B921600 = 921600
}Baud;
​
class Widget : public QWidget
{
    Q_OBJECTpublic:
    Widget(QWidget *parent = nullptr);
    ~Widget();
​
private slots:
     //接收串口数据
     void SerialPortReadyRead();    //比作COM6
     void Serial2PortReadyRead();  //比作COM7
​
private slots:
    void on_PB_receive_clicked();
    void on_PB_send_clicked();
    void on_PB_detectserial_clicked();
    void on_PB_openserial_clicked();
    void on_comboBox_b_currentIndexChanged(int index);
    void on_PB_openserial_2_clicked();
    void on_PB_send_2_clicked();
​
private:
    Ui::Widget *ui;
​
    QSerialPort serial;   //串口1
    QSerialPort serial2; //串口2
};
#endif // WIDGET_H// 源文件
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
​
    //连接信号与槽
    QObject::connect(&serial, SIGNAL(readyRead()),this, SLOT(SerialPortReadyRead()));
    QObject::connect(&serial2, SIGNAL(readyRead()),this, SLOT(Serial2PortReadyRead()));
​
    //发送按钮不使能
    ui->PB_send->setDisabled(true);
    ui->PB_send_2->setDisabled(true);
​
​
}
​
Widget::~Widget()
{
    delete ui;
}
​
//串口2接收串口1数据
void Widget::SerialPortReadyRead()
{
    qDebug()<<"===============有数据==================";
    //从缓冲区读取文件
    QByteArray buffer = serial.readAll();
    //获取界面已经读取的数据
    QString recv = ui->plainTextEdit_2->toPlainText();
    recv +=QString(buffer);
​
    //显示全部
    ui->plainTextEdit_2->appendPlainText(recv);
}
//串口1接收串口2数据
void Widget::Serial2PortReadyRead()
{
    qDebug()<<"===============有数据2==================";
    //从缓冲区读取文件
    QByteArray buffer = serial2.readAll();
    //获取界面已经读取的数据
    QString recv = ui->plainTextEdit->toPlainText();
    recv +=QString(buffer);
​
    //显示全部
    ui->plainTextEdit->appendPlainText(recv);
}
​
​
void Widget::on_PB_receive_clicked()
{
    //清空接收框数据
    ui->plainTextEdit->clear();
    ui->plainTextEdit_2->clear();
}
//作为串口1
void Widget::on_PB_send_clicked()
{
    //向串口写入数据
    QByteArray data = ui->plainTextEdit_2->toPlainText().toUtf8();
    qDebug()<<"发送数据 data = "<<data;
    serial.write(data);
    if(!serial.isWritable())
        qDebug()<<"写入失败";
}
//作为串口2
void Widget::on_PB_send_2_clicked()
{
    //向串口2写入数据
    QByteArray data = ui->plainTextEdit->toPlainText().toUtf8();
    qDebug()<<"发送数据 data2 = "<<data;
    serial2.write(data);
    if(!serial2.isWritable())
        qDebug()<<"写入失败";
}
//检测串口
void Widget::on_PB_detectserial_clicked()
{
    //清空串口id
    ui->comboBox_id->clear();
    ui->comboBox_id_2->clear();
​
    //通过这个类查找可用串口1
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        qDebug()<<"info.portName() = "<<info.portName();
        ui->comboBox_id->addItem(info.portName());
    }
​
    //通过这个类查找可用串口2
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        qDebug()<<"info.portName() = "<<info.portName();
        ui->comboBox_id_2->addItem(info.portName());
    }
}
​
void Widget::on_PB_openserial_clicked()
{
    if(ui->PB_openserial->text() == QString("打开串口1"))
    {
        //设置串口名
        serial.setPortName(ui->comboBox_id->currentText());
        //设置波特率
        serial.setBaudRate(ui->comboBox_b->currentText().toUInt());
        //设置数据位
        switch (ui->comboBox_data->currentIndex())
        {
            case 8:serial.setDataBits(QSerialPort::Data8);break;
            default:break;
        }
        //设置奇偶校验 无
        switch (ui->comboBox_crc->currentIndex())
        {
            case 0:serial.setParity(QSerialPort::NoParity);break;
            default:break;
        }
        //设置停止位
        switch (ui->comboBox_stop->currentIndex())
        {
            case 1:serial.setStopBits(QSerialPort::OneStop);break;
            default:break;
        }
        //设置流控 无
        serial.setFlowControl(QSerialPort::NoFlowControl);
​
        //打开串口
        if(!serial.open(QIODevice::ReadWrite))
        {
            QMessageBox::about(NULL,"提示","串口1打开失败");
        }
        if(serial.isOpen())
                QMessageBox::about(NULL,"提示","串口1打开成功");
        //下拉菜单控件失能
        ui->comboBox_b->setEnabled(false);
        ui->comboBox_id->setEnabled(false);
        ui->comboBox_crc->setEnabled(false);
        ui->comboBox_data->setEnabled(false);
        ui->comboBox_stop->setEnabled(false);
​
        ui->PB_openserial->setText(tr("关闭串口1"));
        //发送按键使能
        ui->PB_send->setEnabled(true);
    }
    else
    {
        //关闭串口
        serial.close();
​
        //下拉使能
        ui->comboBox_b->setEnabled(true);
        ui->comboBox_id->setEnabled(true);
        ui->comboBox_crc->setEnabled(true);
        ui->comboBox_data->setEnabled(true);
        ui->comboBox_stop->setEnabled(true);
​
         ui->PB_openserial->setText(tr("打开串口1"));
​
        //发送失能
        ui->PB_send->setEnabled(false);
    }
}
​
//获取波特率索引
void Widget::on_comboBox_b_currentIndexChanged(int index)
{
     if(ui->comboBox_b->itemText(index).toUInt() == B9600)
     {
         qDebug()<<"itemText = "<<ui->comboBox_b->itemText(index);
         ui->comboBox_b->setCurrentIndex(index);
     }
     else if(ui->comboBox_b->itemText(index).toUInt() == B115200)
     {
         qDebug()<<"itemText = "<<ui->comboBox_b->itemText(index);
         ui->comboBox_b->setCurrentIndex(index);
     }
}
​
​
void Widget::on_PB_openserial_2_clicked()
{
    if(ui->PB_openserial_2->text() == QString("打开串口2"))
    {
        //设置串口名
        serial2.setPortName(ui->comboBox_id_2->currentText());
        //设置波特率
        serial2.setBaudRate(ui->comboBox_b->currentText().toUInt());
        //设置数据位
        switch (ui->comboBox_data->currentIndex())
        {
            case 8:serial2.setDataBits(QSerialPort::Data8);break;
            default:break;
        }
        //设置奇偶校验 无
        switch (ui->comboBox_crc->currentIndex())
        {
            case 0:serial2.setParity(QSerialPort::NoParity);break;
            default:break;
        }
        //设置停止位
        switch (ui->comboBox_stop->currentIndex())
        {
            case 1:serial2.setStopBits(QSerialPort::OneStop);break;
            default:break;
        }
        //设置流控 无
        serial2.setFlowControl(QSerialPort::NoFlowControl);
​
        //打开串口
        if(!serial2.open(QIODevice::ReadWrite))
        {
            QMessageBox::about(NULL,"提示","串口2打开失败");
        }
​
        if(serial2.isOpen())
            QMessageBox::about(NULL,"提示","串口2打开成功");
        //下拉菜单控件失能
        ui->comboBox_b->setEnabled(false);
        ui->comboBox_id_2->setEnabled(false);
        ui->comboBox_crc->setEnabled(false);
        ui->comboBox_data->setEnabled(false);
        ui->comboBox_stop->setEnabled(false);
​
        ui->PB_openserial_2->setText(tr("关闭串口2"));
        //发送按键使能
        ui->PB_send_2->setEnabled(true);
    }
    else
    {
        //关闭串口
        serial2.close();
​
        //下拉使能
        ui->comboBox_b->setEnabled(true);
        ui->comboBox_id_2->setEnabled(true);
        ui->comboBox_crc->setEnabled(true);
        ui->comboBox_data->setEnabled(true);
        ui->comboBox_stop->setEnabled(true);
​
         ui->PB_openserial_2->setText(tr("打开串口2"));
​
        //发送失能
        ui->PB_send_2->setEnabled(false);
    }
}
​
​

ui界面设计:

整体效果演示:

===END===