UEFI开发探索74- YIE002USB开发板(03 Windows编程)


请保留-> 【原文:  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设备时,将调用上述的函数,其流程如下图所示:

图1 枚举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,131 total views, 1 views today

发表评论

电子邮件地址不会被公开。