• Qt for Android : 使用libusb做CH340x串口传输的底层USB库


    简介

    Qt for Android自带的串口方案并没有适用在高的API版本中, 会出现permission denied的访问问题, 所以就需要使用Android API, 也就是在CPP中使用JNI方式进行调用, 为了开发的方便, 使用libusb库作为替代的底层usb传输是比较合适的, 这里基于 Qt for android 串口库使用 这篇所说的java 编写的ch340x的库简单修改成调用libusb进行串口通讯的方法验证。

    代码

    别说一上来就是代码, 其实翻译有啥步骤, 上来就是撸代码

    ch340x.h

    #ifndef CH340X_H
    #define CH340X_H
    
    #include "libusb/libusb.h"
    #include 
    
    class CH340X
    {
    public:
        enum ControlLine { RTS, CTS,  DTR, DSR,  CD, RI };
        enum DataBits
        {
            DATABITS_5 = 5,
            DATABITS_6 = 6,
            DATABITS_7 = 7,
            DATABITS_8 = 8,
        };
        enum Parity
        {
            PARITY_NONE = 0,
            PARITY_ODD = 1,
            PARITY_EVEN = 2,
            PARITY_MARK = 3,
            PARITY_SPACE = 4,
        };
    
        enum STOPBITS
        {
            STOPBITS_1 = 1,
            STOPBITS_1_5 = 3,
            STOPBITS_2 = 2,
        };
    
        CH340X(int fd=-1);
        ~CH340X();
    
        inline bool isValid() { return m_isValid; }
        int send(unsigned char *src, int length, int timeout);
    
    private:
        int controlOut(int request, int value, int index);
        int controlIn(int request, int value, int index, unsigned char *buffer, uint16_t length);;
        int setControlLines();
        int getStatus(char &status);
        int checkState(int request, int value,
                       char *expected,
                       unsigned int length);
        int setBaudRate(int baudRate);
        int setParameters(int baudRate, int dataBits, int stopBits, int parity);
    
        int init_ch34x(int fd);
    
    
    private:
        bool m_isValid = false;
        struct libusb_device_handle *devh = NULL;
    };
    
    #endif // CH340X_H
    
    

    ch340x.cpp

    #include "ch340x.h"
    
    #include 
    
    #include "libusb/libusb.h"
    
    static int LCR_ENABLE_RX   = 0x80;
    static int LCR_ENABLE_TX   = 0x40;
    static int LCR_MARK_SPACE  = 0x20;
    static int LCR_PAR_EVEN    = 0x10;
    static int LCR_ENABLE_PAR  = 0x08;
    static int LCR_STOP_BITS_2 = 0x04;
    static int LCR_CS8         = 0x03;
    static int LCR_CS7         = 0x02;
    static int LCR_CS6         = 0x01;
    static int LCR_CS5         = 0x00;
    
    static int GCL_CTS = 0x01;
    static int GCL_DSR = 0x02;
    static int GCL_RI  = 0x04;
    static int GCL_CD  = 0x08;
    static int SCL_DTR = 0x20;
    static int SCL_RTS = 0x40;
    
    int USB_TIMEOUT_MILLIS = 5000;
    int DEFAULT_BAUD_RATE = 9600;
    bool dtr = false;
    bool rts = false;
    
    #define USB_TYPE_VENDOR 0x40
    #define USB_DIR_OUT 0x0
    // #define EP_DATA_IN        (0x2|LIBUSB_ENDPOINT_IN)
    #define EP_DATA_OUT       (0x2|LIBUSB_ENDPOINT_OUT)
    #define EP_INTR			(1 | LIBUSB_ENDPOINT_IN)
    #define EP_DATA			(2 | LIBUSB_ENDPOINT_IN)
    #define CTRL_IN			(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN)
    #define CTRL_OUT		(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT)
    #define USB_RQ			0x04
    #define INTR_LENGTH		64
    
    
    
    
    CH340X::CH340X(int fd)
    {
        m_isValid = (init_ch34x(fd) == 0);
    }
    
    CH340X::~CH340X()
    {
        // if (recv_bulk_transfer)
        // {
        //     libusb_cancel_transfer(recv_bulk_transfer);
        //     libusb_free_transfer(recv_bulk_transfer);
        // }
    
        if (devh)
        {
            libusb_release_interface(devh, 0);
            libusb_close(devh);
            libusb_exit(NULL);
        }
    }
    
    int CH340X::controlOut(int request, int value, int index)
    {
        return libusb_control_transfer(devh, CTRL_OUT, request, value, index, 0, 0, USB_TIMEOUT_MILLIS);
    }
    
    int CH340X::controlIn(int request, int value, int index, unsigned char *buffer, uint16_t length)
    {
        return libusb_control_transfer(devh, CTRL_IN, request, value, index, buffer, length, USB_TIMEOUT_MILLIS);
    }
    
    int CH340X::setControlLines()
    {
        return controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0);
    }
    
    int CH340X::getStatus(char &status)
    {
        char buffer[2];
        int ret = controlIn(0x95, 0x0706, 0, (unsigned char*)buffer, 2);
        status = buffer[0];
        return ret;
    }
    
    int CH340X::checkState(int request, int value,
                           char *expected,
                           unsigned int length)
    {
        char *buffer = new char[length];
        int ret = controlIn(request, value, 0, (unsigned char*)buffer, length);
    
        if (ret < 0) {
            return ret;
        }
    
        if (ret != length) {
            // qDebug("Expected " [ length] + " bytes, but get " + ret + " [" + msg + "]");
            return ret;
        }
    
        for (int i = 0; i < length; i++)
        {
            if (expected[i] == -1)
            {
                continue;
            }
    
            int current = buffer[i] & 0xff;
            if (expected[i] != current)
            {
                // qDebug("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
                return -1;
            }
        }
    
        return 0;
    }
    
    int CH340X::setBaudRate(int baudRate)
    {
        long factor;
        long divisor;
    
        if (baudRate == 921600)
        {
            divisor = 7;
            factor = 0xf300;
        }
        else
        {
            long BAUDBASE_FACTOR = 1532620800;
            int BAUDBASE_DIVMAX = 3;
    
            factor = BAUDBASE_FACTOR / baudRate;
            divisor = BAUDBASE_DIVMAX;
            while ((factor > 0xfff0) && divisor > 0)
            {
                factor >>= 3;
                divisor--;
            }
            if (factor > 0xfff0) { // 波特率不支持
                return -1;
    
            }
            factor = 0x10000 - factor;
        }
    
        divisor |= 0x0080; // else ch341a waits until buffer full
        int val1 = (int) ((factor & 0xff00) | divisor);
        int val2 = (int) (factor & 0xff);
        // Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2));
        int ret = controlOut(0x9a, 0x1312, val1);
        if (ret < 0) {
            return ret;
            // throw new IOException("Error setting baud rate: #1)");
        }
        ret = controlOut(0x9a, 0x0f2c, val2);
        if (ret < 0) {
            return ret;
            // throw new IOException("Error setting baud rate: #2");
        }
        return 0;
    }
    
    int CH340X::setParameters(int baudRate, int dataBits, int stopBits, int parity)
    {
        if(baudRate <= 0)
        {
            return -1;
            // throw new IllegalArgumentException("Invalid baud rate: " + baudRate);
        }
        setBaudRate(baudRate);
    
        int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX;
    
        switch (dataBits) {
            case DATABITS_5:
                lcr |= LCR_CS5;
                break;
            case DATABITS_6:
                lcr |= LCR_CS6;
                break;
            case DATABITS_7:
                lcr |= LCR_CS7;
                break;
            case DATABITS_8:
                lcr |= LCR_CS8;
                break;
            default:
            return -1;
                // throw new IllegalArgumentException("Invalid data bits: " + dataBits);
        }
    
        switch (parity) {
            case PARITY_NONE:
                break;
            case PARITY_ODD:
                lcr |= LCR_ENABLE_PAR;
                break;
            case PARITY_EVEN:
                lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN;
                break;
            case PARITY_MARK:
                lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE;
                break;
            case PARITY_SPACE:
                lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN;
                break;
            default:
            return -1;
                // throw new IllegalArgumentException("Invalid parity: " + parity);
        }
    
        switch (stopBits) {
            case STOPBITS_1:
                break;
            case STOPBITS_1_5:
            return -1;
                // throw new UnsupportedOperationException("Unsupported stop bits: 1.5");
            case STOPBITS_2:
                lcr |= LCR_STOP_BITS_2;
                break;
            default:
            return -1;
                // throw new IllegalArgumentException("Invalid stop bits: " + stopBits);
        }
    
        int ret = controlOut(0x9a, 0x2518, lcr);
        if (ret < 0) {
            return ret;
                // throw new IOException("Error setting control byte");
        }
        return 0;
    }
    
    int CH340X::init_ch34x(int fd)
    {
        int r = 0;
        libusb_context *ctx = NULL;
        r = libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
        if (r != LIBUSB_SUCCESS) {
            qDebug("libusb_set_option failed: %d\n", r);
            return r;
        }
        r = libusb_init(&ctx);
        if (r < 0) {
            qDebug("libusb_init failed: %d\n", r);
            return r;
        }
        r = libusb_wrap_sys_device(ctx, (intptr_t)fd, &devh);
        if (r < 0) {
            qDebug("libusb_wrap_sys_device failed: %d\n", r);
            return r;
        } else if (devh == NULL) {
            qDebug("libusb_wrap_sys_device returned invalid handle\n");
            return r;
        }
        r = libusb_claim_interface(devh, 0); // 独占
        if (r < 0) {
            qDebug("usb_claim_interface error %d\n", r);
            return r;
        }
        char stateCmd[2] = {static_cast<char>(-1), 0};
        checkState(0x5f, 0, stateCmd, 2);
    
        if (controlOut(0xa1, 0, 0) < 0) {
            return -1;
            // throw new IOException("Init failed: #2");
        }
    
        setBaudRate(DEFAULT_BAUD_RATE);
    
        checkState(0x95, 0x2518, stateCmd, 2);
    
        if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) {
            return -1;
            // throw new IOException("Init failed: #5");
        }
    
        stateCmd[1] = -1;
        checkState(0x95, 0x2518, stateCmd, 2);
    
        if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
            return -1;
            // throw new IOException("Init failed: #7");
        }
    
        setBaudRate(DEFAULT_BAUD_RATE);
    
        setControlLines();
    
        checkState(0x95, 0x2518, stateCmd, 2);
    
    
        r = setParameters(9600, DATABITS_8, STOPBITS_1, PARITY_NONE);
    
        return r;
    }
    
    int CH340X::send(unsigned char *src, int length, int timeout)
    {
        if (!m_isValid)
            return -1;
        int realSendLength = 0;
        return libusb_bulk_transfer(devh, EP_DATA_OUT, src, length, &realSendLength, timeout);
    
    }
    
    

    mainwindows.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "ch340x.h"
    #include 
    #include 
    
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        connect(ui->btnRefreshPortNames, &QPushButton::clicked,
                this, [=]()
        {
            ui->cbPortNames->clear();
            ui->cbPortNames->addItems(getSerialPorts());
        });
        connect(ui->btnTest, &QPushButton::clicked,
                this, &MainWindow::test);
    
        emit ui->btnRefreshPortNames->clicked(); // 借势初始化
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::logger(const QString &text)
    {
        ui->textEdit->append(text);
    }
    
    QList<QString> MainWindow::getSerialPorts()
    {
        QList<QString> names;
    #ifdef Q_OS_ANDROID
        QJniObject usbService = QJniObject::getStaticObjectField("android/content/Context",
                                                                "USB_SERVICE",
                                                                "Ljava/lang/String;");
        if (!usbService.isValid())
        {
            logger("fail to get usb service");
            return names;
        }
    
    
        QJniObject activity = QJniObject(QNativeInterface::QAndroidApplication::context());
    
        QJniObject usbManager = activity.callObjectMethod("getSystemService",
                                                          "(Ljava/lang/String;)Ljava/lang/Object;",
                                                          usbService.object<jstring>());
        if (!usbManager.isValid())
        {
            logger("fail to get usb manager");
            return names;
        }
    
        QJniObject usbDeviceListHashMap = usbManager.callObjectMethod("getDeviceList",
                                                               "()Ljava/util/HashMap;");
        QJniObject devListKeySet = usbDeviceListHashMap.callObjectMethod("keySet",
                                                           "()Ljava/util/Set;");
        QJniObject devListIter = devListKeySet.callObjectMethod("iterator",
                                                           "()Ljava/util/Iterator;");
        jint devListSize = usbDeviceListHashMap.callMethod<jint>("size", "()I");
    
        QJniObject usbDevObj;
        QJniObject usbDevObjIter;
        int vid = 0, pid = 0;
        QString devName;
        for (int i = 0; i < devListSize; ++i)
        {
            usbDevObjIter = devListIter.callObjectMethod("next",
                                                "()Ljava/lang/Object;");
            usbDevObj = usbDeviceListHashMap.callObjectMethod("get",
                                                       "(Ljava/lang/Object;)Ljava/lang/Object;",
                                                       usbDevObjIter.object());
            vid = usbDevObj.callMethod<jint>("getVendorId", "()I");
            pid = usbDevObj.callMethod<jint>("getProductId", "()I");
            devName = usbDevObj.callMethod<jstring>("getDeviceName", "()Ljava/lang/String;").toString();
    
            logger(QString("Name: %1, VID: %2, PID: %3").arg(devName).arg(vid).arg(pid));
            names.append(devName);
        }
    #else
        for (QSerialPortInfo &info : QSerialPortInfo::availablePorts())
        {
            names.append(info.portName());
        }
    #endif
        return names;
    }
    
    
    void MainWindow::test()
    {
        QJniObject str = QJniObject::fromString(ui->cbPortNames->currentText());
        jboolean res = QJniObject::callStaticMethod<jboolean>(
                                           "usb/USBListActivity",
                                           "requestUSBPermission",
                                           "(Ljava/lang/String;)Z",
                                           str.object<jstring>());
        if (!res)
        {
            logger("permission denied");
            return;
        }
        QJniObject usbService = QJniObject::getStaticObjectField("android/content/Context",
                                                                "USB_SERVICE",
                                                                "Ljava/lang/String;");
        if (!usbService.isValid())
        {
            logger("fail to get usb service");
            return;
        }
    
    
        QJniObject activity = QJniObject(QNativeInterface::QAndroidApplication::context());
    
        QJniObject usbManager = activity.callObjectMethod("getSystemService",
                                                          "(Ljava/lang/String;)Ljava/lang/Object;",
                                                          usbService.object<jstring>());
        if (!usbManager.isValid())
        {
            logger("fail to get usb manager");
            return;
        }
    
        QJniObject usbDeviceListHashMap = usbManager.callObjectMethod("getDeviceList",
                                                               "()Ljava/util/HashMap;");
        QJniObject devListKeySet = usbDeviceListHashMap.callObjectMethod("keySet",
                                                           "()Ljava/util/Set;");
        QJniObject devListIter = devListKeySet.callObjectMethod("iterator",
                                                           "()Ljava/util/Iterator;");
        jint devListSize = usbDeviceListHashMap.callMethod<jint>("size", "()I");
    
        QJniObject usbDevObj;
        QJniObject usbDevObjIter;
        QString devName;
        QJniObject selectedUsbDevice;
        for (int i = 0; i < devListSize; ++i)
        {
            usbDevObjIter = devListIter.callObjectMethod("next",
                                                "()Ljava/lang/Object;");
            usbDevObj = usbDeviceListHashMap.callObjectMethod("get",
                                                       "(Ljava/lang/Object;)Ljava/lang/Object;",
                                                       usbDevObjIter.object());
            devName = usbDevObj.callMethod<jstring>("getDeviceName", "()Ljava/lang/String;").toString();
    
    
            if (devName != ui->cbPortNames->currentText())
            {
                continue;
            }
            selectedUsbDevice = usbDevObj;
            break;
        }
        if (!selectedUsbDevice.isValid())
        {
            logger("device not found");
            return;
        }
    
        QJniObject usbConnection = usbManager.callObjectMethod("openDevice",
                                                                       "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;",
                                                                        selectedUsbDevice.object());
        if (!usbConnection.isValid())
        {
            logger("fail to get usb connection");
            return;
        }
        jint fd = usbConnection.callMethod<jint>("getFileDescriptor", "()I");
    
    
        CH340X ch340X(fd);
        const char buf[] = {"Hello World!!!\n"};
        if (!ch340X.isValid())
        {
            logger("fail to init ch340x.");
            return;
        }
        ch340X.send((unsigned char*)buf, sizeof(buf), 1000);
    
        // unrooted_usb_description(fd);
    
        logger("libusb test finished7");
    }
    
    

    共赏

    Qt for Android :使用 libusb做ch340x串口传输

    其他

    沁恒微 官网CH34x Linux驱动
    沁恒微 官网CH34x Android驱动

  • 相关阅读:
    iOS-设置指定边圆角(左上、左下等)
    绘画系统(01):【纲】Paint System[官翻]
    2023年全网最全的软件测试八股文,稳进大厂(含答案)
    linux U盘无法使用,提示“Partition table entries are not in disk order“
    计算机毕业设计之java+ssm企业销售管理系统
    Lint-staged自动修复格式错误及小结
    艾美捷细胞衰老β-半乳糖苷酶染色试剂盒说明书
    .NET Equal、==、ReferenceEqual
    深入理解 Java 对象的内存布局
    @SpringBootApplication注解的理解——如何排除自动装配 & 分布式情况下如何自动加载 & nacos是怎么被发现的
  • 原文地址:https://blog.csdn.net/halo_hsuh/article/details/139309441