如何在 Qt 中使用周立功 USB 轉 CAN 卡
文章目錄
- 如何在 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.user1 directory, 12 files
ZLG_CAN
文件夾中文件如下所示:
將 kerneldlls
文件夾和 zlgcan.dll
文件放入 debug
目錄,如下圖所示:
做好上述準備之后,導入庫文件,按照如下所示操作進行:
-
鼠標在項目文件上右鍵選擇 添加庫:
-
選擇 外部庫 后點擊下一步:
-
然后按照下圖所示進行配置(庫文件為
.lib
),配置完成后點擊下一步即可: -
完成后,會出現如下界面,將在
.pro
文件中添加內容:
或者也可以自行在 .pro
文件中添加如下所示的代碼進行導入:
win32: LIBS += -L$$PWD/ZLG_CAN/ -lzlgcanINCLUDEPATH += $$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_OBJECTpublic: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_OBJECTpublic: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博客
-
Gitee:劉佳豪 (liu-jiahaohappy) - Gitee.com
-
GitHub:Jiahao-Liu29 (github.com)