hidsdi,hidpi.h,hidusage.h,hid.lib,setupapi.lib拷贝进工程文件夹下,接下来调用API函数,完成需求。
extern "C" {
// Declare the C libraries used
#include "hidsdi.h" // Must link in hid.lib
#include <setupapi.h> // Must link in setupapi.lib
}
本文介绍Visual C++6.0环境下利用Windows API函数实现与HID设备类的USB接口通信,并给出了通信程序的部分代码
Windows下,与USB外设的任何通信需通过设备驱动,该驱动知道如何与系统的USB驱动和访问设备的应用程序通信,Windows
包含应用程序与HID通信所需要的各种信息,不需要再安装设备驱动。Win32的应用程序接口API函数,使得设备驱动能与应用程序之间相互通信,应用程序也不需要为了和USB设备通信去了解复杂的USB协议。
下面用Visual C++编写应用程序调用API函数,从而简化了与硬件通信的过程。
1、查找USB设备
在应用程序能与HID交换数据之前,它先要找到设备,获取关于它的报告信息。
1)HidD_GetHidGuid(&guidHID);来获得HID设备的标识,HID类设备是通过GUID类型值作标识的。GUID是16字节大小的结构,用来标识通信接口及类对象,它的定义为:
typedef struct _GUID // size is 16
{
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
2)还调用了其他与硬件相关的API函数,这些函数都在setupapi.h中定义。
SetupDiGetClassDevs函数用来获得一类硬件设备的信息,设备信息集句柄hDevInfo
HDEVINFO hDevInfo=SetupDiGetClassDevs(
&guidHID, //这类设别配置或接口类GUID
NULL, //特定的字符串,用来选择符合条件的设备
0, //与获得信息相关的顶层窗体句柄
DIGCF_PRESENT|DIGCF_DEVICEINTERFACE //给出了设置信息的方式
);
3)SetupDiEnumDeviceInterface函数得到设备接口信息反复调用得到所有设备接口信息strInterfaceData,若要找到特定设备,可在循环语句内调用
该函数,直到找到预期设备或函数返回False值,
定义为:
Bool bSuccess=SetupDiEnumDeviceInterface(
hDevInfo, //感兴趣的接口句柄
NULL, //指向SP_DEVINFO_DATA类型结构的指针,该结构定义了特定接口
&guidHID, //确定了接口的GUID标识
Index, //所关心的索引号,以0为起点
&strInterfaceData, //指向SP_DEVICE_INTERFACE_DATA类型的指针,他所指向的内容就是调用函数的目的所在,当函数返回时,strInterfaceData指向的结构就存在相关接口信息
);
其中SP_DEVINFO_DATA结构定义为:
typedef struct SP_DEVINFO_DATA{
DWORD cbsize; //指定结构的大小
GUID calssGuid; //设备的GUID标识
DWORD DevInst; //用来访问设备的句柄
ULONG_PTR Reserved;
}SP_DEVINFO_DATA,*PSP_DEVINFO_DATA;
SP_DEVICE_INTERFACE_DATA结构的定义如下:
typedef struct SP_DEVICE_INTERFACE_DATA{
DWORD cbsize; //是SP_DEVICE_INTERFACE_DATA结构的大小
GUID InterfaceClassGuid; //指定了接口的GUID标识
DWORD Flags; //接口所处状态
ULONG_PTR Reserved;
}SP_DEVICE_INTERFACE_DATA,*PSP_DEVICE_INTERFACE_DATA;
4)SetupDiGetDeviceInterfaceDetail()
long Result=SetupDiGetDeviceInterfaceDetail(
hDevInfo, //设备信息集句柄
&strInterfaceData, //设备接口信息
NULL, //设备路径
0, //输出缓冲区大小
&Length,
NULL);
再次调用为了得到strInterfaceDetailData
long Result=SetupDiGetDeviceInterfaceDetail(
hDevInfo,
&strInterfaceData,
strInterfaceDetailData,
Length,
&Required,
NULL
);
2、与USB设备交换数据
在Windows中,读写端口与读写文件都是调用同样的API函数,打开或创建端口用CreateFile,从端口读数据用ReadFile,用WriteFile向端口写数据
1)设备的打开与关闭
用API函数CreateFile来打开或创建设备:
HANDLE hCom=CreateFile(
strInterfaceDetailData->DevicePath, //指定打开设备名
GENERIC_READ|GENERIC_WRITE, //允许读写
0, //独占方式
NULL, //安全模式
OPEN_EXISTING, //打开
FILE_ATTRIBUTE_NORNAL, //文件属性
NULL, //临时文件的句柄
);
如果调用成功,函数返回文件的句柄,如果调用失败,则返回INVALID_HANDLE_VALUE,在打开通信设备时,应该以独占方式打开,
不再使用设备句柄时,应该调用CloseHandle(hCom)函数关闭它。
2)Bool Result = HidD_GetAttributes (hCom, &strAttrib);
其中hCom是对应于选定设备的句柄,strAttribute则是指向HIDD_ATTRIBUTES类型的指针,当函数返回时即得到了指定设备的属性
typedef struct _HIDD_ATTRIBUTES {
ULONG Size; // = sizeof (struct _HIDD_ATTRIBUTES)
USHORT VendorID; // Vendor ids of this hid device USHORT ProductID;
USHORT VersionNumber;
// Additional fields will be added to the end of this structure.
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
3)设备的读写操作,读写通信设备可用同步方式执行
HANDLE hCom;
void *pBuffer;
DWORD iLength;
DWORD pReadFact;
Bool ReadFile(hCom,pBuffer,iLength,&pReadFact,NULL);
读到的数据放在内存pBuffer里,pBuffer要先申请内存空间,iLength为需要读的数据长度,pReadFact存放实际的数据长度。
需要注意的是在读写设备之前,应先调用ClearCommError函数清除错误标志,此函数负责报告指定的错误的设备的当前状态,调用PrugeComm函数可以更改正在进行的读写操作方式
3、函数模块实例
1)打开设备
四个模块中,打开设备是最复杂的。因为它需要通过其设备类来枚举设备树上的所有设备,从而得到相匹配的设备,进而得到设备名
BOOL DeviceOpen(HANDLE&handle, WORD wVID, WORD wPID)
{
BOOL bRet = FALSE;
GUID hidGuid;
HDEVINFO hardwareDeviceInfo;
SP_INTERFACE_DEVICE_DATA deviceInfoData;
PSP_INTERFACE_DEVICE_DETAIL_DATA functionClassDeviceData = NULL;
ULONG predictedLength = 0;
ULONG requiredLength = 0;
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
deviceInfoData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
HidD_GetHidGuid(&hidGuid);
hardwareDeviceInfo = SetupDiGetClassDevs(&hidGuid, NULL,NULL, (DIGCF_PRESENT|DIGCF_DEVICEINTERFACE));
for (int i=0; i<128; i++)
{
if (!SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, 0,&hidGuid, i, &deviceInfoData)) continue;
SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, &deviceInfoData,NULL, 0, &requiredLength, NULL);
predictedLength = requiredLength;
functionClassDeviceData =(PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(predictedLength);
if (!functionClassDeviceData) continue;
functionClassDeviceData->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail (hardwareDeviceInfo,&deviceInfoData, functionClassDeviceData, predictedLength,&requiredLength, NULL)) break;
handle = CreateFile(functionClassDeviceData->DevicePath,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING, 0, NULL);
if (handle != INVALID_HANDLE_VALUE)
{
HIDD_ATTRIBUTES attri;
HidD_GetAttributes(handle, &attri);
if ((attri.VendorID == wVID) && (attri.ProductID == wPID))
{
bRet = TRUE;
break;
}
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
}
}
SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
return bRet;
}
2)关闭设备
关闭设备比较简单,只需要直接使用函数CloseHandle即可
void DeviceClose(HANDLE&handle)
{
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
}
3)写数据
假设HID的Report大小为8字节,且第一字节为ID
BOOL DeviceWrite(HANDLEhandle, LPCVOID lpBuffer, DWORD dwSize)
{
BYTE wBuffer[8] = {0};
DWORD dwRet;
BOOL bRet;
wBuffer[0] = 0x01;
wBuffer[1] = 0x00;
memcpy(&wBuffer[2], lpBuffer, min(6, dwSize));
bRet = WriteFile(handle, wBuffer, 8, &dwRet, NULL);
return bRet;
}
4)读数据
BOOL DeviceRead(HANDLEhandle, LPVOID lpBuffer, DWORD dwSize)
{
BYTE rBuffer[8] = {0};
DWORD dwRet;
BOOL bRet;
rBuffer[0] = 0x01;
rBuffer[1] = 0xff;
bRet = WriteFile(handle, rBuffer, 8, &dwRet, NULL);
bRet &= ReadFile(handle, rBuffer, 8, &dwRet, NULL);
memcpy(lpBuffer, &rBuffer[1], min(7, dwSize));
return bRet;
}