雷达智富

首页 > 内容 > 程序笔记 > 正文

程序笔记

Linux中libusb读写CDC类型USB设备的代码及解析

2024-07-30 154

前言

在Linux中对USB类型的CDC设备,默认识别为一个USBTTY设备,也就是串口设备,可以使用串口通讯的方式进行通讯,但在USB固件开发时,CDC类型的设备更容易实现,参考的代码也更多,实际并不是一个串口设备,或者想通过USB的EndPoint调用libusb的API直接进行通讯。

参考代码

以下是在linux中直接调用libusb进行USB通讯的参考代码:

/*
 * This is a simple example to communicate with a CDC-ACM USB device
 * using libusb.
 */
#include unistd.h
#include stdlib.h
#include stdio.h
#include errno.h

#include libusb-1.0/libusb.h

/* You may want to change the VENDOR_ID and PRODUCT_ID
 * depending on your device.
 */
#define VENDOR_ID      0x2341   // Arduino LLC
#define PRODUCT_ID     0x0034   // Arduino Leonardo

#define ACM_CTRL_DTR   0x01
#define ACM_CTRL_RTS   0x02

/* We use a global variable to keep the device handle
 */
static struct libusb_device_handle *devh = NULL;

/* The Endpoint address are hard coded. You should use lsusb -v to find
 * the values corresponding to your device.
 */
static int ep_in_addr  = 0x83;
static int ep_out_addr = 0x02;

void write_char(unsigned char c)
{
    /* To send a char to the device simply initiate a bulk_transfer to the
     * Endpoint with address ep_out_addr.
     */
    int actual_length;
    if (libusb_bulk_transfer(devh, ep_out_addr, c, 1,
                             actual_length, 0)  0) {
        fprintf(stderr, Error while sending char\n);
    }
}

int read_chars(unsigned char * data, int size)
{
    /* To receive characters from the device initiate a bulk_transfer to the
     * Endpoint with address ep_in_addr.
     */
    int actual_length;
    int rc = libusb_bulk_transfer(devh, ep_in_addr, data, size, actual_length,
                                  1000);
    if (rc == LIBUSB_ERROR_TIMEOUT) {
        printf(timeout (%d)\n, actual_length);
        return -1;
    } else if (rc  0) {
        fprintf(stderr, Error while waiting for char\n);
        return -1;
    }

    return actual_length;
}

int main(int argc, char **argv)
{
    int rc;

    /* Initialize libusb
     */
    rc = libusb_init(NULL);
    if (rc  0) {
        fprintf(stderr, Error initializing libusb: %s\n, libusb_error_name(rc));
        exit(1);
    }

    /* Set debugging output to max level.
     */
    libusb_set_debug(NULL, 3);

    /* Look for a specific device and open it.
     */
    devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);
    if (!devh) {
        fprintf(stderr, Error finding USB device\n);
        goto out;
    }

    /* As we are dealing with a CDC-ACM device, its highly probable that
     * Linux already attached the cdc-acm driver to this device.
     * We need to detach the drivers from all the USB interfaces. The CDC-ACM
     * Class defines two interfaces: the Control interface and the
     * Data interface.
     */
    for (int if_num = 0; if_num  2; if_num++) {
        if (libusb_kernel_driver_active(devh, if_num)) {
            libusb_detach_kernel_driver(devh, if_num);
        }
        rc = libusb_claim_interface(devh, if_num);
        if (rc  0) {
            fprintf(stderr, Error claiming interface: %s\n,
                    libusb_error_name(rc));
            goto out;
        }
    }

    /* Start configuring the device:
     * - set line state
     */
    rc = libusb_control_transfer(devh, 0x21, 0x22, ACM_CTRL_DTR | ACM_CTRL_RTS,
                                0, NULL, 0, 0);
    if (rc  0) {
        fprintf(stderr, Error during control transfer: %s\n,
                libusb_error_name(rc));
    }

    /* - set line encoding: here 9600 8N1
     * 9600 = 0x2580 ~ 0x80, 0x25 in little endian
     */
    unsigned char encoding[] = { 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
    rc = libusb_control_transfer(devh, 0x21, 0x20, 0, 0, encoding,
                                sizeof(encoding), 0);
    if (rc  0) {
        fprintf(stderr, Error during control transfer: %s\n,
                libusb_error_name(rc));
    }

    /* We can now start sending or receiving data to the device
     */
    unsigned char buf[65];
    int len;
    
    while(1) {
        write_char(t);
        len = read_chars(buf, 64);
        buf[len] = 0;
        fprintf(stdout, Received: \%s\\n, buf);
        sleep(1);
    }

    libusb_release_interface(devh, 0);

out:
    if (devh)
            libusb_close(devh);
    libusb_exit(NULL);
    return rc;
}

代码解析

以上代码,初始化init和open都与正常调用libusb设备一样,不同的是如下代码:

    for (int if_num = 0; if_num  2; if_num++) {
        if (libusb_kernel_driver_active(devh, if_num)) {
            libusb_detach_kernel_driver(devh, if_num);
        }
        rc = libusb_claim_interface(devh, if_num);
        if (rc  0) {
            fprintf(stderr, Error claiming interface: %s\n,
                    libusb_error_name(rc));
            goto out;
        }
    }

由于我们要使用CDC-ACM设备,很可能Linux已经将cdc-acm驱动程序连接到此设备,我们需要从所有USB接口上分离驱动程序。这里CDC-ACM类定义了两个接口:Control接口和数据接口。所以,这里循环遍历两个接口,将接口已经配置的内核驱动进行分离,然后claim接口就可以调用libusb与设备进行通讯了。

然后,再调用libusb_control_transfer对串口参数进行配置(如果需要)后,就可以试用libusb_bulk_transfer进行数据传输了。

更新于:3个月前
赞一波!1

文章评论

评论问答