QT Android 串口通信

目录

1. 实现Java层的USB串口通信

1.1 初始化与权限管理

1.2 获取设备列表并请求权限

1.3 打开串口并启动I/O管理器

2. 接收串口数据并通过JNI传递到C++

2.1 数据接收与打印

2.2 通过JNI传递数据到C++

3. 关闭串口

2. 实现C++层的JNI交互与Qt UI集成

2.1 创建 PortWidgets 类

2.2 初始化与刷新串口列表

PortWidgets.cpp 文件中构造函数的实现

2.3 使用JNI与Java层交互

刷新串口设备列表

连接并打开串口

2.4 处理接收到的串口数据

JNI 回调函数与数据处理

2.5 关闭窗口

本文将介绍如何在Android平台上通过Java代码实现USB串口通信,并通过JNI(Java Native Interface)将数据传递到C++层进行处理。这个过程涵盖了从权限管理、串口数据收发、到C++信号与槽的实现。 

完整代码:GitHub - TryTryTL/serialport

1. 实现Java层的USB串口通信

1.1 初始化与权限管理

首先,我们需要创建一个 UsbController 类来管理USB设备。这个类负责初始化 UsbManager,请求用户权限,并监听设备权限的授予情况。

package org.qtproject.example;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.util.Log;

import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UsbController implements SerialInputOutputManager.Listener {

    private Context context;
    private UsbManager usbManager;
    private PendingIntent permissionIntent;
    private UsbSerialPort pendingUsbSerialPort;
    private SerialInputOutputManager mSerialIoManager;
    private ExecutorService mExecutor = Executors.newSingleThreadExecutor();

在构造函数中,我们初始化了 UsbManager 并注册了一个 BroadcastReceiver,以便监听用户是否授予了对设备的访问权限。

    public UsbController(Context context) {
        this.context = context;
        this.usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        this.permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.qtproject.example.USB_PERMISSION"), 0);

        IntentFilter filter = new IntentFilter("org.qtproject.example.USB_PERMISSION");
        context.registerReceiver(usbPermissionReceiver, filter);
    }
1.2 获取设备列表并请求权限

我们提供了一个 getDeviceList 方法,返回当前连接的USB设备列表,并请求用户权限。

    public String[] getDeviceList() {
        HashMap usbDevices = usbManager.getDeviceList();
        String[] deviceNames = new String[usbDevices.size()];
        int index = 0;
        for (UsbDevice device : usbDevices.values()) {
            deviceNames[index++] = device.getDeviceName();
            usbManager.requestPermission(device, permissionIntent);
        }
        return deviceNames;
    }
1.3 打开串口并启动I/O管理器

当用户授予权限后,我们会打开USB串口,并启动 SerialInputOutputManager 以处理串口数据的收发。

    public void requestPermissionAndOpenPort(UsbSerialPort usbSerialPort, int baudRate, int dataBits, int stopBits, int parity) {
        UsbDevice device = usbSerialPort.getDriver().getDevice();
        if (usbManager.hasPermission(device)) {
            openSerialPort(usbSerialPort, baudRate, dataBits, stopBits, parity);
        } else {
            pendingUsbSerialPort = usbSerialPort;
            usbManager.requestPermission(device, permissionIntent);
        }
    }

    private void openSerialPort(UsbSerialPort usbSerialPort, int baudRate, int dataBits, int stopBits, int parity) {
        UsbDeviceConnection connection = usbManager.openDevice(usbSerialPort.getDriver().getDevice());
        if (connection != null) {
            try {
                usbSerialPort.open(connection);
                usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
                Log.d("USB", "Serial port opened successfully.");

                startIoManager(usbSerialPort);
            } catch (IOException e) {
                Log.e("USB", "Error opening serial port: " + e.getMessage());
            }
        } else {
            Log.e("USB", "Could not open connection - permission might be required.");
        }
    }

    private void startIoManager(UsbSerialPort usbSerialPort) {
        if (usbSerialPort != null) {
            mSerialIoManager = new SerialInputOutputManager(usbSerialPort, this);
            mExecutor.submit(mSerialIoManager);
        }
    }

2. 接收串口数据并通过JNI传递到C++

2.1 数据接收与打印

当串口接收到数据时,onNewData 方法会被回调。我们可以在此将数据转换为字符串并打印出来,确认接收是否成功。

    @Override
    public void onNewData(final byte[] data) {
        try {
            String receivedData = new String(data, "UTF-8");
            Log.d("USB", "Received data: " + receivedData);
        } catch (UnsupportedEncodingException e) {
            Log.e("USB", "UTF-8 encoding is not supported", e);
        }

        onDataReceivedFromJava(data);
        if (onDataReceivedListener != null) {
            onDataReceivedListener.onDataReceived(data);
        }
    }
2.2 通过JNI传递数据到C++

onDataReceivedFromJava 是一个本地方法,负责将接收到的数据传递到C++层。

    public native void onDataReceivedFromJava(byte[] data);
3. 关闭串口

在不再需要使用串口时,我们提供了一个 closeSerialPort 方法来停止I/O管理器并关闭串口。

    public void closeSerialPort() {
        stopIoManager();
        if (pendingUsbSerialPort != null) {
            try {
                pendingUsbSerialPort.close();
                Log.d("USB", "Serial port closed.");
            } catch (IOException e) {
                Log.e("USB", "Error closing serial port: " + e.getMessage());
            }
            pendingUsbSerialPort = null;
        }
    }

2. 实现C++层的JNI交互与Qt UI集成

现在我们将进一步探讨如何在C++中处理这些数据,并将其与Qt的UI集成。

2.1 创建 PortWidgets

PortWidgets 类是Qt中的一个自定义控件,它负责管理串口通信,并通过JNI与Java层进行交互。我们首先在构造函数中初始化UI并设置相关的串口参数。

PortWidgets.h 文件结构

#ifndef PORTWIDGETS_H
#define PORTWIDGETS_H

#include 
#include 
#include 
#include 

namespace Ui {
class PortWidgets;
}

class PortWidgets : public QWidget
{
    Q_OBJECT

public:
    explicit PortWidgets(QWidget *parent = nullptr);
    ~PortWidgets();

    // JNI 方法,用于从 Java 接收数据
    void onDataReceivedFromJava(const QByteArray &data);

signals:
    void receivedata(QByteArray data);

private slots:
    void on_ptn_refresh_clicked();
    void receive_data();
    void on_ptn_conn_clicked();
    void on_pushButton_clicked();

public:
    void write_data(QByteArray writedata);

private:
    Ui::PortWidgets *ui;
    QSerialPort *myPort;  // 串口指针
    QAndroidJniObject javaUsbController;

    static PortWidgets *instance;

    std::unordered_map baudRateMap = {
        { 0, QSerialPort::Baud1200 },{ 1, QSerialPort::Baud2400 },{ 2, QSerialPort::Baud4800 },
        { 3, QSerialPort::Baud9600 },{ 4, QSerialPort::Baud19200 },{ 5, QSerialPort::Baud38400 },
        { 6, QSerialPort::Baud57600 },{ 7, QSerialPort::Baud115200 }
    };

    std::unordered_mapdataMap = {
        { 0, QSerialPort::Data5 },{ 1, QSerialPort::Data6 },{ 2, QSerialPort::Data7 },{ 3, QSerialPort::Data8 }
    };

    std::unordered_mapparityMap = {
        { 0, QSerialPort::NoParity },{ 1, QSerialPort::OddParity },{ 2, QSerialPort::EvenParity }
    };

    std::unordered_map stopMap = {
        {0, QSerialPort::OneStop}, {1, QSerialPort::OneAndHalfStop}, {2, QSerialPort::TwoStop}
    };
};

#endif // PORTWIDGETS_H
2.2 初始化与刷新串口列表

PortWidgets 的构造函数中,我们初始化了UI组件,并为不同的串口参数(波特率、数据位、校验位、停止位)设置了选项。此外,我们还调用了 on_ptn_refresh_clicked() 函数来刷新并显示当前可用的串口列表。

PortWidgets.cpp 文件中构造函数的实现
PortWidgets* PortWidgets::instance = nullptr;

PortWidgets::PortWidgets(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::PortWidgets)
{
    // 在构造函数中初始化静态实例指针
    instance = this;
    ui->setupUi(this);
    this->setWindowTitle("PortWidgets");
    this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);

    // 设置串口参数
    QStringList baudratelist = {"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"};
    ui->CmbBaud->addItems(baudratelist);
    ui->CmbBaud->setCurrentIndex(3);

    QStringList datalist = {"5", "6", "7", "8"};
    ui->CmbDataBits->addItems(datalist);
    ui->CmbDataBits->setCurrentIndex(3);

    QStringList checklist = {"No", "Even", "Odd"};
    ui->CmbParity->addItems(checklist);
    ui->CmbParity->setCurrentIndex(0);

    QStringList stoplist = {"1", "1.5", "2"};
    ui->CmbStopBits->addItems(stoplist);
    ui->CmbStopBits->setCurrentIndex(0);

    // 刷新串口列表
    on_ptn_refresh_clicked();

    myPort = new QSerialPort(this);

    connect(myPort, &QSerialPort::readyRead, this, &PortWidgets::receive_data);
}

PortWidgets::~PortWidgets()
{
    // 在析构函数中清除静态实例指针
    instance = nullptr;
    delete ui;
}
2.3 使用JNI与Java层交互

我们通过JNI与Java层进行交互,获取串口设备列表并打开串口。在 on_ptn_refresh_clicked()on_ptn_conn_clicked() 方法中,我们使用 QAndroidJniObject 来调用Java方法。

刷新串口设备列表
void PortWidgets::on_ptn_refresh_clicked() {
    QAndroidJniObject javaUsbController = QAndroidJniObject("org/qtproject/example/UsbController",
                                                            "(Landroid/content/Context;)V",
                                                            QtAndroid::androidContext().object());

    QAndroidJniObject result = javaUsbController.callObjectMethod("getAllSerialPort", "()Ljava/util/List;");
    QAndroidJniEnvironment env;
    jobject listObject = result.object();

    jclass listClass = env->FindClass("java/util/List");
    jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I");
    jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;");

    int size = env->CallIntMethod(listObject, sizeMethod);
    QStringList deviceList;
    for (int i = 0; i < size; i++) {
        jobject serialPortObject = env->CallObjectMethod(listObject, getMethod, i);
        QAndroidJniObject usbDevice = QAndroidJniObject::fromLocalRef(serialPortObject).callObjectMethod("getDevice", "()Landroid/hardware/usb/UsbDevice;");
        QString deviceName = usbDevice.callObjectMethod("getDeviceName", "()Ljava/lang/String;").toString();
        deviceList << deviceName;
        qDebug() << "Serial Port found: " << deviceName;
    }

    ui->CmPortlist->clear();  // 清除旧的列表项
    ui->CmPortlist->addItems(deviceList);  // 添加新的端口列表
    if (!deviceList.isEmpty()) {
        ui->CmPortlist->setCurrentIndex(0);  // 默认选择第一个端口
    }
}
连接并打开串口
void PortWidgets::on_ptn_conn_clicked()
{
    int portIndex = 0;
    int baudRate = 9600; // 示例波特率
    int dataBits = 8; // 示例数据位
    int stopBits = 1; // 示例停止位
    int parity = 0; // 示例校验位,无校验

    QAndroidJniObject javaUsbController = QAndroidJniObject("org/qtproject/example/UsbController",
                                                            "(Landroid/content/Context;)V",
                                                            QtAndroid::androidContext().object());

    QAndroidJniObject serialPorts = javaUsbController.callObjectMethod("getAllSerialPort", "()Ljava/util/List;");
    QAndroidJniEnvironment env;
    jobject listObject = serialPorts.object();

    jobject serialPortObject = env->CallObjectMethod(listObject, env->GetMethodID(env->GetObjectClass(listObject), "get", "(I)Ljava/lang/Object;"), portIndex);

    javaUsbController.callMethod("requestPermissionAndOpenPort",
                                       "(Lcom/hoho/android/usbserial/driver/UsbSerialPort;IIII)V",
                                       serialPortObject,
                                       baudRate,
                                       dataBits,
                                       stopBits,
                                       parity);
}
2.4 处理接收到的串口数据

当从Java层接收到串口数据时,通过JNI回调函数将数据传递到C++层,并通过Qt的信号槽机制将数据传递到UI或其他需要处理的地方。

JNI 回调函数与数据处理
extern "C" JNIEXPORT void JNICALL
Java_org_qtproject_example_UsbController_onDataReceivedFromJava(JNIEnv *env, jobject, jbyteArray data) {

    jsize length = env->GetArrayLength(data);
    jbyte* byteArray = env->GetByteArrayElements(data, nullptr);
    QByteArray receivedData((const char*)byteArray, length);

    if (PortWidgets::instance) {
        PortWidgets::instance->onDataReceivedFromJava(receivedData);
    }
    env->ReleaseByteArrayElements(data, byteArray, 0);
}

void PortWidgets::onDataReceivedFromJava(const QByteArray &data)
{
    emit receivedata(data);
}
2.5 关闭窗口

为了确保应用程序的UI响应,我们在关闭按钮的点击事件中调用了 close() 方法,以关闭当前窗口。

void PortWidgets::on_pushButton_clicked()
{
    this->close();
}

你可能感兴趣的:(QT,qt,c++,android)