跳转至

示波器接口调用案例

一、新建Qt项目并编写.pro文件载入必要库

1. 在Qt(5.12版本)菜单“文件”→“新建项目”→ “选择Qt Widgets Application”,启动创建项目向导。

image1

  1. 设置项目名称以及一些项目配置。这里注意在build system中选择qmake版本,Kit Selection中选中64位,其它配置使用默认就好。
    image2

    image3

    image4

  2. 找到厂家提供的SDK库,路径如下(64位库为例)。

    image5

函数库具体路径如下

image6

image7

  1. 在Qt的项目文件(.pro)中添加头文件路径以及库路径,相对或绝对路径都可以。

    image8

  2. 声明用到的头文件

    #include <CNCApi/CncDef.h>
    
    #include <CNCApi/CommApi.h>
    
    #include <CNCApi/ProxySys.h>
    
    #include <CNCApi/ProxyEntry.h>
    
    #include <CNCApi/ProxyMotion.h>
    
    #include <CNCApi/ProxyStatus.h>
    
    #include <CNCApi/ProxyScope.h>
    
     

二、查看函数手册,熟悉相关函数接口

image9

1.连接控制器接口:

函数名 功能
connect 连接控制器

2.电子齿轮比相关接口:

函数名 功能
startCollect 启动示波器采集功能
stopCollect 停止示波器采集功能
setScopeCallback 回调传回示波器采集到的数据

三、基础示波器演示案例

1. 先在Qt的UI设计界面中,放置好之后会用到的组件,包含三个Push Button组件和一个Widget组件

image10

image11

控件说明:

控件 对象名 说明
QPushButton btnConnect 连接
QPushButton btnStart 开始采集
QPushButton btnStop 停止采集
Widget oscWidget 显示示波曲线
  1. 创建相关头文件及源代码文件

    image12

    其中oscilloscopewidget为示波器窗口类,qcustomplot详细介绍见下文

  2. 提升widget为oscilloscopewidget类

    ①在UI设计界面选中widget,右键点击“提升为”,在弹出的窗口中填写类名:OscilloscopeWidget,头文件:oscilloscopewidget.h,后点击添加(Add) → 提升(Promote)

    image13

  3. 使用Qt C++的一个绘图小部件QCustomPlot实现波形显示的组件

    从官网qcustomplot.com中下载源码解压后加入项目目录下,包括如下文件。这是一个用于2D绘图和数据可视化的部件,这里仅作示例用,主要关注示波器函数相关调用即可,如果在有其它控件可以实现类似效果或非Qt端项目想调用示波器功能,可以此做参考。

    image14

    同时还需要注意,由于QCustomPlot 在内部使用了QPrinter,所以在.pro文件中,需要添加QtPrintSupport模块

    image15

  4. 实现效果

    image16

  5. 具体代码实现

    mainwindow.h:

    #pragma once
    #include <QMainWindow>
    #include <CNCApi/CncDef.h>
    #include <CNCApi/CommApi.h>
    #include <CNCApi/ProxySys.h>
    #include <CNCApi/ProxyEntry.h>
    #include <CNCApi/ProxyMotion.h>
    #include <CNCApi/ProxyStatus.h>
    
    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE
    
    class MainWindow : public QMainWindow {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void onStartClicked();
        void onStopClicked();
        void on_btnConnect_clicked();
    
    private:
        Ui::MainWindow *ui;  /* UI对象指针 */
        weconcnc::CCommApi *commApi;  /* 通信API对象指针,用于与CNC设备交互 */
    };
    
    mainwindow.cpp:
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "oscilloscopewidget.h"
    #include <QDebug>
    
    /* 构造函数:初始化主窗口,设置UI、通信API和信号槽连接 */
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
        , commApi(new weconcnc::CCommApi())
    {
        ui->setupUi(this);
        /* 连接按钮和滑块的信号与槽函数 */
        connect(ui->btnStart, &QPushButton::clicked, this, &MainWindow::onStartClicked);
        connect(ui->btnStop,  &QPushButton::clicked, this, &MainWindow::onStopClicked);
    
        WSADATA wsaData;
        int iResult;
    
        /* 初始化 Winsock */
        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != 0) {
            printf("WSAStartup failed with error: %d\n", iResult);
        }
    }
    MainWindow::~MainWindow() {
        delete ui;
    }
    
    /* 槽函数:启动示波器数据收集 */
    void MainWindow::onStartClicked() {
        ui->oscWidget->startCollect();
    }
    /* 槽函数:停止示波器数据收集 */
    void MainWindow::onStopClicked() {
        ui->oscWidget->stopCollect();
    }
    /* 槽函数:触发示波器连接操作 */
    void MainWindow::on_btnConnect_clicked()
    {
        ui->oscWidget->btnConnect();
    }
    
    oscilloscopewidget.h:
    #pragma once
    #include <QWidget>
    #include <QTimer>
    #include <QVector>
    #include "qcustomplot.h"
    #include <CNCApi/CncDef.h>
    #include <CNCApi/CommApi.h>
    #include <CNCApi/ProxySys.h>
    #include <CNCApi/ProxyEntry.h>
    #include <CNCApi/ProxyMotion.h>
    #include <CNCApi/ProxyStatus.h>
    #include <CNCApi/ProxyScope.h>
    
    class OscilloscopeWidget : public QWidget {
        Q_OBJECT
    public:
        explicit OscilloscopeWidget(QWidget *parent = nullptr);
    
        void startCollect();
        void stopCollect();
        void btnConnect();
    
    private:
        QCustomPlot *plot;
        QTimer updateTimer;
        QVector<QVector<QPointF>> channelData;
        qint64 baseTimestamp = -1;
        int channelCount = 2;
        double timeScale = 5.0;
        weconcnc::CCommApi *commApi;
    
        static int onScopeData(const char *pData, int32_t nLen, void *param);
        void parseData(const char *pData, int len, int channelCount);
    };
    
    oscilloscopewidget.cpp:
    #include "oscilloscopewidget.h"
    #include <QVBoxLayout>
    #include <QDebug>
    using namespace weconcnc;
    
    /* 构造函数:初始化示波器控件,设置图表、布局和定时器 */
    OscilloscopeWidget::OscilloscopeWidget(QWidget *parent)
        : QWidget(parent), plot(new QCustomPlot(this))
        , commApi(new weconcnc::CCommApi())
    {
        auto layout = new QVBoxLayout(this);
        layout->setMargin(0);
        layout->addWidget(plot); /* 将图表添加到布局 */
    
        /* 初始化通道数据存储,这里采用2个通道仅作示例用 */
        channelData.resize(channelCount);
        for (int i = 0; i < channelCount; ++i) {
            plot->addGraph();  /* 为每个通道添加图表 */
    
            if (i == 1) {
                plot->yAxis2->setVisible(true);  /* 为第二个通道启用右侧Y轴 */
                plot->graph(i)->setValueAxis(plot->yAxis2);
            }
    
            plot->graph(i)->setName(QString("Ch%1").arg(i + 1)); /* 设置通道名称 */
        }
    
        /* 配置图表显示属性 */
        plot->legend->setVisible(true);
        plot->xAxis->setLabel("Time (ms)");
        plot->yAxis->setLabel("Value");
    
        plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
    
        /* 定期更新图表数据 */
        connect(&updateTimer, &QTimer::timeout, [this]() {
            for (int i = 0; i < plot->graphCount(); ++i) {
                QVector<double> x, y;
                for (const auto &p : channelData[i]) {
                    x.append(p.x());  /* 时间数据 */
                    y.append(p.y());  /* 值数据 */
                }
                plot->graph(i)->setData(x, y);
            }
            plot->xAxis->setRange(0, timeScale * 1000);
            plot->rescaleAxes(true);
            plot->replot();
    
            /* 设置通道线条样式 */
            QPen pen1(Qt::red);
            QPen pen2(Qt::green);
            pen1.setWidth(2);
            pen2.setWidth(2);
    
            plot->graph(0)->setPen(pen1);
            plot->graph(1)->setPen(pen2);
    
        });
    
        updateTimer.start(100);
    }
    
    /* 连接函数 */
    void OscilloscopeWidget::btnConnect(){
        std::string sIp = "192.168.54.98";
        uint16_t port = 19996;
    
        WECONCNC_ERROR_E ret = commApi->connect(sIp, port);
        if (WECONCNC_ERROR_SUCCESS != ret) {
            std::cout << "connect failed!" << std::endl;
        }
        else {
            std::cout << "connect success!" << std::endl;
        }
    }
    
    /* 开始数据收集,设置回调并启动收集 */
    void OscilloscopeWidget::startCollect() {
        CProxyScope *scope = CProxyScope::getInstance(commApi);   /* 获取示波器实例 */
        std::vector<std::string> channels = {"axis.0.motor-pos-cmd", "ini.0.max_limit"};  /* 定义收集的通道 */
        scope->setScopeCallback(onScopeData, this);   /* 设置数据回调函数 */
        scope->startCollect(10, channels); // 10ms间隔
    }
    
    /* 停止数据收集 */
    void OscilloscopeWidget::stopCollect() {
        CProxyScope *scope = CProxyScope::getInstance(commApi);
        scope->stopCollect();
        updateTimer.stop();
    }
    
    /* 回调函数,处理从CNC设备接收的原始数据 */
    int OscilloscopeWidget::onScopeData(const char *pData, int32_t nLen, void *param) {
        auto *self = reinterpret_cast<OscilloscopeWidget*>(param);
        self->parseData(pData, nLen, self->channelCount);
        return 0;
    }
    
    /* 解析原始数据,提取时间戳和通道值 */
    void OscilloscopeWidget::parseData(const char *pData, int len, int channelCount) {
        int pos = 0;
        while (pos + 8 <= len) {
            qint64 timestamp;
            memcpy(&timestamp, pData + pos, 8); /* 读取8字节时间戳 */
            pos += 8;
    
            if (baseTimestamp < 0)
                baseTimestamp = timestamp; /* 设置基准时间戳 */
            double elapsed = (timestamp - baseTimestamp) / 1000.0;
    
            for (int i = 0; i < channelCount; ++i) {
                if (pos + 8 > len) break;
                double value;
                memcpy(&value, pData + pos, 8); /* 读取8字节通道值 */
                pos += 8;
    
                channelData[i].append(QPointF(elapsed, value));  /* 添加数据点到通道 */
                if (channelData[i].size() > 2000)
                    channelData[i].removeFirst();
            }
        }
    }