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中。

2.3 根据HidGuid来获取设备信息集合

//根据HidGuid来获取设备信息集合。其中Flags参数设置为

//DIGCF_DEVICEINTERFACE|DIGCF_PRESENT,前者表示使用的GUID为

//接口类GUID,后者表示只列举正在使用的设备,因为我们这里只

//查找已经连接上的设备。返回的句柄保存在hDevinfo中。注意设备

//信息集合在使用完毕后,要使用函数SetupDiDestroyDeviceInfoList

//销毁,不然会造成内存泄漏。

hDevInfoSet=SetupDiGetClassDevs(&HidGuid,

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 写读操作注意事项:

读操作一般会使用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

2022年9月更新新的使用 Windows API 实现 HID 访问的模板例程

例程模板及开发记录路径如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值