QT serial port receives data and displays waveform (including source code)

**Use QT to implement waveform display based on the serial port debugging assistant (including source code)

If you have a lot of comments and leave messages that require source code, it would be troublesome to email them one by one and you will not be able to reply in time. Now upload the source code to the link (no points required to download)https://download.csdn .net/download/m0_51294753/87743394, if you can’t download it, please send me a private message and leave your email address.

I. Introduction

Background: Use ADS1255 to sample analog signals and send the converted data to the computer through the serial port. Use QT to write host computer software to receive the serial port data and display the sampling waveform. Because there are no specific requirements, it is just a simple test, and the program is not perfect. I simply record the process to facilitate the new friends to learn together.

2. Test effect

The interface is modified based on the serial port assistant. It has the basic functions of the serial port debugging assistant. It adds a line chart display, but rearranges the last serial port program. The display effect is as follows:

20230302_104834

Insert image description here

The sampling board is not very well designed. When the analog input terminal is open, the noise floor is basically around 0.6mV, and the designed input voltage is ±2.5V. The serial port input used has a baud rate of 1,500,000. The input data has a fixed format. The data input form is as follows:

Insert image description here
3. Problems encountered during the implementation process

Using QT's serialport and charts libraries, the simple process will not be explained. The source program is given at the end of the article. Here is a brief description of the problems I encountered in the implementation:

1. Regular scanning of serial ports and updating of serial port names

Originally, the serial port scan was only performed at the beginning of the program, but then it was discovered that if the device could not detect the serial port after the program was run, the serial port would not be updated if it was occupied. The serial port is scanned regularly (500ms) through the timer and the associated slot function, but I still don’t understand what’s going on with the line of code that disables the serial port (if you know, please tell me in the comment area). See the source code for the specific implementation.

for(int i = 0;i<portStringList.size();i++)
{
    serial->setPortName(portStringList[i]);
    if(serial->open(QIODevice::ReadWrite))
        ui->comboSerialPort->addItem(portStringList.at(i));
    else
    {
        ui->comboSerialPort->addItem(portStringList.at(i) + "(不可用)");
        ui->comboSerialPort->setItemData(i,(QVariant)0,Qt::UserRole-1);     //串口禁用??
    }
    serial->close();
}

2. The content displayed on the chart needs to be moved, similar to the waveform displayed on an oscilloscope.

For the abscissa, I fixed the width of the abscissa time and changed the coordinate range of the abscissa. For example, if the data output rate is 10 and the fixed abscissa only displays 50 points, then set the abscissa width to 5.

    t += 0.1;
    qreal value = valueStr.toDouble();
    serices0->append(t,value);
    if(t>50)
        axisX->setRange(t-50,t);

For the ordinate, store the 50 data displayed on the chart through a linked list, and then update the queue in a first-in, first-out manner similar to a queue. Find the maximum and minimum values ​​of the queue and update the coordinate range of the ordinate.

    if(listvalue.size()<=500)
        listvalue.push_front(value);
    else
    {
        listvalue.pop_back();
        listvalue.push_front(value);
    }
    qreal minvalue = *std::min_element(listvalue.begin(),listvalue.end());
    qreal maxvalue = *std::max_element(listvalue.begin(),listvalue.end());
    axisY->setRange(minvalue-0.00001,maxvalue+0.00001);

3. Extract the data from the sent string and display it

I didn't expect a good solution. Currently, we need to pay attention to the form of sending strings and change the program according to the string form. For example, the type sent by the slave computer is value: 0.0000000V. You can use the string interception function to separate the middle data. , because the data sent by my slave computer has a fixed width, so I use the mid() function to intercept it directly. If the lengths are inconsistent, I can also use the split() function to separate the data from the text.

    receiveBuff = serial->readAll();
    receiveBytes += receiveBuff.length();
    QByteArray valueStr;
    valueStr = receiveBuff.mid(QString("value:").size(),QString("-0.000000").size());

4. Problem setting coordinate axes

This piece of code is often used when assigning coordinate axes to sequences in charts.

chart->setAxisX(axisX,serices0);
chart->setAxisY(axisY,serices0);

However, there will generally be a warning that the code is outdated, and it is recommended to use the addAxis() function instead, so it is changed to

chart->addAxis((QAbstractAxis*)axisX,Qt::AlignBottom);
chart->addAxis((QAbstractAxis*)axisY,Qt::AlignLeft);

The yellow warning disappeared and there were no errors in compilation. However, there are some problems with the coordinate axis when running. I still don't understand what happened.

4. Program code

1.pro project file, mainly adding two lines of core library and resource files

QT       += core gui
QT       += serialport
QT       += charts

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#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

RESOURCES += \
    res.qrc

2.ui design file, layout according to your needs, name the controls
Insert image description here
3.h file, some variable and function declarations

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtCharts>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QStringList>
#include <QMessageBox>
#include <QFileDialog>
#include <QList>

using namespace QtCharts;

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;

private:
    qreal t;
    QChart *chart;
    QLineSeries *serices0;
    QValueAxis *axisX,*axisY;
    QSerialPort *serial;                        //串口端口
    QStringList portStringList;                 //端口链表
    QTimer *timer;                              //定时器
    QByteArray sendBuff,receiveBuff;            //发送、接收缓存区
    long int sendBytes,receiveBytes;            //发送、接收字节数
    QList <qreal>  listvalue;

    void InitSerialPort();
    void InitChart();

private slots:
    void serialPort_readyRead();
    void portTimerEvent();

    void on_btnOpenSerial_clicked();
    void on_btnSend_clicked();
    void on_btnClearRevBuff_clicked();
    void on_btnSaveFile_clicked();
    void on_btnOpenFile_clicked();
    void on_btnClearSendBuff_clicked();
    void on_btnResetCount_clicked();
    void on_chkFixedSend_clicked();
    void on_lineEditTime_editingFinished();
    void on_textEditRev_textChanged();
};
#endif // MAINWINDOW_H

4.c file, function implementation

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

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

    t = 0;
    sendBytes = 0;
    receiveBytes = 0;

    chart = new QChart();
    serices0 = new QLineSeries();
    axisX = new QValueAxis();
    axisY = new QValueAxis();
    timer = new QTimer();
    serial = new QSerialPort(this);
    QTimer *portTimer = new QTimer(this);
    connect(portTimer,SIGNAL(timeout()),this,SLOT(portTimerEvent()));
    connect(serial,SIGNAL(readyRead()),this,SLOT(serialPort_readyRead()));

    InitSerialPort();
    InitChart();

    portTimer->start(500);
}

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

void MainWindow::InitSerialPort()
{
    ui->comboSerialPort->clear();
    portStringList.clear();
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
        portStringList += info.portName();
    for(int i = 0;i<portStringList.size();i++)
    {
        serial->setPortName(portStringList[i]);
        if(serial->open(QIODevice::ReadWrite))
            ui->comboSerialPort->addItem(portStringList.at(i));
        else
        {
            ui->comboSerialPort->addItem(portStringList.at(i) + "(不可用)");
            ui->comboSerialPort->setItemData(i,(QVariant)0,Qt::UserRole-1);     //串口禁用??
        }
        serial->close();
    }
    ui->comboBaudRate->setCurrentIndex(5);
    ui->comboDataBits->setCurrentIndex(3);
    ui->comboParity->setCurrentIndex(2);
    ui->comboStop->setCurrentIndex(0);

    ui->btnSend->setEnabled(false);
    ui->chkFixedSend->setEnabled(false);
    ui->lineEditTime->setEnabled(false);

    ui->lineEditTime->setText("1000");
    ui->radioTextReceive->setChecked(Qt::Checked);
    ui->radioTextSend->setChecked(Qt::Checked);
}

void MainWindow::InitChart()
{
    ui->chartView->setChart(chart);
    QMargins mgs(5,5,5,5);
    chart->setMargins(mgs);
    chart->setTitle("数据曲线");

    //创建折线序列
    serices0->setName("时间-电压曲线");
    chart->addSeries(serices0);

    //创建坐标轴
    axisX->setRange(0,5);
    axisX->setTitleText("time(secs)");

    axisY->setRange(-2,2);
    axisY->setTitleText("value");

    chart->setAxisX(axisX,serices0);
    chart->setAxisY(axisY,serices0);

   //chart->addAxis((QAbstractAxis*)axisX,Qt::AlignBottom);
   //chart->addAxis((QAbstractAxis*)axisY,Qt::AlignLeft);
}

void MainWindow::serialPort_readyRead()
{
    QByteArray lastStr;
    if(!ui->radioStopReceive->isChecked())
    {
        lastStr = ui->textEditRev->toPlainText().toUtf8();
        receiveBuff = serial->readAll();
        receiveBytes += receiveBuff.length();

        QByteArray valueStr;
        valueStr = receiveBuff.mid(QString("value:").size(),QString("-0.000000").size());

        t += 0.1;
        qreal value = valueStr.toDouble();
        serices0->append(t,value);

        if(t>50)
            axisX->setRange(t-50,t);

        if(listvalue.size()<=500)
            listvalue.push_front(value);
        else
        {
            listvalue.pop_back();
            listvalue.push_front(value);
        }
        qreal minvalue = *std::min_element(listvalue.begin(),listvalue.end());
        qreal maxvalue = *std::max_element(listvalue.begin(),listvalue.end());
        axisY->setRange(minvalue-0.00001,maxvalue+0.00001);


        ui->labRevBytesCount->setText(QString::number(receiveBytes));

        if(ui->radioHexReceive->isChecked())
        {
            receiveBuff = receiveBuff.toHex().toUpper();
            int length = receiveBuff.length();
            for(int i = 0;i<=length/2;i++)
                receiveBuff.insert((2+3*i), QByteArray(" "));
        }
        lastStr = lastStr.append(receiveBuff);
        ui->textEditRev->setText(lastStr);
    }
    else
        serial->clear(QSerialPort::Input);
}

void MainWindow::portTimerEvent()
{
    QStringList newPortStringList;
    newPortStringList.clear();
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
        newPortStringList += info.portName();
    if(newPortStringList.size() != portStringList.size())
    {
        portStringList = newPortStringList;
        ui->comboSerialPort->clear();
        ui->comboSerialPort->addItems(portStringList);
    }
}


void MainWindow::on_btnOpenSerial_clicked()
{
    if(ui->btnOpenSerial->text() == QString("打开串口"))
    {
        //串口设置
        serial->setPortName(ui->comboSerialPort->currentText());
        serial->setBaudRate(ui->comboBaudRate->currentText().toInt());
        switch(ui->comboDataBits->currentText().toInt())
        {
        case 5: serial->setDataBits(QSerialPort::Data5);break;
        case 6: serial->setDataBits(QSerialPort::Data6);break;
        case 7: serial->setDataBits(QSerialPort::Data7);break;
        case 8: serial->setDataBits(QSerialPort::Data8);break;
        default: serial->setDataBits(QSerialPort::UnknownDataBits);
        }
        switch(ui->comboParity->currentIndex())
        {
        case 0: serial->setParity(QSerialPort::EvenParity);break;
        case 1: serial->setParity(QSerialPort::MarkParity);break;
        case 2: serial->setParity(QSerialPort::NoParity);break;
        case 3: serial->setParity(QSerialPort::OddParity);break;
        default: serial->setParity(QSerialPort::UnknownParity);
        }
        switch (ui->comboStop->currentIndex())
        {
        case 0: serial->setStopBits(QSerialPort::OneStop);break;
        case 1: serial->setStopBits(QSerialPort::OneAndHalfStop);break;
        case 2: serial->setStopBits(QSerialPort::TwoStop);break;
        default: serial->setStopBits(QSerialPort::UnknownStopBits);
        }

        serial->setFlowControl(QSerialPort::NoFlowControl);

        if(!serial->open(QIODevice::ReadWrite))
        {
            QMessageBox::warning(this,"提示","无法打开串口",QMessageBox::Ok);
            return;
        }

        ui->comboSerialPort->setEnabled(false);
        ui->comboBaudRate->setEnabled(false);
        ui->comboDataBits->setEnabled(false);
        ui->comboParity->setEnabled(false);
        ui->comboStop->setEnabled(false);

        ui->btnSend->setEnabled(true);
        ui->chkFixedSend->setEnabled(true);
        ui->lineEditTime->setEnabled(true);
        ui->btnOpenSerial->setText("关闭串口");
    }
    else
    {
        serial->close();

        ui->comboSerialPort->setEnabled(true);
        ui->comboBaudRate->setEnabled(true);
        ui->comboDataBits->setEnabled(true);
        ui->comboParity->setEnabled(true);
        ui->comboStop->setEnabled(true);

        ui->btnSend->setEnabled(false);
        ui->chkFixedSend->setEnabled(false);
        ui->lineEditTime->setEnabled(false);
        ui->btnOpenSerial->setText("打开串口");
    }
}


void MainWindow::on_btnSend_clicked()
{
    sendBuff = ui->textEditSend->toPlainText().toUtf8();
    if(ui->radioHexSend->isChecked())
        sendBuff = QByteArray::fromHex(sendBuff);
    if(ui->chkLineFeed->isChecked())
        sendBuff += '\n';
    serial->write(sendBuff);
    sendBytes += sendBuff.length();
    ui->labSendBytesCount->setText(QString::number(sendBytes));
    ui->textEditSend->moveCursor(QTextCursor::End);
}


void MainWindow::on_btnClearRevBuff_clicked()
{
    ui->textEditRev->clear();
}


void MainWindow::on_btnSaveFile_clicked()
{
    QString curPath = QDir::currentPath();
    QString dlgTilte = "保存文件";
    QString filter = "文本文件(*.txt);;所有文件(*.*)";
    QString fileName = QFileDialog::getSaveFileName(this,dlgTilte,curPath,filter);
    if(fileName.isEmpty())
        return;
    QFile file(fileName);
    if(!file.open(QIODevice::ReadWrite | QIODevice::Text))
        QMessageBox::warning(this,"文档编辑器",tr("无法写入文件 %1:\n%2").arg(fileName,file.errorString()));
    QTextStream stream(&file);
    stream.setAutoDetectUnicode(true);
    stream<<ui->textEditRev->toPlainText().toUtf8();
    file.close();
}


void MainWindow::on_btnOpenFile_clicked()
{
    QString curPath = QDir::currentPath();
    QString dlgTilte = "打开文件";
    QString filter = "文本文件(*.txt);;所有文件(*.*)";
    QString fileName = QFileDialog::getOpenFileName(this,dlgTilte,curPath,filter);
    if(fileName.isEmpty())
        return;
    QFile file(fileName);
    if(!file.open(QIODevice::ReadWrite | QIODevice::Text))
        QMessageBox::warning(this,"文档编辑器",tr("无法读取文件 %1:\n%2").arg(fileName,file.errorString()));
    ui->textEditSend->setText(file.readAll());
    file.close();
}


void MainWindow::on_btnClearSendBuff_clicked()
{
    ui->textEditSend->clear();
}


void MainWindow::on_btnResetCount_clicked()
{
    receiveBytes = 0;
    sendBytes = 0;
    ui->labRevBytesCount->setText(QString::number(receiveBytes));
    ui->labSendBytesCount->setText(QString::number(sendBytes));
}


void MainWindow::on_chkFixedSend_clicked()
{
    if(ui->chkFixedSend->isChecked())
    {
        int fixedTime = ui->lineEditTime->text().toInt();
        timer->start(fixedTime);
        connect(timer,SIGNAL(timeout()),this,SLOT(on_btnSend_clicked()));
    }
    else
    {
        timer->stop();
    }
}


void MainWindow::on_lineEditTime_editingFinished()
{
    on_chkFixedSend_clicked();
}


void MainWindow::on_textEditRev_textChanged()
{
    ui->textEditRev->moveCursor(QTextCursor::End);
}

The basic framework of the program is not much different from the serial port debugging assistant based on QT5 published in the previous article. The only difference is that the naming of the controls has been modified to facilitate understanding, and the redundant code from last time has been simplified and charts have been added. The program needs to be understood and modified by itself before it can run. Otherwise, if the data sent from the lower computer is inconsistent with the current string format, an error will occur or the program will be forced to exit. I'm just getting started and there are still many problems, so please forgive me.

Guess you like

Origin blog.csdn.net/m0_51294753/article/details/129306887