|
Windows 主機(jī)端與自定義 USB HID 設(shè)備通信詳解
說(shuō)明:
- 以下結(jié)論都是基于 Windows XP 系統(tǒng)所得出的,不保證在其他系統(tǒng)的適用性。
- 在此討論的是 HID 自定義設(shè)備,對(duì)于標(biāo)準(zhǔn)設(shè)備,譬如 USB 鼠標(biāo)和鍵盤(pán),由于操作系統(tǒng)對(duì)
其獨(dú)占,許多操作未必能正確執(zhí)行。
1 . 所使用的典型 Windows API
CreateFile
ReadFile
WriteFile
以下函數(shù)是 DDK 的內(nèi)容:
HidD_SetFeature
HidD_GetFeature
HidD_SetOutputReport
HidD_GetInputReport
其中, CreateFile 用于打開(kāi)設(shè)備; ReadFile 、 HidD_GetFeature 、 HidD_GetInputRep
ort 用于設(shè)備到主機(jī)方向的數(shù)據(jù)通信; WriteFile 、 HidD_SetFeature 、 HidD_SetOutput
Report 用于主機(jī)到設(shè)備方向的數(shù)據(jù)通信。鑒于實(shí)際應(yīng)用,后文主要討論 CreateFile , Write
File , ReadFile , HidD_SetFeature 四個(gè)函數(shù),明白了這四個(gè)函數(shù),其它的可以類(lèi)推之。
2 . 幾個(gè)常見(jiàn)錯(cuò)誤
當(dāng)使用以上 API 時(shí),如果操作失敗,調(diào)用 GetLastError() 會(huì)得到以下常見(jiàn)錯(cuò)誤:
6 : 句柄無(wú)效
23 : 數(shù)據(jù)錯(cuò)誤(循環(huán)冗余碼檢查)
87 : 參數(shù)錯(cuò)誤
1784 : 用戶提供的 buffer 無(wú)效
后文將會(huì)詳細(xì)說(shuō)明這些錯(cuò)誤情況。
3. 主機(jī)端設(shè)備枚舉程序流程
4. 函數(shù)使用說(shuō)明
CreateFile(devDetail->DevicePath, // 設(shè)備路徑
GENERIC_READ | GENERIC_WRITE, // 訪問(wèn)方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享模式
NULL,
OPEN_EXISTING, // 文件不存在時(shí),返回失敗
FILE_FLAG_OVERLAPPED, // 以重疊(異步)模式打
開(kāi)
NULL);
在這里, CreateFile 用于打開(kāi) HID 設(shè)備,其中設(shè)備路徑通過(guò)函數(shù) SetupDiGetInterfaceDe
viceDetail 取得。 CreateFile 有以下幾點(diǎn)需要注意:
- 訪問(wèn)方式: 如果是系統(tǒng)獨(dú)占設(shè)備,例如鼠標(biāo)、鍵盤(pán)等等,應(yīng)將此參數(shù)設(shè)置為 0 ,否則后續(xù)
函數(shù)操作將失。ㄆ┤ HidD_GetAttributes );也就是說(shuō),不能對(duì)獨(dú)占設(shè)備進(jìn)行除了查詢以
外的任何操作,所以能夠使用的函數(shù)也是很有限的,下文的一些函數(shù)并不一定適合這些設(shè)備。在
此順便列出 MSDN 上關(guān)于此參數(shù)的說(shuō)明:
If this parameter is zero, the application can query file and device attributes
without accessing the device. This is useful if an application wants to determin
e the size of a floppy disk drive and the formats it supports without requiring
a floppy in the drive. It can also be used to test for the file's or directory's ex
istence without opening it for read or write access。
- 重疊(異步)模式:此參數(shù)并不會(huì)在此處表現(xiàn)出明顯的意義,它主要是對(duì)后續(xù)的 WriteFil
e , ReadFile 有影響。如果這里設(shè)置為重疊(異步)模式,那么在使用 WriteFile , ReadFi
le 時(shí)也應(yīng)該使用重疊(異步)模式,反之亦然。這首先要求 WriteFile , ReadFile 的最后一
個(gè)參數(shù)不能為空(NULL )。否則,便會(huì)返回 87 (參數(shù)錯(cuò)誤)錯(cuò)誤號(hào)。當(dāng)然, 87 號(hào)錯(cuò)誤并
不代表就是此參數(shù)不正確,更多的信息將在具體講述這兩個(gè)函數(shù)時(shí)指出。此參數(shù)為 0 時(shí),代表
同步模式,即 WriteFile , ReadFile 操作會(huì)在數(shù)據(jù)處理完成之后才返回,否則阻塞在函數(shù)內(nèi)
部。
ReadFile(hDev, | // 設(shè)備句柄,即 CreateFile 的返回值 | recvBuffer, | // 用于接收數(shù)據(jù)的 buffer | IN_REPORT_LEN, | // 要讀取數(shù)據(jù)的長(zhǎng)度 | &recvBytes, | // 實(shí)際收到的數(shù)據(jù)的字節(jié)數(shù) | &ol); | // 異步模式 | 在這里, ReadFile 用于讀取 HID 設(shè)備通過(guò)中斷 IN 傳輸發(fā)來(lái)的輸入報(bào)告 | 。有以下幾點(diǎn)要注意: |
1 、 ReadFile 的調(diào)用不會(huì)引起設(shè)備的任何反應(yīng),即 HID 設(shè)備與主機(jī)之間的中斷 IN 傳輸不與 R
eadFile 打交道。實(shí)際上主機(jī)會(huì)在最大間隔時(shí)間(由設(shè)備的端點(diǎn)描述符來(lái)指定)內(nèi)輪詢?cè)O(shè)備,發(fā)
出中斷 IN 傳輸?shù)恼?qǐng)求。“讀取” 即意味著從某個(gè) buffer 里面取回?cái)?shù)據(jù),實(shí)際上這個(gè) buffer 就
是 HID 設(shè)備驅(qū)動(dòng)中的 buffer 。這個(gè) buffer 的大小可以通過(guò) HidD_SetNumInputBuffer
s 來(lái)改變。在 XP 上缺省值是 32 (個(gè)報(bào)告)。
2 、讀取的數(shù)據(jù)對(duì)象是輸入報(bào)告,也即通過(guò)中斷輸入管道傳入的數(shù)據(jù)。所以,如果設(shè)備不支持中
斷 IN 傳輸,那么是無(wú)法使用此函數(shù)來(lái)得到預(yù)期結(jié)果的。實(shí)際上這種情況不可能在 HID 中出現(xiàn),
因?yàn)閰f(xié)議指明了至少要有一個(gè)中斷 IN 端點(diǎn)。
3 、 IN_REPORT_LEN 代表要讀取的數(shù)據(jù)的長(zhǎng)度(實(shí)際的數(shù)據(jù)正文 + 一個(gè) byte 的報(bào)告 ID ),
這里是一個(gè)常數(shù),主要是因?yàn)樵O(shè)備固件的信息我是完全知道的,當(dāng)然知道要讀取多少數(shù)據(jù)(也就
是報(bào)告的長(zhǎng)度);不過(guò)也可以通過(guò)另外的函數(shù)(HidD_GetPreparsedData )來(lái)事先取得報(bào)告
的長(zhǎng)度,這里不做詳細(xì)討論。因?yàn)楹茈y想象在不了解固件信息的情況下來(lái)做自定義設(shè)備的 HI
D 通信,在實(shí)際應(yīng)用中一般來(lái)說(shuō)就是固件與 PC 程序匹配著來(lái)開(kāi)發(fā)。此參數(shù)如果設(shè)置過(guò)大,不
會(huì)有實(shí)質(zhì)性的錯(cuò)誤,在 recvBytes 參數(shù)中會(huì)輸出實(shí)際讀到的長(zhǎng)度;如果設(shè)置過(guò)小,即小于報(bào)告
的長(zhǎng)度,會(huì)返回 1784 號(hào)錯(cuò)誤(用戶提供的 buffer 無(wú)效)。
4 、關(guān)于異步模式。前面已經(jīng)提過(guò),此參數(shù)的設(shè)置必須與 CreateFile 時(shí)的設(shè)置相對(duì)應(yīng),否則會(huì)
返回 87 號(hào)錯(cuò)誤(參數(shù)錯(cuò)誤)。如果不需要異步模式,此參數(shù)需置為 NULL 。在這種情況下, R
eadFile 會(huì)一直等待直到數(shù)據(jù)讀取成功,所以會(huì)阻塞住程序的當(dāng)前過(guò)程。
WriteFile(hDev, | // 設(shè)備句柄,即 CreateFile 的返回值 | reportBuf, | // 存有待發(fā)送數(shù)據(jù)的 buffer | OUT_REPORT_LEN, | // 待發(fā)送數(shù)據(jù)的長(zhǎng)度 | &sendBytes, | // 實(shí)際收到的數(shù)據(jù)的字節(jié)數(shù) | &ol); | // 異步模式 |
在這里, WriteFile 用于傳輸一個(gè)輸出報(bào)告 給 HID 設(shè)備。有以下幾點(diǎn)要注意:
1、 與 ReadFile 不同, WriteFile 函數(shù)被調(diào)用后,雖然也是經(jīng)過(guò)驅(qū)動(dòng)程序,但是最終會(huì)反映
到設(shè)備中。也就是說(shuō),調(diào)用 WriteFile 后,設(shè)備會(huì)接收到輸出報(bào)告的請(qǐng)求。如果設(shè)備使用了中
斷 OUT 傳輸,則 WriteFile 會(huì)通過(guò)中斷 OUT 管道來(lái)進(jìn)行傳輸;否則會(huì)使用 SetReport 請(qǐng)求
通過(guò)控制管道來(lái)傳輸。
2、 OUT_REPORT_LEN 代表要寫(xiě)入的數(shù)據(jù)長(zhǎng)度(實(shí)際的數(shù)據(jù)正文 + 一個(gè) byte 的報(bào)告 ID )。
如果大于實(shí)際報(bào)告的長(zhǎng)度,則使用實(shí)際報(bào)告長(zhǎng)度;如果小于實(shí)際報(bào)告長(zhǎng)度,會(huì)返回 1784 號(hào)錯(cuò)
誤(用戶提供的 buffer 無(wú)效)。
3、 reportBuf [0] 必須存有待發(fā)送報(bào)告的 ID ,并且此報(bào)告 ID 指示的必須是輸出報(bào)告,否則
會(huì)返回 87 號(hào)錯(cuò)誤(參數(shù)錯(cuò)誤)。這種情況可能容易被程序員忽略,結(jié)果不知錯(cuò)誤號(hào)所反映的是
什么,網(wǎng)上也經(jīng)常有類(lèi)似疑問(wèn)的帖子。順便指出,輸入報(bào)告、輸入報(bào)告、特征報(bào)告這些報(bào)告類(lèi)型,
是反映在 HID 設(shè)備的報(bào)告描述符中。后文將做舉例討論。
4、 關(guān)于異步模式。前面已經(jīng)提過(guò),此參數(shù)的設(shè)置必須與 CreateFile 時(shí)的設(shè)置相對(duì)應(yīng),否則會(huì)
返回 87 號(hào)錯(cuò)誤(參數(shù)錯(cuò)誤)。如果不需要異步模式,此參數(shù)需置為 NULL 。在這種情況下, W
riteFile 會(huì)一直等待直到數(shù)據(jù)讀取成功,所以會(huì)阻塞住程序的當(dāng)前過(guò)程。
HidD_SetFeature(hDev, | // 設(shè)備句柄,即 CreateFile 的返回值 | reportBuf, | // 存有待發(fā)送數(shù)據(jù)的 buffer | FEATURE_REPORT_LEN); | //buffer 的長(zhǎng)度 | HidD_SetOutputReport(hDev, | // 設(shè)備句柄,即 CreateFile 的返回值 | reportBuf, | // 存有待發(fā)送數(shù)據(jù)的 buffer | OUT_REPORT_LEN); | //buffer 的長(zhǎng)度 |
HidD_SetFeature 發(fā)送一個(gè)特征報(bào)告 給設(shè)備, HidD_ SetOutputReport 發(fā)送一個(gè)輸出報(bào)
告 給設(shè)備。注意以下幾點(diǎn):
1、 跟 WriteFile 類(lèi)似,必須在 reportBuf [0] 中指明要發(fā)送的報(bào)告的 ID ,并且和各自適合
的類(lèi)型相對(duì)應(yīng)。也就是說(shuō), HidD_SetFeature 只能發(fā)送特征報(bào)告,因此報(bào)告 ID 必須是特征
報(bào)告的 ID ; HidD_SetOutputReport 只能發(fā)送輸出報(bào)告,因此報(bào)告 ID 只能是輸出報(bào)告的 I
D 。
2、 這兩個(gè)函數(shù)最常返回的錯(cuò)誤代碼是 23 (數(shù)據(jù)錯(cuò)誤)。包括但不僅限于以下情況:
- 報(bào)告 ID 與固件描述的不符。
- 傳入的 buffer 長(zhǎng)度少于固件描述的報(bào)告的長(zhǎng)度。
據(jù)有關(guān)資料反映(非官方文檔),只要是驅(qū)動(dòng)程序?qū)φ?qǐng)求無(wú)反應(yīng),都會(huì)產(chǎn)生此錯(cuò)誤。
5. 常見(jiàn)錯(cuò)誤匯總
- HID ReadFile
- Error Code 6 (handle is invalid)
傳入的句柄無(wú)效
- Error Code 87 ( 參數(shù)錯(cuò)誤 )
很可能是 createfile 時(shí)聲明了異步方式,但是讀取時(shí)按同步讀取。
- Error Code 1784 ( 用戶提供的 buffer 無(wú)效 ):
傳參時(shí)傳入的“讀取 buffer 長(zhǎng)度” 與實(shí)際的報(bào)告長(zhǎng)度不符。
- HID WriteFile
- Error Code 6 (handle is invalid)
傳入的句柄無(wú)效
- Error Code 87 (參數(shù)錯(cuò)誤)
- CreateFile 時(shí)聲明的同步 / 異步方式與實(shí)際調(diào)用 WriteFile 時(shí)傳入的不同。
- 報(bào)告 ID 與固件中定義的不一致(buffer 的首字節(jié)是報(bào)告 ID )
- Error Code 1784 ( 用戶提供的 buffer 無(wú)效 )
傳參時(shí)傳入的“寫(xiě)入 buffer 長(zhǎng)度” 與實(shí)際的報(bào)告長(zhǎng)度不符。
- HidD_SetFeature
- HidD_SetOutputReport
- Error Code 1 (incorrect function)
不支持此函數(shù),很可能是設(shè)備的報(bào)告描述符中未定義這樣的報(bào)告類(lèi)型(輸入、輸出、特征)
- Error Code 6 (handle is invalid)
傳入的句柄無(wú)效
- Error Code 23 (數(shù)據(jù)錯(cuò)誤(循環(huán)冗余碼檢查))
- 報(bào)告 ID 與固件中定義的不相符(buffer 的首字節(jié)是報(bào)告 ID )
- 傳入的 buffer 長(zhǎng)度少于固件定義的報(bào)告長(zhǎng)度(報(bào)告正文 +1byte, 1byte 為報(bào)告 ID )
- 據(jù)相關(guān)資料反映(非官方文檔),只要是驅(qū)動(dòng)程序不接受此請(qǐng)求(對(duì)請(qǐng)求無(wú)反應(yīng)),都會(huì)
產(chǎn)生此錯(cuò)誤
6. 報(bào)告描述符及數(shù)據(jù)通信程序示例
報(bào)告描述符(注意表中的每個(gè)數(shù)據(jù)都占 1 個(gè)字節(jié)):
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
0x05, 0x8c, /* USAGE_PAGE (ST Page) */
0x09, 0x01, /* USAGE (Demo Kit) */
0xa1, 0x01, /* COLLECTION (Application) */
// The Input report
0x09,0x03, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00, 0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x81,0x02, // INPUT (Data,Var,Abs)
// The Output report
0x09,0x04, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00,0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x91,0x02, // OUTPUT (Data,Var,Abs)
0xc0 /* END_COLLECTION */
}; /* CustomHID_ReportDescriptor */
下面用一個(gè)簡(jiǎn)單的示例來(lái)描述 PC 端與 USB HID 設(shè)備進(jìn)行通信的一般方法。
void HIDSampleFunc()
{
HANDLE hDev;
BYTE recvDataBuf[1024];
BYTE reportBuf[1024];
DWORD bytes;
hDev = OpenMyHIDDevice(0); // 打開(kāi)設(shè)備,不使用重疊(異步)方式 ;
if (hDev == INVALID_HANDLE_VALUE){
printf("INVALID_HANDLE_VALUE\n");
return;
}
reportBuf[0] = 0; // 輸出報(bào)告的報(bào)告 ID 是 0
for(int i=0;i<REPORT_COUNT;i++){
reportBuf[i+1]=i+1;
}
if (!WriteFile(hDev, reportBuf, REPORT_COUNT+1, &bytes, NULL)){// 寫(xiě)入數(shù)據(jù)到設(shè)備
printf("write data error! %d\n",GetLastError());
return;
}
if(!ReadFile(hDev, recvDataBuf, REPORT_COUNT+1, &bytes, NULL)){ // 讀取設(shè)備發(fā)給主機(jī)的數(shù)據(jù)
printf("read data error! %d\n",GetLastError());
return;
}
for(int i=0;i<REPORT_COUNT;i++){
printf("0x%02X ",recvDataBuf[i+1]);
}
printf("\n\r");
}
HANDLE OpenMyHIDDevice(int overlapped)
{
HANDLE hidHandle;
GUID hidGuid;
HidD_GetHidGuid(&hidGuid);
HDEVINFO hDevInfo = SetupDiGetClassDevs(&hidGuid,NULL,NULL,(DIGCF_PRESENT |
DIGCF_DEVICEINTERFACE));
if (hDevInfo == INVALID_HANDLE_VALUE)
{
return INVALID_HANDLE_VALUE;
}
SP_DEVICE_INTERFACE_DATA devInfoData;
devInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);
int deviceNo = 0;
SetLastError(NO_ERROR);
while (GetLastError() != ERROR_NO_MORE_ITEMS)
{
if (SetupDiEnumInterfaceDevice (hDevInfo,0,&hidGuid,deviceNo,&devInfoData))
{
ULONG requiredLength = 0;
SetupDiGetInterfaceDeviceDetail(hDevInfo,
&devInfoData,
NULL,
0,
&requiredLength,
NULL);
PSP_INTERFACE_DEVICE_DETAIL_DATA devDetail =
(SP_INTERFACE_DEVICE_DETAIL_DATA*)malloc(requiredLength);
devDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
if(!SetupDiGetInterfaceDeviceDetail(hDevInfo,
&devInfoData,
devDetail,
requiredLength,
NULL,
NULL))
{
free(devDetail);
SetupDiDestroyDeviceInfoList(hDevInfo);
return INVALID_HANDLE_VALUE;
}
if (overlapped)
{
hidHandle = CreateFile(devDetail->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
}
else
{
hidHandle = CreateFile(devDetail->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
}
free(devDetail);
if (hidHandle==INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(hDevInfo);
free(devDetail);
return INVALID_HANDLE_VALUE;
}
_HIDD_ATTRIBUTES hidAttributes;
if(!HidD_GetAttributes(hidHandle, &hidAttributes))
{
CloseHandle(hidHandle);
SetupDiDestroyDeviceInfoList(hDevInfo);
return INVALID_HANDLE_VALUE;
}
if (USB_VID == hidAttributes.VendorID
&& USB_PID == hidAttributes.ProductID)
{
printf("找到了我想要的設(shè)備,哈哈哈....\n");
break;
}
else
{
CloseHandle(hidHandle);
++deviceNo;
}
}
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return hidHandle;
}
|
評(píng)分
-
查看全部評(píng)分
|