7

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

 3 years ago
source link: http://yiiyee.cn/blog/2021/02/02/uefi%e5%bc%80%e5%8f%91%e6%8e%a2%e7%b4%a274-yie002usb%e5%bc%80%e5%8f%91%e6%9d%bf%ef%bc%8803-windows%e7%bc%96%e7%a8%8b%ef%bc%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

请保留-> 【原文:  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-exolorer
项目代码位于:/ 74 UsbHID工具下

43 total views, 1 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK