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

发布于:2025-09-06 ⋅ 阅读:(22) ⋅ 点赞:(0)

如何在 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 产品概述 中所示,首先需要安装相应的设备驱动,驱动列表链接为:驱动下载

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

  • https://manual.zlg.cn/web/#/146

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

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

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

  • https://manual.zlg.cn/web/#/152?page_id=5332

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

在这里插入图片描述

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

三、使用

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

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

在这里插入图片描述

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

.
|-- 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 文件中添加如下所示的代码进行导入:

win32: LIBS += -L$$PWD/ZLG_CAN/ -lzlgcan

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

这里需要注意路径的问题,$$PWD 路径为 .pro 文件所在的目录。

上述两种方法用一种即可!!!效果是一样的!!!

接下来就是代码部分:

  • mainwindows.h

    #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

    #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

欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!
另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。


网站公告

今日签到

点亮在社区的每一天
去签到