You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity安卓游戏访问硬件UART串口技术求助

解决方案:Unity安卓访问硬件串口(tty*)

一、确认硬件串口信息与权限

  • 从硬件厂商获取串口节点路径(如/dev/ttyS0、/dev/ttyS1),以及串口参数(波特率、数据位、校验位、停止位)。
  • 确认APP权限:安卓默认禁止普通APP访问/dev下的设备,需厂商配合:
    • 将APP签名为系统APP;
    • 或在系统中修改串口节点权限(如设置为666);
    • 或把APP的UID加入tty/dialout用户组(需定制系统)。

二、搭建安卓端串口操作层

1. 创建JNI串口配置代码(C++)

编写serialport.cpp,用于底层配置串口参数(Java无法直接操作):

#include <jni.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <android/log.h>

#define LOG_TAG "SerialPortJNI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

extern "C" JNIEXPORT jint JNICALL
Java_com_yourcompany_serialport_SerialPortManager_openSerialPort(JNIEnv *env, jobject thiz, jstring path, jint baudRate) {
    const char *serialPath = env->GetStringUTFChars(path, nullptr);
    int fd = open(serialPath, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        LOGD("Failed to open serial port: %s", serialPath);
        env->ReleaseStringUTFChars(path, serialPath);
        return -1;
    }

    struct termios options;
    tcgetattr(fd, &options);
    cfsetispeed(&options, baudRate);
    cfsetospeed(&options, baudRate);
    // 配置8N1:8数据位、无校验、1停止位
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    // 禁用硬件流控,启用接收器与本地模式
    options.c_cflag &= ~CRTSCTS;
    options.c_cflag |= CREAD | CLOCAL;
    // 禁用软件流控与规范模式
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;
    // 设置1秒读取超时
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 10;

    if (tcsetattr(fd, TCSANOW, &options) != 0) {
        LOGD("Failed to set serial port attributes");
        close(fd);
        env->ReleaseStringUTFChars(path, serialPath);
        return -1;
    }

    env->ReleaseStringUTFChars(path, serialPath);
    return fd;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_yourcompany_serialport_SerialPortManager_readSerialPort(JNIEnv *env, jobject thiz, jint fd, jbyteArray buffer) {
    jbyte *buf = env->GetByteArrayElements(buffer, nullptr);
    int len = env->GetArrayLength(buffer);
    int bytesRead = read(fd, buf, len);
    env->ReleaseByteArrayElements(buffer, buf, 0);
    return bytesRead;
}

extern "C" JNIEXPORT jint JNICALL
Java_com_yourcompany_serialport_SerialPortManager_writeSerialPort(JNIEnv *env, jobject thiz, jint fd, jbyteArray buffer) {
    jbyte *buf = env->GetByteArrayElements(buffer, nullptr);
    int len = env->GetArrayLength(buffer);
    int bytesWritten = write(fd, buf, len);
    env->ReleaseByteArrayElements(buffer, buf, 0);
    return bytesWritten;
}

extern "C" JNIEXPORT void JNICALL
Java_com_yourcompany_serialport_SerialPortManager_closeSerialPort(JNIEnv *env, jobject thiz, jint fd) {
    close(fd);
}

将编译好的.so库(如libserialport.so)放入Unity项目的Plugins/Android/libs/[架构]/目录(架构需匹配硬件,如armeabi-v7a、arm64-v8a)。

2. 编写Java串口管理类

创建SerialPortManager.java,作为Unity与JNI的中间层,路径为Plugins/Android/src/main/java/com/yourcompany/serialport/SerialPortManager.java:

package com.yourcompany.serialport;

public class SerialPortManager {
    static {
        System.loadLibrary("serialport");
    }

    private int mFd = -1;

    public native int openSerialPort(String path, int baudRate);
    public native int readSerialPort(int fd, byte[] buffer);
    public native int writeSerialPort(int fd, byte[] buffer);
    public native void closeSerialPort(int fd);

    public boolean open(String portPath, int baudRate) {
        mFd = openSerialPort(portPath, baudRate);
        return mFd != -1;
    }

    public int read(byte[] buffer) {
        if (mFd == -1) return -1;
        return readSerialPort(mFd, buffer);
    }

    public int write(byte[] buffer) {
        if (mFd == -1) return -1;
        return writeSerialPort(mFd, buffer);
    }

    public void close() {
        if (mFd != -1) {
            closeSerialPort(mFd);
            mFd = -1;
        }
    }
}

3. 配置AndroidManifest权限

在Plugins/Android/AndroidManifest.xml中添加必要权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yourcompany.yourapp">
    <uses-permission android:name="android.permission.ACCESS_SUPERUSER"/> <!-- 仅root设备需要 -->
    <application android:allowBackup="true" android:label="@string/app_name">
        <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

三、Unity端C#调用代码

创建SerialPortController.cs脚本,挂载到场景中的GameObject:

using UnityEngine;
using System;

public class SerialPortController : MonoBehaviour
{
    private AndroidJavaObject serialPortManager;

    void Start()
    {
        try
        {
            // 初始化安卓串口管理类
            serialPortManager = new AndroidJavaObject("com.yourcompany.serialport.SerialPortManager");
            // 替换为你的串口路径与波特率
            bool isOpened = serialPortManager.Call<bool>("open", "/dev/ttyS0", 9600);
            if (isOpened)
                Debug.Log("串口打开成功");
            else
                Debug.LogError("串口打开失败");
        }
        catch (Exception e)
        {
            Debug.LogError("初始化串口出错: " + e.Message);
        }
    }

    // 发送数据到串口
    public void SendData(byte[] data)
    {
        if (serialPortManager == null) return;
        try
        {
            int bytesWritten = serialPortManager.Call<int>("write", data);
            Debug.Log("已写入 " + bytesWritten + " 字节");
        }
        catch (Exception e)
        {
            Debug.LogError("发送数据出错: " + e.Message);
        }
    }

    // 每帧读取串口数据
    void Update()
    {
        if (serialPortManager == null) return;
        try
        {
            byte[] buffer = new byte[1024];
            int bytesRead = serialPortManager.Call<int>("read", buffer);
            if (bytesRead > 0)
            {
                string receivedData = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);
                Debug.Log("收到数据: " + receivedData);
                // 在此处理游戏逻辑的串口数据
            }
        }
        catch (Exception e)
        {
            Debug.LogError("读取数据出错: " + e.Message);
        }
    }

    void OnDestroy()
    {
        // 关闭串口
        if (serialPortManager != null)
        {
            serialPortManager.Call("close");
            serialPortManager.Dispose();
        }
    }
}

四、测试与调试

  • 使用adb logcat过滤SerialPortJNI和SerialPortManager的日志,排查串口打开失败、读写错误的问题。
  • 确保串口参数(波特率、数据位等)与硬件端完全一致。
  • 若权限不足,联系硬件厂商调整系统权限配置。

内容的提问来源于stack exchange,提问作者Club Unity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:55:54