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
相关产品推荐
相关产品推荐

