Windows下USB HID设备通信

部署运行你感兴趣的模型镜像

1. HID API

前面有写过一文来介绍 STM32F072 HID 自定义设备,这里记录windows下如何与之进行通信,也就是上位机的编写。windows作为主机端与HID设备通信流程大致如下:

  1. 通过VID/PID等信息查找到对应的设备路径
  2. 通过CreateFile获取设备的操作句柄
  3. 使用WriteFile/ReadFile、HidD_SetFeature/HidD_GetFeature 发送接收数据
  4. 关闭对应句柄,释放资源

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设备固件端配合好,应该能正常通信。

您可能感兴趣的与本文相关的镜像

HunyuanVideo-Foley

HunyuanVideo-Foley

语音合成

HunyuanVideo-Foley是由腾讯混元2025年8月28日宣布开源端到端视频音效生成模型,用户只需输入视频和文字,就能为视频匹配电影级音效

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值