Qt5-實現串口助手版“示波器”

一.前言

  • 閒了好久沒更新博客,偷懶的我就拿了去年暑假備戰電賽時做的一個串口版“示波器”來充充水。拋磚引玉哈。
  • 因爲以前發過一篇主題一樣的文章,所以就當該文就相當於上版的升級版(其實兩者沒啥關係,僅是主題一樣,以前那篇是單純爲賽題弄的,這篇更有通用性)
  • 是當時爲電賽準備的(吐槽一下,原本我組準備的電源題,怎麼想着也會用上adc、pwm啥的吧,結果19年的A題一出來真是讓我一言難盡啊)
  • 實現的功能有:自定義數據格式,多個數據圖線顯示,自動和手動兩種模式,顯示鼠標當前的位置
  • 因爲時間有點久,很多註釋沒寫到還請諒解。

二.效果介紹

  • 因爲一直宅在家,又沒帶板子回來,所以就只能看看界面了,紅色字體爲說明
  • 在這裏插入圖片描述

三.軟件思路介紹

  1. 大概思路,串口接收數據,然後按照自定義格式解析數據,最後繪畫波形圖,因爲總代碼有點多,就不全貼出來,有空我上傳到github上去,也可以評論發郵箱號我來發給你
  2. 這裏用到到繪畫波形圖的庫是qcustomplot,不清楚的可以百度一下,它的官網介紹得挺詳細的。
  3. 先是定義一個Handle類裏有串口的自動檢測,即如果檢測有串口出現會自動加到串口端口的欄中,和串口數據的接收、數據解析和曲線圖的繪畫(放在一個線程裏執行,防止界面顯示卡死)代碼如下,先是頭文件:
#include <QObject>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include "qcustomplot.h"
#include <QVector>
#define MAX_COUNT       1500			// x軸可顯示的最大範圍
#define cout qDebug() << __LINE__ << ":"
class Handle : public QObject
{
    Q_OBJECT
public:
    explicit Handle(QCustomPlot *P,QObject *parent = nullptr);
signals:
    void finish();						// 數據解析完成時發出信號
public slots:
    void getValidPort(QComboBox* p);    // 獲取有效串口端口
    void getText(QString t);
    void analyData();					// 解析數據
    void setFlag(QString b = "[",QString m=",",QString e="]"){
        begin = b;mid = m; end = e;
    }									// 自定義數據格式,分隔符
    void clear();						// 數據清空
    void setAuto(bool e){				// 是否設置爲自動模式
        isAuto = e;
    }
    void setRun(bool e){				// 是否設置爲運行
        isRun = e;
    }
    void setOpen(bool e){				// 是否打開串口
        isPortOpen = e;
    }
    bool getOpen(){return isPortOpen;}	// 獲取串口打開狀態
    bool getRun(){return isRun;}		// 獲取串口是否在運行
    bool getAuto(){return isAuto;}		// 獲取是否爲自動模式
private:
    QString begin,mid,end;              // 自定義數據格式
    QString text;
    bool isPortOpen;                    // 標識串口端口是否打開
    QStringList list;

    QCustomPlot *Plot;                  // 圖層
    QVector <QCPItemTracer *>   trace;  // 曲線上的跟隨點
    QVector <QCPItemText *  >   Ptext;  // 每條曲線上的顯示的文字
    QVector <QVector<double>>   YData;  // Y軸數據,可具有多個數據曲線
    QVector <double>            XData;  // x軸數據
    QVector<QPen>               pen;    // 曲線顏色的區分
    int Clk,xMid;                       // x軸的刻度
    int countLine;                      // 曲線的數量
    int firstLine;
    bool isAuto,isRun;                  // 標識是否自動,是否在運行
};
  1. 再貼點串口數據解析函數的代碼
/*
 * 串口數據的解析
 */
void Handle::analyData()
{
	// 進行數據提取,比如begin="[",mid=",",end="]",串口數據爲,30][10,20,30][...
	// 
    int _end = text.indexOf(begin);
    text.remove(0,_end+1);		// 移除前面多餘的數據,比如移除,30][,還剩10,20,30][...
    if( _end < 0 || !isRun){
        return ;
    }
    // 將數據分段,比如分出10,20,30]和下一段[...
    QStringList _be = text.split(begin,QString::SkipEmptyParts);
    for(auto _b:_be){
        int _e = _b.indexOf(end);	// 找出數據段裏的]的位置
        if(_e > 0){
            QStringList _list = _b.left(_e).split(mid,QString::SkipEmptyParts);	// 將數據10,20,30中的各個數據分離出來
            countLine = 0;			// 統計一段數據裏的數據個數
            for(auto i:list){		// 分離出的數據放入各個曲線數據裏
                YData[countLine].push_back(i.toDouble());
                if(YData[countLine].count() >= MAX_COUNT){
                    YData[countLine].pop_front();
                }
                countLine ++;
            }
            XData.push_back(Clk++);
            if(XData.count() >= MAX_COUNT){
                XData.pop_front();
            }
            for(int _i = firstLine; _i < countLine && _i < 6; _i ++){
                firstLine = countLine;
                Plot->addGraph();
                Plot->graph(_i)->setPen(pen.at(_i));	// 給數據曲線上色
                Plot->graph(_i)->setVisible(true);		// 曲線顯示
                trace[_i] = new QCPItemTracer(Plot);	// 生成一個跟隨點
                trace[_i]->setPen(pen.at(_i));			// 設置點的顏色
                trace[_i]->setSize(10);					// 設置點的大小
                trace[_i]->setBrush(QBrush(pen.at(_i).color()));
                trace[_i]->setGraph(Plot->graph(_i));	// 設置在該曲線顯示
                trace[_i]->setStyle(QCPItemTracer::tsCircle);	// 圓形點
                trace[_i]->setInterpolating(true);

                Ptext[_i] = new QCPItemText(Plot);	// 跟隨點的文本信息
                Ptext[_i]->setPen(pen.at(_i));
                Ptext[_i]->setText(QString("Graph:%1").arg(_i));
                Ptext[_i]->setRotation(4);
                Ptext[_i]->setTextAlignment(Qt::AlignRight | Qt::AlignBottom|Qt::AlignJustify);
                Ptext[_i]->setPositionAlignment(Qt::AlignRight | Qt::AlignBottom|Qt::AlignJustify);
                Ptext[_i]->position->setType(QCPItemPosition::ptPlotCoords);
                Ptext[_i]->setFont(QFont("Helvetica [Cronyx]", 12));
                Ptext[_i]->setPadding(QMargins(8, 0, 0, 0));

                YData[_i].resize(MAX_COUNT);
            }
            for(int _i = 0; _i < countLine; _i ++){
                Plot->graph(_i)->setData(XData,YData[_i]);
                if(Clk <= xMid){
                    Plot->xAxis->setRange(0,Clk);
                }
                else{
                    Plot->xAxis->setRange(Clk-xMid,Clk);
                }
                Ptext[_i]->position->setCoords(Clk,YData[_i].last());	// 文本顯示的位置
                Ptext[_i]->setText(QString("%1:(%2,%3)").arg(_i+1).arg(Clk).arg(YData[_i].last()));	// 文本內容
                trace[_i]->setGraphKey(Clk);			// 跟隨點的位置
                if(isAuto){								// 是否自動適配縮放
                    Plot->yAxis->rescale(true);
                }
            }
        }
    }
    Plot->replot();				// 圖層更新
    if(text.length() > 200){	// 判斷串口接收數據是否過多,過多久丟棄
        text.clear();
    }
    emit finish();
}
  1. 然後在widget類中,這裏比較簡單,開了個線程給Handle類,然後綁定各種信號與槽,先上頭文件:
#ifndef WIDGET_H
#define WIDGET_H

#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QWidget>
#include <QDebug>
#include <QMessageBox>
#include "qcustomplot.h"
#include <QVector>
#include <QTimer>
#include <QPen>
#include <QMouseEvent>
#include <QToolTip>
#include "handle.h"
#include <QThread>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
    void goClear();				// 清除
    void autoHandle();			// 沒用上 
signals:
    void analy(QString t);		
    void clearT();
    void getValidP(QComboBox* p);
    void startA();
private slots:
    void on_pushOpen_clicked();
    void on_pushClear_clicked();
    void on_pushRun_clicked();
    void on_pushAuto_clicked();
private:
    Ui::Widget *ui;
    Handle *hand;
    QCustomPlot *Plot;
    QSerialPort *SerialPort;

    QThread *thread;
};
#endif // WIDGET_H

  1. widget類構造函數裏的各種信號與槽的綁定
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    SerialPort = new QSerialPort(this);
    Plot = ui->widget;
    Plot->setMouseTracking(true);	// 使能圖層鼠標跟隨
    // 開啓圖層上的圖層刻度可手動調動
    Plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
    connect(Plot->xAxis, SIGNAL(rangeChanged(QCPRange)), Plot->xAxis2, SLOT(setRange(QCPRange)));
    connect(Plot->yAxis, SIGNAL(rangeChanged(QCPRange)), Plot->yAxis2, SLOT(setRange(QCPRange)));

    connect(Plot,&QCustomPlot::mouseMove,[=](QMouseEvent *e){
        double _x = Plot->xAxis->pixelToCoord(e->x()),_y = Plot->yAxis->pixelToCoord(e->y());
        ui->labelWhere->setText(QString("(%1,%2)").arg(_x).arg(_y));

    });
    connect(Plot,&QCustomPlot::plottableClick,[=](
            QCPAbstractPlottable * plottable,
            int ,
            QMouseEvent * ){

        ui->labelGraph->setText(plottable->name());
    });

    ui->tabWidget->setTabText(0,"Gui");
    ui->tabWidget->setTabText(1,"Data");

    hand = new Handle(Plot);
    thread = new QThread(this);
    hand->moveToThread(thread);
    // 獲取自定義數據
    hand->setFlag(ui->lineEditBegin->text(),ui->lineEditMid->text(),ui->lineEditEnd->text());
    // 綁定清除按鍵的響應
    connect(this,&Widget::clearT,hand,&Handle::clear,Qt::QueuedConnection);
    // 有串口信息時,將數據給Handle類處理
    connect(this,&Widget::analy,hand,&Handle::getText,Qt::QueuedConnection);
	// 綁定獲取有效串口端口信息    
   	connect(this,&Widget::getValidP,hand,&Handle::getValidPort,Qt::QueuedConnection);
	// 這個也沒用上,還請注意    
    connect(hand,&Handle::finish,this,&Widget::autoHandle,Qt::QueuedConnection);
    connect(SerialPort,&QSerialPort::readyRead,[=](){
       emit analy(SerialPort->readAll());
    });
    connect(SerialPort,&QSerialPort::errorOccurred,[=](QSerialPort::SerialPortError e){
        cout << "error";
        if(e == QSerialPort::DeviceNotFoundError){
            SerialPort->close();
            hand->setOpen(false);
            on_pushClear_clicked();
        }
    });
    thread->start();		// 開啓線程
    goClear();
}

四.完結

注:這個程序有個小問題,就是當窗口全屏時,就會發生數據丟失加劇的現象。當時的我本想試着再加一個線程來專門處理曲線繪畫的,後來發現數據丟失的現象更嚴重,就沒加那個線程了。數據丟失是由下位機發信息太過於頻繁,比如頻率大於1000HZ時,頻率降低可解決。

發佈了21 篇原創文章 · 獲贊 34 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章