轉載請註明出處
作者:小馬
本文章主要介紹一下如何在XP下做一個基於usb hid裝置的上位機程式,實現簡單的上位機與硬體裝置的通訊. 由於本人自身的能力限制,有不足和出錯的地方,希望讀者見諒.我假設這篇文章的讀者已經對USB, HID,報告描述符等相關概念都至少有所瞭解,如果不是的話,自行學習.
開發環境, vs2005, DDK的支援.如果沒有安裝DDK,去網上找相關的庫檔案和標頭檔也行. 有以下幾個檔案是所需的:
basetsd.h
hidclass.h
hidpddi.h
hidpi.h
hidsdi.h
hidusage.h
hid.lib
hidclass.lib
hidparse.lib
setupapi.lib
開發這種程式並不複雜,起碼跟用DDK自己寫驅動比起來,簡單很多, 主要是對DDK裡的一些介面的功能要熟悉,這樣才能用起來得心應手
一 識別裝置
要和自己的HID裝置通訊,第一步當然是找到裝置.找到裝置的原理很簡單,我們把讀到的裝置資訊與實際裝置的資訊相比較,就可以知道是否讀到了正確的裝置. 在USB裝置中,裝置描述符裡的資訊可以唯一的標識不同的USB裝置. 我們一般用
idVendor idProduct bcdDevice
這三個資訊識別一個USB裝置. 這三個資訊都是裝置描述符裡的屬性. 所以我們的上位機程式可以把讀到的上述三個資訊與實際裝置的相比較,從而確定是否正確的串連到了裝置. 實際裝置的裝置描述符在裝置的韌體程式中可以找得到,如果你沒有韌體程式的源碼,也可以通過一些工具軟體讀出來裝置的描述符資訊,比如USB View就是一個很好用的工具.
知道了識別裝置的原理,就可以通過DDK裡相關的API介面去實現了.
HidD_GetAttributes函數可以擷取到上面的屬性資訊, 它的定義如下:
BOOLEAN
HidD_GetAttributes(
IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes
);
第二個參數是一個指向
HIDD_ATTRIBUTES結構體的指標, 這個結構體的定義如下:
typedef struct _HIDD_ATTRIBUTES {
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES
所以,這個函數可以從裝置中讀到我們想要的資訊. 但是,函數還有一個入口參數, HidDeviceObject,這是一個指向裝置的控制代碼,所以在調用HidD_GetAttributes前,先要調用CreateFile函數返回一個有效裝置操作控制代碼. 有了這個控制代碼才能與裝置進行正常的通訊.
CreateFile的第一個參數要求提供一個裝置名稱,這裡我們要提供一個完整的裝置路徑名,否則將返回無效的控制代碼. 這個路徑名是作業系統在識別到裝置後分配給裝置的, 可以通過DDK裡的介面SetupDiGetDeviceInterfaceDetail來擷取到, 這個函數的定義如下:
SetupDiGetDeviceInterfaceDetailW(
__in HDEVINFO DeviceInfoSet,
__in PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
__out_bcount_opt(DeviceInterfaceDetailDataSize) PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData,
__in DWORD DeviceInterfaceDetailDataSize,
__out_opt PDWORD RequiredSize,
__out_opt PSP_DEVINFO_DATA DeviceInfoData
);
該函數可以擷取到一個裝置介面的詳細資料, 注意第三個參數, 我們要的那個路徑名就由第三個參數返回. 它的結構體定義如下:
typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA_W {
DWORD cbSize;
WCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA_W
第二個資料就是我們要的路徑名.
這個函數的參數比較多,先來看一下第三個參數, 它用來接收裝置的相關資訊, 根據MSDN上的說明,我們可以這樣定義:
PSP_DEVICE_INTERFACE_DETAIL_DATADetailDataBuffer;
DetailDataBuffer = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(RequiredSize);
DetailDataBuffer -> cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
第四個參數指明第三個參數的大小, 第五個參數是一個出口參數, 它由系統返回,告訴我們實際需要的空間大小. 所以我們可以調用兩次SetupDiGetDeviceInterfaceDetail函數, 第一次擷取RequiredSize的值, 然後把它當作第四個參數來用, 如下:
SetupDiGetDeviceInterfaceDetail (DeviceInfoSet, &MyDeviceInterfaceData,
NULL, 0, &RequiredSize, NULL);
SetupDiGetDeviceInterfaceDetail(DeviceInfoSet,&MyDeviceInterfaceData,
DetailDataBuffer, RequiredSize,&RequiredSize, NULL);
前兩個參數都是入參,要擷取它們的值,還需要調用其它的一些DDK 介面. 第二個參數MyDeviceInterfaceData 需要用SetupDiEnumDeviceInterfaces來擷取到, 該函數的定義如下:
SetupDiEnumDeviceInterfaces(
__in HDEVINFO DeviceInfoSet,
__in_opt PSP_DEVINFO_DATA DeviceInfoData,
__in CONST GUID *InterfaceClassGuid,
__in DWORD MemberIndex,
__out PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData
);
它的功能是可以枚舉到某一類裝置中,某一個裝置的介面資訊, InterfaceClassGuid指明裝置的類別, 用GUID標識, 我的裝置就是標準的HID裝置, MemberIndex具體指示某一個裝置, 比如我電腦上串連了兩個HID裝置,分別是滑鼠和鍵盤,它們共用一個GUID,我用MemberIndex來區分它們,可能0對應滑鼠,1對應鍵盤. 所以很明顯,這個函數可以迴圈調用,通過改變MemberIndex的值(從0到n), 一直到找到我們所需的裝置為止.
最後就是如何擷取裝置的GUID了, 要用一個函數, HidD_GetHidGuid (
OUT LPGUID HidGuid
);
這個函數傳出一個GUID類型的資料, 得到的結果類似下面的形式:
GUID: {4D1E55B2-F16F-11CF-88CB-001111000030}
有了上面的函數已經可以正確的識別到一個USB HID的裝置了, DDK裡還提供了一些函數,可以在找到裝置後, 擷取裝置更詳細的資訊,下面舉幾個比較常用的.
BOOLEAN __stdcall HidD_GetProductString(
__in HANDLE HidDeviceObject,
__out PVOID Buffer,
__in ULONG BufferLength
);
這個函數可以擷取到裝置的產品字串, 當然,前提是裝置裡要有產品字串描述符, 因為字串描述符在裝置中是可選的. 該函數的第一個參數是CreateFile返回的控制代碼, 字串描述符是用寬字元來表示的,所以讀取時要注意轉換. 同一類型的函數還有:
HidD_GetManufacturerString
HidD_GetSerialNumberString1
HidD_GetIndexedString
另外,還有一個比較重要的函數
NTSTATUS HidP_GetCaps(
PHIDP_PREPARSED_DATA PreparsedData,
PHIDP_CAPS Capabilities
);
這個函數可以擷取裝置的通訊能力, 它的第二個出口參數是這樣的一個結構體:
typedef struct _HIDP_CAPS
{
USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
這個結構體裡的屬性直接描述了一個裝置的具體功能, 比如InputReportByteLength和OutputReportByteLength這兩個屬性分別表示裝置在通訊端點輸入和輸出的能力. 這些值是在裝置的端點描述符裡讀到的. 這些值直接決定了下面如何讀寫HID裝置.
還可以再進一步擷取某個用途的功能(比如邏輯最大值,最小值等), 比如有個函數:
NTSTATUS HidP_GetValueCaps(
HIDP_REPORT_TYPE ReportType,
PHIDP_VALUE_CAPS ValueCaps,
PULONG ValueCapsLength,
PHIDP_PREPARSED_DATA PreparsedData
);
可以擷取實值型別用途的功能, 在成功調用HidP_GetCaps後可以這樣調用HidP_GetValueCaps
///////////////////////////////////////////////////////////
Result = HidP_GetCaps(PreparsedData, &Capabilities);
WORD nValueCount = Capabilities.NumberInputValueCaps;
PHIDP_VALUE_CAPS valueCaps =(PHIDP_VALUE_CAPS)malloc(nValueCount*sizeof(PHIDP_VALUE_CAPS));
Result = HidP_GetValueCaps(HidP_Input, valueCaps, &nValueCount, PreparsedData);
//////////////////////////////////////////////////////////
未完待續.