Qt展示动态波形

需求描述

  1. 接入串口,配置串口顺序进行接收数据;
  2. 数据分成两个串口分别传入,使用多线程并发接入;
  3. 时域数据有两个通道(I,Q),分别以实时波形展示;
  4. 频域数据有三个通道(I,Q,C),C表示联合通道,分别展示FFT波形;
  5. 状态数据列表实时展示;
  6. 时域可以配置时间窗口大小,也就是展示的波形最大时间段;
  7. 频域可以配置频域段;
  8. 数据支持随时保存,并可以打开查看;

成品展示

成品展示

实现难点

Qt多线程

Qt实现多线程,有两种方式:

  1. 一种是使用QThread
    1.1 定义个类,继承QThread;
    1.2 然后重写类的run()方法;
    1.3 在主线程中通过类对象的start()方法启动线程;
  2. 一种是使用自定义类(任务类),继承QObject
    2.1 在任务类中定义公共任务方法,实现具体的任务处理逻辑;
    2.2 在主线程中创建QThread对象(千万不要给创建对象指定父对象);
    2.3 通过调用QObject类提供的moveToThread方法,将任务类对象移动到创建的子线程对象QThread中;
    2.4 调用子线程对象的start()方法,子线程开始启动,但是移动到子线程中的对象并没有工作;
    2.5 通过调用自定义类对象的工作函数,让这个函数开始执行,这时候就是在移动到的子线程中执行的。

两种方式的区别:

  1. 第一种由于run()方法不可以带参数,所以如果要传参数,需要把参数作为成员变量,通过信号槽机制进行传递;第二种没有这种限制,在任务类中定义的任务函数可以带有参数,所以更加灵活;
  2. 第一种方式,一般适用于单个任务,第二种方式,可以有多个任务,而且不同任务可以指定到不同的线程中,也可以指定到相同的线程中,如果指定到同一个线程中,则逻辑顺序为线性执行;

实现细节

任务类:

//tasks.h

#ifndef TASKS_H
#define TASKS_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>

class Tasks :public QObject
{
    Q_OBJECT

public:
    Tasks();
    ~Tasks();
    void working();
    void initSerialPort(QString port);

signals:
//    void started(int iNum, int qNum);
    void started(QString text);
    void serialInputError();

private:
    QSerialPort *m_serialPort;
};

#endif // TASKS_H
//tasks.cpp
#include "tasks.h"

Tasks::Tasks()
{

}
Tasks::~Tasks()
{
    if(m_serialPort!=nullptr)
    {
        m_serialPort->close();
        delete m_serialPort;
        m_serialPort = nullptr;
    }
}

void Tasks::working()
{
    QByteArray buf;
    if (m_serialPort){

        buf = m_serialPort->readAll();

        if (!buf.isEmpty())
        {
            QString temp = QString(buf);
            if(temp.contains("breathch"))
            {
                emit serialInputError();
                m_serialPort->close();
                delete m_serialPort;
                m_serialPort = nullptr;
                return;
            }
            emit started(temp);
        }
    }
}

void Tasks::initSerialPort(QString port)
{
    QString baudRate = "115200";   //波特率
    QString parityBit = "无"; //校验位
    QString dataBits = "8";   //数据位
    QString stopBits = "1";   //停止位
    m_serialPort = new QSerialPort();
    m_serialPort->setPortName(port);
    if(m_serialPort->open(QIODevice::ReadOnly))
    {
        if(baudRate == "115200")
        {
            m_serialPort->setBaudRate(QSerialPort::Baud115200);
        }

        if(parityBit == "无")
        {
            m_serialPort->setParity(QSerialPort::NoParity);
        }
        if(dataBits == "8")
        {
            m_serialPort->setDataBits(QSerialPort::Data8);
        }
        if(stopBits == "1")
        {
            m_serialPort->setStopBits(QSerialPort::OneStop);
        }
        connect(m_serialPort, &QSerialPort::readyRead, this, &Tasks::working);
    }
}

主线程:

//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread>
#include "qcustomplot/qcustomplot.h"
#include "tasks.h"
#include "labelingdialog.h"
#include <QDateTime>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

struct BreatheDataItem
{
    BreatheDataItem()
    {
        this->breathch = "N/A";
        this->breath = 0;
        this->movech = "N/A";
        this->mean = 0.0;
        this->variance = 0.0;
        this->range = 0.0;
        this->zeronum = 0;
        this->status = "N/A";
        this->fftMaxValue = 0.0;
    }
    QString breathch;
    int breath;
    QString movech;
    double mean;
    double variance;
    double range;
    int zeronum;
    QString status;
    double fftMaxValue;
    };

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void initSerialInfo(QString port);
    void initUI();
    void startSubTread(QString port);
    void initPlots();
    void cleanPlots();

public slots:
    void serialInfoInit(QString port1, QString port2);
    void readReadySlot();
    void drawPlots(QString text);
    void drawFFTPlots(QString text, QCustomPlot *plot);
    void mouseMove_slot_I(QMouseEvent *event); // 响应鼠标移动时事件,显示坐标值
    void mouseMove_slot_Q(QMouseEvent *event);
    void mouseMove_slot_fft_I(QMouseEvent *event);
    void mouseMove_slot_fft_Q(QMouseEvent *event);
    void mouseMove_slot_fft_C(QMouseEvent *event);

    void actionSerialSlot();//串口配置弹窗
    void actionResetSlot();//清空配置和数据
    void actionTimePlotSlot();//时域波形配置
    void actionFrequencyPlotSlot();//频域波形配置

    void timeConfigSlot(int value);
    void frequencyConfigSlot(int min, int max);

    void serialErrorHandler();//串口数据错误的处理

signals:
    void killSubThread();
    void setTimeSpan(int value);
    void setFrequencyXSpan(int min, int max);
    void serialInputError();

private:
    Ui::MainWindow *ui;
    BreatheDataItem m_statusInfo;
    QSerialPort *m_serialPort = nullptr;
    static int receiveBytes;
    static QString preStr;
    static int m_s_plot_graph_num;
    int m_timeSpan = 30;
    int m_frequency_x_min = 0;
    int m_frequency_x_max = 300;
    QMessageBox *m_warning_box = nullptr;
    QString fftIStr;
    QString fftQStr;
    QString fftCStr;
    static QThread *preThread;
    static Tasks *preTask;
    QCustomPlot * m_plot_i = nullptr;
    QCustomPlot * m_plot_q = nullptr;
    QCustomPlot * m_fft_plot_i = nullptr;
    QCustomPlot * m_fft_plot_q = nullptr;
    QCustomPlot * m_fft_plot_c = nullptr;
    QVector<QCPItemTracer*> m_tracers;
    QVector<QCPItemText*> m_tracerTexts;
    QString fftXValuesString = "0,0.585937500000000,1.17187500000000,1.75781250000000,2.34375000000000,2.92968750000000,3.51562500000000,4.10156250000000,4.68750000000000,5.27343750000000,5.85937500000000,6.44531250000000,7.03125000000000,7.61718750000000,8.20312500000000,8.78906250000000,9.37500000000000,9.96093750000000,10.5468750000000,11.1328125000000,11.7187500000000,12.3046875000000,12.8906250000000,13.4765625000000,14.0625000000000,14.6484375000000,15.2343750000000,15.8203125000000,16.4062500000000,16.9921875000000,17.5781250000000,18.1640625000000,18.7500000000000,19.3359375000000,19.9218750000000,20.5078125000000,21.0937500000000,21.6796875000000,22.2656250000000,22.8515625000000,23.4375000000000,24.0234375000000,24.6093750000000,25.1953125000000,25.7812500000000,26.3671875000000,26.9531250000000,27.5390625000000,28.1250000000000,28.7109375000000,29.2968750000000,29.8828125000000,30.4687500000000,31.0546875000000,31.6406250000000,32.2265625000000,32.8125000000000,33.3984375000000,33.9843750000000,34.5703125000000,35.1562500000000,35.7421875000000,36.3281250000000,36.9140625000000,37.5000000000000,38.0859375000000,38.6718750000000,39.2578125000000,39.8437500000000,40.4296875000000,41.0156250000000,41.6015625000000,42.1875000000000,42.7734375000000,43.3593750000000,43.9453125000000,44.5312500000000,45.1171875000000,45.7031250000000,46.2890625000000,46.8750000000000,47.4609375000000,48.0468750000000,48.6328125000000,49.2187500000000,49.8046875000000,50.3906250000000,50.9765625000000,51.5625000000000,52.1484375000000,52.7343750000000,53.3203125000000,53.9062500000000,54.4921875000000,55.0781250000000,55.6640625000000,56.2500000000000,56.8359375000000,57.4218750000000,58.0078125000000,58.5937500000000,59.1796875000000,59.7656250000000,60.3515625000000,60.9375000000000,61.5234375000000,62.1093750000000,62.6953125000000,63.2812500000000,63.8671875000000,64.4531250000000,65.0390625000000,65.6250000000000,66.2109375000000,66.7968750000000,67.3828125000000,67.9687500000000,68.5546875000000,69.1406250000000,69.7265625000000,70.3125000000000,70.8984375000000,71.4843750000000,72.0703125000000,72.6562500000000,73.2421875000000,73.8281250000000,74.4140625000000,75,75.5859375000000,76.1718750000000,76.7578125000000,77.3437500000000,77.9296875000000,78.5156250000000,79.1015625000000,79.6875000000000,80.2734375000000,80.8593750000000,81.4453125000000,82.0312500000000,82.6171875000000,83.2031250000000,83.7890625000000,84.3750000000000,84.9609375000000,85.5468750000000,86.1328125000000,86.7187500000000,87.3046875000000,87.8906250000000,88.4765625000000,89.0625000000000,89.6484375000000,90.2343750000000,90.8203125000000,91.4062500000000,91.9921875000000,92.5781250000000,93.1640625000000,93.7500000000000,94.3359375000000,94.9218750000000,95.5078125000000,96.0937500000000,96.6796875000000,97.2656250000000,97.8515625000000,98.4375000000000,99.0234375000000,99.6093750000000,100.195312500000,100.781250000000,101.367187500000,101.953125000000,102.539062500000,103.125000000000,103.710937500000,104.296875000000,104.882812500000,105.468750000000,106.054687500000,106.640625000000,107.226562500000,107.812500000000,108.398437500000,108.984375000000,109.570312500000,110.156250000000,110.742187500000,111.328125000000,111.914062500000,112.500000000000,113.085937500000,113.671875000000,114.257812500000,114.843750000000,115.429687500000,116.015625000000,116.601562500000,117.187500000000,117.773437500000,118.359375000000,118.945312500000,119.531250000000,120.117187500000,120.703125000000,121.289062500000,121.875000000000,122.460937500000,123.046875000000,123.632812500000,124.218750000000,124.804687500000,125.390625000000,125.976562500000,126.562500000000,127.148437500000,127.734375000000,128.320312500000,128.906250000000,129.492187500000,130.078125000000,130.664062500000,131.250000000000,131.835937500000,132.421875000000,133.007812500000,133.593750000000,134.179687500000,134.765625000000,135.351562500000,135.937500000000,136.523437500000,137.109375000000,137.695312500000,138.281250000000,138.867187500000,139.453125000000,140.039062500000,140.625000000000,141.210937500000,141.796875000000,142.382812500000,142.968750000000,143.554687500000,144.140625000000,144.726562500000,145.312500000000,145.898437500000,146.484375000000,147.070312500000,147.656250000000,148.242187500000,148.828125000000,149.414062500000,150,150.585937500000,151.171875000000,151.757812500000,152.343750000000,152.929687500000,153.515625000000,154.101562500000,154.687500000000,155.273437500000,155.859375000000,156.445312500000,157.031250000000,157.617187500000,158.203125000000,158.789062500000,159.375000000000,159.960937500000,160.546875000000,161.132812500000,161.718750000000,162.304687500000,162.890625000000,163.476562500000,164.062500000000,164.648437500000,165.234375000000,165.820312500000,166.406250000000,166.992187500000,167.578125000000,168.164062500000,168.750000000000,169.335937500000,169.921875000000,170.507812500000,171.093750000000,171.679687500000,172.265625000000,172.851562500000,173.437500000000,174.023437500000,174.609375000000,175.195312500000,175.781250000000,176.367187500000,176.953125000000,177.539062500000,178.125000000000,178.710937500000,179.296875000000,179.882812500000,180.468750000000,181.054687500000,181.640625000000,182.226562500000,182.812500000000,183.398437500000,183.984375000000,184.570312500000,185.156250000000,185.742187500000,186.328125000000,186.914062500000,187.500000000000,188.085937500000,188.671875000000,189.257812500000,189.843750000000,190.429687500000,191.015625000000,191.601562500000,192.187500000000,192.773437500000,193.359375000000,193.945312500000,194.531250000000,195.117187500000,195.703125000000,196.289062500000,196.875000000000,197.460937500000,198.046875000000,198.632812500000,199.218750000000,199.804687500000,200.390625000000,200.976562500000,201.562500000000,202.148437500000,202.734375000000,203.320312500000,203.906250000000,204.492187500000,205.078125000000,205.664062500000,206.250000000000,206.835937500000,207.421875000000,208.007812500000,208.593750000000,209.179687500000,209.765625000000,210.351562500000,210.937500000000,211.523437500000,212.109375000000,212.695312500000,213.281250000000,213.867187500000,214.453125000000,215.039062500000,215.625000000000,216.210937500000,216.796875000000,217.382812500000,217.968750000000,218.554687500000,219.140625000000,219.726562500000,220.312500000000,220.898437500000,221.484375000000,222.070312500000,222.656250000000,223.242187500000,223.828125000000,224.414062500000,225,225.585937500000,226.171875000000,226.757812500000,227.343750000000,227.929687500000,228.515625000000,229.101562500000,229.687500000000,230.273437500000,230.859375000000,231.445312500000,232.031250000000,232.617187500000,233.203125000000,233.789062500000,234.375000000000,234.960937500000,235.546875000000,236.132812500000,236.718750000000,237.304687500000,237.890625000000,238.476562500000,239.062500000000,239.648437500000,240.234375000000,240.820312500000,241.406250000000,241.992187500000,242.578125000000,243.164062500000,243.750000000000,244.335937500000,244.921875000000,245.507812500000,246.093750000000,246.679687500000,247.265625000000,247.851562500000,248.437500000000,249.023437500000,249.609375000000,250.195312500000,250.781250000000,251.367187500000,251.953125000000,252.539062500000,253.125000000000,253.710937500000,254.296875000000,254.882812500000,255.468750000000,256.054687500000,256.640625000000,257.226562500000,257.812500000000,258.398437500000,258.984375000000,259.570312500000,260.156250000000,260.742187500000,261.328125000000,261.914062500000,262.500000000000,263.085937500000,263.671875000000,264.257812500000,264.843750000000,265.429687500000,266.015625000000,266.601562500000,267.187500000000,267.773437500000,268.359375000000,268.945312500000,269.531250000000,270.117187500000,270.703125000000,271.289062500000,271.875000000000,272.460937500000,273.046875000000,273.632812500000,274.218750000000,274.804687500000,275.390625000000,275.976562500000,276.562500000000,277.148437500000,277.734375000000,278.320312500000,278.906250000000,279.492187500000,280.078125000000,280.664062500000,281.250000000000,281.835937500000,282.421875000000,283.007812500000,283.593750000000,284.179687500000,284.765625000000,285.351562500000,285.937500000000,286.523437500000,287.109375000000,287.695312500000,288.281250000000,288.867187500000,289.453125000000,290.039062500000,290.625000000000,291.210937500000,291.796875000000,292.382812500000,292.968750000000,293.554687500000,294.140625000000,294.726562500000,295.312500000000,295.898437500000,296.484375000000,297.070312500000,297.656250000000,298.242187500000,298.828125000000,299.414062500000,300";
    QVector<double> m_fft_x;
    QVector<double> m_fft_y;

    int writeToFile(QString path, QString text, int recordType);//recordType=0:IQ通道时域数据,recordType=1:状态信息数据
    void initTracers();
    bool isSerialExist();
    void updateUiByKey(QString key, QString value);
    void updateUiByQString(QString qstring);
    void cleanFFTPlots();
    void recordHardWareVersion(QString version);
    QString recordStr;
    QString statusInfoStr;
    bool isRecord = false;
};
#endif // MAINWINDOW_H

//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "serialconfigdialog.h"
#include "timeplotconfigdialog.h"
#include "frequencyplotconfigdialog.h"
#include "scenedialog.h"


int MainWindow::receiveBytes = 0;
QThread * MainWindow::preThread = nullptr;
Tasks * MainWindow::preTask = nullptr;
QString MainWindow::preStr;
int MainWindow::m_s_plot_graph_num = 5;

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

MainWindow::~MainWindow()
{
    delete ui;
    if(m_serialPort)
    {
        m_serialPort->close();
        delete m_serialPort;
        m_serialPort = nullptr;
    }
}

void MainWindow::initUI()
{
    setFont(QFont("Microsoft Yahei", 9));
    setWindowIcon(QIcon(":/images/breathe.png"));
    setWindowTitle("呼吸监测v1.8.0 CopyRight©2013 PowerBy 工程化部");
    ui->actionserial->setIcon(QIcon(":/images/serial_port.png"));
    ui->actionreset->setIcon(QIcon(":/images/clear.png"));
//    ui->centralwidget->setStyleSheet("background:#fff");
    ui->label_breathch->setText(m_statusInfo.breathch);
    ui->label_breathch->setStyleSheet("color:blue");
    ui->label_breathTimes->setText(QString::number(m_statusInfo.breath));
    ui->label_breathTimes->setStyleSheet("color:blue");
    ui->label_movech->setText(m_statusInfo.movech);
    ui->label_movech->setStyleSheet("color:blue");
    ui->label_mean->setText(QString::number(m_statusInfo.mean));
    ui->label_mean->setStyleSheet("color:blue");
    ui->label_variance->setText(QString::number(m_statusInfo.variance));
    ui->label_variance->setStyleSheet("color:blue");
    ui->label_range->setText(QString::number(m_statusInfo.range));
    ui->label_range->setStyleSheet("color:blue");
    ui->label_zeronum->setText(QString::number(m_statusInfo.zeronum));
    ui->label_zeronum->setStyleSheet("color:blue");
    ui->label_status->setText(m_statusInfo.status);
    ui->label_status->setStyleSheet("color:blue");
    ui->label_fft_max->setText(QString::number(m_statusInfo.fftMaxValue));
    ui->label_fft_max->setStyleSheet("color:blue");
    ui->btn_record_start->setAutoDefault(true);
    defaultPath = QDir::cleanPath(QDir::currentPath() + QDir::separator()  + QString("data"));

    connect(ui->btn_set_directory, &QPushButton::clicked, this, &MainWindow::onSetDirectory);
    connect(ui->btn_labeling, &QPushButton::clicked, this, &MainWindow::onStartLabeling);
    connect(ui->btn_record_start, &QPushButton::clicked, this, &MainWindow::onRecordStart);
    connect(ui->btn_record_stop, &QPushButton::clicked, this, &MainWindow::onRecordStop);
    connect(ui->btn_record_dir, &QPushButton::clicked, this, &MainWindow::onOpenRecordDir);
    connect(ui->btn_set_scene, &QPushButton::clicked, this, &MainWindow::onSetScene);

    connect(ui->actionserial, &QAction::triggered, this, &MainWindow::actionSerialSlot);
    connect(ui->actionreset, &QAction::triggered, this, &MainWindow::actionResetSlot);
    connect(ui->actiontimeplot, &QAction::triggered, this, &MainWindow::actionTimePlotSlot);
    connect(ui->actionfrequencyplot, &QAction::triggered, this, &MainWindow::actionFrequencyPlotSlot);

    connect(this, &MainWindow::serialInputError, this, &MainWindow::serialErrorHandler);
    connect(this, &MainWindow::startWriteLabelFile, this, &MainWindow::onStartWriteLabelFile);
}
bool MainWindow::isSerialExist()
{
    const auto infos = QSerialPortInfo::availablePorts();
    QStringList portNames;
    for(const QSerialPortInfo &info : infos)
    {
        QSerialPort serial;
        serial.setPort(info);
        if(serial.open(QIODevice::ReadOnly))
        {
            portNames.append(info.portName());
            serial.close();
        }
    }
    if(portNames.size() == 0)
    {
        QMessageBox box(QMessageBox::Critical, "提示", "没有检测到串口连接,请检测是否连接?", QMessageBox::Ok);
        box.setButtonText(QMessageBox::Ok, "确定");
        box.exec();
        return false;
    }
    else
    {
        return true;
    }
}
void MainWindow::actionSerialSlot()
{
    if(isSerialExist())
    {
        SerialConfigDialog *dlg = new SerialConfigDialog(this);
        connect(dlg, &SerialConfigDialog::infoConfigEnd, this, &MainWindow::serialInfoInit);
        dlg->setAttribute(Qt::WA_DeleteOnClose);
        dlg->show();
    }
}
void MainWindow::actionTimePlotSlot()
{
    TimePlotConfigDialog *dlg = new TimePlotConfigDialog;
    connect(dlg, &TimePlotConfigDialog::timeConfigEnd, this, &MainWindow::timeConfigSlot);
    connect(this, &MainWindow::setTimeSpan, dlg, &TimePlotConfigDialog::setTimeSpanSlot);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->show();
    emit setTimeSpan(m_timeSpan);
}

void MainWindow::actionFrequencyPlotSlot()
{
    FrequencyPlotConfigDialog *dlg = new FrequencyPlotConfigDialog;
    connect(dlg, &FrequencyPlotConfigDialog::frequencyConfigEnd, this, &MainWindow::frequencyConfigSlot);
    connect(this, &MainWindow::setFrequencyXSpan, dlg, &FrequencyPlotConfigDialog::setFrequencyXSpanSlot);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->show();
    emit setFrequencyXSpan(m_frequency_x_min, m_frequency_x_max);
}

void MainWindow::timeConfigSlot(int value)
{
    m_timeSpan = value;
    cleanPlots();
}

void MainWindow::frequencyConfigSlot(int min, int max)
{
    m_frequency_x_min = min;
    m_frequency_x_max = max;
    cleanFFTPlots();
}
void MainWindow::serialInfoInit(QString port1, QString port2)
{
    initSerialInfo(port1);
    startSubTread(port2);
}
void MainWindow::actionResetSlot()
{
    QMessageBox box;
    box.setIcon(QMessageBox::Warning);
    box.setText("清空配置");
    box.setInformativeText("会清空所有配置和数据,是否确认要执行?");
    box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
    box.setDefaultButton(QMessageBox::Ok);
    box.setButtonText(QMessageBox::Ok, QString("确 定"));
    box.setButtonText(QMessageBox::Cancel, QString("取 消"));
    int ret = box.exec();

    if(ret == QMessageBox::Cancel)
    {
        return;
    }
    if(m_serialPort)
    {
        m_serialPort->close();
        delete m_serialPort;
        m_serialPort = nullptr;
    }
    emit killSubThread();
    cleanPlots();
}
void MainWindow::initPlots()
{
    m_plot_i = ui->plot_I;
    m_plot_q = ui->plot_Q;

    m_fft_plot_i = ui->fft_plot_i;
    m_fft_plot_q = ui->fft_plot_q;
    m_fft_plot_c = ui->fft_plot_c;

    m_plot_i->xAxis->setLabel("Time");
    m_plot_i->yAxis->setLabel("I");

    m_plot_q->xAxis->setLabel("Time");
    m_plot_q->yAxis->setLabel("Q");

    m_fft_plot_i->xAxis->setLabel("Hz");
    m_fft_plot_i->yAxis->setLabel("I");

    m_fft_plot_q->xAxis->setLabel("Hz");
    m_fft_plot_q->yAxis->setLabel("Q");

    m_fft_plot_c->xAxis->setLabel("Hz");
    m_fft_plot_c->yAxis->setLabel("C");

    m_plot_i->addGraph(); // blue line
    m_plot_i->graph(0)->setPen(QPen(QColor(40, 110, 255)));

    m_plot_q->addGraph(); // red line
    m_plot_q->graph(0)->setPen(QPen(QColor(255, 110, 40)));

    m_fft_plot_i->addGraph();
    m_fft_plot_i->graph(0)->setPen(QPen(QColor(50,205,50)));//绿色

    m_fft_plot_q->addGraph();
    m_fft_plot_q->graph(0)->setPen(QPen(QColor(153,50,204)));//紫色

    m_fft_plot_c->addGraph();
    m_fft_plot_c->graph(0)->setPen(QPen(QColor(230,162,60)));//橙色

    QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
    timeTicker->setTimeFormat("%h:%m:%s");
    m_plot_i->xAxis->setTicker(timeTicker);
    m_plot_i->axisRect()->setupFullAxesBox();
    m_plot_i->yAxis->setRange(0, 2100);

    m_plot_q->xAxis->setTicker(timeTicker);
    m_plot_q->axisRect()->setupFullAxesBox();
    m_plot_q->yAxis->setRange(0, 2100);

    m_fft_plot_i->axisRect()->setupFullAxesBox();
    m_fft_plot_q->axisRect()->setupFullAxesBox();
    m_fft_plot_c->axisRect()->setupFullAxesBox();

//    m_plot_i->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
//    m_plot_q->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);

    // make left and bottom axes transfer their ranges to right and top axes:
    connect(m_plot_i->xAxis, SIGNAL(rangeChanged(QCPRange)), m_plot_i->xAxis2, SLOT(setRange(QCPRange)));
    connect(m_plot_i->yAxis, SIGNAL(rangeChanged(QCPRange)), m_plot_i->yAxis2, SLOT(setRange(QCPRange)));

    connect(m_plot_q->xAxis, SIGNAL(rangeChanged(QCPRange)), m_plot_q->xAxis2, SLOT(setRange(QCPRange)));
    connect(m_plot_q->yAxis, SIGNAL(rangeChanged(QCPRange)), m_plot_q->yAxis2, SLOT(setRange(QCPRange)));

    connect(m_fft_plot_i->xAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_i->xAxis2, SLOT(setRange(QCPRange)));
    connect(m_fft_plot_i->yAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_i->yAxis2, SLOT(setRange(QCPRange)));

    connect(m_fft_plot_q->xAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_q->xAxis2, SLOT(setRange(QCPRange)));
    connect(m_fft_plot_q->yAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_q->yAxis2, SLOT(setRange(QCPRange)));

    connect(m_fft_plot_c->xAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_c->xAxis2, SLOT(setRange(QCPRange)));
    connect(m_fft_plot_c->yAxis, SIGNAL(rangeChanged(QCPRange)), m_fft_plot_c->yAxis2, SLOT(setRange(QCPRange)));

    //初始化fft横坐标
    QStringList xList = fftXValuesString.split(',');
    for(QStringList::const_iterator it = xList.cbegin(); it != xList.cend(); ++it)
    {
        m_fft_x.push_back((*it).toDouble());
    }

    initTracers();

}
void MainWindow::initTracers()
{
    m_tracers.resize(m_s_plot_graph_num);
    m_tracerTexts.resize(m_s_plot_graph_num);

    //生成游标
    for(int i = 0; i < m_s_plot_graph_num; i++)
    {
        QCustomPlot *plot = nullptr;
        if(i==0)
        {
            plot = m_plot_i;
        }
        else if(i==1)
        {
            plot = m_plot_q;
        }
        else if(i==2)
        {
            plot = m_fft_plot_i;
        }
        else if(i==3)
        {
            plot = m_fft_plot_q;
        }
        else if(i==4)
        {
            plot = m_fft_plot_c;
        }
        m_tracers[i] = new QCPItemTracer(plot); //生成游标
        m_tracers[i]->setPen(QPen(Qt::black));//圆圈轮廓颜色
        m_tracers[i]->setBrush(QBrush(Qt::red));//圆圈圈内颜色
        m_tracers[i]->setStyle(QCPItemTracer::tsCircle);//圆圈
        m_tracers[i]->setSize(5);//设置大小
        //生成游标说明
        m_tracerTexts[i] = new QCPItemText(plot); //生成游标说明
        m_tracerTexts[i]->setLayer("overlay");//设置图层为overlay,因为需要频繁刷新
        m_tracerTexts[i]->setPen(QPen(Qt::black));//设置游标说明颜色
        m_tracerTexts[i]->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop);//左上
        m_tracerTexts[i]->position->setParentAnchor(m_tracers[i]->position);//将游标说明锚固在tracer位置处,实现自动跟随
        m_tracerTexts[i]->setVisible(false);
        if(i==0)
        {
            connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove_slot_I(QMouseEvent*)));
        }
        else if(i==1)
        {
            connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove_slot_Q(QMouseEvent*)));
        }
        else if(i==2)
        {
            connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove_slot_fft_I(QMouseEvent*)));
        }
        else if(i==3)
        {
            connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove_slot_fft_Q(QMouseEvent*)));
        }
        else if(i==4)
        {
            connect(plot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove_slot_fft_C(QMouseEvent*)));
        }

    }
}
void MainWindow::cleanPlots()
{
    m_plot_i->graph(0)->data()->clear();
    m_plot_q->graph(0)->data()->clear();
    m_fft_plot_i->graph(0)->data()->clear();
    m_fft_plot_q->graph(0)->data()->clear();
    m_fft_plot_c->graph(0)->data()->clear();
    for(int i = 0; i < m_s_plot_graph_num; i++)
    {
        if(m_tracers[i])
        {
            m_tracers[i]->setVisible(false);
        }
        if(m_tracerTexts[i])
        {
            m_tracerTexts[i]->setVisible(false);
        }
    }
    m_plot_i->replot();
    m_plot_q->replot();
    m_fft_plot_i->replot();
    m_fft_plot_q->replot();
    m_fft_plot_c->replot();
    ui->label_breathch->setText("N/A");
    ui->label_breathTimes->setText("0");
    ui->label_movech->setText("N/A");
    ui->label_mean->setText("0");
    ui->label_variance->setText("0");
    ui->label_range->setText("0");
    ui->label_zeronum->setText("0");
    ui->label_status->setText("N/A");
}
void MainWindow::initSerialInfo(QString port)
{
    QString baudRate = "115200";   //波特率
    QString parityBit = "无"; //校验位
    QString dataBits = "8";   //数据位
    QString stopBits = "1";   //停止位
    m_serialPort = new QSerialPort();
    m_serialPort->setPortName(port);
    if(m_serialPort->open(QIODevice::ReadOnly))
    {
        if(baudRate == "115200")
        {
            m_serialPort->setBaudRate(QSerialPort::Baud115200);
        }

        if(parityBit == "无")
        {
            m_serialPort->setParity(QSerialPort::NoParity);
        }
        if(dataBits == "8")
        {
            m_serialPort->setDataBits(QSerialPort::Data8);
        }
        if(stopBits == "1")
        {
            m_serialPort->setStopBits(QSerialPort::OneStop);
        }
        connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::readReadySlot);
    }
}
void MainWindow::readReadySlot()
{
    QByteArray buf;
    if (m_serialPort){

        buf = m_serialPort->readAll();

        if (!buf.isEmpty())
        {
            QString myStrTemp = QString(buf);
//            qDebug()<<myStrTemp;
//            if(!myStrTemp.contains("###1")&&!myStrTemp.contains("###2")&&!myStrTemp.contains("###3"))
//            {
//                if(m_serialPort)
//                {
//                    m_serialPort->close();
//                    delete m_serialPort;
//                    m_serialPort = nullptr;
//                }
//                emit serialInputError();
//                return;
//            }
            if(!myStrTemp.contains("\r\n"))
            {
                preStr.append(myStrTemp);
            }
            else
            {
                int idx = myStrTemp.indexOf("\r\n");
                preStr.append(myStrTemp.constData(),idx);
                //qDebug()<<preStr;
                if(preStr.startsWith("###1,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_i);

                }
                else if(preStr.startsWith("###2,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_q);
                }
                else if(preStr.startsWith("###3,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_c);
                }
                else if(preStr.startsWith("====================================================="))//breachch
                {
                    preStr.replace("=====================================================","");

                    if(preStr.contains("\r\n"))
                    {
                        int idx = preStr.indexOf("\r\n");
                        QString substr = preStr.mid(0,idx);
                        QString substr2 = preStr.mid(idx+2, preStr.size()-idx-2);
                        //qDebug()<<"123:"<<substr2;

                        if(substr2.startsWith(("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")))
                        {
                            if(substr2.contains("\r\n"))
                            {
                                int idx = substr2.indexOf("\r\n");
                                //QString subsubstr = substr2.mid(0,idx);
                                QString subsubstr2 = substr2.mid(idx+2, preStr.size()-idx-2);
                                if(subsubstr2.startsWith("@@@@@@@@@@@@@@@@@@@@@@@@@@@@"))
                                {
                                    subsubstr2.replace("@@@@@@@@@@@@@@@@@@@@@@@@@@@@","");
                                    updateUiByQString(subsubstr2);
                                    if(isRecord)
                                    {
                                        QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                                        statusInfoStr.append(dataTimeStr+"\t"+subsubstr2+"\r\n");
                                    }
                                    else
                                    {
                                        statusInfoStr.clear();
                                    }
                                    return;
                                }
                            }
                            else
                            {
                                updateUiByQString(substr);
                                if(isRecord)
                                {
                                    QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                                    statusInfoStr.append(dataTimeStr+"\t"+substr+"\r\n");
                                }
                                else
                                {
                                    statusInfoStr.clear();
                                }
                            }
                            preStr = substr;

                        }
                        return;
                    }
                    //qDebug()<<preStr;
                    if(isRecord)
                    {
                        QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                        statusInfoStr.append(dataTimeStr+"\t"+preStr+"\r\n");
                    }
                    else
                    {
                        statusInfoStr.clear();
                    }
                    updateUiByQString(preStr);
                }
                else if(preStr.startsWith("@@@@@@@@@@@@@@@@@@@@@@@@@@@@"))//move
                {
                    preStr.replace("@@@@@@@@@@@@@@@@@@@@@@@@@@@@","");

                    if(preStr.contains("\r\n"))
                    {
                        int idx = preStr.indexOf("\r\n");
                        QString substr = preStr.mid(0,idx);
                        QString substr2 = preStr.mid(idx+2, preStr.size()-idx-2);
                        if(substr2.startsWith("###1,"))
                        {
                            drawFFTPlots(substr2, m_fft_plot_i);
                        }
                        preStr = substr;
                    }
                    //qDebug()<<preStr;
                    if(isRecord)
                    {
                        QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                        statusInfoStr.append(dataTimeStr+"\t"+preStr+"\r\n");
                    }
                    else
                    {
                        statusInfoStr.clear();
                    }
                    updateUiByQString(preStr);
                }
                else if(preStr.startsWith("--------------------------------------"))//move body move
                {
                    preStr.replace("--------------------------------------","");
                    //qDebug()<<preStr;
                    if(preStr.contains("\r\n"))
                    {
                        int idx = preStr.indexOf("\r\n");
                        QString substr = preStr.mid(0,idx);
                        QString substr2 = preStr.mid(idx+2, preStr.size()-idx-2);
                        updateUiByQString(substr);
                        if(isRecord)
                        {
                            QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                            statusInfoStr.append(dataTimeStr+"\t"+substr+"\r\n");
                        }
                        else
                        {
                            statusInfoStr.clear();
                        }
                        updateUiByQString(substr2);
                        if(isRecord)
                        {
                            QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                            statusInfoStr.append(dataTimeStr+"\t"+substr2+"\r\n");
                        }
                        else
                        {
                            statusInfoStr.clear();
                        }
                    }
                    else
                    {
                        if(isRecord)
                        {
                            QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                            statusInfoStr.append(dataTimeStr+"\t"+preStr+"\r\n");
                        }
                        else
                        {
                            statusInfoStr.clear();
                        }
                        updateUiByQString(preStr);
                    }
                }
                else if(preStr.startsWith("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"))//handware version
                {
                    preStr.replace("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","");
                    if(preStr.contains("\r\n"))
                    {
                        int idx = preStr.indexOf("\r\n");
                        QString substr = preStr.mid(0,idx);
                        QString substr2 = preStr.mid(idx+2, preStr.size()-idx-2);
                        if(substr2.startsWith("@@@@@@@@@@@@@@@@@@@@@@@@@@@@"))
                        {
                            substr2.replace("@@@@@@@@@@@@@@@@@@@@@@@@@@@@","");
                            if(isRecord)
                            {
                                QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                                statusInfoStr.append(dataTimeStr+"\t"+substr2+"\r\n");
                            }
                            else
                            {
                                statusInfoStr.clear();
                            }
                            updateUiByQString(substr2);
                        }
                        preStr = substr;
                    }
                    if(!preStr.isEmpty())
                    {
                        recordHardWareVersion(preStr);
                    }
                }
                preStr.clear();
                if(idx != myStrTemp.size()-2)
                {
                    preStr.append(myStrTemp.midRef(idx+2,myStrTemp.size()-2-idx));
                }

/*                if(preStr.contains("\r\n"))
                {

                    int idx = preStr.indexOf("\r\n");
                    QString substr = preStr.mid(idx+2, preStr.size()-idx-2);
                    if(substr.contains("movech"))
                    {
                        //直接刷新move属性的值
                        if(substr.contains("\r\n"))
                        {
                            int idx = substr.indexOf("\r\n");
                            substr = substr.mid(0,idx);
                            //updateUiByQString(substr);
                            preStr.clear();
                            preStr.append(substr.midRef(idx+2, substr.size()-idx-2));
                        }
                        qDebug()<<substr;
                        updateUiByQString(substr);
                    }
                    else
                    {
                        QString breathStr = preStr.mid(0, idx);
                        //直接刷新breath属性值,breath是1秒1次,move是3秒1次
                        qDebug()<<breathStr;
                        updateUiByQString(breathStr);
                        preStr.clear();
                        preStr.append(substr);
                    }
                }

                if(preStr.startsWith("###1,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_i);

                }
                else if(preStr.startsWith("###2,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_q);
                }
                else if(preStr.startsWith("###3,"))
                {
                    //qDebug()<<preStr;
                    drawFFTPlots(preStr, m_fft_plot_c);
                }
                else if(preStr.startsWith("breathch"))
                {
                    if(preStr.contains("\r\n"))
                    {
                        int idx = preStr.indexOf("\r\n");
                        QString breathStr = preStr.mid(0, idx);
                        qDebug()<<breathStr;
                        updateUiByQString(breathStr);
                    }
                }
//                else if(preStr.startsWith("movech"))//前面已经显示过了,不需要了
//                {
//                    qDebug()<<preStr;
//                }
                preStr.clear();
                if(idx != myStrTemp.size()-2)
                {
                    preStr.append(myStrTemp.midRef(idx+2,myStrTemp.size()-2-idx));
                }*/
            }
        }
        buf.clear();
    }
}
void MainWindow::updateUiByQString(QString qstring)
{
    QStringList sList = qstring.split(',');
    for(QStringList::const_iterator it=sList.cbegin(); it != sList.cend(); ++it)
    {
        QStringList itemList = (*it).split(':');
        if(itemList.size()==2)
        {
            QString key = itemList[0];
            updateUiByKey(key, itemList[1]);
        }
    }
}

void MainWindow::cleanFFTPlots()
{
    m_fft_plot_i->graph(0)->data()->clear();
    m_fft_plot_q->graph(0)->data()->clear();
    m_fft_plot_c->graph(0)->data()->clear();
    for(int i = 0; i < 3; i++)
    {
        if(m_tracers[i+2])
        {
            m_tracers[i+2]->setVisible(false);
        }
        if(m_tracerTexts[i+2])
        {
            m_tracerTexts[i+2]->setVisible(false);
        }
    }
    m_fft_plot_i->replot();
    m_fft_plot_q->replot();
    m_fft_plot_c->replot();
}

bool isVersionSave = false;
void MainWindow::recordHardWareVersion(QString version)
{
    if(isVersionSave==false)
    {
        QFile file(defaultPath+"/version.txt");
        isVersionSave = true;
        bool isOk = file.open(QIODevice::WriteOnly);
        if(isOk)
        {
            file.write(version.toUtf8());
        }
        file.close();
    }

}
void MainWindow::updateUiByKey(QString key, QString value)
{
    if(key == "movech")
    {
        ui->label_movech->setText(value);
    }
    if(key == "mean")
    {
        ui->label_mean->setText(value);
    }
    if(key == "variance")
    {
        ui->label_variance->setText(value);
    }
    if(key == "range")
    {
        ui->label_range->setText(value);
    }
    if(key == "zeronum")
    {
        ui->label_zeronum->setText(value);
    }
    if(key == "status")
    {
        ui->label_status->setText(value);
    }
    if(key == "breathch")
    {
        ui->label_breathch->setText(value);
    }
    if(key == "breath")
    {
        ui->label_breathTimes->setText(value);
    }
    if(key == "max_value")
    {
        ui->label_fft_max->setText(value);
    }
}
void MainWindow::startSubTread(QString port)
{
    QThread *t1 = new QThread;
    preThread = t1;
    Tasks *task = new Tasks;

    task->initSerialPort(port);
    connect(task, &Tasks::started, this, &MainWindow::drawPlots);
    connect(task, &Tasks::serialInputError, this, &MainWindow::serialErrorHandler);

    task->moveToThread(t1);
    t1->start();
    disconnect(this, &MainWindow::killSubThread, this, nullptr);
    connect(this, &MainWindow::killSubThread, this, [=]()mutable{
        if(t1 != nullptr)
        {
            t1->terminate();
            delete t1;
            t1 = nullptr;
            preThread = nullptr;
            delete task;
            task = nullptr;
            preTask = nullptr;
        }
    });
    disconnect(this, &MainWindow::destroyed, this, nullptr);
    connect(this, &MainWindow::destroyed, this, [=]()mutable{//窗口关闭释放线程
        if(t1 != nullptr && t1 == preThread)
        {
            t1->quit();
            t1->wait();
            t1->deleteLater();
            delete t1;
            t1 = nullptr;
        }
        if(task != nullptr && task == preTask)
        {
            delete task;
            task = nullptr;
        }
    });
}
void MainWindow::serialErrorHandler()
{
    if(m_warning_box == nullptr)
    {
        m_warning_box = new QMessageBox;
        m_warning_box->setIcon(QMessageBox::Warning);
        m_warning_box->setText("配置错误");
        m_warning_box->setInformativeText("串口数据不一致,检查串口配置是否有误?");
        m_warning_box->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
        m_warning_box->setDefaultButton(QMessageBox::Ok);
        m_warning_box->setButtonText(QMessageBox::Ok, QString("确 定"));
        m_warning_box->setButtonText(QMessageBox::Cancel, QString("取 消"));
        m_warning_box->open();
    }
    else
    {
        m_warning_box->reject();
        m_warning_box->open();
    }


    if(m_warning_box->result() == QMessageBox::Cancel)
    {
        return;
    }
    if(m_serialPort)
    {
        m_serialPort->close();
        delete m_serialPort;
        m_serialPort = nullptr;
    }
    emit killSubThread();
    cleanPlots();
}

void MainWindow::onSetDirectory()
{
    auto selectDir = QFileDialog::getExistingDirectory();
    defaultPath = selectDir;
}

void MainWindow::onStartLabeling()
{
    LabelingDialog *dlg = new LabelingDialog;
    emit startWriteLabelFile(QDateTime::currentDateTime());
    connect(dlg, &LabelingDialog::writeToLabelFile, this, &::MainWindow::onWriteToLabelFile);
    connect(dlg, &LabelingDialog::writeFileDone, this, &MainWindow::onWriteLabelFileDone);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->show();
}
static int elaspedTime = 0;
void MainWindow::drawPlots(QString text)
{
    QStringList list = text.split("\r\n");
    int count = list.size();
    if(count>2)//表示是第一次获得数据
    {
        QString temp = list[0];//第一个数据可能会包含特殊字符???\n,进行如下过滤
        QStringList pair = temp.split(',');
        QString tl = pair[0];
        tl.replace(QRegExp("(\\D+)\n(\\d+)$"), "\\2");
        QString tr= pair[1];
        list[0] = QString("%1,%2").arg(tl, tr);
    }
    if(!list.isEmpty())
    {
        list.pop_back();//去掉空字符串
    }
    int len = list.size();
    if(len >= 1)
    {
        for(int i = 0; i < len; i++)
        {
            elaspedTime +=50;
            double x = (double)elaspedTime/1000;
            if(isRecord)
            {
                QString dataTimeStr = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
                recordStr.append(dataTimeStr+"\t"+list[i]+"\r\n");
            }
            else
            {
                recordStr.clear();
            }
            QStringList slist = list[i].split(',');
            double y1 = slist[0].toDouble();
            double y2 = slist[1].toDouble();
            m_plot_i->graph(0)->addData(x, y1);
            m_plot_q->graph(0)->addData(x, y2);
            m_plot_i->graph(0)->rescaleAxes();
            m_plot_q->graph(0)->rescaleAxes();
            m_plot_i->xAxis->setRange(x, m_timeSpan, Qt::AlignRight);//setRange一定要在rescaleAxes之后,否则不生效
            m_plot_q->xAxis->setRange(x, m_timeSpan, Qt::AlignRight);
            m_plot_i->replot();
            m_plot_q->replot();
        }
    }
}

void MainWindow::drawFFTPlots(QString text, QCustomPlot *plot)
{
    QStringList list = text.split(',');
    list.pop_front();//删除"###1"
    if(!m_fft_y.isEmpty())
    {
        m_fft_y.clear();
    }
    for(QStringList::const_iterator it = list.cbegin(); it != list.cend(); ++it)
    {
        m_fft_y.push_back((*it).toDouble());
    }
    if(m_fft_y.size()>513)
    {
        m_fft_y.resize(513);
    }
    int min_indx = 0;
    int max_indx = 512;
    bool is_min_find = false;
    bool is_max_find = false;
    for(int i = 0; i < m_fft_x.size(); i++)
    {
        if(m_fft_x[i] >= m_frequency_x_min && !is_min_find)
        {
            min_indx = i;
            is_min_find = true;
        }
        if(m_fft_x[i] >= m_frequency_x_max && !is_max_find)
        {
            max_indx = i;
            is_max_find = true;
        }
    }

    int length = max_indx - min_indx + 1;
    plot->graph(0)->setData(m_fft_x.mid(min_indx,length), m_fft_y.mid(min_indx,length));
    plot->graph(0)->rescaleAxes();
    plot->replot();
}

void MainWindow::mouseMove_slot_I(QMouseEvent *event)
{
    QCustomPlot *plot = m_plot_i;
    //获得鼠标位置处对应的横坐标数据x
    double x = plot->xAxis->pixelToCoord(event->pos().x());

    if(plot->graph(0)->data().data()->size() != 0)
    {
        QCPItemTracer * tracer = nullptr;
        tracer = m_tracers[0];
        tracer->setGraph(plot->graph(0)); //将游标和该曲线图层想连接
        tracer->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
        tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得
        tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效

        //更新游标说明的内容
        double xValue = tracer->position->key();
        double yValue = tracer->position->value();
        QCPItemText *label = nullptr;
        label = m_tracerTexts[0];
        if(event->pos().x() > 15 && event->pos().x() < (plot->width()-15)
            && event->pos().y() > 15 && event->pos().y() < (plot->height()-15))
        {
            label->setVisible(true);
            label->setText(QString("[ %1, %2 ]").arg(xValue).arg(yValue));
        }
        else
        {
            label->setVisible(false);
        }
        plot->replot(); //重绘
    }
}
void MainWindow::mouseMove_slot_Q(QMouseEvent *event)
{
    QCustomPlot *plot = m_plot_q;
    //获得鼠标位置处对应的横坐标数据x
    double x = plot->xAxis->pixelToCoord(event->pos().x());

    if(plot->graph(0)->data().data()->size() != 0)
    {
        QCPItemTracer * tracer = nullptr;
        tracer = m_tracers[1];
        tracer->setGraph(plot->graph(0)); //将游标和该曲线图层想连接
        tracer->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
        tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得
        tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效

        //更新游标说明的内容
        double xValue = tracer->position->key();
        double yValue = tracer->position->value();
        QCPItemText *label = nullptr;
        label = m_tracerTexts[1];
        if(event->pos().x() > 15 && event->pos().x() < (plot->width()-15)
            && event->pos().y() > 15 && event->pos().y() < (plot->height()-15))
        {
            label->setVisible(true);
            label->setText(QString("[ %1, %2 ]").arg(xValue).arg(yValue));
        }
        else
        {
            label->setVisible(false);
        }
        plot->replot(); //重绘
    }
}
void MainWindow::mouseMove_slot_fft_I(QMouseEvent *event)
{
    QCustomPlot *plot = m_fft_plot_i;
    //获得鼠标位置处对应的横坐标数据x
    double x = plot->xAxis->pixelToCoord(event->pos().x());

    if(plot->graph(0)->data().data()->size() != 0)
    {
        QCPItemTracer * tracer = nullptr;
        tracer = m_tracers[2];
        tracer->setGraph(plot->graph(0)); //将游标和该曲线图层想连接
        tracer->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
        tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得
        tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效

        //更新游标说明的内容
        double xValue = tracer->position->key();
        double yValue = tracer->position->value();
        QCPItemText *label = nullptr;
        label = m_tracerTexts[2];
        if(event->pos().x() > 15 && event->pos().x() < (plot->width()-15)
            && event->pos().y() > 15 && event->pos().y() < (plot->height()-15))
        {
            label->setVisible(true);
            label->setText(QString("[ %1, %2 ]").arg(xValue).arg(yValue));
        }
        else
        {
            label->setVisible(false);
        }
        plot->replot(); //重绘
    }
}
void MainWindow::mouseMove_slot_fft_Q(QMouseEvent *event)
{
    QCustomPlot *plot = m_fft_plot_q;
    //获得鼠标位置处对应的横坐标数据x
    double x = plot->xAxis->pixelToCoord(event->pos().x());

    if(plot->graph(0)->data().data()->size() != 0)
    {
        QCPItemTracer * tracer = nullptr;
        tracer = m_tracers[3];
        tracer->setGraph(plot->graph(0)); //将游标和该曲线图层想连接
        tracer->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
        tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得
        tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效

        //更新游标说明的内容
        double xValue = tracer->position->key();
        double yValue = tracer->position->value();
        QCPItemText *label = nullptr;
        label = m_tracerTexts[3];
        if(event->pos().x() > 15 && event->pos().x() < (plot->width()-15)
            && event->pos().y() > 15 && event->pos().y() < (plot->height()-15))
        {
            label->setVisible(true);
            label->setText(QString("[ %1, %2 ]").arg(xValue).arg(yValue));
        }
        else
        {
            label->setVisible(false);
        }
        plot->replot(); //重绘
    }
}
void MainWindow::mouseMove_slot_fft_C(QMouseEvent *event)
{
    QCustomPlot *plot = m_fft_plot_c;
    //获得鼠标位置处对应的横坐标数据x
    double x = plot->xAxis->pixelToCoord(event->pos().x());

    if(plot->graph(0)->data().data()->size() != 0)
    {
        QCPItemTracer * tracer = nullptr;
        tracer = m_tracers[4];
        tracer->setGraph(plot->graph(0)); //将游标和该曲线图层想连接
        tracer->setGraphKey(x); //将游标横坐标设置成刚获得的横坐标数据x
        tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得
        tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效

        //更新游标说明的内容
        double xValue = tracer->position->key();
        double yValue = tracer->position->value();
        QCPItemText *label = nullptr;
        label = m_tracerTexts[4];
        if(event->pos().x() > 15 && event->pos().x() < (plot->width()-15)
            && event->pos().y() > 15 && event->pos().y() < (plot->height()-15))
        {
            label->setVisible(true);
            label->setText(QString("[ %1, %2 ]").arg(xValue).arg(yValue));
        }
        else
        {
            label->setVisible(false);
        }
        plot->replot(); //重绘
    }
}

void openFile(const QString &fileName)
{

    QFile file(fileName);
    if (file.exists()) {
        QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
    }
}


int MainWindow::writeToFile(QString path, QString text, int recordType)
{
//    QString path = QFileDialog::getSaveFileName(this,"保存文件","../untitled.txt","Text files (*.txt)");
//    QString path = QDir::cleanPath(QDir::currentPath() + QDir::separator()  + QString("data"));
    if(path.isEmpty() == false)
    {
        QFile file;            //创建对象
        QString suffix;
        if(recordType == 0)
        {
            suffix = "time_for_iq_"+QDateTime::currentDateTime().toString("yyyy-MM-dd_hh_mm_ss")+".txt";
        }
        else if(recordType == 1)
        {
            suffix = "time_for_info_"+QDateTime::currentDateTime().toString("yyyy-MM-dd_hh_mm_ss")+".txt";
        }
        QString filename = path+"/"+suffix;
        file.setFileName(filename);
        //打开文件,只写
        bool isok = file.open(QIODevice::WriteOnly);
        if(isok == true)
        {
            //把读取到的文本写进文件file对象中
            //由于文件写函数是qbytearray类型的数据,所以需要在写之前将str转换成bytearray类型
            //在qt中有直接转换的函数,toutf8();
            file.write(text.toUtf8());
        }
        else
        {
            return -1;
        }
        file.close();
        openFile(filename);
    }
    return 0;
}

03-16
### 如何在 Qt 中实现 FFT 或使用 FFT 库 #### 1. 基于自定义代码的 FFT 实现 FFT 的核心在于利用单位根 $ w_n^k = e^{-j \frac{2\pi k}{n}} $ 进行快速变换。对于 Qt 环境下的 C++ 编程,可以参考如下代码片段: ```cpp #include <complex> #include <vector> typedef std::complex<double> cp; // 预处理单位根及其倒数 void init_w(cp *w, int n) { double theta = 2 * M_PI / n; for(int i=0;i<n;i++) { w[i] = cp(cos(i*theta), sin(i*theta)); // 单位根 } } // FFT 函数 void fft(cp *arr, cp *w, int n) { if(n == 1) return; // 终止条件 static cp tmp[static_cast<size_t>(1<<20)]; int half = n >> 1; // 分离奇偶项 for(int i=0;i<half;i++) { tmp[i] = arr[i << 1]; tmp[half+i] = arr[(i << 1)+1]; } for(int i=0;i<n;i++) arr[i] = tmp[i]; // 对两个子序列分别递归调用 FFT fft(arr, w, half); fft(arr+half, w, half); // 合并结果 for(int i=0;i<half;i++) { cp t = w[n/2+i]*arr[i+half]; // 计算蝶形运算 tmp[i] = arr[i]+t; tmp[i+half] = arr[i]-t; } for(int i=0;i<n;i++) arr[i]=tmp[i]; } ``` 上述代码展示了基于分治策略的 FFT 实现[^1]。 --- #### 2. 使用第三方库 Armadillo 实现 FFT Armadillo 是一个高效的线性代数库,支持矩阵操作以及 FFT 功能。以下是其基本用法: ```cpp #include <armadillo> using namespace arma; int main() { vec signal = linspace<vec>(0, 2*M_PI, 100); // 创建时域信号 cx_vec freq_domain_signal = fft(signal); // 调用 FFT 将时域转为频域 // 输出频率成分 for(auto val : freq_domain_signal){ cout << real(val) << "+" << imag(val) << "j" << endl; } return 0; } ``` 此方法适用于需要高效完成 FFT 变换的任务,并且能够轻松集成到 Qt 工程中[^4]。 --- #### 3. 利用 FFTW 完成高性能 FFT 处理 FFTW(Fastest Fourier Transform in the West)是一个广泛使用的开源 FFT 库。它提供了高度优化的函数用于执行傅里叶变换。下面是结合 QT 和 FFTW 的简单例子: ```c++ #include <fftw3.h> #define FFTWN 1024 int main(){ fftw_complex *in,*out; fftw_plan p; in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*FFTWN); out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*FFTWN); p = fftw_plan_dft_1d(FFTWN,in,out,FFTW_FORWARD,FFTW_ESTIMATE); /* 初始化输入数据 */ for(int i=0;i<FFTWN;i++){ in[i][0] = cos(2*M_PI*i/FFTWN); // 输入实部 in[i][1] = 0; // 输入虚部设为零 } /* 执行计划 */ fftw_execute(p); /* 查看输出结果 */ for(int i=0;i<FFTWN;i++) printf("%f %f\n",out[i][0],out[i][1]); /* 清理资源 */ fftw_destroy_plan(p); fftw_free(in); fftw_free(out); return 0; } ``` 该方案特别适合大规模数据集上的 FFT 操作,具有良好的性能表现[^3]。 --- #### 总结 以上介绍了三种方式来实现或应用 FFT 技术:一是手动编写 FFT 程序;二是借助强大的数学工具包——Armadillo 来简化开发过程;三是采用专门设计的 FFTW 库达成更优效率目标。开发者可以根据实际需求选择合适的路径加以实践。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值