一种全双工USB HID控制台的实现方法

Posted NiceBT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一种全双工USB HID控制台的实现方法相关的知识,希望对你有一定的参考价值。

为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板【淘宝链接:思度科技CSR开发板】

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–

1. 引言

USB HID类设备可以很方便地在主机和从机之间建立数据交互的通道。一般主机在有需要的时候会发起数据交互请求,从机收到请求后响应请求并发送应答信息。符合上述逻辑的伪代码如下:

while(1)

	if (需要发起数据交互)
	
		发送交互数据;
	
	
	sleep (等待从机收到并反馈应答);
	
	if (收到应答信息) 
	
		处理应答信息;
	

从上述伪代码中可见,进程在每次发送和接收数据之间需要等待若干时间,且这个时间并不能完全确定,即如果等待的时间不够,则会漏处理某些应答信息。另一个角度,因为加入了sleep,进程对发起数据交互的响应会变慢,整体效率会降低。

如果尝试用等待应答信息的超时阻塞机制取代sleep固定时长,伪代码如下:

while(1)

	if (需要发起数据交互)
	
		发送交互数据;
	

	if (wait until (在等待时间内收到应答信息))
	
		处理应答信息;
	

这种实现方式的改进处是使应答信息能够最快得到处理,不足之处仍是进程在等待不到数据时会阻塞。

为了克服上述不足,本文给出了一种基于OVERLAPPED机制的全双工USB HID控制台的实现方法,可以在不阻塞主进程的前提下以最快速度响应发送数据请求和处理应答消息。

2. OVERLAPPED简介

引用自博文(Windows Overlapped I/O详解

I/O设备处理必然让主程序停下来干等I/O的完成,
对这个问题有

  • 方法一:使用另一个线程进行I/O。这个方案可行,但是麻烦。 即 CreateThread(…………);创建一个子线程做其他事情。Readfile(^…………);阻塞方式读数据。
  • 方法二:使用overlapped I/O。overlapped I/O是WIN32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成overlapped I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价

可以理解成,使用OVERLAPPED机制后,每次I/O操作会起一个新的线程,这个新线程里会阻塞,但不会阻塞主进程。主进程会在后续的I/O操作中获取到之前的I/O操作的结果。

2.1 OVERLAPPED方式打开USB-HID设备

调用OpenDevice函数打开目标USB-HID设备。

/* returns handle when device found or NULL when not found */
HANDLE OpenDevice(void) 
	wchar_t device_path[MAX_PATH];
	HANDLE DeviceHandle;
	if (EnumerateDevices(device_path)) 
		/* create handle to the device */
		DeviceHandle=CreateFile(device_path, 
				 				GENERIC_READ | GENERIC_WRITE, 
								FILE_SHARE_READ|FILE_SHARE_WRITE, 
								(LPSECURITY_ATTRIBUTES)NULL,
								OPEN_EXISTING, 
#ifndef USE_OVERLAPPED
								FILE_ATTRIBUTE_NORMAL, 
#else
								FILE_FLAG_OVERLAPPED,
#endif
								NULL);
		if (DeviceHandle!=INVALID_HANDLE_VALUE) 
			return(DeviceHandle);
		
	
	return(NULL);

OpenDevice函数调用EnumerateDevice函数查询目标USB-HID设备是否已正常枚举:


#define USB_VID			0x0a12
#define USB_PID			0x1004
#define USB_USAGE_PAGE  0xff00
#define USB_USAGE		0x0001

int EnumerateDevices(wchar_t *device_path) 
	SP_DEVICE_INTERFACE_DATA devInfoData;
	int MemberIndex;
	ULONG Length;
	GUID HidGuid;
	HANDLE hDevInfo;
	HANDLE LocDevHandle;
	HIDD_ATTRIBUTES Attributes;
	PSP_DEVICE_INTERFACE_DETAIL_DATA detailData;
	PHIDP_PREPARSED_DATA PreparsedData;
	HIDP_CAPS Capabilities;
	int result=0;

	/* get HID GUID */
	HidD_GetHidGuid(&HidGuid);
	/* get pointer to the device information */
	hDevInfo = SetupDiGetClassDevs(&HidGuid,
			  					   NULL, 
								   NULL, 
								   DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
	/* go through all the device infos and find devices we are interested in */
	devInfoData.cbSize = sizeof(devInfoData);
	MemberIndex = 0;
	while((SetupDiEnumDeviceInterfaces(hDevInfo, 
								      0, 
									  &HidGuid, 
									  MemberIndex, 
									  &devInfoData))&&(result==0)) 
		/* first get the size of memory needed to hold the device interface info */
		SetupDiGetDeviceInterfaceDetail(hDevInfo, 
		   							    &devInfoData, 
										NULL, 
										0, 
										&Length, 
										NULL);
		/* allocate memory */
		detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length);
		/* and set the size in the structure */
		detailData -> cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
		/* now get the actual device interface info */
		SetupDiGetDeviceInterfaceDetail(hDevInfo, 
										&devInfoData, 
										detailData, 
										Length, 
										NULL, 
										NULL);
#ifdef DEBUG
		wprintf(L"%s\\n",detailData->DevicePath);
#endif
		/* create handle to the device */
		LocDevHandle=CreateFile(detailData->DevicePath, 
				 				GENERIC_READ | GENERIC_WRITE, 
								FILE_SHARE_READ|FILE_SHARE_WRITE, 
								(LPSECURITY_ATTRIBUTES)NULL,
								OPEN_EXISTING, 
								FILE_ATTRIBUTE_NORMAL, 
								NULL);
		/* set the size in the structure */
		Attributes.Size = sizeof(Attributes);
		/* get and test the VID and PID */
		HidD_GetAttributes(LocDevHandle,&Attributes);
		if ((Attributes.ProductID == USB_PID) &&
			(Attributes.VendorID == USB_VID)) 
			/* found the right device */
			/* is it the right HID collection? */
			HidD_GetPreparsedData(LocDevHandle, &PreparsedData);
			HidP_GetCaps(PreparsedData, &Capabilities);
			wprintf(L"%04x %04x\\n",Capabilities.UsagePage,Capabilities.Usage);
			if ((Capabilities.UsagePage == USB_USAGE_PAGE) &&
				(Capabilities.Usage == USB_USAGE)) 
					/* this is the correct HID collection */
				if (device_path!=NULL) 
					wcscpy(device_path,detailData->DevicePath);
				
#ifdef DEBUG				
				wprintf(L"Device Found\\n");
#endif
				result=1;
			
		
		/* close the device handle again */
		CloseHandle(LocDevHandle);
		/* and free the memory used to hold device info */
		free(detailData);
		/* try the next device */
		MemberIndex++;
	
	/* free memory used for the device information set */
	SetupDiDestroyDeviceInfoList(hDevInfo);
	return result;

CSR8670的USB-HID类设备描述符需改为:

/* usb_device_hid*/
/* HID Report Descriptor - HID Control Device */
static const uint8 report_descriptor_hid_control[] = 
   0x06, 0x00, 0xff,              /* USAGE_PAGE (Vendor Defined Page 1) */
    0x09, 0x01,                    /* USAGE (Vendor Usage 1) */
    0xa1, 0x01,                    /* COLLECTION (Application) */
    0x15, 0x80,                    /*   LOGICAL_MINIMUM (-128) */
    0x25, 0x7f,                    /*   LOGICAL_MAXIMUM (127) */
    0x85, 0x01,                    /*   REPORT_ID (1) */
    0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */
    0x95,                          /*   REPORT_COUNT */
    (REPORT_REQ_SIZE&0xff),
    0x75, 0x08,                    /*   REPORT_SIZE (8) */
    0x91, 0x02,                    /*   OUTPUT (Data,Var,Abs) */
    0x85, 0x02,                    /*   REPORT_ID (2) */
    0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */
    0x95,                          /*   REPORT_COUNT */
    (REPORT_RSP_SIZE&0xff),
    0x75, 0x08,                    /*   REPORT_SIZE (8) */
    0x81, 0x02,                    /*   INPUT (Data,Var,Abs) */
    /*0xb1, 0x02,*/
    
    0xc0                           /* END_COLLECTION */
;

设备描述符中包含了2个REPORT ID,REQ REPORT ID是host->device的消息,RSP REPORT ID是device->host的消息,类似于TX和RX。

REPORT ID的描述符如下:

#define HID_BUF_SIZE 29

typedef struct 
    uint8 report_id;
    uint8 report_len;
    uint8 report_buf[HID_BUF_SIZE];
 hid_req_t;

typedef struct 
    uint8 report_id;
    uint8 report_len;
    uint8 report_buf[HID_BUF_SIZE];
 hid_rsp_t;

#define REPORT_REQ_ID          1
#define REPORT_REQ_SIZE        ((sizeof(hid_req_t)/sizeof(uint8))-1)
#define REPORT_RSP_ID          2
#define REPORT_RSP_SIZE        ((sizeof(hid_rsp_t)/sizeof(uint8))-1)

2.2. OVERLAPPED方式发送REPORT_ID

通过USB-HID发送REPORT ID给device设备:

bool write(HANDLE hComm, hid_req_t * phid_send, DWORD * pCount)

	OVERLAPPED osWrite = 0;
	BOOL fRes;

	// Create this writes OVERLAPPED structure hEvent.
	osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (osWrite.hEvent == NULL)
		// Error creating overlapped event handle.
		return false;

	// Issue write.
	if (!WriteFile(hComm, phid_send, sizeof(hid_req_t), pCount, &osWrite)) 
	
		if (GetLastError() != ERROR_IO_PENDING) 
		 
			// WriteFile failed, but it isn't delayed. Report error and abort.
			fRes = FALSE;
		
		else 
		
			// Write is pending.

			#define WRITE_TIMEOUT 25 //write timeout
			DWORD dwRes = WaitForSingleObject(osWrite.hEvent, WRITE_TIMEOUT);
			switch(dwRes)
			
				// Read completed.
				case WAIT_OBJECT_0:
					if (!GetOverlappedResult(hComm, &osWrite, pCount, TRUE))
						fRes = FALSE;
					else
						// Write operation completed successfully.
						fRes = TRUE;
				case WAIT_TIMEOUT:
					// This is a good time to do some background work.
					break; 

				default:
					// Error in the WaitForSingleObject; abort.
					// This indicates a problem with the OVERLAPPED structure's
					// event handle.
					break;
			
		
	
	else
		// WriteFile completed immediately.
		fRes = TRUE;

	CloseHandle(osWrite.hEvent);
	return fRes;

2.3. OVERLAPPED方式接收REPORT_ID

通过USB-HID接收device设备发送给host设备的REPORT_ID:

bool read(HANDLE hComm, hid_rsp_t * phid_recv, DWORD * pCount)

	OVERLAPPED osReader = 0;
	BOOL fRes;

	// Create this read OVERLAPPED structure hEvent.
	osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (osReader.hEvent == NULL)
		// Error creating overlapped event handle.
		return false;

	// Issue read.
	if (!ReadFile(hComm, phid_recv, sizeof(hid_rsp_t), pCount, &osReader)) 
	
		if (GetLastError() != ERROR_IO_PENDING) 
		 
			// WriteFile failed, but it isn't delayed. Report error and abort.
			fRes = FALSE;
		
		else 
		
			// Read is pending.

			#define READ_TIMEOUT 25 //read timeout
			DWORD dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
			switch(dwRes)
			
				// Read completed.
				case WAIT_OBJECT_0:
					if (!GetOverlappedResult(hComm, &osReader, pCount, TRUE))
						fRes = FALSE;
					else
						// Write operation completed successfully.
						fRes = TRUE;
				case WAIT_TIMEOUT:
					// This is a good time to do some background work.
					break; 

				default:
					// Error in the WaitForSingleObject; abort.
					// This indicates a problem with the OVERLAPPED structure's
					// event handle.
					break;
			
		
	
	else
		// WriteFile completed immediately.
		fRes = TRUE;

	CloseHandle(osReader.hEvent);
	return fRes;

2.4. 参考示例进程

给出参考示例进程,可接收键盘输入的字符串后检索命令字典,检索到匹配项后发送REQ_REPORT_ID给device。device处理完后发送RSP_REPORT_ID给host设备。

while(1)

	if (_kbhit()) //从键盘接收字符输入
	
		key_val = _getch();
		printf("%c", key_val);
		if (key_val == '\\r') //收到回车符后作为完整字符串处理
		
			printf("\\n");
			key_buf[key_idx++] = '\\0';
			
			if (get_cmd_content(key_buf, key_idx, cmd_buf, &cmd_size)) //匹配命令字典
			
				memset(hid_req.report_buf, 0, HID_BUF_SIZE);//数组清零

				hid_req.report_id = REPORT_REQ_ID;
				hid_req.report_len = cmd_size;
				memcpy(hid_req.report_buf, cmd_buf, cmd_size);

				printf("PC->Dongle: report_id=%d ", hid_req.report_id);
				//printf("0x%02x ",hid_req.report_len);
				for(j=0;j<HID_BUF_SIZE;j++)
				
					//printf("0x%02x ",hid_req.report_buf[j]);
					printf("%c",hid_req.report_buf[j]);
				
				printf("\\n");
				error_code = write(DeviceHandle, &hid_req, &count); //调用OVERLAPPED写REPORT_ID
			
			else
			
				printf("no cmd match!\\n");
			

			key_idx = 0;
			memset(key_buf, 0, KEY_BUF_SIZE);
		
		else
		
			if (key_idx < KEY_BUF_SIZE)
			
				key_buf[key_idx++] = key_val;				
			
		
	

	memset(hid_rsp.report_buf, 0, HID_BUF_SIZE);//数组清零
	hid_rsp.report_id=0;

	error_code = read(DeviceHandle, &hid_rsp, &count);//调用OVERLAPPED函数读REPORT_ID
	if (hid_rsp.report_id == REPORT_RSP_ID)
	
		printf("Dongle->PC: report_id=%d ", hid_rsp.report_id);
		//printf("0x%02x ",hid_rsp.report_len);
		for(j=0;j<HID_BUF_SIZE;j++)
		
			printf("%c",hid_rsp.report_buf[j]);
		
		printf("\\n");
	

实际运行效果如下:

3. 总结

在此控制台的基础上,可以添加用户界面成为简易蓝牙发射终端的控制台,也可以作为模块集成到更大的系统中。

以上是关于一种全双工USB HID控制台的实现方法的主要内容,如果未能解决你的问题,请参考以下文章

spi和dmx区别

USB鼠标实现——HID 报告的返回

USB-HID的介绍

新买的USB解码音箱插入电脑后提示正在安装驱动,但“符合HID标准的用户控制设备”显示失败为啥?

求救!!usb hid设备(模拟键盘)遇到的问题

62 stm32 usb自定义hid复合设备修改实验