Qt Widget 之简易串口助手(QSerialPort)

Posted 火山上的企鹅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Widget 之简易串口助手(QSerialPort)相关的知识,希望对你有一定的参考价值。


GitHub 地址:     QWidgetPro选择子工程 QSerialAssistant .pro(目前只有这一个)

QT 其它文章请点击这里:     QT QUICK QML 学习笔记


一、演示

Qt 作上位机,与硬件连接中,串口是最最最常用的功能。 本文写了一个很容易入手的简单串口程序,演示如下:

二、前端 UI

添加一个新的 Qt Widgets 工程

1. UI 布局

布局在设计中拖拽生成:

2. 控件改名

如下修改控件名:

3. 建立槽函数

创建如下控件的槽函数,点击 UI 自动生成的

private slots:
    void on_pushButton_operate_clicked();
    void on_pushButton_send_clicked();
    void on_pushButton_clearRcv_clicked();
    void on_pushButton_clearSend_clicked();

其它如下拉框中的选项、默认值都在程序中设定。

三、后端实现思路

1. 添加 QSerialPort 模块

在.pro 文件中增加 QT += serialport

然后在 mainwindow.h 中添加:

#include <QSerialPort>        //提供访问串口的功能
#include <QSerialPortInfo>    //提供系统中存在的串口的信息

注意,在 Qt4 的时候有第三方模块 QextSerialPort,到了Qt5.1 官方提供了 QSerialPort 模块。

2. 初始化设置

实例化对象、建立信号槽、初始化控件的内容等

void MainWindow::initConfig() {

    //创建对象,并建立信号槽
    serial = new QSerialPort(this);
    //读函数的信号槽,详情见后文
    QObject::connect(serial, &QSerialPort::readyRead, this, &MainWindow::serial_readyRead);
    //端口号下拉框升级后的点击事件,见后文
    QObject::connect(ui->comboBox_port, SIGNAL(clicked()), this, SLOT(updataPortNum()));

    //添加端口号
    updataPortNum();
    ui->comboBox_port->setCurrentIndex(0);

    //添加波特率
    QStringList baudList;
    baudList << "115200" << "57600" << "9600" ;
    ui->comboBox_baud->addItems(baudList);
    ui->comboBox_baud->setCurrentText("115200");

    //添加停止位、添加数据位、添加校验位
    ...
	//设置默认文本
    ...
}

3. 串口设置

在单击 “OPEN” 的时候触发,主要为检查串口是否打开、是否被占用、串口配置、设置串口状态, 打开串口,核心代码如下:

void MainWindow::on_pushButton_operate_clicked()
{
    if (ui->pushButton_operate->text() == QString("OPEN")) {
    	//检查串口是否被占用
        const QString portnameStr = ui->comboBox_port->currentText();
        QSerialPortInfo info(portnameStr);
        if(info.isBusy()){
            qDebug()<< "The serial port is occupied" <<portnameStr;
            return;
        }
		...
		///串口配置
        //清空缓冲区
        serial->flush();
        //设置端口号
        serial->setPortName(portnameStr);
        //设置波特率
        serial->setBaudRate( static_cast<QSerialPort::BaudRate> (ui->comboBox_baud->currentText().toInt()) );
        //设置停止位、设置数据位、设置校验、设置流控
		...
		
        isSerialOpen = serial->open(QIODevice::ReadWrite);
        ...
    }
}

4. 数据发送

点击发送按钮,发送数据

void MainWindow::on_pushButton_send_clicked()
{
	//简单文本框用 toPlainText() 取文本框的内容 toUtf8 是转换成utf8格式的字节流
    QByteArray data = ui->textEdit_send->toPlainText().toUtf8();
    serial->write(data);
}

5. 数据接收

前文在初始化中已经建立了信号槽:

QObject::connect(serial, &QSerialPort::readyRead, this, &MainWindow::serial_readyRead);

在设置了串口,并打开了串口后,每次串口收到数据后都会发出这个 QSerialPort::readyRead 信号。我们的程序中需要定义一个 slot,并将其与这个 signal 相连接。这样,每次新数据到来后,我们就可以在 slot 中读取数据了。这时一定要将串口缓冲区中的数据全部读出来,可以利用 readAll() 来实现,如下:

void MainWindow::serial_readyRead()
{
    //从界面中读取以前收到的数据
    QString recv = ui->textEdit_rcv->toPlainText();
    //从接收缓冲区中读取数据
    QByteArray buffer = serial->readAll();
    //界面中读取的数据中加入刚读取的数据
    recv += QString(buffer);
    //清除显示
    ui->textEdit_rcv->clear();
    //更新显示
    ui->textEdit_rcv->append(recv);
}

6. 自动获取硬件上串口

● 获取串口号的核心程序

    //清除串口号
    ui->comboBox_port->clear();
    //遍历 QSerialPortInfo, 添加到串口下拉框中
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){
        ui->comboBox_port->addItem(info.portName());
    }

想每次点击串口号下拉框的时候,也就是下面这个,能实时更新串口号。

可是 QComboBox 控件竟然没有点击事件,可恶!

于是乎找到了这篇文章,QT中ui界面的控件QComboBox实现鼠标点击事件

● 提升 QComboBox

具体细节可以点进去看,核心思路如下:

自定义一个类 MyComboBox 继承 QComboBox 类。在 MyComboBox 类中添加了 mousePressEvent 鼠标点击事件

void MyComboBox::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton){
        emit clicked();     //触发clicked信号
    }
    //将该事件传给父类处理,这句话很重要,如果没有,父类无法处理本来的点击事件
    QComboBox::mousePressEvent(event);
}

然后在 ui 界面将 ComboBox 提升为自创建的 MyComboBox。具体操作为:

打开ui界面----->>选中QComboBox控件,右击----->>选择“提升为”----->>在“提升的类名称里面”填入新建的类“MyComboBox”名称----->>点击“添加”按钮----->>再点击“提升”按钮。 具体参考上面的链接文章。

● 建立信号槽

//连接信号槽,前文已经初始化
QObject::connect(ui->comboBox_port, SIGNAL(clicked()), this, SLOT(updataPortNum()));

//槽函数中,更新串口号
void MainWindow:: updataPortNum(void) {

    //清除串口号
    ui->comboBox_port->clear();

    //遍历 QSerialPortInfo, 添加到串口下拉框中
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){
        ui->comboBox_port->addItem(info.portName());
    }
}

四、部分代码

mainwindow.cpp 中:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , isSerialOpen(false)
{
    ui->setupUi(this);

    //初始化配置
    initConfig();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::initConfig() {

    //创建对象,并建立信号槽
    serial = new QSerialPort(this);
    //读函数的信号槽, 具体见博客
    QObject::connect(serial, &QSerialPort::readyRead, this, &MainWindow::serial_readyRead);
    //端口号下拉框升级后的点击事件
    QObject::connect(ui->comboBox_port, SIGNAL(clicked()), this, SLOT(updataPortNum()));

    //添加端口号
    updataPortNum();
    ui->comboBox_port->setCurrentIndex(0);

    //添加波特率
    QStringList baudList;
    baudList << "115200" << "57600" << "9600" ;
    ui->comboBox_baud->addItems(baudList);
    ui->comboBox_baud->setCurrentText("115200");

    //添加停止位
    QStringList stopBitsList;
    stopBitsList << "1" << "1.5" << "2";
    ui->comboBox_stop->addItems(stopBitsList);
    ui->comboBox_stop->setCurrentText("1");

    //添加数据位
    QStringList dataBitsList;
    dataBitsList << "8" << "7" << "6";
    ui->comboBox_data->addItems(dataBitsList);
    ui->comboBox_data->setCurrentText("8");

    //添加校验位
    QStringList checkList;
    checkList << "NO" << "EVEN"<< "ODD" ;
    ui->comboBox_check->addItems(checkList);
    ui->comboBox_check->setCurrentText("NO");

    ui->pushButton_operate->setText("OPEN");
    ui->textEdit_send->setText("123456789\\r\\n");
}

//--槽函数,点击端口下拉框的时候更新
void MainWindow:: updataPortNum(void) {

    //清除串口号
    ui->comboBox_port->clear();

    //遍历 QSerialPortInfo, 添加到串口下拉框中
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){
        ui->comboBox_port->addItem(info.portName());
    }
}

//槽函数:读串口数据并更新
void MainWindow::serial_readyRead()
{
    //从界面中读取以前收到的数据
    QString recv = ui->textEdit_rcv->toPlainText();

    //从接收缓冲区中读取数据
    QByteArray buffer = serial->readAll();

    //界面中读取的数据中加入刚读取的数据
    recv += QString(buffer);

    //清除显示
    ui->textEdit_rcv->clear();

    //更新显示
    ui->textEdit_rcv->append(recv);
}


void MainWindow::configSetEnable(bool b)
{
    ui->comboBox_port->setEnabled(b);
    ui->comboBox_baud->setEnabled(b);
    ui->comboBox_stop->setEnabled(b);
    ui->comboBox_data->setEnabled(b);
    ui->comboBox_check->setEnabled(b);

    //
    ui->pushButton_send->setEnabled(!b);
}

void MainWindow::on_pushButton_operate_clicked()
{
    if (ui->pushButton_operate->text() == QString("OPEN")) {

        const QString portnameStr = ui->comboBox_port->currentText();

        QSerialPortInfo info(portnameStr);
        if(info.isBusy()){
            qDebug()<< "The serial port is occupied" <<portnameStr;
            return;
        }

        ui->pushButton_operate->setText("CLOSE");
        //清空缓冲区
        serial->flush();
        //设置端口号
        serial->setPortName(portnameStr);
        //设置波特率
        serial->setBaudRate( static_cast<QSerialPort::BaudRate> (ui->comboBox_baud->currentText().toInt()) );
        //设置停止位
        serial->setStopBits( static_cast<QSerialPort::StopBits> (ui->comboBox_stop->currentText().toInt()));
        //设置数据位
        serial->setDataBits( static_cast<QSerialPort::DataBits> (ui->comboBox_data->currentText().toInt()) );
        //设置校验
        serial->setParity  ( static_cast<QSerialPort::Parity>   (ui->comboBox_check->currentIndex()));
        //设置流控
        serial->setFlowControl(QSerialPort::NoFlowControl);

        isSerialOpen = serial->open(QIODevice::ReadWrite);
        if (!isSerialOpen) {
            qDebug()<< QString("Failed to open serial port:") << portnameStr << serial->errorString();
            serial->clearError();
            configSetEnable(true);
        }
        else {
            qDebug()<< QString("The serial port is open: ") <<portnameStr;
            configSetEnable(false);
        }
    }
    else {
         ui->pushButton_operate->setText("OPEN");
         serial->close();
         configSetEnable(true);
    }
}

//
void MainWindow::on_pushButton_send_clicked()
{
    //简单文本框用 toPlainText() 取文本框的内容 toUtf8 是转换成utf8格式的字节流    
    QByteArray data = ui->textEdit_send->toPlainText().toUtf8();
    serial->write(data);
}

void MainWindow::on_pushButton_clearRcv_clicked()
{
    ui->textEdit_rcv->clear();
}

void MainWindow::on_pushButton_clearSend_clicked()
{
    ui->textEdit_send->clear();
}

mainwindow.h :

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <QSerialPort>
#include <QSerialPortInfo>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void configSetEnable(bool b);
    void initConfig();

private slots:
    void serial_readyRead();
    //updata port number
    void updataPortNum();
    void on_pushButton_operate_clicked();
    void on_pushButton_send_clicked();
    void on_pushButton_clearRcv_clicked();
    void on_pushButton_clearSend_clicked();

private:
    Ui::MainWindow *ui;
    QSerialPort *serial;
    bool isSerialOpen;
};
#endif // MAINWINDOW_H

myComboBox.cpp 和 myComboBox.h 省略

此版本仅是最简单的串口程序,相比正常的串口助手,缺少 hex 发送、时间戳、定时发送、文件保存等等。仅供学习 Qt Weiget 的串口使用,后续有时间再慢慢完善,也会用 QT Quick 来写一个串口助手。

【参考】

QT中ui界面的控件QComboBox实现鼠标点击事件

QT5串口编程——编写简单的上位机


GitHub 地址:     QWidgetPro选择子工程 QSerialAssistant .pro

QT 其它文章请点击这里:     QT QUICK QML 学习笔记

以上是关于Qt Widget 之简易串口助手(QSerialPort)的主要内容,如果未能解决你的问题,请参考以下文章

49.Qt-网络编程之QTCPSocket和QTCPServer(实现简易网络调试助手)

Qt5学习笔记之串口助手四:增加16进制/ASCII切换周期发送

Serial port

串口助手可以连qt连不了

基于QT的串口助手

干货|手把手教你写一个串口调试助手