You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

libusb_bulk_transfer仅接收1字节问题排查与解决咨询

USB Bulk传输仅读取单字节数据的问题排查与解决

问题背景

使用libusb实现USB设备数据读取时,99%的场景下每次libusb_bulk_transfer仅能读取1字节数据,但设备实际会发送19-20字节的完整响应。

测试代码

#include <iostream>
#include <libusb.h>
#include <string.h>

#define VENDOR_ID      0x10C4
#define PRODUCT_ID     0xEA60
#define IN_ENDPOINT    0x81

int main(int argc, char *argv[])
{
    int err = libusb_init(NULL);

    libusb_device_handle *device_handle = NULL;
    unsigned char value_str[64];
    device_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);

    libusb_set_auto_detach_kernel_driver(device_handle, 1);

    err = libusb_claim_interface(device_handle, 0);

    int transfered = 0;
    unsigned char out[6] = {'A', 'T', '$', 'S', 'R', '\r'};
    libusb_bulk_transfer(device_handle, 0x01, out, 6, &transfered, 0);

    int res = libusb_bulk_transfer(device_handle, IN_ENDPOINT, value_str, 64, &transfered, 0);

    while (res == 0)
    {
        if (transfered == 0) continue;
        std::cout << "Reading Successful: ";

        for (int i = 0; i < transfered; i++) 
        {
            std::cout << value_str[i];
        }

        std::cout << std::endl;
        res = libusb_bulk_transfer(device_handle, IN_ENDPOINT, value_str, 64, &transfered, 0);
    }

    libusb_release_interface(device_handle, 0);
    libusb_close(device_handle);

    libusb_exit(NULL);
    return 0;
}

实际输出

Reading Successful: A
Reading Successful: T
Reading Successful: $
Reading Successful: S
Reading Successful: R
Reading Successful:
Reading Successful: -
Reading Successful: 4
Reading Successful: 1
Reading Successful: 8
Reading Successful: 0
Reading Successful: 0
Reading Successful: 0
Reading Successful: 7
Reading Successful: 9
Reading Successful: 5
Reading Successful: x
Reading Successful: x
Reading Successful: x
Reading Successful: :
Reading Successful: h
Reading Successful: e
Reading Successful: l
Reading Successful: p
Reading Successful: :
Reading Successful: b
Reading Successful: a
Reading Successful: t
Reading Successful: o
Reading Successful: k
Reading Successful:
Reading Successful:

原因分析

  • USB Bulk传输的分包特性:Bulk端点传输时,libusb_bulk_transfer默认收到任意长度的有效数据包就立即返回,不会等待填满请求的缓冲区。设备可能将完整响应拆分为多个小数据包(甚至单字节包)发送,这完全符合USB规范。
  • 设备硬件逻辑:你的设备是Silicon Labs的CP210x串口转USB芯片(VID 0x10C4/PID 0xEA60),这类设备默认配置为“每收到串口的一个字节就立即打包成USB包转发”,而非积累到一定长度再发送。

解决方案

方式1:缓冲区拼接+超时判断(通用方案)

修改读取逻辑,循环累加数据直到超时(代表设备停止发送),或匹配到响应结束标记(比如换行符),最终拼接出完整响应。

修改后的示例代码:

#include <iostream>
#include <libusb.h>
#include <string.h>

#define VENDOR_ID          0x10C4
#define PRODUCT_ID         0xEA60
#define IN_ENDPOINT        0x81
#define RESPONSE_TIMEOUT   1000  // 超时时间,单位ms
#define MAX_RESPONSE_LEN   64

int main(int argc, char *argv[])
{
    int err = libusb_init(NULL);
    if (err != 0) {
        std::cerr << "libusb初始化失败: " << libusb_error_name(err) << std::endl;
        return 1;
    }

    libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);
    if (!device_handle) {
        std::cerr << "打开设备失败" << std::endl;
        libusb_exit(NULL);
        return 1;
    }

    libusb_set_auto_detach_kernel_driver(device_handle, 1);
    err = libusb_claim_interface(device_handle, 0);
    if (err != 0) {
        std::cerr << "抢占接口失败: " << libusb_error_name(err) << std::endl;
        libusb_close(device_handle);
        libusb_exit(NULL);
        return 1;
    }

    // 发送指令
    int transfered = 0;
    unsigned char out[6] = {'A', 'T', '$', 'S', 'R', '\r'};
    err = libusb_bulk_transfer(device_handle, 0x01, out, 6, &transfered, 0);
    if (err != 0) {
        std::cerr << "发送指令失败: " << libusb_error_name(err) << std::endl;
        libusb_release_interface(device_handle, 0);
        libusb_close(device_handle);
        libusb_exit(NULL);
        return 1;
    }

    unsigned char full_response[MAX_RESPONSE_LEN] = {0};
    int total_read = 0;
    int res;
    unsigned char buffer[64];

    // 循环读取直到超时或达到最大长度
    while (total_read < MAX_RESPONSE_LEN) {
        res = libusb_bulk_transfer(device_handle, IN_ENDPOINT, buffer, 64, &transfered, RESPONSE_TIMEOUT);
        if (res == LIBUSB_ERROR_TIMEOUT) {
            // 超时表示设备已停止发送数据
            break;
        } else if (res != 0) {
            std::cerr << "读取失败: " << libusb_error_name(res) << std::endl;
            break;
        }

        if (transfered > 0) {
            memcpy(full_response + total_read, buffer, transfered);
            total_read += transfered;

            // 可选:如果响应有结束标记(比如换行),提前终止读取
            for (int i = 0; i < transfered; i++) {
                if (buffer[i] == '\n' || buffer[i] == '\r') {
                    goto end_read;
                }
            }
        }
    }
end_read:

    // 输出完整响应
    std::cout << "完整响应:\n";
    for (int i = 0; i < total_read; i++) {
        std::cout << full_response[i];
    }
    std::cout << std::endl;

    // 释放资源
    libusb_release_interface(device_handle, 0);
    libusb_close(device_handle);
    libusb_exit(NULL);
    return 0;
}

方式2:修改设备串口缓冲配置(针对CP210x芯片)

利用CP210x的厂商自定义控制请求,修改设备的“发送缓冲区触发阈值”,让设备积累指定字节数后再发送USB包,减少分包次数。

在libusb_claim_interface成功后添加以下代码:

// 设置CP210x发送缓冲区触发阈值为20字节(积累20字节再发送)
err = libusb_control_transfer(device_handle, 
    LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT,
    0x02, 0x0014, 0x0000, NULL, 0, 1000);
if (err != 0) {
    std::cerr << "设置缓冲阈值失败: " << libusb_error_name(err) << std::endl;
}

内容的提问来源于stack exchange,提问作者rudolfninja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 02:53:09