USB/HID 上位机开发流程及注意事项-----/*自己编写*/

参考示例:PCR项目上位机


1. 预定义的变量

//用来保存读数据的设备句柄
HANDLE hReadHandle=INVALID_HANDLE_VALUE;
//用来保存写数据的设备句柄
HANDLE hWriteHandle=INVALID_HANDLE_VALUE;
//发送报告的缓冲区,1字节报告ID+8字节报告数据
UCHAR WriteReportBuffer[130];
//接收报告的缓冲区,1字节报告ID+8字节报告数据
UCHAR ReadReportBuffer[130];
//发送报告用的OVERLAPPED。
OVERLAPPED WriteOverlapped;
//接收报告用的OVERLAPPED。
OVERLAPPED ReadOverlapped;
//用来注册设备通知事件用的广播接口。
//要使用该结构体,需要在StdAfx.h中将增加语句#define WINVER 0x0500
DEV_BROADCAST_DEVICEINTERFACE DevBroadcastDeviceInterface;
//重要!!!
HIDP_CAPS  Capabilities;

//定义一个GUID的结构体HidGuid来保存HID设备的接口类GUID。
 GUID HidGuid;
//定义一个DEVINFO的句柄hDevInfoSet来保存获取到的设备信息集合句柄。
 HDEVINFO hDevInfoSet;
//定义MemberIndex,表示当前搜索到第几个设备,0表示第一个设备。
 DWORD MemberIndex;
//DevInterfaceData,用来保存设备的驱动接口信息
 SP_DEVICE_INTERFACE_DATA DevInterfaceData;
//定义一个BOOL变量,保存函数调用是否返回成功
 BOOL Result;
 //定义一个RequiredSize的变量,用来接收需要保存详细信息的缓冲长度。
 ULONG RequiredSize;
//定义一个指向设备详细信息的结构体指针。
 PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData;
//定义一个用来保存打开设备的句柄。
 HANDLE hDevHandle;
//定义一个HIDD_ATTRIBUTES的结构体变量,保存设备的属性。
 HIDD_ATTRIBUTES DevAttributes;

2. 查找并打开设备流程
2.1初始化变量
//初始化读、写句柄为无效句柄。
hReadHandle=INVALID_HANDLE_VALUE;
hWriteHandle=INVALID_HANDLE_VALUE;

//对DevInterfaceData结构体的cbSize初始化为结构体大小
DevInterfaceData.cbSize=sizeof(DevInterfaceData);
//对DevAttributes结构体的Size初始化为结构体大小
DevAttributes.Size=sizeof(DevAttributes); 

2.2获取HID设备的GUID
//调用HidD_GetHidGuid函数获取HID设备的GUID,并保存在HidGuid中。
HidD_GetHidGuid(&HidGuid); 

2.3 根据HidGuid来获取设备信息集合
//根据HidGuid来获取设备信息集合。其中Flags参数设置为
//DIGCF_DEVICEINTERFACE|DIGCF_PRESENT,前者表示使用的GUID为
//接口类GUID,后者表示只列举正在使用的设备,因为我们这里只
//查找已经连接上的设备。返回的句柄保存在hDevinfo中。注意设备
//信息集合在使用完毕后,要使用函数SetupDiDestroyDeviceInfoList
//销毁,不然会造成内存泄漏。

hDevInfoSet=SetupDiGetClassDevs(&HidGuid,

                           NULL,

NULL,

DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);

DevInterfaceData.cbSize = sizeof(DevInterfaceData);

2.4 然后对设备集合中每个设备进行列举,检查是否是我们要找的设备
//当找到我们指定的设备,或者设备已经查找完毕时,就退出查找。
//首先指向第一个设备,即将MemberIndex置为0。
MemberIndex=0;
while(1) //循环到整个查询结束,这里只是介绍步骤,所以只是提示在这里开始循环,结束位置就不标出了 
{
//调用SetupDiEnumDeviceInterfaces在设备信息集合中获取编号为
//MemberIndex的设备信息。
Result=SetupDiEnumDeviceInterfaces(hDevInfoSet,
                                   NULL,
&HidGuid,
MemberIndex,
&DevInterfaceData);
//如果获取信息失败,则说明设备已经查找完毕,退出循环。
if(Result==FALSE) break;
  //将MemberIndex指向下一个设备

MemberIndex++;


2.5 如果获取信息成功,则继续获取该设备的详细信息
//在获取设备详细信息时,需要先知道保存详细信息需要多大的缓冲区,这通过
//第一次调用函数SetupDiGetDeviceInterfaceDetail来获取。这时
//提供缓冲区和长度都为NULL的参数,并提供一个用来保存需要多大
//缓冲区的变量RequiredSize。
Result=SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                  &DevInterfaceData,
NULL,
NULL,
&RequiredSize,
NULL);

//然后,分配一个大小为RequiredSize缓冲区,用来保存设备详细信息。
pDevDetailData=(PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(RequiredSize);
if(pDevDetailData==NULL) //如果内存不足,则直接返回。
{
MessageBox("内存不足!");
SetupDiDestroyDeviceInfoList(hDevInfoSet);
return;
}

//并设置pDevDetailData的cbSize为结构体的大小(注意只是结构体大小,
//不包括后面缓冲区)。
pDevDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

2.6 然后再次调用SetupDiGetDeviceInterfaceDetail函数来获取设备的详细信息
//这次调用设置使用的缓冲区以及缓冲区大小。
Result=SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                  &DevInterfaceData,
pDevDetailData,
RequiredSize,
&Required, 
NULL);

//将设备路径复制出来,然后销毁刚刚申请的内存。
MyDevPathName=pDevDetailData->DevicePath;
free(pDevDetailData);
//如果调用失败,则查找下一个设备。
if(Result==FALSE) continue;

2.7 如果调用成功,则使用不带读写访问的CreateFile函数来获取设备的属性,包括VID、PID、版本号等
//对于一些独占设备(例如USB键盘),使用读访问方式是无法打开的,
//而使用不带读写访问的格式才可以打开这些设备,从而获取设备的属性。
  hDevHandle=CreateFile(MyDevPathName, 
             NULL,
                        FILE_SHARE_READ|FILE_SHARE_WRITE, 
                        (LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
0,
NULL); 

2.8 如果打开成功,则获取设备属性
if(hDevHandle!=INVALID_HANDLE_VALUE) //具体结束位置看实例
{
DevAttributes.Size = sizeof(DevAttributes);
//获取设备的属性并保存在DevAttributes结构体中
Result=HidD_GetAttributes(hDevHandle,
                        &DevAttributes);

GetDeviceCapabilities(hDevHandle);//重要,读写函数操作时需要此函数中的参数,下面是该函数具体实现
void CMyUsbHidTestAppDlg::GetDeviceCapabilities(HANDLE pHidHandle)
{
//Get the Capabilities structure for the device.

PHIDP_PREPARSED_DATAPreparsedData;

/*
API function: HidD_GetPreparsedData
Returns: a pointer to a buffer containing the information about the device's capabilities.
Requires: A handle returned by CreateFile.
There's no need to access the buffer directly,
but HidP_GetCaps and other API functions require a pointer to the buffer.
*/

HidD_GetPreparsedData 
(pHidHandle, 
&PreparsedData);
//DisplayLastError("HidD_GetPreparsedData: ");

/*
API function: HidP_GetCaps
Learn the device's capabilities.
For standard devices such as joysticks, you can find out the specific
capabilities of the device.
For a custom device, the software will probably know what the device is capable of,
and the call only verifies the information.
Requires: the pointer to the buffer returned by HidD_GetPreparsedData.
Returns: a Capabilities structure containing the information.
*/
HidP_GetCaps 
(PreparsedData, 
&Capabilities);
//DisplayLastError("HidP_GetCaps: ");

//No need for PreparsedData any more, so free the memory it's using.

HidD_FreePreparsedData(PreparsedData);
//DisplayLastError("HidD_FreePreparsedData: ") ;
}

 //获取失败,查找下一个
    if(Result==FALSE) continue;

2.9 如果获取成功,则将属性中的VID、PID以及设备版本号与我们需要的进行比较
//如果都一致的话,则说明它就是我们要找的设备。
// if(DevAttributes.VendorID==MyVid) //如果VID相等
// if(DevAttributes.ProductID==MyPid) //并且PID相等
// if(DevAttributes.VersionNumber==MyPvn) //并且设备版本号相等
if(DevAttributes.VendorID==0x0683) //如果VID相等,这是具体实例
if(DevAttributes.ProductID==0x5850) //并且PID相等,这是具体实例
{
//那么就是我们要找的设备,分别使用读写方式打开之,并保存其句柄
//并且选择为异步访问方式。

//写方式打开设备
hWriteHandle=CreateFile(MyDevPathName, 
GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, 
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
0, 
NULL);
if(hWriteHandle!=INVALID_HANDLE_VALUE)AddToInfOut("写访问打开设备成功");
else AddToInfOut("写访问打开设备失败");

//读方式打开设备
hReadHandle=CreateFile(MyDevPathName, 
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE, 
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if(hReadHandle!=INVALID_HANDLE_VALUE)AddToInfOut("读访问打开设备成功");
else AddToInfOut("读访问打开设备失败");
//找到设备,退出循环。本程序只检测一个目标设备,查找到后就退出
//查找了。如果你需要将所有的目标设备都列出来的话,可以设置一个
//数组,找到后就保存在数组中,直到所有设备都查找完毕才退出查找

2.10 调用SetupDiDestroyDeviceInfoList函数销毁设备信息集合
SetupDiDestroyDeviceInfoList(hDevInfoSet);

3. 读写操作使用函数及注意事项:

3.1 写、读操作调用函数

Result=WriteFile
(hWriteHandle,
WriteReportBuffer,
Capabilities.OutputReportByteLength,

// &BytesWritten,
// NULL);
NULL,
&WriteOverlapped);


Result = ReadFile(hReadHandle,
ReadReportBuffer,
Capabilities.InputReportByteLength,

&NumberOfBytesRead,
(LPOVERLAPPED)&ReadOverlapped);


3.2 写读操作注意事项:

USB/HID 上位机开发流程及注意事项-----/*自己编写*/_第1张图片

读操作一般会使用WaitForSingleObject函数来配合读操作过程中的线程挂起。

这时一般会在调用ReadFile函数之前创建一个事件,并把创建事件的指针赋值给ReadFile函数的第五个参数(该参数是个结构体,里面含一个事件指针)。

由此,当调用ReadFile函数操作完之后,会触发该事件,让WaitForSingleObject函数相应,当ReadFile函数操作失败,则不会触发事件,WaitForSingleObject根据设置等待时间会返回timeout,如果此时WaitForSingleObject设置的等待时间为INFINITE时,则程序就会被卡住。

关于创建时间和WaitForSingleObject函数,可参考下面两文:

http://blog.csdn.net/phenixyf/article/details/71136838

http://blog.csdn.net/phenixyf/article/details/71126707



你可能感兴趣的:(USB)