如何在Qt中使用周立功USB转CAN卡

如何在 Qt 中使用周立功 USB 转 CAN 卡

文章目录

一、简介

最近在工程中用到了周立功的 USBCAN 卡,需求是要通过上位机进行通信,因此有了这篇文章。

有关 周立功 USBCAN 的内容在官网中: USB接口CAN卡-广州致远电子股份有限公司

其中有多种型号,我所使用的是 USBCAN-2E-U,如下所示:

上述图片来源于ZLG致远电子-广州致远电子股份有限公司如构成侵权,请联系本人删除!!!

因此,本文是基于 USBCAN-2E-U 的开发示例,适合于有一定 Qt 经验基础的朋友!

本文开发环境如下所示:

  • USBCAN-2E-U
  • Windows 10 x64
  • Qt 5.12.3

二、准备工作

有关 USBCAN-2E-U 的文档在 USBCAN-2E-U 产品概述 中所示,首先需要安装相应的设备驱动,驱动列表链接为:驱动下载

如若点击无法跳转,可自行复制以下链接进行跳转

安装驱动过程作者在这里就不赘述了,在前面提到的 ++产品概述++ 中有相关文档,不清楚的朋友可以自行阅读。

有关库函数需要在 函数库/例程下载 中进行下载,其中还有部分例程可以做参考。

如若点击无法跳转,可自行复制以下链接进行跳转

下载解压后得到如下所示文件:

  • zlgcan_x64 :对应 ++64位++ 操作系统所使用的库文件。
  • zlgcan_x86 :对应 ++32位++ 操作系统所使用的库文件。
  • 使用手册 :使用手册中包含使用方法及 API 解释。

三、使用

我这里以 zlgcan_x64 进行开发!!! x86 同理。

新建 Qt 项目,我这里做了一个简单的 ui 用于后面的测试,如下所示:

.lib.h 文件放入工程目录(.pro 所在的目录)下,树状图如下所示:

shell 复制代码
.
|-- ZLG_CAN
|   |-- canframe.h
|   |-- config.h
|   |-- typedef.h
|   |-- zlgcan.h
|   `-- zlgcan.lib
|-- main.cpp
|-- mainwindow.cpp
|-- mainwindow.cpp.autosave
|-- mainwindow.h
|-- mainwindow.ui
|-- zlglibTest.pro
`-- zlglibTest.pro.user

1 directory, 12 files

ZLG_CAN 文件夹中文件如下所示:

kerneldlls 文件夹和 zlgcan.dll 文件放入 debug 目录,如下图所示:

做好上述准备之后,导入库文件,按照如下所示操作进行:

  1. 鼠标在项目文件上右键选择 添加库:

  2. 选择 外部库 后点击下一步:

  3. 然后按照下图所示进行配置(库文件为 .lib ),配置完成后点击下一步即可:

  4. 完成后,会出现如下界面,将在 .pro 文件中添加内容:

或者也可以自行在 .pro 文件中添加如下所示的代码进行导入:

cpp 复制代码
win32: LIBS += -L$$PWD/ZLG_CAN/ -lzlgcan

INCLUDEPATH += $$PWD/ZLG_CAN
DEPENDPATH += $$PWD/ZLG_CAN

这里需要注意路径的问题,$$PWD 路径为 .pro 文件所在的目录。
上述两种方法用一种即可!!!效果是一样的!!!

接下来就是代码部分:

  • mainwindows.h

    cpp 复制代码
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QMessageBox>
    #include <QDateTime>
    #include <QThread>
    
    #include "zlgcan.h"
    
    namespace Ui {
    class MainWindow;
    }
    
    typedef struct {
        DEVICE_HANDLE _dhandle;         // 驱动设备接口
        CHANNEL_HANDLE _chHandle;    // 通道接口
    }zlgStruct;
    
    enum OutPutLevel
    {
        OUT_INFO = 0,
        OUT_SUCCESS,
        OUT_WARN,
        OUT_ERROR,
    };
    
    // uint8_t 数组 转 QString 十六进制
    // eg: 00 01 02 03 4F
    // upper 参数为 True 则输出大写十六进制,反之则小写
    static QString arrayToHexString(const uint8_t* pdata, uint16_t length, bool upper) {
        QString hexStr;
    
        QByteArray byteArray(reinterpret_cast<const char *>(pdata), length);
    
        hexStr = byteArray.toHex();
    
        if (upper)
            hexStr = hexStr.toUpper();
    
        for (int i = 2; i < hexStr.length(); i += 3) {
            hexStr.insert(i, ' ');
        }
    
        return hexStr;
    }
    
    class ZlgControlDev : public QObject
    {
        Q_OBJECT
    
    public:
        explicit ZlgControlDev(QObject *parent = nullptr);
        ~ZlgControlDev() {
            if (zlgGetStatus()) {
                ZCAN_CloseDevice(_zlgStr._dhandle);
            }
        }
    
        void zlgEntry(void) {
            emit zlgControlLogSignal(QString("[ZlgControlDev::zlgEntry] threadId: 0x%1")
                                         .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                     OUT_INFO);
        }
        void zlgConnect(void);
        bool zlgDisConnect(void) {
            if (zlgGetStatus()) {
                ZCAN_CloseDevice(_zlgStr._dhandle);
                return true;
            }
            return false;
        }
    
        void zlgSendData(QByteArray data);
        void zlgRecvData(void);
    
        bool zlgGetStatus(void) {
            return this->_zlgStr._dhandle == Q_NULLPTR ? false : true;
        }
    
    private:
        zlgStruct _zlgStr;
    
    signals:
        void zlgControlLogSignal(QString log, OutPutLevel level);
        void zlgConnectSignal(bool status);
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    private slots:
        void on_openButton_released();
    
        void on_sendButton_released();
    
    private:
        Ui::MainWindow *ui;
    
        QThread* _mainThreadPtr;
        ZlgControlDev* _zlgDevPtr;
    
        QString praseText(QString text, OutPutLevel level)
        {
            QString str;
    
            // 加入时间信息
            QString timeSlamp = QDateTime::currentDateTime().toString("[yyyy-MM-dd HH:mm:ss] > ");
            str.append(QString("<font color = blue>%1</font>").arg(timeSlamp));
    
            switch (level) {
            case OUT_INFO:
                str.append(QString("<font color = black>%1</font>").arg(text));
                break;
            case OUT_SUCCESS:
                str.append(QString("<font color = green>%1</font>").arg(text));
                break;
            case OUT_WARN:
                str.append(QString("<font color = orange>%1</font>").arg(text));
                break;
            case OUT_ERROR:
                str.append(QString("<font color = red>%1</font>").arg(text));
            }
    
            return str;
        }
    
    signals:
        void startConnectSignal(void);
        void startZlgRecvSignal(void);
    };
    
    #endif // MAINWINDOW_H
  • mainwindows.cpp

    cpp 复制代码
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        ui->textEdit->append(praseText(QString("[MainWindow::MainWindow] threadId: 0x%1")
                                           .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                       OUT_INFO));
    
        // 注册类型
        qRegisterMetaType<QTextCursor>("QTextCursor");
        qRegisterMetaType<OutPutLevel>("OutPutLevel");
        qRegisterMetaType<QByteArray>("QByteArray");
    
        _mainThreadPtr = new QThread();
        _zlgDevPtr = new ZlgControlDev();
    
        _zlgDevPtr->moveToThread(_mainThreadPtr);
        connect(_mainThreadPtr, &QThread::started, _zlgDevPtr, &ZlgControlDev::zlgEntry);
        connect(this, &MainWindow::startConnectSignal, _zlgDevPtr, &ZlgControlDev::zlgConnect);
        connect(this, &MainWindow::startZlgRecvSignal, _zlgDevPtr, &ZlgControlDev::zlgRecvData);
        connect(_zlgDevPtr, &ZlgControlDev::zlgControlLogSignal, this, [=](QString log, OutPutLevel level) {
            ui->textEdit->append(praseText(log, level));
        }, Qt::DirectConnection);
        connect(_zlgDevPtr, &ZlgControlDev::zlgConnectSignal, [=](bool status) {
            if (!status) {
                ui->textEdit->append(praseText("ZLG_USB_CAN 设备连接失败!", OUT_ERROR));
                _mainThreadPtr->requestInterruption();
                _zlgDevPtr->zlgDisConnect();
            } else {
                ui->textEdit->append(praseText("ZLG_USB_CAN 设备连接成功!", OUT_SUCCESS));
                ui->openButton->setText("断开连接");
                emit startZlgRecvSignal();
            }
        });
    
        _mainThreadPtr->start();
    }
    
    MainWindow::~MainWindow()
    {
        _mainThreadPtr->requestInterruption();
        _mainThreadPtr->quit();
        _mainThreadPtr->wait();
    
        delete _zlgDevPtr;
    
        delete ui;
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : 打开/关闭 设备槽函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 11:18:41
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void MainWindow::on_openButton_released()
    {
        if (!QString::compare(ui->openButton->text(), "打开设备")) {
    
            emit startConnectSignal();
        } else {
            _mainThreadPtr->requestInterruption();
            if (_zlgDevPtr->zlgDisConnect()) {
                ui->textEdit->append(praseText("ZLG_USB_CAN 设备断开连接成功!", OUT_SUCCESS));
            } else {
                ui->textEdit->append(praseText("ZLG_USB_CAN 设备断开连接失败!", OUT_ERROR));
            }
            ui->openButton->setText("打开设备");
        }
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : 模拟报文发送槽函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 14:23:39
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void MainWindow::on_sendButton_released()
    {
        if (!_zlgDevPtr->zlgGetStatus()) {
            ui->textEdit->append(praseText("ZLG_USB_CAN 设备未打开,打开后重试!", OUT_ERROR));
            return;
        }
    
        QByteArray data;
        data.append(static_cast<char>(0x00));
        data.append(static_cast<char>(0x01));
    
        _zlgDevPtr->zlgSendData(data);
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : 构造函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 14:34:20
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    ZlgControlDev::ZlgControlDev(QObject *parent) :
        QObject(parent)
    {
        _zlgStr._dhandle = Q_NULLPTR;
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : zlg 设备连接线程入口函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 14:35:04
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void ZlgControlDev::zlgConnect()
    {
        emit zlgControlLogSignal(QString("[ZlgControlDev::zlgConnect] threadId: 0x%1")
                                     .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                 OUT_INFO);
    
        // 打开设备,获取设备句柄
        _zlgStr._dhandle = ZCAN_OpenDevice(ZCAN_USBCAN_2E_U, 0, 0);
        if (_zlgStr._dhandle == Q_NULLPTR) {
            emit zlgControlLogSignal("[ZCAN_OpenDevice] 打开设备失败!", OUT_ERROR);
            emit zlgConnectSignal(false);
            return;
        }
    
        emit zlgControlLogSignal("[ZCAN_OpenDevice] 打开设备成功!", OUT_SUCCESS);
    
        // 设置波特率
        if (ZCAN_SetValue(_zlgStr._dhandle,
                          QString("0/baud_rate").toStdString().c_str(),
                          QString("500000").toStdString().c_str()) != STATUS_OK) {
            emit zlgControlLogSignal("[ZCAN_SetValue] 设置波特率失败!", OUT_ERROR);
            emit zlgConnectSignal(false);
            return;
        }
    
        emit zlgControlLogSignal("[ZCAN_SetValue] 设置波特率成功!", OUT_SUCCESS);
        
        // 配置通道参数
        ZCAN_CHANNEL_INIT_CONFIG cfg;
        memset(&cfg, 0, sizeof(cfg));
        cfg.can_type        = TYPE_CAN;     // 设备为 CAN 设备
        cfg.can.filter      = 0;            // 双滤波
        cfg.can.mode        = 0;            // 正常模式
        cfg.can.acc_code    = 0;            // 帧过滤验收码
        cfg.can.acc_mask    = 0xFFFFFFFF;   // 帧过滤屏蔽码
        // 初始化 CAN 通道
        _zlgStr._chHandle = ZCAN_InitCAN(_zlgStr._dhandle,
                                                static_cast<UINT>(0),
                                                &cfg);
        if (_zlgStr._chHandle == Q_NULLPTR) {
            emit zlgControlLogSignal("[ZCAN_InitCAN] 初始化CAN通道 [0] 失败!",
                                     OUT_ERROR);
            emit zlgConnectSignal(false);
            return;
        }
    
        emit zlgControlLogSignal("[ZCAN_InitCAN] 初始化CAN通道 [0] 成功!",
                                 OUT_SUCCESS);
    
        // 启动通道
        if (ZCAN_StartCAN(_zlgStr._chHandle) != STATUS_OK) {
            emit zlgControlLogSignal("[ZCAN_StartCAN] 启用CAN通道 [0] 失败!",
                                     OUT_ERROR);
            emit zlgConnectSignal(false);
            return;
        }
    
        emit zlgControlLogSignal("[ZCAN_StartCAN] 启用CAN通道 [0] 成功!",
                                 OUT_SUCCESS);
    
        emit zlgConnectSignal(true);
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : zlg 接收数据函数
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 15:54:59
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void ZlgControlDev::zlgRecvData()
    {
        if (!zlgGetStatus()) {
            return;
        }
        emit zlgControlLogSignal(QString("[ZlgControlDev::zlgRecvData] threadId: 0x%1 start!!!")
                                     .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                 OUT_INFO);
        while(!QThread::currentThread()->isInterruptionRequested()) {
            /* 获取收到的报文数(确保缓冲区有数据) */
            uint32_t size = ZCAN_GetReceiveNum(this->_zlgStr._chHandle, 0);
            if (size) {
                ZCAN_Receive_Data recvData;
                /* 接收数据 */
                ZCAN_Receive(this->_zlgStr._chHandle, &recvData, 1);
    
                QString logStr("[ZlgControlDev::zlgRecvData] %1 %2 %3x Rx d %4 %5");
    
                QString hexStr = arrayToHexString(reinterpret_cast<const uint8_t*>(&recvData.frame.data[0]), static_cast<uint16_t>(recvData.frame.can_dlc), false);
    
                logStr = logStr.arg(recvData.timestamp)
                             .arg(1)
                             .arg(QString::number(static_cast<uint32_t>(recvData.frame.can_id) & static_cast<uint32_t>(0x1FFFFFFF), 16))
                             .arg(recvData.frame.can_dlc)
                             .arg(hexStr);
    
                emit zlgControlLogSignal(logStr,
                                         OUT_INFO);
            }
            QThread::msleep(10); // 延迟 10ms
        }
        emit zlgControlLogSignal(QString("[ZlgControlDev::zlgRecvData] threadId: 0x%1 end!!!")
                                     .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                 OUT_INFO);
    }
    
    /*!
     *  @File        : mainwindow.cpp
     *  @Brief       : None
     *  @Details     : None
     *  @Param       : void
     *  @Return      : void
     *  @Author      : Liu Jiahao
     *  @Date        : 2025-09-04 15:52:29
     *  @Version     : v1.1
     *  @Copyright   : Copyright By Liu Jiahao, All Rights Reserved
     *
     */
    void ZlgControlDev::zlgSendData(QByteArray data)
    {
        if (!zlgGetStatus())
            return;
    
        emit zlgControlLogSignal(QString("[ZlgControlDev::zlgSendData] threadId: 0x%1")
                                     .arg(QString::number(reinterpret_cast<quintptr>(QThread::currentThreadId()), 16).toUpper()),
                                 OUT_INFO);
    
        ZCAN_Transmit_Data frame;
        memset(&frame, 0, sizeof(frame));
    
        // 写入数据
        frame.transmit_type = 2;
        frame.frame.can_id = static_cast<canid_t>(0x51801);
        frame.frame.can_dlc = static_cast<BYTE>(data.size());
        for (int index = 0; index < data.size(); index++) {
            frame.frame.data[index] = static_cast<BYTE>(data.at(index));
        }
    
        // 发送数据
        UINT ret = ZCAN_Transmit(this->_zlgStr._chHandle, &frame, 1);
        if (ret != STATUS_OK)
        {
            emit zlgControlLogSignal("通道 [0] 发送报文失败!", OUT_ERROR);
            return;
        }
        emit zlgControlLogSignal("通道 [0] 发送报文成功!", OUT_SUCCESS);
    }

大致思路就是,开启一个线程用于接收数据,发送与接收不能在同一个线程中,理由很简单,接收线程一直在 while 循环,则无法继续操作这个线程。其实也可以把发送单独放一个线程中,这里我就不做过多赘述,代码仅供参考,实现方式多样,可自行斟酌。

值得注意的是,我在发送数据的时候,设置 frame.transmit_type = 2; 其含义在 zlg 官方 API 文档中也有描述:

具体解释还请查看 API 文档。


四、运行效果

软件运行起来之后效果如下所示:


五、写在最后

本文介绍了 如何在 Qt 中使用 周立功 USBCAN

欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!

另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript