Linux 下使用libusb 与USB-HID 设备通讯之中断传输

时间:2021-02-06 16:12:01

一、前言

        上一篇文章记录到如何在ubuntu 安装开源项目libusb,这篇将记录,如下使用libusb 提供的api 方便的与USB-HID 设备通讯,通讯方式为中断传输。


二、中断传输方式原理,可以我写安卓的那边文章 Android USB HID bulkTransfer()参数解析,下面开始记录。

在libusb中关于中断传输所使用的api 与android 的api 有点相似,其实在android 关于USB-HOST通讯使用的api 底层就是谷歌移植好的libusb 通过JNI 调用的方式通讯。

三、相关API如下:

int LIBUSB_CALL libusb_init(libusb_context **ctx);
libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid(
	libusb_context *ctx, uint16_t vendor_id, uint16_t product_id);
int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle,
	int interface_number);
int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle,
	int interface_number);
int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev_handle,
	int interface_number);
int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev_handle,
	int interface_number);
int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle);
void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle);
libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle);
int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle,
	unsigned char endpoint, unsigned char *data, int length,
	int *actual_length, unsigned int timeout);
int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev,
	struct libusb_device_descriptor *desc);
int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev,
	uint8_t config_index, struct libusb_config_descriptor **config);
主要使用上述api, 具体使用方法可以查看相关的说明文档。


四、流程

我操作hid设备的一般步骤是,

a、根据设备的vid 和 pid搜索设备。

b、根据搜索到设备的信息,打开设备。

c、读写设备。

d、关闭设备。

五、根据VID 和PID 搜索设备,并获取设备接口描述符信息和端点描述符信息。

代码如下,修改libusb 提供的官方代码:

int FindDevice(libusb_device *dev)
{
	int error = 0, ik = 0, jk = 0, kk = 0, errorCode = -1;
	struct libusb_device_descriptor desc;
	//libusb_device_handle *handle = NULL;

	//获取设备的设备描述符
	error = libusb_get_device_descriptor(dev, &desc);
	if (error < 0) 
	{
		if(gUsbDebugEnable)
			PrintError("llibusb get device descriptor failed ", __FILE__, __FUNCTION__, __LINE__, error);

		return -1;
	}

	//根据设备描述符中的其他描述符个数,获取其他配置描述符
	for (ik = 0; ik < desc.bNumConfigurations; ik++) 
	{
		struct libusb_config_descriptor *config;

		//获取配置描述符
		error = libusb_get_config_descriptor(dev, ik, &config);
		if (LIBUSB_SUCCESS != error)
		{	
			if(gUsbDebugEnable)
				PrintError("libusb get config descriptor failed", __FILE__, __FUNCTION__, __LINE__, error);

			return -1;
		}

		if((desc.idProduct == DEVICE_PID_FOR_BULKTRANSFER) && (desc.idVendor == DEVICE_VID_FOR_BULKTRANSFER)) 	
		{
			if(gUsbDebugEnable)
				printf("\n pid = %.4X, vid = %.4X\n", desc.idProduct, desc.idVendor);
		
			//轮询接口描述符		
			for (jk = 0; jk < config->bNumInterfaces; jk++)
			{
				const struct libusb_interface *interface = &config->interface[jk];
				for (kk = 0; kk < interface->num_altsetting; kk++)
				{	
					//设备接口描述符信息,和输入输出端点
					if(GetUsbHidDeviceInterface(&interface->altsetting[kk])	== 0)	

					{
						mDeviceInterfaceType = 0;					
						errorCode = 0;			
					}
				}
			}
		}
		else if((desc.idProduct == DEVICE_PID_FOR_CONTRALTRANSFER) && (desc.idVendor == DEVICE_VID_FOR_CONTRALTRANSFER)) 	
		{
			if(gUsbDebugEnable)
				printf("\n pid = %.4X, vid = %.4X\n", desc.idProduct, desc.idVendor);
		
			mDeviceInterfaceType = 1;
			errorCode = 0;
		}
		
		libusb_free_config_descriptor(config);
	}
	
	/*
	if (handle)
		libusb_close(handle);*/

	return errorCode;
}
通过上面的函数,可以获取到设备的输入和输出端点信息,目的就是为了,下面读写设备时做铺垫。

六、打开设备

打开设备使用  libusb_open_device_with_vid_pid() 函数,

其中,第一个函数ctx 为 使用libusb_init()函数返回的上下文对象,

第二个参数和第三个参数为设备的VID 和 PID,这个两个参数可以从问做底层的工程师,也可以通过UsbViewtree 这个软件查看。

如下图:

Linux 下使用libusb 与USB-HID 设备通讯之中断传输


七、在上一个步骤中,已经打开USB 设备,获取到设备的句柄,接下来就开始读写设备,即与设备进行通讯,通讯分为两个方向

Host 主机 ----》 HID 设备   这个方向定义为输出

HID 设备 ----》Host 主机    这个方向定义为输入

设备虽然打开了,但是不能立马通讯,需要设备申请接口,因此在hid 中,系统是根据设备的接口进行数据的收发的。

此时代码如下:

if(libusb_kernel_driver_active(mHandleDev, 0) == 1)                //查看当前的接口是否被占用
{
	print("接口被占用", 0);
	if(libusb_detach_kernel_driver(mHandleDev, 0) == 0)        //被占用则解除
	{
	    print("解除接口占用", 0); 
	}
}
error = libusb_claim_interface(mHandleDev, 0);
if (error < 0) 
{
	print("设备申请接口失败 !", error);
	goto Out1;
}

设备申请好后,开始读写,使用的是libusb_bulk_tansfer()这个函数

int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle,unsigned char endpoint, unsigned char *data, int length,int *actual_length, unsigned int timeout);

这个函数是输入输出函数,也就是发送和接收都是使用这个函数,

其中何时为输入、何时为输出,取决于endpoint 这个参数,

当这个参数为输入端点时,则为接收(HID -> Host)

当这个参数为输出端点时,则为发送(Host -> HID)

参数解析:

(1) libusb_device_handle *dev_handle :  这个参数为设备句柄

(2) unsigned char endpoint: 通讯端点号,输入输出取决于它,这个端点号可以通过获取设备的输入输出端点描述符即可,

其中,端点描述符中,端点号的最高为可以区分这个端点是输入还是输出,如下:

if(endpoint->bEndpointAddress & 0x80)			//最高为表示为1 表示输入
{
	//输入端点	
}
else
{
	//输出端点
}

(3) unsigned char *data: 输入输出缓冲区

(4)  int length: 输入输出报告的长度,一般中断传输为64字节,这个值可以通过设备的端点描述符中获取,一般都是这么做的。

(5) int *actual_length: 实际数据收发的长度。

(6) int timeout: 通讯超时时间

上述的参数解析可以看出,这个函数与Android中的USB Host方法中的bulkTransfer()方法的参数一样。

  /**
     * Performs a bulk transaction on the given endpoint.
     * The direction of the transfer is determined by the direction of the endpoint.
     * <p>
     * This method transfers data starting from index 0 in the buffer.
     * To specify a different offset, use
     * {@link #bulkTransfer(UsbEndpoint, byte[], int, int, int)}.
     * </p>
     *
     * @param endpoint the endpoint for this transaction
     * @param buffer buffer for data to send or receive
     * @param length the length of the data to send or receive
     * @param timeout in milliseconds
     * @return length of data transferred (or zero) for success,
     * or negative value for failure
     */
    public int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout);

可见,Android usb 底层应该就是libusb.Linux 下使用libusb 与USB-HID 设备通讯之中断传输


八、关闭设备

系统资源,用完记得关闭

int CloseUsbBulkTransferDevice()
{
	if(mHandleDev != NULL)
	{
		libusb_release_interface(mHandleDev, mInterface.bInterfaceNumber);
		libusb_close(mHandleDev);	
	}

	if(ctxForBulkTransfer != NULL)
		libusb_exit(ctxForBulkTransfer);

	return DEVICE_ERROR_OK;
}

好了,libusb中,关于与中断传输的部分就是这么多。

其实,关于设备接口描述符和获取接口及其端点描述符还有一部分需要详细说的,

关于这部分具体的介绍请看我的另一篇博客,<USB-HID设备中的复合设备>有关于接口获取详细说明。