请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
在上篇学习了访问HID设备的Windows API之后,本篇开始着手进行Windows上位机的编程。所编写的程序名为UsbHID,其主要功能在上一篇中已经介绍过,下面介绍其编写过程。
1 添加库文件
我是使用MFC编写的上位机程序,开发工具为VS2015。建立基于Dialog的工程,并在对话框的CPP文件中,添加如下语句:
#include <hidsdi.h>
#include <setupapi.h>
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "hid.lib")
所添加的头文件和库文件,包含UsbHID编程时需要用到的SetupDi系列函数,以及HID设备获取信息的函数和数据通信函数。早期的VS中,需要将库文件拷贝到工程的目录下,并手动添加库文件到工程中。
2 枚举HID设备
枚举HID设备包括获取HID类的GUID、查找所有HID设备、获取设备的信息等步骤。枚举过程中,用到了上篇博客所说的获取设备属性的若干函数。另外,也使用了几个SetupAPI函数,这几个函数的原型列举如下。
2.1 SetupAPI函数
返回一个设备信息集的句柄,包含本地计算机所请求的设备信息元素。
WINSETUPAPI HDEVINFO SetupDiGetClassDevsW(
const GUID *ClassGuid, //指向设备安装类或接口类的GUID指针,可以为空
PCWSTR Enumerator, //指向空字符结尾的字符串
HWND hwndParent, //与设备实例相关的用户界面的顶级窗口句柄,可为空
DWORD Flags //过滤设备用的标识
);
请求获得设备信息集内某个设备的信息
WINSETUPAPI BOOL SetupDiEnumDeviceInterfaces(
HDEVINFO DeviceInfoSet, //指向设备信息集
PSP_DEVINFO_DATA DeviceInfoData, //指向设备信息参数指针,可为空
const GUID *InterfaceClassGuid, //指向设备安装类或接口类的GUID指针
DWORD MemberIndex, //位于设备信息集中的序号,以0起始
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData //回传的设备接口数据
);
请求获得设备的路径
WINSETUPAPI BOOL SetupDiGetDeviceInterfaceDetailW(
HDEVINFO DeviceInfoSet, //指向设备信息集
PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, //设备接口数据
PSP_DEVICE_INTERFACE_DETAIL_DATA_W DeviceInterfaceDetailData,
DWORD DeviceInterfaceDetailDataSize,
PDWORD RequiredSize,
PSP_DEVINFO_DATA DeviceInfoData
);
2.2 枚举HID设备的流程图
枚举HID设备时,将调用上述的函数,其流程如下图所示:

2.3 枚举HID设备的代码
实现代码如下:
CString sterr;
GUID HidGuid;
HANDLE hDeviceHandle;
HDEVINFO hDevInfo = NULL;
static LV_ITEM pLvi_item;
// 1 查找本系统中HID类的GUID标识
HidD_GetHidGuid(&HidGuid);
// 2 准备查找符合HID规范的USB设备
hDevInfo = SetupDiGetClassDevs(&HidGuid,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
sterr.Format(_T("SetupDiGetClassDevs failed :%d"), GetLastError());
throw sterr;
}
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
//3 循环获取指定HID设备,以序号寻找
int MemberIndex = -1;
while (MemberIndex++ < 100) // 小于一个足够大的数,防止设备列举不完全
{
BOOL bSuccess = SetupDiEnumDeviceInterfaces(hDevInfo,
NULL,
&HidGuid,
(ULONG)MemberIndex,
&DeviceInterfaceData);
if (!bSuccess)
{
if (GetLastError() == ERROR_NO_MORE_ITEMS) // 没有找到更多设备,失败退出循环
break;
continue;
}
DWORD Length = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo,
&DeviceInterfaceData,
NULL,
0,
&Length,
NULL);
PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetailData;
pDeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length);
if (pDeviceInterfaceDetailData == NULL) // 分配堆内存
{
sterr.Format(_T("PSP_DEVICE_INTERFACE_DETAIL_DATA malloc failed :%d"), GetLastError());
throw sterr;
}
pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDevInfo,
&DeviceInterfaceData,
pDeviceInterfaceDetailData,
Length,
NULL,
NULL))
{
free(pDeviceInterfaceDetailData);
continue;
}
hDeviceHandle = CreateFile(
pDeviceInterfaceDetailData->DevicePath,
0,//GENERIC_READ | GENERIC_WRITE ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
free(pDeviceInterfaceDetailData);
if (hDeviceHandle == INVALID_HANDLE_VALUE)
{
continue;
}
//获取HID信息,显示到界面中(显示部分的代码略)
WCHAR mString[256];
HIDD_ATTRIBUTES Attributes;
HidD_GetAttributes(hDeviceHandle, &Attributes);
HidD_GetManufacturerString(hDeviceHandle, mString, sizeof(mString));
HidD_GetProductString(hDeviceHandle, mString, sizeof(mString));
//...显示信息(代码省略)
//.....................
CloseHandle(hDeviceHandle);
}
if (INVALID_HANDLE_VALUE != hDevInfo)
SetupDiDestroyDeviceInfoList(hDevInfo);
3 与HID设备通信
枚举HID设备时,找到了指定的HID设备(也即通过CreateFile()获得了句柄hDeviceHandle),即可通过上一篇博客中所述的三种方法之一进行通信。
我使用YIE002设计的HID设备中,三种通信方式都允许进行16字节的交换。也即设计的输入报告、输出报告和功能报告,其数据正文都是16字节的。
一般来说,很难想象设计者不了解设备固件中的报告允许通信的数据长度。理论上来说,可以通过Windows API把报告读出来,用程序去分析报告的内容。不过,这只能了解HID设备的通信能力,具体如何去操作硬件,还是由设计者来规定的。
比如在我的设想中,发送”LED1,on”的字符串,将点亮LED1。这种设定,是无法通过报告得到的,完全由开发者自由设定。
UsbHID工具是转为我在YIE002开发的USB HID设备服务的,其三种通信方式的编写过程介绍如下。
3.1 ReadFile()和WriteFile()方式
代码不解释了,熟悉了上一篇博客的函数,一看就明白。
BYTE bReportArray[17];
memset(bReportArray, 0x0, 17); //Report ID为0
if (!WriteFile(hDeviceHandle,
bReportArray,
17, //Report ID+数据正文
&dwWritten,
NULL))
{
sterr.Format(_T("WriteFile failed : %d"), GetLastError());
throw sterr;
}
memset(bReportArray, 0x0, 17);
if (!ReadFile(hDeviceHandle,
bReportArray,
17,
&nRead,
NULL))
{
sterr.Format(_T("ReadFile failed : %d"), GetLastError());
throw sterr;
}
3.2 输入报告和输出报告的方式
代码如下:
BYTE bReportArray[17];
memset(bReportArray, 0x0, 17); //Report ID为0
if (!HidD_SetOutputReport(hDeviceHandle, bReportArray, (ULONG)17))
{
sterr.Format(_T("HidD_SetOutputReport failed : %d"), GetLastError());
throw sterr;
}
memset(bReportArray, 0x0, 17);
if (!HidD_GetInputReport(hDeviceHandle, bReportArray, 17))
{
sterr.Format(_T("HidD_GetInputReport failed : %d"), GetLastError());
throw sterr;
}
3.3 功能报告方式
代码如下:
BYTE bReportArray[17];
memset(bReportArray, 0x0, 17); //Report ID为0
if (!HidD_SetFeature(hDeviceHandle, bReportArray, (ULONG)17))
{
sterr.Format(_T("HidD_SetFeature failed : %d"), GetLastError());
throw sterr;
}
memset(bReportArray, 0x0, 17);
if (!HidD_GetFeature(hDeviceHandle, bReportArray, 17))
{
sterr.Format(_T("HidD_GetFeature failed : %d"), GetLastError());
throw sterr;
}
至于UsbHID工具其他部分的编程,包括显示和用户接口设计等,这些不涉及到USB HID的通信,就不一一介绍了。
Windows编程部分的内容到此就结束了,在博客末尾,提供了UsbHID工具的下载地址(64位程序)。如果也是按照16字节数据正文设计的HID设备,可以直接使用此工具。
关于如何编写USB HID设备,这是个比较长的话题,将在后面的篇章中,以YIE002为样板,讲述开发过程。
题外话:YIE002的板子拿到了,这几天会拿去焊接。我是交给我一个做硬件的死党去设计的,特别强调是为UEFI开发做的,做成U盘大小就行。他倒好,在我原来设想的基础上,又添加了不少功能。有3个按键、2个串口,1个485接口,5个LED灯,足够用来做嵌入式开发的教材板了。
这样也好,本来就计划要好好整理下嵌入式开发方面的资料,花点时间把这个计划完成吧。
Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ 74 UsbHID工具下
1,529 total views, 5 views today