程式語言 - GNU - C/C++ - Virtual /dev/ttyUSB(PL2303)



假如Linux系統上沒有USB Serial Kernel Module,可以透過libusb + PTY做USB Bridge轉換

main.c

#define _GNU_SOURCE
#include <libusb-1.0/libusb.h>
#include <pty.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <termios.h>

#define VID     0x067b
#define PID     0x2303
#define IFACE   0x00
#define EP_OUT  0x02
#define EP_IN   0x83

#define BUF_SIZE 512

void pl2303_set_line_coding(libusb_device_handle *dev)
{
    unsigned char buf[7];
    unsigned int baud = 115200;

    buf[0] = baud & 0xff;
    buf[1] = (baud >> 8) & 0xff;
    buf[2] = (baud >> 16) & 0xff;
    buf[3] = (baud >> 24) & 0xff;
    buf[4] = 0; // stop bits: 0 = 1
    buf[5] = 0; // parity: 0 = none
    buf[6] = 8; // data bits: 8

    int r = libusb_control_transfer(dev,
        LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
        0x20,
        0,
        IFACE,
        buf,
        sizeof(buf),
        1000
    );

    if (r < 0) {
        fprintf(stderr, "Failed SET_LINE_CODING: %d\n", r);
        exit(1);
    }
}

void pl2303_init(libusb_device_handle *dev)
{
    int r = libusb_control_transfer(dev,
        LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
        0x22,
        0x0003, // DTR + RTS
        IFACE,
        NULL, 0,
        1000);

    if (r < 0) {
        fprintf(stderr, "SET_CONTROL_LINE_STATE failed: %d\n", r);
        exit(1);
    }

    pl2303_set_line_coding(dev);
}

int main(void)
{
    libusb_context *ctx = NULL;
    libusb_device_handle *dev;
    int pty_master;
    int pty_slave;
    char pty_name[64];
    unsigned char buf[BUF_SIZE];

    libusb_init(&ctx);
    dev = libusb_open_device_with_vid_pid(ctx, VID, PID);
    if (!dev) {
        fprintf(stderr, "USB device not found\n");
        return 1;
    }

    libusb_detach_kernel_driver(dev, IFACE);
    libusb_claim_interface(dev, IFACE);

    pl2303_init(dev);

    if (openpty(&pty_master, &pty_slave, pty_name, NULL, NULL) < 0) {
        perror("openpty");
        return 1;
    }
    printf("TTY available at: %s\n", pty_name);

    struct pollfd fds[1];
    fds[0].fd = pty_master;
    fds[0].events = POLLIN;

    while (1) {
        int r = poll(fds, 1, 10);
        if (r > 0 && (fds[0].revents & POLLIN)) {
            int n = read(pty_master, buf, BUF_SIZE);
            if (n > 0) {
                int sent;
                libusb_bulk_transfer(dev, EP_OUT, buf, n, &sent, 0);
            }
        }

        int received;
        int rc = libusb_bulk_transfer(dev, EP_IN, buf, BUF_SIZE, &received, 10);
        if (rc == 0 && received > 0) {
            write(pty_master, buf, received);
        }
    }

    libusb_release_interface(dev, IFACE);
    libusb_close(dev);
    libusb_exit(ctx);
    return 0;
}

編譯、測試

$ gcc main.c -o usb_pl2303_bridge -lusb-1.0
$ sudo ./usb_pl2303_bridge &
    TTY available at: /dev/pts/2

$ lsusb -v -d 067b:2303 | egrep -e "EP|Transfer"
        bEndpointAddress     0x81  EP 1 IN
          Transfer Type            Interrupt
        bEndpointAddress     0x02  EP 2 OUT
          Transfer Type            Bulk
        bEndpointAddress     0x83  EP 3 IN
          Transfer Type            Bulk

$ sudo ln -s /dev/pts/2 /dev/ttyUSB9999
$ sudo minicom -D /dev/ttyUSB9999 -b 115200