QT:串口通信、串口发送与接收

Source

使用版本为Qt 5.9.8(Qt5.1以上6以下的版本适用)

目录

一、效果演示

二、ui设计

三、代码部分

四、完整代码

五*、功能优化

(1)清空接收区

(2)16进制显示

(3)16进制发送

(4)时间戳

(5)检测串口

一、效果演示

        这里用的虚拟串口COM1、COM2模拟串口通信。左边为QT设计的串口工具为COM2串口,右边串口助手为COM1,可以看到设计的串口工具可以完成简单的收发数据功能,此外可以对串口波特率等等参数进行设置。

二、ui设计

控件布局、控件名称如图所示:

波特率下拉栏添加了两个item:第一个115200,第二个9600。

数据位下拉栏添加了两个item:第一个8位,第二个6位。

校验位下拉栏添加了三个item:第一个无,第二个偶校验,第三个奇检验。

停止位下拉栏添加了三个item:第一个1位,第二个1.5位,第三个2位。

另外右下角红色指示灯实际上是一个Label(宽高:5*5),预设样式表代码为:

border-radius:5px;  //设置圆角半径
background-color: rgb(255, 0, 0);  //设置背景颜色

三、代码部分

serial.pro 工程文件开头加入:

QT       += serialport

头文件mainwindow.h 开头加入:

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

头文件mainwindow.h 内,在 private:中加入:用于定义一个串口对象

QSerialPort  *serial; //定义串口对象

mainwindow.cpp窗口构造函数中加入以下代码:(三部分功能见注释)

//获取可用串口名到下拉栏
QList<QSerialPortInfo> list = QSerialPortInfo::availablePorts();
    for(int i=0; i<list.size(); i++)
    {
        ui->comboBox_port->addItem(list.at(i).portName());
    }
//创建串口对象
serial = new QSerialPort;
//连接信号与槽(接收数据)
QObject::connect(serial,&QSerialPort::readyRead,this,&MainWindow::ReadData);

        关键部分是写点击“打开串口”按钮的槽函数:打开还是关闭串口是通过判断按钮文本进行的。整体设计框架为:(具体代码见部分四:完整代码)

如果按钮文本为“打开串口”:
    依次设置:串口名、波特率、数据位、校验位、停止位、流控制。
    再打开串口。
    如果成功打开:指示灯变绿、禁用下拉栏、改变按钮文本为“关闭窗口”。
    如果打开失败:QDebug报错

如果按钮文本为“关闭串口”:
    关闭串口、指示灯变红、恢复下拉栏使能、改变按钮文本为“打开串口”。
*****************************************************************************************

void MainWindow::on_Button_openserial_clicked()//点击打开串口按钮
{
    if(ui->Button_openserial->text() == "打开串口")
    {
        //设置串口为下拉栏所选串口名
        //设置波特率
        //设置数据位数
        //设置校验位
        //设置停止位
        //设置流控制
        //打开串口 同时判断是否成功
        bool info = serial->open(QIODevice::ReadWrite);
        if(info == true)
        {
            qDebug()<<"success";
            //改变label颜色(指示灯)
            //关闭下拉栏设置使能
            //改变按钮文本为"关闭串口"
        }
        else
        {
            qDebug()<<"fail";
        }
    else
    {   
        //关闭串口
        //改变label颜色(指示灯)
        //恢复下拉栏设置使能
        //改变按钮文本为"打开串口"
    }
}

接着写点击接收数据的函数(读数据):

void MainWindow::ReadData()//读取接收到的信息
{
    QByteArray buf = serial->readAll();
    ui->textBrowser->append(buf);
}

最后写点击“发送”按钮的槽函数:

void MainWindow::on_Button_send_clicked()//点击发送按钮
{
    //检查串口是否打开
    if (serial->isOpen())
    {
        //获取要发送的数据:lineEdit_send内容
        QString sendData = ui->lineEdit_send->text()+"\r\n";
        //将字符串转为QByteArray
        QByteArray data = sendData.toUtf8();
        //发送数据
        serial->write(data);
    }
    else
    {
        qDebug()<<"serial is not open";
    }
}

四、完整代码

头文件mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

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

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_Button_openserial_clicked();//点击打开串口按钮

    void ReadData();//串口读数据(接收数据)

    void on_Button_send_clicked();//点击发送按钮

private:
    Ui::MainWindow *ui;
    QSerialPort *serial;//定义串口对象
};

#endif // MAINWINDOW_H

mainwindow.cpp文件

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

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

    //获取可用串口名到下拉栏
    QList<QSerialPortInfo> list = QSerialPortInfo::availablePorts();
        for(int i=0; i<list.size(); i++)
        {
            ui->comboBox_port->addItem(list.at(i).portName());
        }
    //创建串口对象
    serial = new QSerialPort;
    //连接信号与槽(接收数据)
    QObject::connect(serial,&QSerialPort::readyRead,this,&MainWindow::ReadData);
}

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

void MainWindow::on_Button_openserial_clicked()//点击打开串口按钮
{
    if(ui->Button_openserial->text() == "打开串口")
    {
        //设置串口为下拉栏所选串口名
        serial->setPortName(ui->comboBox_port->currentText());
        //设置波特率
        switch (ui->comboBox_baud->currentIndex())
        {
            case 0: serial->setBaudRate(QSerialPort::Baud115200); break;//115200
            case 1: serial->setBaudRate(QSerialPort::Baud9600);   break;
            default: break;
        }
        //设置数据位数
        switch (ui->comboBox_databit->currentIndex())
        {
            case 0:serial->setDataBits(QSerialPort::Data8); break;//1位
            case 1:serial->setDataBits(QSerialPort::Data6); break;
            default: break;
        }
        //设置校验位
        switch (ui->comboBox_parity->currentIndex())
        {
            case 0:serial->setParity(QSerialPort::NoParity);   break;//无校验位
            case 1:serial->setParity(QSerialPort::EvenParity); break;
            case 2:serial->setParity(QSerialPort::OddParity);  break;
            default: break;
        }
        //设置停止位
        switch (ui->comboBox_stopbit->currentIndex())
        {
            case 0:serial->setStopBits(QSerialPort::OneStop); break;//1位
            case 1:serial->setStopBits(QSerialPort::OneAndHalfStop); break;
            case 2:serial->setStopBits(QSerialPort::TwoStop); break;
            default: break;
        }
        //设置流控制
        serial->setFlowControl(QSerialPort::NoFlowControl);//无流控制
        //打开串口 同时判断是否成功
        bool info = serial->open(QIODevice::ReadWrite);
        if(info == true)
        {
            qDebug()<<"success";
            //改变label颜色(指示灯)
            ui->label_light->setStyleSheet("background-color:rgb(0,255,0);border-radius:5px;");
            //关闭下拉栏设置使能
            ui->comboBox_port->setEnabled(false);
            ui->comboBox_baud->setEnabled(false);
            ui->comboBox_databit->setEnabled(false);
            ui->comboBox_parity->setEnabled(false);
            ui->comboBox_stopbit->setEnabled(false);
            //改变按钮文本为“关闭串口”
            ui->Button_openserial->setText(tr("关闭串口"));
        }
        else
        {
            qDebug()<<"fail";
        }
    }
    else
    {   //关闭串口
        serial->clear();
        serial->close();
        //改变label颜色(指示灯)
        ui->label_light->setStyleSheet("background-color:rgb(255,0,0);border-radius:5px;");
        //恢复下拉栏设置使能
        ui->comboBox_port->setEnabled(true);
        ui->comboBox_baud->setEnabled(true);
        ui->comboBox_databit->setEnabled(true);
        ui->comboBox_parity->setEnabled(true);
        ui->comboBox_stopbit->setEnabled(true);
        //改变按钮文本为“打开串口”
        ui->Button_openserial->setText(tr("打开串口"));
    }
}

void MainWindow::ReadData()//接收数据函数(并显示)
{
    QByteArray buf = serial->readAll();
    ui->textBrowser->append(buf);
}

void MainWindow::on_Button_send_clicked()//点击发送按钮
{
    //检查串口是否打开
    if (serial->isOpen())
    {
        //获取要发送的数据:lineEdit_send内容
        QString sendData = ui->lineEdit_send->text();
        //将字符串转为QByteArray
        QByteArray data = sendData.toUtf8();
        //发送数据
        serial->write(data);
    }
    else
    {
        qDebug()<<"serial is not open";
    }
}

五*、功能优化

        以上代码仅仅实现了简单的收发功能,但还可以做一些使用功能上的优化:

(1)清空接收区

        接收显示的数据过多需要清空,可以设计点击按钮实现清空的功能,因此只需要添加一个按钮控件,编写点击按钮的槽函数即可:

void MainWindow::on_pushButton_2_clicked()//点击清空接收区按钮
{
    ui->textBrowser->clear();
}

(2)16进制显示

        要接收数据以16进制显示,且每个字节用空格隔开便于查看。方法为添加一个checkBox控件,在接收数据函数内判断checkBox勾选情况,执行不同的操作,接收数据函数修改如下:

void MainWindow::ReadData()//读取接收到的信息
{
    QByteArray buf = serial->readAll();
    if(ui->checkBox->isChecked()==true)//已经被勾选
    {
         QString hexData= buf.toHex().toUpper();//将数据转换为16进制字符串并大写
         QString spacedData;//用于存储每个字节隔开一个空格的字符串
         for(int i=0;i<hexData.length();i+=2)//在每个字节后添加一个空格
         {
             spacedData += hexData.mid(i,2);
             spacedData += " ";
         }
         ui->textBrowser->append(spacedData);//将转换后的数据显示
    }
    else 
    {
         ui->textBrowser->append(buf);//未勾选则直接显示
    }
}

(3)16进制发送

        前面已经实现了输入内容点击按钮发送,但是发送的是字符串,如果想要直接发送16进制数(如输入1a2b,发送出去16进制数1a2b),可用设计一个16进制发送功能,将符合16进制格式的字符串转换为QByteArray形式进行发送。方法为添加一个checkBox控件,在点击发送按钮槽函数内判断checkBox勾选情况,执行不同的操作,具体如下:

1.mainwindow.h文件public:中
    定义static函数(实现符合16进制格式的QString数据转QByteArray数据)
***************************************************************************

static QByteArray hexStringToByteArray(const QString &hexString)
    {
        QByteArray byteArray;

        QString sanitizedHex = hexString;//确保字符串长度是偶数
        if (sanitizedHex.length() % 2 != 0){
            sanitizedHex.prepend('0');//如果长度是奇数,前面补零
        }
        for (int i=0; i<sanitizedHex.length(); i+=2)
        {
            QString byteString = sanitizedHex.mid(i,2);//取两位
            bool ok;
            byteArray.append(static_cast<char>(byteString.toInt(&ok,16)));//转为字节
            if (!ok){
                qDebug() << "Invalid hex byte:" << byteString;//字符串非16进制格式,转换报错
            }
        }
        return byteArray;//返回转换结果
    }
2.修改点击发送按钮槽函数如下:
****************************************************************************
void MainWindow::on_Button_send_clicked()//点击发送按钮
{
    //检查串口是否打开
    if (serial->isOpen())
    {
        if(ui->checkBox_2->isChecked()==true)//16进制发送被选中
        {
            //获取要发送的数据:lineEdit_send内容
            QString sendData = ui->lineEdit_send->text();
            //将字符串转换为QByteArray形式(调用了hexStringToByteArray函数)
            QByteArray data  = hexStringToByteArray(sendData);
            //发送数据
            serial->write(data);
        }
        else
        {
            //获取要发送的数据:lineEdit_send内容
            QString sendData = ui->lineEdit_send->text();
            //将字符串转为QByteArray
            QByteArray data  = sendData.toUtf8();
            //发送数据
            serial->write(data);
        }
    }
    else { qDebug()<<"serial is not open"; }
}

如图:勾选16进制发送后,输入16进制数发送内容与即为接收方接收结果,所键即所得。

(4)时间戳

        时间戳功能就是显示接收数据时,一并显示接收时间,需要添加一个头文件,并且在接收数据函数内加入第二段代码(添加到 “ui->textBrowser->append(xxx)” 上方):

#include <QDateTime> //获取当前时间
QString currentTime = QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss]");//定义字符串 = 当前时间并转换为字符串
ui->textBrowser->append(currentTime);//设置标签内容

(5)检测串口

        当打开程序后,可用串口会自动添加到下拉栏,但如果可用串口增加或减少时,可用串口选项并不会发生变化,因此可设置一个按钮进行手动刷新(另外也可以用定时器实现定时刷新),点击按钮的槽函数与构造函数内获取可用串口是一样的:

void MainWindow::on_Button_check_clicked()//点击检测串口按钮
{
    //获取可用串口名到下拉栏
    QList<QSerialPortInfo> list = QSerialPortInfo::availablePorts();
    for(int i=0; i<list.size(); i++)
    {
        ui->comboBox_port->addItem(list.at(i).portName());
    }
}

如图:当发现没有可用串口时可点击检测窗口按钮刷新可用串口