Qt usb通訊

涼天滿月發表於2021-10-11

一、前言

  Qt通訊方式有很多,如Tcp/Ip、串列埠等,但對Usb通訊支援較弱,此篇主要描述Qt與plc裝置通過usb進行通訊的解決方法;

  開發環境:Qt5.5、VS2013

  優勢:支援熱插拔usb線

二、實現

  1、採用執行緒,通過hidapi方式讀寫usb資訊

  1)hidapi原始碼下載地址:https://github.com/signal11/hidapi

  2)定義usb描述符

          hid_device *m_Handle;

  3)執行緒中,m_Handle預設為空,定時1秒檢測是否有接入usb

Qt usb通訊
 1 void SerialThread::run()
 2 {
 3     while(m_IsRun)
 4     {
 5         m_Mutex.lock();
 6         if(NULL == m_Handle)
 7             openUsb();
 8 
 9         if(m_StartSend)
10             sendData();
11         m_Mutex.unlock();
12 
13         if(NULL == m_Handle)
14             msleep(1000);
15         else
16             exec();
17     }
18 }
執行緒讀寫

  4)根據指定pid、vid,開啟usb口

Qt usb通訊
 1 void SerialThread::openUsb()
 2 {
 3     int result = hid_init();
 4 
 5     if(0 != result)
 6         emit funcSig(SERIAL_FIND, QVariantList() << 1);
 7     else
 8     {
 9         //開啟pid=0x1FC9,vid=0x00A2的usb
10         m_Handle = hid_open(0x1FC9, 0x00A2, NULL);
11         if(NULL == m_Handle)
12         {
13             //判斷是否第一次open,第一次需報錯
14             if(m_IsSendError)
15             {
16                 m_IsSendError = false;
17                 emit funcSig(SERIAL_OPEN, QVariantList() << 1);
18             }
19         }
20         else
21         {
22             hid_set_nonblocking(m_Handle, 1);//非阻塞方式
23         }
24     }
25 }
開啟usb

  5)使用函式hid_read/hid_write讀寫usb口,例如:寫入開始測試

Qt usb通訊
 1 void SerialThread::sendData()
 2 {
 3     int len = 0;
 4     int write = PACKET_LEN + 1;
 5     unsigned char buf[PACKET_LEN + 2] = {0};
 6 
 7     buf[0] = 0x00;
 8 
 9     buf[1] = 0xeb;
10     buf[2] = 0x90;
11     buf[3] = 0x02;
12     buf[4] = 0xff;
13 
14     buf[5] = 0xff;
15     buf[6] = 0x03;
16     buf[7] = 0xff;
17     buf[8] = 0x00;
18 
19     m_StartSend = false;
20     len = hid_write(m_Handle, buf, write);
21     if(write == len)
22         emit funcSig(SERIAL_SEND, QVariantList() << 0);
23     else
24         emit funcSig(SERIAL_SEND, QVariantList() << 1);
25 }
寫usb口

  6)讀取usb口資料

Qt usb通訊
 1 void SerialThread::readUsb()
 2 {
 3     unsigned char buf[ONE_PACKET_LEN + 1] = {0};
 4 
 5     //讀取usb資料
 6     int len = hid_read(m_Handle, buf, ONE_PACKET_LEN);
 7     if(len > 0)
 8     {
 9         //存入快取
10         for(int index = 0;index < len;index++)
11             m_ByteArray.append(buf[index]);
12         //長度大於等於一條指令長度時,進行解析
13         if(m_ByteArray.length() >= ONE_PACKET_LEN)
14         {
15             parseData();
16             m_ByteArray.remove(0, ONE_PACKET_LEN);
17         }
18     }
19 }
讀取usb

 

  2、解決粘包

  1)讀取到的資料,先存在m_ByteArray中

  2)當m_ByteArray的長度大於等於一條指令長度時進行解析

  3)解析時注意先把char轉為16進位制,再進行數值提取,如下提取第5為資料

  QByteArray(1, bytes.at(4)).toHex().toInt(&ok, 16);

 

  3、usb熱插拔

  1)QWidget物件中註冊usb事件

Qt usb通訊
 1 void Widget::registerDevice()
 2 {
 3     const GUID GUID_DEVINTERFACE_LIST[] = {
 4         { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } }, //USB裝置的GUID
 5         { 0x53f56307, 0xb6bf, 0x11d0, { 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b } }};
 6 
 7     HDEVNOTIFY hDevNotify;
 8     DEV_BROADCAST_DEVICEINTERFACE NotifacationFiler;
 9     ZeroMemory(&NotifacationFiler,sizeof(DEV_BROADCAST_DEVICEINTERFACE));
10     NotifacationFiler.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
11     NotifacationFiler.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
12 
13     for (int i = 0; i < sizeof(GUID_DEVINTERFACE_LIST)/sizeof(GUID); i++)
14     {
15         NotifacationFiler.dbcc_classguid = GUID_DEVINTERFACE_LIST[i];
16         hDevNotify = RegisterDeviceNotification((HANDLE)this->winId(), &NotifacationFiler, DEVICE_NOTIFY_WINDOW_HANDLE);
17         if (!hDevNotify)
18             qCritical() << QStringLiteral("註冊失敗!");
19     }
20 }
註冊Usb事件

  2)繼承QWidget的nativeEvent事件,原型如下

        bool nativeEvent(const QByteArray &eventType, void *message, long *result);

Qt usb通訊
 1 bool Widget::nativeEvent(const QByteArray &eventType, void *message, long *result)
 2 {
 3     Q_UNUSED(eventType);
 4     Q_UNUSED(result);
 5 
 6     MSG *msg = reinterpret_cast<MSG *>(message);
 7 
 8     int msgType = msg->message;
 9     if (WM_DEVICECHANGE == msgType)
10     {
11         PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)msg->lParam;
12         switch (msg->wParam)
13         {
14         case DBT_DEVICEARRIVAL:
15         {
16             if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
17             {
18                 PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
19                 if (0 == lpdbv->dbcv_flags)
20                     m_TipQlb->setText("已檢測到USB裝置插入");
21                 //else if (DBTF_MEDIA == lpdbv->dbcv_flags)
22                     //qDebug() << "CD_Arrived.";
23             }
24             else if (DBT_DEVTYP_DEVICEINTERFACE == lpdb->dbch_devicetype)
25             {
26                 PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
27                 QString name = QString::fromWCharArray(pDevInf->dbcc_name);
28 
29                 checkUsb(name);
30             }
31         }
32             break;
33         case DBT_DEVICEREMOVECOMPLETE:
34             if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
35             {
36                 PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
37                 if (0 == lpdbv->dbcv_flags)
38                     m_TipQlb->setText("USB裝置已拔出");
39 
40                 if (DBTF_MEDIA == lpdbv->dbcv_flags)
41                     m_TipQlb->setText("CD_Removed.");
42             }
43             break;
44         }
45     }
46 
47     return false;
48 }
插拔Usb檢測

        3)檢測到指定的pid、vid後,重新開啟usb

Qt usb通訊
 1 void Widget::checkUsb(const QString &name)
 2 {
 3     int errorCode = 0;
 4 
 5     if (name.contains("USB#"))
 6     {
 7         QStringList listAll = name.split('#');
 8         QStringList listID = listAll.at(1).split('&');
 9         QString vid = listID.at(0).right(4);
10         QString pid = listID.at(1).right(4);
11         if(0 == vid.compare("1FC9", Qt::CaseInsensitive) &&
12            0 == pid.compare("00A2", Qt::CaseInsensitive))
13         {
14             errorCode = m_SerialThread->resetOpen();
15             if(0 == errorCode)
16                 m_TipQlb->setText("重新開啟USB,成功");
17             else if(1000 == errorCode)
18                 m_TipQlb->setText("初始化USB,失敗");
19             else
20                 m_TipQlb->setText("重新開啟USB,失敗");
21         }
22     }
23 }
檢查插入的usb

  4)執行緒中執行開啟操作,建立新的usb描述符

Qt usb通訊
 1 int SerialThread::resetOpen()
 2 {
 3     int errorCode = 0;
 4     int result = hid_init();
 5 
 6     if(0 != result)
 7         errorCode = 1000;
 8     else
 9     {
10         m_Handle = hid_open(0x1FC9, 0x00A2, NULL);
11         if(NULL == m_Handle)
12             errorCode = 1001;
13         else
14             hid_set_nonblocking(m_Handle, 1);
15     }
16 
17     return errorCode;
18 }
重新開啟usb

  5)此時可繼續讀寫usb口

相關文章