如何在 Qt 中使用周立功 USB 转 CAN 卡
一、简介
最近在工程中用到了周立功的 USB
转 CAN
卡,需求是要通过上位机进行通信,因此有了这篇文章。
有关 周立功 USB
转 CAN
卡 的内容在官网中: 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
目录,如下图所示:
做好上述准备之后,导入库文件,按照如下所示操作进行:
鼠标在项目文件上右键选择 添加库:
选择 外部库 后点击下一步:
然后按照下图所示进行配置(库文件为
.lib
),配置完成后点击下一步即可:完成后,会出现如下界面,将在
.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
中使用 周立功 USB
转 CAN
盒。
欢迎广大读者提出问题以及修改意见,本人看到后会给予回应,欢迎留言,后续会逐步进行开源!!!
另外,由于文章是作者手打的文字,有些地方可能文字会出错,望谅解,也可私信联系我,我对其进行更改。
个人CSDN账号:刘梓谦_-CSDN博客
GitHub:Jiahao-Liu29 (github.com)