赞
踩
目录
本文将介绍如何在Android平台上通过Java代码实现USB串口通信,并通过JNI(Java Native Interface)将数据传递到C++层进行处理。这个过程涵盖了从权限管理、串口数据收发、到C++信号与槽的实现。
完整代码:GitHub - TryTryTL/serialport
首先,我们需要创建一个 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);
- }
我们提供了一个 getDeviceList
方法,返回当前连接的USB设备列表,并请求用户权限。
- public String[] getDeviceList() {
- HashMap<String, UsbDevice> 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;
- }
当用户授予权限后,我们会打开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);
- }
- }
当串口接收到数据时,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);
- }
- }
onDataReceivedFromJava
是一个本地方法,负责将接收到的数据传递到C++层。
public native void onDataReceivedFromJava(byte[] data);
在不再需要使用串口时,我们提供了一个 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;
- }
- }
现在我们将进一步探讨如何在C++中处理这些数据,并将其与Qt的UI集成。
PortWidgets
类PortWidgets
类是Qt中的一个自定义控件,它负责管理串口通信,并通过JNI与Java层进行交互。我们首先在构造函数中初始化UI并设置相关的串口参数。
PortWidgets.h
文件结构
- #ifndef PORTWIDGETS_H
- #define PORTWIDGETS_H
-
- #include <QWidget>
- #include <QSerialPort>
- #include <unordered_map>
- #include <QAndroidJniObject>
-
- 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<int, QSerialPort::BaudRate> 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_map<int, QSerialPort::DataBits>dataMap = {
- { 0, QSerialPort::Data5 },{ 1, QSerialPort::Data6 },{ 2, QSerialPort::Data7 },{ 3, QSerialPort::Data8 }
- };
-
- std::unordered_map<int, QSerialPort::Parity>parityMap = {
- { 0, QSerialPort::NoParity },{ 1, QSerialPort::OddParity },{ 2, QSerialPort::EvenParity }
- };
-
- std::unordered_map<int, QSerialPort::StopBits> stopMap = {
- {0, QSerialPort::OneStop}, {1, QSerialPort::OneAndHalfStop}, {2, QSerialPort::TwoStop}
- };
- };
-
- #endif // PORTWIDGETS_H
在 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;
- }
我们通过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<jobject>();
-
- 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>();
-
- jobject serialPortObject = env->CallObjectMethod(listObject, env->GetMethodID(env->GetObjectClass(listObject), "get", "(I)Ljava/lang/Object;"), portIndex);
-
- javaUsbController.callMethod<void>("requestPermissionAndOpenPort",
- "(Lcom/hoho/android/usbserial/driver/UsbSerialPort;IIII)V",
- serialPortObject,
- baudRate,
- dataBits,
- stopBits,
- parity);
- }
当从Java层接收到串口数据时,通过JNI回调函数将数据传递到C++层,并通过Qt的信号槽机制将数据传递到UI或其他需要处理的地方。
- 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);
- }
为了确保应用程序的UI响应,我们在关闭按钮的点击事件中调用了 close()
方法,以关闭当前窗口。
- void PortWidgets::on_pushButton_clicked()
- {
- this->close();
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。