一、前言
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
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口
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 }
5)使用函式hid_read/hid_write讀寫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 }
6)讀取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 }
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事件
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 }
2)繼承QWidget的nativeEvent事件,原型如下
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
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 }
3)檢測到指定的pid、vid後,重新開啟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 }
4)執行緒中執行開啟操作,建立新的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 }
5)此時可繼續讀寫usb口