Windows下USB HID设备通信
1. HID API
前面有写过一文来介绍 STM32F072 HID 自定义设备,这里记录windows下如何与之进行通信,也就是上位机的编写。windows作为主机端与HID设备通信流程大致如下:
- 通过VID/PID等信息查找到对应的设备路径
- 通过CreateFile获取设备的操作句柄
- 使用WriteFile/ReadFile、HidD_SetFeature/HidD_GetFeature 发送接收数据
- 关闭对应句柄,释放资源
windows 相关API
-
HANDLE CreateFile(
LPCWSTR lpFileName, // 指向路径的指针
DWORD dwDesiredAccess, // 访问模式(读/写)
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性的指针
DWORD dwCreationDisposition, // 如何创建
DWORD dwFlagsAndAttributes, // 文件属性(同步或异步)
HANDLE hTemplateFile // 用于复制文件句柄
); -
WriteFile(
HANDLE hFile, // 设备句柄,可通过CreateFile得到
LPCVOID lpBuffer, // 要发送的buffer(指针)
DWORD nNumberOfBytesToWrite, // 要发送数据的长度
LPDWORD lpNumberOfBytesWritten, // 实际发送数据的长度
LPOVERLAPPED lpOverlapped // OVERLAPPED结构体指针,如果设备是以FILE_FLAG_OVERLAPPED方式打开的话,那么这个指针就不能为NULL
); -
ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
); -
HidD_SetFeature (
HANDLE HidDeviceObject, // 设备句柄,通过CreateFile得到
PVOID ReportBuffer, // 待发送的数据
ULONG ReportBufferLength // 待发送数据的长度
); -
HidD_GetFeature (
HANDLE HidDeviceObject,
PVOID ReportBuffer,
ReportBufferLength
); -
HidD_SetOutputReport (
HANDLE HidDeviceObject,
PVOID ReportBuffer,
ULONG ReportBufferLength
); -
HidD_GetInputReport (
HANDLE HidDeviceObject,
PVOID ReportBuffer,
ULONG ReportBufferLength
); -
CloseHandle(
HANDLE hObject
);
所需开发库及头文件
-
添加库文件(附加依赖项)
setupapi.lib
hid.lib -
包含头文件
extern “C” {
#include “hidsdi.h”
#include “setupapi.h”
}
2. 查找设备
直接上代码
/*
HANDLE hWriteHandle = INVALID_HANDLE_VALUE;
HANDLE hReadHandle = INVALID_HANDLE_VALUE;
CString DevicePathName = "";
BOOL DeviceFound = FALSE;
UINT FoundDevice_Num;
HIDP_CAPS Capabilities;
PHIDP_PREPARSED_DATA HidParsedData;
*/
BOOL CUSB_HID_APPDlg::FindHidDevice(DWORD vid, DWORD pid)
{
GUID HidGuid; //定义一个GUID的结构体HidGuid来保存HID设备的接口类GUID
HDEVINFO hDevInfoSet; //定义一个DEVINFO的句柄hDevInfoSet来保存获取到的设备信息集合句柄
SP_DEVICE_INTERFACE_DATA DevInterfaceData; //DevInterfaceData,用来保存设备的驱动接口信息
PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData; //定义一个指向设备详细信息的结构体指针
HIDD_ATTRIBUTES DevAttributes; //定义一个HIDD_ATTRIBUTES的结构体变量,保存设备的属性
DWORD RequiredSize; //定义一个RequiredSize的变量,用来接收需要保存详细信息的缓冲长度
HANDLE hDevHandle;
BOOL Result;
DWORD MemberIndex = 0; //定义MemberIndex,表示当前搜索到第几个设备,0表示第一个设备
FoundDevice_Num = 0;
DeviceFound = FALSE;
hWriteHandle = INVALID_HANDLE_VALUE;
hReadHandle = INVALID_HANDLE_VALUE;
hDevHandle = INVALID_HANDLE_VALUE;
DevInterfaceData.cbSize = sizeof(DevInterfaceData);
DevAttributes.Size = sizeof(DevAttributes);
HidD_GetHidGuid(&HidGuid);
hDevInfoSet = SetupDiGetClassDevs(&HidGuid,
NULL,
NULL,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
while (1)
{
// 1
Result = SetupDiEnumDeviceInterfaces(hDevInfoSet,//设备信息集合句柄
NULL,
&HidGuid,
MemberIndex,
&DevInterfaceData);
if (Result == FALSE) break;
MemberIndex++; // search next
Result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
&DevInterfaceData,
NULL,
NULL,
&RequiredSize,
NULL);
pDevDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(RequiredSize);
if (pDevDetailData == NULL)
{
SetupDiDestroyDeviceInfoList(hDevInfoSet);
return FALSE;
}
pDevDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
// 2
Result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
&DevInterfaceData,
pDevDetailData,
RequiredSize,
NULL,
NULL);
DevicePathName = pDevDetailData->DevicePath;
free(pDevDetailData);
if (Result == FALSE) continue;
// 3
hDevHandle = CreateFile(DevicePathName,
NULL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,//FILE_FLAG_OVERLAPPED
NULL);
if (hDevHandle != INVALID_HANDLE_VALUE)
{
Result = HidD_GetAttributes(hDevHandle,
&DevAttributes);
if (Result == FALSE) continue;
if (DevAttributes.VendorID == vid)
if (DevAttributes.ProductID == pid)
// if(DevAttributes.VersionNumber==Ver) //判断设备版本号是否相等
{
HidD_GetPreparsedData(hDevHandle, &HidParsedData);
/* extract the capabilities info */
HidP_GetCaps(HidParsedData, &Capabilities);
/* Free the memory allocated when getting the preparsed data */
HidD_FreePreparsedData(HidParsedData);
// if ( (Capabilities.OutputReportByteLength > 0) )
{
FoundDevice_Num++;
CString str = " ";
str.Format("%d", FoundDevice_Num);
//Info("Device " + str + L":" + DevicePathName.Mid(8));
str.Format("%x", Capabilities.Usage);
//Info("Usage : " + str);
str.Format("%x", Capabilities.UsagePage);
//Info("UsagePage : " + str);
str.Format("%d", Capabilities.InputReportByteLength);
//Info("InputReportByteLength : " + str);
str.Format("%d", Capabilities.OutputReportByteLength);
//Info("OutputReportByteLength : " + str);
str.Format("%d", Capabilities.FeatureReportByteLength);
//Info("FeatureReportByteLength : " + str);
//Info("------------------------------------------------------------------------------------------------");
}
DeviceFound = TRUE;
}
CloseHandle(hDevHandle);
}
else continue;
}
SetupDiDestroyDeviceInfoList(hDevInfoSet);
if (DeviceFound)
{
return TRUE;
}
//("Not Found Device!");
return FALSE;
}
需要注意下,如果HID设备枚举上去的设备不只一个,通过VID/PID来查找的话,会查到多个设备路径,可以再通过其他属性来判断是否为需要的设备,可再判断Capabilities.Usage和Capabilities.UsagePage是否与设备描述表一致。
3. 打开设备
if (hWriteHandle == INVALID_HANDLE_VALUE)
{
if (waiting) //同步
{
hWriteHandle = CreateFile(DevicePathName,
GENERIC_WRITE, //写
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
}
else //异步
{
hWriteHandle = CreateFile(DevicePathName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
}
}
//if (hWriteHandle == INVALID_HANDLE_VALUE)
//{
// Info("WriteHandle Invalid");
// return;
//}
if (hReadHandle == INVALID_HANDLE_VALUE)
{
if (waiting)
{
hReadHandle = CreateFile(DevicePathName,
GENERIC_READ, //读取
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
}
else
{
hReadHandle = CreateFile(DevicePathName,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, //0,
NULL);
}
}
查找到正确的设备路径后,通过CreateFile获取HANDLE,这里有同步和异步两种方式,同步即在读写数据时会阻塞程序,异步会立即返回,WriteFile或ReadFile时需要与这里设置的方式一致。
另外试图使用读写方式打开标准USB键盘或鼠标等独占设备时,可能会FAIL。
4. 读写数据
- 发送数据
BOOL CUSB_HID_APPDlg::SendDataToHidDevice(UCHAR *data, UINT length, UCHAR out_index, BOOL Waiting)
{
BOOL Result;
UINT LastError;
DWORD dwSend;
OVERLAPPED osWriter = { 0 };
osWriter.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWriter.hEvent == NULL)
{
return FALSE;
}
if (hWriteHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
switch (out_index)
{
case 0:
Result = HidD_SetOutputReport(hWriteHandle, data, length);
break;
case 1:
Result = HidD_SetFeature(hWriteHandle, data, length);
break;
case 2:
if (Waiting)
{
Result = WriteFile(hWriteHandle, //同步,执行这里会阻塞程序
data,
length,
&dwSend,
NULL);
}
else
{
Result = WriteFile(hWriteHandle, //异步
data,
length,
&dwSend,
&osWriter);
}
break;
default:
return FALSE;
break;
}
if (Result == FALSE)
{
//LastError = GetLastError();
//if ((LastError == ERROR_IO_PENDING) || (LastError == ERROR_SUCCESS))
//{
// return FALSE;
//}
return FALSE;
}
}
可根据底层USB设备固件设置来选择不同的通信方式,当底层支持Set Out Report请求时,可调用HidD_SetOutputReport发送数据,支持Set Feature Report时,可使用HidD_SetFeature发送数据,
WriteFile会通过Out Report 或 OUT端点中断方式来发送数据,看底层固件支持哪一种方式。
要注意下发送长度的设置,需多加1byte,当底层设备支持64byte数据时,则需要发送65byte的数据,byte[0]为Report id,底层没有设置的话固定为0即可,byte[1-64]为实际的数据。
- 读取数据
BOOL CUSB_HID_APPDlg::GetDataFormHidDevice(UCHAR *data, UINT length, UCHAR in_index, BOOL Waiting)
{
BOOL Result;
UINT LastError;
DWORD dwRead = length;
OVERLAPPED osReader = { 0 };
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
{
return FALSE;
}
if (hReadHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
switch (in_index)
{
case 0:
Result = HidD_GetInputReport(hReadHandle, data, length);
break;
case 1:
Result = HidD_GetFeature(hReadHandle, data, length);
break;
case 2:
#define READ_TIMEOUT 500
if (Waiting)
{
Result = ReadFile(hReadHandle, //同步
data,
length,
&dwRead,
NULL);
}
else
{
SetEvent(ReadOverlapped.hEvent);
return TRUE;
}
break;
default:
return FALSE;
break;
}
if (Result == FALSE)
{
//LastError = GetLastError();
return FALSE;
}
return TRUE;
}
ReadFile用于读取HID设备通过中断IN传输的数据,读取的是Windows HIDClass驱动buffer里的数据,如果设为同步方式读取,会阻塞直到IN端点上传数据,如果驱动buffer里有数据会立即返回。一般会新建一个线程来读取IN端点上传的数据。
- 错误返回
可以通过GetLastError()来获取当前的错误值,一般错误情形如下
1. Handle无效
2. 读写数据长度错误,与固件长度不匹配
3. 不支持的请求,如HidD_SetFeature时,设备的FeatureReportByteLength为0
5. 设备拔插检测
需响应 ON_WM_DEVICECHANGE 消息
#include "dbt.h"
HDEVNOTIFY hDevNotify;
bool CUSB_HID_APPDlg::RegisterDevice()
{
const GUID GUID_DEVINTERFACE_LIST[] = {
{ 0xA5DCBF10, 0x6530, 0x11D2,{ 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } },
{ 0x53f56307, 0xb6bf, 0x11d0,{ 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b } },
{ 0x4D1E55B2, 0xF16F, 0x11CF,{ 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }, /* HID */
{ 0xad498944, 0x762f, 0x11d0,{ 0x8d, 0xcb, 0x00, 0xc0, 0x4f, 0xc3, 0x35, 0x8c } } };
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_LIST[2];
hDevNotify = RegisterDeviceNotification(this->GetSafeHwnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
if (!hDevNotify) {
return false;
}
return true;
}
bool CUSB_HID_APPDlg::UnRegisterDevice()
{
if (hDevNotify)
{
if (UnregisterDeviceNotification(hDevNotify))
{
hDevNotify = NULL;
return TRUE;
}
}
return FALSE;
}
afx_msg BOOL CUSB_HID_APPDlg::OnDeviceChange(UINT nEventType, DWORD_PTR dwData)
{
DEV_BROADCAST_DEVICEINTERFACE* dbd = (DEV_BROADCAST_DEVICEINTERFACE*)dwData;
PDEV_BROADCAST_HDR devHdr = (PDEV_BROADCAST_HDR)dwData;
PDEV_BROADCAST_DEVICEINTERFACE devInterface = (PDEV_BROADCAST_DEVICEINTERFACE)devHdr;
switch (nEventType)
{
case DBT_DEVICEARRIVAL:
if (devHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
CString vid_str, pid_str;
GetDlgItemText(IDC_EDIT_VID, vid_str);
GetDlgItemText(IDC_EDIT_PID, pid_str);
CString str(devInterface->dbcc_name);
CString str_find = "VID_" + vid_str + "&PID_" + pid_str;
str_find.MakeUpper();
if (str.CompareNoCase(DevicePathName) == 0)
{
//Info("Device Attached");
}
}
break;
case DBT_DEVICEREMOVECOMPLETE:
if (devHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
CString vid_str, pid_str;
GetDlgItemText(IDC_EDIT_VID, vid_str);
GetDlgItemText(IDC_EDIT_PID, pid_str);
CString str(devInterface->dbcc_name);
CString str_find = L"VID_" + vid_str + L"&PID_" + pid_str;
str_find.MakeUpper();
if (str.CompareNoCase(OpenDevicePathName) == 0)
{
//CloseDevice();
//Info("Device Removed");
}
}
break;
default:
break;
}
return TRUE;
}
6. 关闭设备
if (hWriteHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(hWriteHandle);
hWriteHandle = INVALID_HANDLE_VALUE;
}
if (hReadHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(hReadHandle);
hReadHandle = INVALID_HANDLE_VALUE;
}
7. 小结
windows下与USB HID设备通信的流程大致如上,只要与HID设备固件端配合好,应该能正常通信。
988

被折叠的 条评论
为什么被折叠?



