libusb使用详解七(USB设备的数据传输之异步模式)

该文章已生成可运行项目,

通过libusb实现USB设备数据的异步传输主要有如下几步:

  1. 创建libusb_transfer对象
  2. 根据不同的传输方式填充相应信息给libusb_transfer对象
  3. 提交libusb_transfer对象给libusb API进行执行
  4. 在传输回调函数中检查libusb_transfer对象中的状态信息
  5. 释放libusb_transfer对象

下面我们来依次介绍

1. 创建libusb_transfer对象

可以使用libusb_alloc_transfer函数来创建libusb_transfer对象,函数定义如下:

struct libusb_transfer * libusb_alloc_transfer (int iso_packets)

其中:
iso_packets:用于指明在实时传输时需要传输的包的数量。对于其它三种传输方式设为0即可
返回值:libusb_transfer对象指针

2. 填充libusb_transfer对象

填充libusb_transfer对象的函数根据传输方式各不相同。

2.1 控制传输

控制传输主要使用libusb_fill_control_setup和libusb_fill_control_transfer函数把控制传输所需信息填充到libusb_transfer对象中。

static void libusb_fill_control_setup (
    unsigned char *buffer,
    uint8_t bmRequestType,
    uint8_t bRequest,
    uint16_t wValue,
    uint16_t wIndex,
    uint16_t wLength)

大多数的参数和同步模式传输函数libusb_control_transfer中的类似,可参考上一节的内容。其中:
buffer:为待生成的Setup命令的Buffer指针,大小必须是2的倍数
bmRequestType:  为Control Setup包的request type部分
bRequest: 为Control Setup包的request命令
wValue: 为Control Setup包的value部分,该值需要根据Request命令设定相应的值
wIndex: 为Control Setup包的index部分,设置方式和wValue类似
wLength:为Control Setup包请求读取数据时传入buffer的最大长度,只有从USB设备读取数据时需要。

而后需要使用函数libusb_fill_control_transfer把使用libusb_fill_control_setup构建的Setup包和额外数据buffer填充给libusb_transfer对象。libusb_fill_control_transfer函数定义如下:

static void libusb_fill_control_transfer (
    struct libusb_transfer *transfer,
    libusb_device_handle *dev_handle,
    unsigned char *buffer,
    libusb_transfer_cb_fn callback,
    void *user_data,
    unsigned int timeout)

其中:
transfer:为待填充的libusb_transfer对象,后续不再赘述
dev_handle:为待传输的USB设备的设备句柄,后续不再赘述
buffer: 为刚刚构建的Setup包和额外的数据Buffer,额外的数据Buffer追加在Setup包后
callback: 为本次传输的回调函数,关于libusb_transfer_cb_fn回调函数后续会详细介绍
user_data: 为传入回调函数的user data
timeout:为传输的超时时间,单位ms。0表示永不超时

2.2 中断传输

中断传输使用libusb_fill_interrupt_transfer来填充信息到libusb_transfer对象中。函数定义如下:

static void libusb_fill_interrupt_transfer (
    struct libusb_transfer *transfer,
    libusb_device_handle *dev_handle,
    unsigned char endpoint,
    unsigned char *buffer,
    int length,
    libusb_transfer_cb_fn callback,
    void *user_data,
    unsigned int timeout)

其中:
endpoint:为端口地址
buffer:为待传输的buffer指针
length:为待传输buffer的长度
callback:为本次传输的回调函数
user_data: 为传入回调函数的user data
timeout:为传输的超时时间,单位ms。0表示永不超时

2.3 批量传输

批量传输使用libusb_fill_bulk_transfer来填充信息到libusb_transfer对象中。函数定义如下:

static void libusb_fill_bulk_transfer (
    struct libusb_transfer *transfer,
    libusb_device_handle *dev_handle,
    unsigned char endpoint,
    unsigned char *buffer,
    int length,
    libusb_transfer_cb_fn callback,
    void *user_data,
    unsigned int timeout)

函数参数和中断传输使用的函数libusb_fill_interrupt_transfer中的参数类似,这里不再赘述。

2.4 实时传输

实时传输使用libusb_fill_iso_transfer来填充信息到libusb_transfer对象中,然后需要使用libusb_set_iso_packet_lengths设定每个iso包的长度。

libusb_fill_iso_transfer函数定义如下:

static void libusb_fill_iso_transfer (
    struct libusb_transfer *transfer,
    libusb_device_handle *dev_handle,
    unsigned char endpoint,
    unsigned char *buffer,
    int length,
    int num_iso_packets,
    libusb_transfer_cb_fn callback,
    void *user_data,
    unsigned int timeout)

和前面两种传输函数相比,只多了num_iso_packets参数。它用于指定待传输的iso包的个数。并且该参数也影响libusb_set_iso_packet_lengths函数需要设定的值。

libusb_set_iso_packet_lengths函数的定义如下:

static void libusb_set_iso_packet_lengths (
    struct libusb_transfer *transfer,
    unsigned int length)

其中:
length:用于设定iso包的长度,一般此长度等于libusb_fill_iso_transfer函数中设定的待传输buffer的长度除以设定的iso包个数num_iso_packets。

3. 提交libusb_transfer对象

我们可以使用libusb_submit_transfer函数来把填充了传输信息的libusb_transfer对象提交给libusb API进行执行。libusb_submit_transfer函数定义如下:

int libusb_submit_transfer (struct libusb_transfer *transfer)

函数很简单,传入一个libusb_transfer对象。返回值为LIBUSB_SUCCESS表示成功,否则返回一个LIBUSB_ERROR_XXX的错误码。

4. 检查回调函数libusb_transfer_cb_fn

libusb通过在各个填充函数中传入的回调函数libusb_transfer_cb_fn来返回libusb_transfer对象的执行情况。libusb_transfer_cb_fn回调函数的定义如下:

typedef void(* libusb_transfer_cb_fn) (struct libusb_transfer *transfer)

所以在回调发生时,它会传回libusb_transfer对象。传输的结果就保存在libusb_transfer对象中。所以让我们看看libusb_transfer对象的定义:

struct libusb_transfer {
	libusb_device_handle *dev_handle;
	uint8_t flags;
	unsigned char endpoint;
	unsigned char type;
	unsigned int timeout;
	enum libusb_transfer_status status;
	int length;
	int actual_length;
	libusb_transfer_cb_fn callback;
	void *user_data;
	unsigned char *buffer;
	int num_iso_packets;
	struct libusb_iso_packet_descriptor iso_packet_desc[LIBUSB_FLEXIBLE_ARRAY]
};

其中的成员变量大多是通过填充函数进行填充的:
dev_handle:就是填充函数提供的相关USB设备的句柄
flags:用于手动设定一些特殊的传输特性,这些特性可以使用bitwise或来进行组合。比如,LIBUSB_TRANSFER_FREE_BUFFER: 设定在调用libusb_free_transfer时自动free传输的buffer指针;LIBUSB_TRANSFER_FREE_TRANSFER: 在回调结束时自动调用libusb_free_transfer释放libusb_transfer对象。
endpoint: 就是填充函数提供的端口地址
type: 表示此libusb_transfer对象的传输方式,控制(LIBUSB_TRANSFER_TYPE_CONTROL )、中断(LIBUSB_TRANSFER_TYPE_INTERRUPT)、批量(LIBUSB_TRANSFER_TYPE_BULK)、批量流(LIBUSB_TRANSFER_TYPE_BULK_STREAM)和实时(LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
buffer,length:分别是填充的待传输buffer指针和buffer最大长度
actual_length:是buffer实际传输长度
callback:就是填充的回调函数
status: 表示传输结果(对于实时传输来说,还需要查看各个iso包的状态)。LIBUSB_TRANSFER_COMPLETED表示传输成功,LIBUSB_TRANSFER_ERROR表示传输失败。具体可参见libusb_transfer_status枚举类型
num_iso_packets:就是填充的iso包个数
iso_packet_desc: 用于表示每个iso包的传输信息
iso_packet_desc->status:表示单个iso包的传输结果
iso_packet_desc->length:  表示填充时设定的单个iso包的长度
iso_packet_desc->actual_length: 表示实际传输的iso包的长度

我们在处理回调函数时,一般通过读取libusb_transfer对象中的status变量来判断传输结果。对于实时传输,则还需要判断iso_packet_desc中各个iso包的结果来进行判断。如果是读取传输则需要复制buffer指针所指向的内容,复制的长度由actual_length决定。

5. 释放libusb_transfer对象

上面已经提到过,我们通过libusb_free_transfer函数来释放libusb_transfer对象,函数定义如下:

void libusb_free_transfer (struct libusb_transfer *transfer)

上面5步就是整个异步传输过程。还需要注意一点:异步传输因为和USB Hotplug一样使用回调函数来处理异步操作,所以必须另起线程调用Event处理函数libusb_handle_events系列函数来保证回调函数的正常工作。并且需要注意它和Hotplug事件是合用一个Event处理函数的,所以只有两者都停止的情况下才能退出Event处理线程。
当然如果你喜欢使用单一线程控制整个USB处理,也可以把Event处理过程放在主线程中。还可以使用libusb的libusb_get_pollfds函数获取需要监听的fd列表,然后使用polling方式来监听回调事件,然后调用libusb_handle_events系列函数来触发回调,但注意这个方法并不适用于Windows平台。

至此,整个异步传输的实现方法已介绍完毕。下面是几个使用异步模式实现的例程:

1)异步模式控制传输:

static void LIBUSB_CALL cb_mode_changed(struct libusb_transfer *transfer)
{
	if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
		fprintf(stderr, "mode change transfer not completed!\n");
		request_exit(2);
	}

	printf("async cb_mode_changed length=%d actual_length=%d\n",
		transfer->length, transfer->actual_length);
	if (next_state() < 0)
		request_exit(2);

    libusb_free_transfer(transfer);
}

static int set_mode_async(unsigned char data) {
	unsigned char *buf = malloc(LIBUSB_CONTROL_SETUP_SIZE + 1);
	struct libusb_transfer *transfer;

	if (!buf) {
		errno = ENOMEM;
		return -1;
	}

	transfer = libusb_alloc_transfer(0);
	if (!transfer) {
		free(buf);
		errno = ENOMEM;
		return -1;
	}

	printf("async set mode %02x\n", data);
	libusb_fill_control_setup(buf, CTRL_OUT, USB_RQ, 0x4e, 0, 1);
	buf[LIBUSB_CONTROL_SETUP_SIZE] = data;
	libusb_fill_control_transfer(transfer, devh, buf, cb_mode_changed, NULL,
		1000);

	transfer->flags = LIBUSB_TRANSFER_SHORT_NOT_OK
		| LIBUSB_TRANSFER_FREE_BUFFER | LIBUSB_TRANSFER_FREE_TRANSFER;
	return libusb_submit_transfer(transfer);
}

2)  异步模式中断传输

void InterruptTransPktAsync(u8 ep, u8* buffer, int length, u32 timeout) {
    if (mDevHandle == nullptr) {
        LOGE("Failed to transfer packet async as device isn't initialized properly");
        return;
    }
    libusb_transfer *transfer = libusb_alloc_transfer(0);
    if (transfer == nullptr) {
        LOGE("Failed to alloc usb transfer object");
        return;
    }

    libusb_fill_interrupt_transfer(transfer, mDevHandle, ep, buffer, length, [](libusb_transfer *transfer){
        if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
            LOGE("Transfer packet async failed, status: %d", transfer->status);
        } else {
            if ((transfer->endpoint & 0x80) == LIBUSB_ENDPOINT_IN) {
                LOGD("Input packet async successfully, data: %s", bufferToString(transfer->buffer, transfer->actual_length).c_str());
            } else if ((transfer->endpoint & 0x80) == LIBUSB_ENDPOINT_OUT) {
                LOGD("Ouput packet async successfully, length: %d", transfer->actual_length);
            }
        }
        libusb_free_transfer(transfer);
    }, nullptr, timeout);

    int ret = libusb_submit_transfer(transfer);
    if (ret != LIBUSB_SUCCESS) {
        LOGE("Failed to submit transfer packet, error %d", ret);
    }
}

3)异步模式实时传输

static void LIBUSB_CALL cb_xfr(struct libusb_transfer *xfr)
{
	int i;

	if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
		fprintf(stderr, "transfer status %d\n", xfr->status);
		libusb_free_transfer(xfr);
		exit(3);
	}

	if (xfr->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
		for (i = 0; i < xfr->num_iso_packets; i++) {
			struct libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[i];

			if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
				fprintf(stderr, "Error: pack %d status %d\n", i, pack->status);
				exit(5);
			}

			printf("pack%d length:%u, actual_length:%u\n", i, pack->length, pack->actual_length);
		}
	}

	printf("length:%u, actual_length:%u\n", xfr->length, xfr->actual_length);
	for (i = 0; i < xfr->actual_length; i++) {
		printf("%02x", xfr->buffer[i]);
		if (i % 16)
			printf("\n");
		else if (i % 8)
			printf("  ");
		else
			printf(" ");
	}
	num_bytes += xfr->actual_length;
	num_xfer++;

	if (libusb_submit_transfer(xfr) < 0) {
		fprintf(stderr, "error re-submitting URB\n");
		exit(1);
	}
}

static int benchmark_in(uint8_t ep)
{
	static uint8_t buf[2048];
	static struct libusb_transfer *xfr;
	int num_iso_pack = 0;

	if (ep == EP_ISO_IN)
		num_iso_pack = 16;

	xfr = libusb_alloc_transfer(num_iso_pack);
	if (!xfr) {
		errno = ENOMEM;
		return -1;
	}

	if (ep == EP_ISO_IN) {
		libusb_fill_iso_transfer(xfr, devh, ep, buf,
				sizeof(buf), num_iso_pack, cb_xfr, NULL, 0);
		libusb_set_iso_packet_lengths(xfr, sizeof(buf)/num_iso_pack);
	} else
		libusb_fill_bulk_transfer(xfr, devh, ep, buf,
				sizeof(buf), cb_xfr, NULL, 0);

	get_timestamp(&tv_start);

	return libusb_submit_transfer(xfr);
}
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值