Qt串口基本设置与协议收发、波形显示

2023.10.23更新

2023.9.18更新

增加了一个PID调参的模块

PID算法之前在验证电机时是可用的

由于我没有实物可演示PID,所以写了个虚拟的PID,故调参效果看起来怪怪的

不过重点不在PID调参,而是命令的下发与接收

其他方面跟之前更新的波形显示没什么区别

Qt上位机PID调参_哔哩哔哩_bilibili

Qt上位机PID调参


2023.9.17更新

支持开始显示、停止显示

波形数据写到TXT文件

Excel、Python或MATLAB对数据进行进一步地分析

QVector<QString> waveArray;

QDateTime curTime = QDateTime::currentDateTime();
QString date = curTime.toString("yyyy-MM-dd"); //设置显示格式
QString time = curTime.toString("hh:mm:ss:zzz"); //设置显示格式
waveArray<<date<<"\t"<<time<<"\t"<<QString::number(receiveParsingData)<<"\n";

每接受一帧数据则对数据进行保存 

void MainWindow::waveDataOutputToFile()
{
    QFileDialog dlg(this);

    //获取内容的保存路径
    QString fileName = dlg.getSaveFileName(this, tr("Save As"), "./", tr("Text File(*.txt)"));

    if( fileName == "" )
    {
        return;
    }

    //内容保存到路径文件
    QFile file(fileName);

    //以文本方式打开
    if( file.open(QIODevice::WriteOnly | QIODevice::Text) )
    {
        QTextStream out(&file); //IO设备对象的地址对其进行初始化

        QVector<QString>::iterator iter;
        for (iter=waveArray.begin();iter!=waveArray.end();iter++)
            out  <<  *iter << "\0";

        QMessageBox::warning(this, tr("Finish"), tr("Successfully save the file!"));

        file.close();
        waveArray.clear();
    }
    else
    {
        QMessageBox::warning(this, tr("Error"), tr("File to open file!"));
    }
}

文件输出

Qt上位机波形显示与数据保存_哔哩哔哩_bilibili

Qt上位机波形显示与数据保存


2023.9.15更新

将单片机ADC采集到的电压进行组包

Qt收到后进行拆包

void MainWindow::parsingData(QByteArray *protocalData)
{
    uchar preFix = 0xA5;
    uchar cmd1Rcv = 0xAB;
    uchar cmd2Rcv = 0x11;
    uchar crc = 0;
    uchar temp = 0;
    temp = static_cast<uchar>(protocalData->at(0));
    if(static_cast<uchar>(protocalData->at(0)) == preFix)
    {
        for(int i=1; i<protocalData->length()-2; i++)
        {
            temp = static_cast<uchar>(protocalData->at(i));
            crc += static_cast<uchar>(protocalData->at(i));
        }
        temp = static_cast<uchar>(protocalData->at(protocalData->length()-2));
        if(crc != static_cast<uchar>(protocalData->at(protocalData->length()-2)))
        {
            return;
        }
        uchar cmd1 = protocalData->at(2);
        uchar cmd2 = protocalData->at(3);
        uchar data = protocalData->at(4);

        if((cmd1==cmd1Rcv) && (cmd2 ==cmd2Rcv))
        {
            uchar uData[4];
            uData[0] = static_cast<uchar>(protocalData->at(4));
            uData[1] = static_cast<uchar>(protocalData->at(5));
            uData[2] = static_cast<uchar>(protocalData->at(6));
            uData[3] = static_cast<uchar>(protocalData->at(7));
            *(int *)&receiveParsingData = *(int *)&uData;
            ui->lcdnumber_vlotage->display(receiveParsingData);
        }
    }
}

LCD显示当前电压

Qt上位机显示动态数据_哔哩哔哩_bilibili

Qt上位机显示动态数据


2023.9.14更新

从协议中进行数据截取

波形显示

graphicsView放大缩小、原始大小


2023.9.12更新

中文显示

发送/接受 帧计数

文件输出

支持时间戳模式自动换行

HEX接收时自动用空格分隔


前言

1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下

2.comboBox没有点击的信号,所以做了一个触发的功能

3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊

4.给自己挖个坑,下一期做一个查看波形的上位机

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

串口功能

波特率设置

串口开关

串口异常检测

字符串/HEX收发

定时发送

接收数据分隔

协议组包

协议拆包

源代码

widget.h

#ifndef WIDGET_H
#define WIDGET_H

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

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

    //用于表格设置
    enum FieldColNum {
        colTime,
        colCmd1,
        colCmd2,
        colData,
    };
    enum CellType {
        ctTime,
        ctCmd1,
        ctCmd2,
        ctData,
    };
public:
    int tableRowCnt = 0;
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void serialPortInit();  //串口初始化
    void windowInit();      //显示窗口初始化
    void refreshCom();      //刷新串口
    void tableInit();       //表格初始化
    void createItemsARow(int rowNum, QByteArray *protocalData); //表格新建一行
    QString ByteArrayToHexString(QByteArray &ba);
private:
    Ui::Widget *ui;
    QSerialPort* serialPort;
public slots:
    void comboBoxClicked(); //comboBox被点击
private slots:

    void sendData();            //发送串口数据
    void receiveData();         //接收串口数据
    void openSerialport();      //串口开启
    void closeSerialport();     //串口关闭
    void setBuad(int);          //设置波特率
    void clearRcv();            //清楚接收缓存
    void on_btnConvert_clicked();   //转换按钮被点击
    void on_btnClear_clicked();     //清楚转化的按钮被点击
    void sendProtocalHexData();     //以hex格式发送串口数据
    void handleSerialError(QSerialPort::SerialPortError serialPortErr); //串口异常捕获
};


#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "newcombobox.h"
#include <algorithm>


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

    //创建一个定时器用来定时发送
    QTimer *timer1 = new QTimer();
    timer1->start(1000);
    connect(timer1,&QTimer::timeout,[=](){
        int timed = ui->comboBox_timedSend->currentText().toInt();
        timer1->start(timed);
        if(ui->checkBox_timedSend->isChecked() == true)
        {
            sendData();
        }
    });

    windowInit();
    tableInit();
    serialPort = new QSerialPort();
    serialPortInit();


    connect(serialPort,SIGNAL(readyRead()),this,SLOT(receiveData()));
    connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
    connect(ui->pushButton_sendData,SIGNAL(clicked()),this,SLOT(sendData()));
    connect(ui->pushButton_openSerialPort,SIGNAL(clicked()),this,SLOT(openSerialport()));
    connect(ui->pushButton_closeSerialPort,SIGNAL(clicked()),this,SLOT(closeSerialport()));
    connect(ui->comboBox_chooseCom,SIGNAL(clicked()),this,SLOT(comboBoxClicked()));
    connect(ui->pushButton_clearRcv,SIGNAL(clicked()),this,SLOT(clearRcv()));
    connect(ui->pushButton_convert,SIGNAL(clicked()),this,SLOT(on_btnConvert_clicked()));
    connect(ui->pushButton_clearConvertData,SIGNAL(clicked()),this,SLOT(on_btnClear_clicked()));
    connect(ui->pushButton_sendProtocalData,SIGNAL(clicked()),this,SLOT(sendProtocalHexData()));
    connect(serialPort,SIGNAL(errorOccurred(QSerialPort::SerialPortError)),this,SLOT(handleSerialError(QSerialPort::SerialPortError)));
    connect(ui->comboBox_setBuad,SIGNAL(activated(int)),this,SLOT(setBuad(int)));
}

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

//串口异常捕获
void Widget::handleSerialError(QSerialPort::SerialPortError serialPortErr)
{
    if(serialPortErr == QSerialPort::ResourceError)
    {
        QMessageBox::critical(NULL, "critical", "设备拔出", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        closeSerialport();
    }
    if(serialPortErr == QSerialPort::DeviceNotFoundError)
    {
        QMessageBox::critical(NULL, "critical", "找不到串口", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        closeSerialport();
    }
}

void Widget::comboBoxClicked()
{
    refreshCom();
}

void Widget::windowInit()
{
    ui->pushButton_closeSerialPort->setEnabled(false);
    ui->pushButton_openSerialPort->setEnabled(false);
    ui->pushButton_sendData->setEnabled(false);
    setWindowTitle(tr("串口收发"));

    ui->comboBox_timedSend->addItem("10");
    ui->comboBox_timedSend->addItem("100");
    ui->comboBox_timedSend->addItem("1000");
    ui->comboBox_timedSend->setCurrentIndex(2);
}

void Widget::refreshCom()
{
    //显示串口列表
    ui->comboBox_chooseCom->clear();
    foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts())
        ui->comboBox_chooseCom->addItem(portInfo.portName()+":"+portInfo.description());
    ui->pushButton_openSerialPort->setEnabled(ui->comboBox_chooseCom->count()>0);	//
}

void Widget::serialPortInit(){

    refreshCom();

    ui->comboBox_setBuad->addItem("9600");
    ui->comboBox_setBuad->addItem("115200");
    ui->comboBox_setBuad->addItem("921600");
    ui->comboBox_setBuad->setCurrentIndex(1);

}

void Widget::tableInit()
{
    QStringList headerText;
    headerText<<"时间"<<"命令1"<<"命令2"<<"数据";
    ui->tableWidget->setColumnCount(headerText.size());      //设置表格列数
    ui->tableWidget->resizeColumnsToContents();
    for (int i=0;i<ui->tableWidget->columnCount();i++)
    {
        QTableWidgetItem *headerItem=new QTableWidgetItem(headerText.at(i));
        QFont font=headerItem->font();   //获取原有字体设置
        font.setBold(true);              //设置为粗体
        font.setPointSize(10);           //字体大小
        headerItem->setForeground(QBrush(Qt::black));  //设置文字颜色
        headerItem->setFont(font);       //设置字体
        ui->tableWidget->setHorizontalHeaderItem(i,headerItem);    //设置表头单元格的item
    }
    ui->tableWidget->setColumnWidth(0, 60);
    ui->tableWidget->setColumnWidth(1, 50);
    ui->tableWidget->setColumnWidth(2, 50);
    ui->tableWidget->setColumnWidth(3, 250);
}

//为一行的单元格创建 Items
void Widget::createItemsARow(int rowNum, QByteArray *protocalData)
{
    uchar preFix = 0xA5;
    uchar crc = 0;
    uchar temp = 0;
    temp = static_cast<uchar>(protocalData->at(0));
    if(static_cast<uchar>(protocalData->at(0)) == preFix)
    {
        for(int i=1; i<protocalData->length()-2; i++)
        {
            temp = static_cast<uchar>(protocalData->at(i));
            crc += static_cast<uchar>(protocalData->at(i));
        }
        temp = static_cast<uchar>(protocalData->at(protocalData->length()-2));
        if(crc != static_cast<uchar>(protocalData->at(protocalData->length()-2)))
        {
            return;
        }
        uchar len = protocalData->at(1);
        uchar cmd1 = protocalData->at(2);
        uchar cmd2 = protocalData->at(3);
        QByteArray data = protocalData->mid(4,len-6);
        QDateTime curTime = QDateTime::currentDateTime();//获取系统现在的时间
        QString time = curTime.toString("hh:mm:ss"); //设置显示格式

        uint8_t str1 = static_cast<uint8_t>(cmd1);
        QString hexStr1 = QString("%1").arg(str1, 2, 16, QLatin1Char('0')).toUpper();
        uint8_t str2 = static_cast<uint8_t>(cmd2);
        QString hexStr2 = QString("%1").arg(str2, 2, 16, QLatin1Char('0')).toUpper();
        QString testdata = ByteArrayToHexString(data).toLatin1().toUpper();

        QTableWidgetItem *item = new QTableWidgetItem(time, ctTime);
        ui->tableWidget->setItem(rowNum, colTime, item);

        item = new QTableWidgetItem(hexStr1, ctCmd1);
        ui->tableWidget->setItem(rowNum, colCmd1, item);

        item = new QTableWidgetItem(hexStr2, ctCmd2);
        ui->tableWidget->setItem(rowNum, colCmd2, item);

        item = new QTableWidgetItem(testdata, ctData);
        ui->tableWidget->setItem(rowNum, colData, item);
    }


    auto lastRowIndex = ui->tableWidget->rowCount()-1; // 最后一行的索引
    auto lastModelIndex = ui->tableWidget->model()->index(lastRowIndex, 0);
    ui->tableWidget->scrollTo(lastModelIndex);         // 滚动到最后一行
}

QString Widget::ByteArrayToHexString(QByteArray &ba)
{
    QDataStream out(&ba,QIODevice::ReadWrite);   //将str的数据 读到out里面去
    QString buf;
    while(!out.atEnd())
    {
        qint8 outChar = 0;
        out >> outChar;   //每次一个字节的填充到 outchar
        QString str = QString("%1").arg(outChar&0xFF,2,16,QLatin1Char('0')).toUpper() + QString(" ");   //2 字符宽度
        buf += str;
    }
    return buf;
}

void Widget::sendData()
{
    QString message = ui->lineEdit_sendData->text();
    if(ui->checkBox_hexSend->isChecked() == true)
    {
        serialPort->write(QByteArray::fromHex(message.toLatin1()));
    }
    else
    {
        serialPort->write(message.toLatin1());
    }
}

void Widget::receiveData()
{
    QByteArray message;
    QString hexMsg;
    message.append(serialPort->readAll());
    QDateTime time = QDateTime::currentDateTime();  //获取系统现在的时间
    QString date = time.toString("hh:mm:ss");       //设置显示格式
    if(ui->checkBox_hexRcv->isChecked() == true)
    {
        tableRowCnt++;
        ui->tableWidget->setRowCount(tableRowCnt);
        createItemsARow(tableRowCnt-1,&message);

        hexMsg = ByteArrayToHexString(message).toLatin1();
        ui->textEdit_RecData->append(date+QString("->  ")+hexMsg.toUpper());
    }
    else
    {
        ui->textEdit_RecData->append(date+QString("->  ")+message);
    }
}
void Widget::openSerialport()
{
    ui->pushButton_closeSerialPort->setEnabled(true);
    ui->pushButton_openSerialPort->setEnabled(false);

    QList<QSerialPortInfo>  comList = QSerialPortInfo::availablePorts();
    QSerialPortInfo portInfo = comList.at(ui->comboBox_chooseCom->currentIndex());
    serialPort->setPort(portInfo);      //设置使用哪个串口

    if(serialPort->open(QIODevice::ReadWrite) == false)
    {
        QMessageBox::critical(NULL, "critical", "找不到串口/串口被占用", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
        closeSerialport();
    }
    else
    {
        serialPort->setBaudRate(QSerialPort::Baud115200);
        serialPort->setDataBits(QSerialPort::Data8);
        serialPort->setParity(QSerialPort::NoParity);
        serialPort->setStopBits(QSerialPort::OneStop);
        serialPort->setFlowControl(QSerialPort::NoFlowControl);

        ui->pushButton_sendData->setEnabled(true);
    }
}

void Widget::closeSerialport()
{
    if(serialPort->isOpen()){
        serialPort->clear();
        serialPort->close();
    }
    ui->pushButton_closeSerialPort->setEnabled(false);
    ui->pushButton_openSerialPort->setEnabled(true);
}

void Widget::setBuad(int buad)
{
    QString str = ui->comboBox_setBuad->currentText();
    serialPort->setBaudRate(str.toInt());
}

void Widget::clearRcv()
{
    ui->textEdit_RecData->clear();
}

void Widget::on_btnClear_clicked()
{
    ui->lineEdit_protocalData->clear();
}

void Widget::on_btnConvert_clicked()
{
    ui->lineEdit_protocalData->clear();
    bool ok;
    QString str = "A5";
    int val1= ui->lineEditCmd1->text().toInt(&ok,16);  //以十六进制数读入
    QString str1 = QString("%1").arg(val1, 2, 16, QLatin1Char('0'));

    int val2= ui->lineEditCmd2->text().toInt(&ok,16);  //以十六进制数读入
    QString str2 = QString("%1").arg(val2, 2, 16, QLatin1Char('0'));

    if ((str1.isEmpty())||(str2.isEmpty()))
        return;

    int val3= ui->lineEditData->text().toInt(&ok,16);  //以十六进制数读入
    QString str3 = QString("%1").arg(val3, 2, 16, QLatin1Char('0'));

    uint8_t len = 6 + static_cast<uint8_t>(str3.length()/2);
    QString hexStr = QString("%1").arg(len, 2, 16, QLatin1Char('0'));

    str.append(hexStr);
    str.append(str1);
    str.append(str2);
    str.append(str3);
    uint8_t crc;
    QString tmp;
    for(int i=0; i<str3.length(); i+=2)
    {
        tmp = ui->lineEditData->text()[i];
        tmp += ui->lineEditData->text()[i+1];
        crc+= tmp.toInt(&ok,16);
        tmp = "";
    }
    crc += len;
    crc += val1;
    crc += val2;
    QString hexCrc= QString("%1").arg(crc, 2, 16, QLatin1Char('0'));
    str.append(hexCrc);
    str.append("5A");
    str = str.toUpper();
    ui->lineEdit_protocalData->insert(str);
}

void Widget::sendProtocalHexData()
{
    QString message = ui->lineEdit_protocalData->text();
    serialPort->write(QByteArray::fromHex(message.toLatin1()));
}

ui

代码框架概览

演示视频

串口上位机(基本设置/协议收发)演示_哔哩哔哩_bilibili

串口上位机(基本设置/协议收发)演示

  • 4
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
Qt串口通信接收数据并进行波形显示,可以按照以下步骤进行: 1. 首先,需要引入Qt相关的串口通信库。可以使用Qt的QSerialPort类来进行串口通信操作。 2. 设置串口参数。通过QSerialPort类的setPortName()方法设置串口号,例如COM1、COM2等。然后通过setBaudRate()方法设置波特率,setParity()方法设置奇偶校验位,setDataBits()方法设置数据位,setStopBits()方法设置停止位等。 3. 打开串口。通过QSerialPort类的open()方法打开串口。 4. 设置数据接收的方式。可以选择使用信号槽机制接收串口数据。使用QSerialPort类的readyRead信号,当串口接收到数据时会自动发送该信号,然后在槽函数中读取接收到的数据。 5. 解析接收到的数据。对于串口通信而言,接收到的数据可能是原始的字节数组或者字符串。根据实际情况,可以将数据解析为需要显示的数值。 6. 进行波形显示。可以通过Qt自带的绘图类进行波形显示,例如QGraphicsView类,QChart类等。在槽函数中将解析后的数据添加到波形图中,并实时刷新显示。 7. 关闭串口。在结束串口通信时,通过QSerialPort类的close()方法关闭串口。 需要注意的是,对于串口通信而言,可能需要考虑数据的校验、数据的完整性等问题。此外,还需要处理异常情况,例如串口打开失败、接收数据异常等情况。 以上是一个简单的Qt串口通信接收数据并进行波形显示基本步骤,具体的实现方式和细节还需根据实际需求进行调整和补充。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值