Rockchip系列之深度分析UART接口系列(1)_一歲抬頭的博客-CSDN博客
正常的App开发过uart的应该知道github 或 google 有个标准的android_serialport_api so库吧 , 这套做的是挺丰富的 但是我挺嫌弃的, 我的目的是在系统增加串口服务 , 并且提供丰富的接口 , 客户或自己只需要导入系统aar 就能调用 加快开发效率 , 而不需要单独在app中增加一套别人的库! 咱们玩的就是效率 直接开干。
本博客将对Android源码中的com_android_server_SerialService.cpp
文件进行功能分析。由于Android系统本身并不直接支持对串口设备的访问和控制,因此需要使用JNI接口来调用底层的C/C++代码来实现串口服务 我新增了com_android_server_CustomSerialService.cpp
。通过JNI接口,可以直接与底层的硬件和系统功能进行交互,实现更底层、更精确的控制和操作。该文件属于Rockchip系列中的UART接口系列的一部分,实现了自定义的串口服务。
jni部分
自定义Serial JNI代码功能介绍
在Android系统的frameworks/base/services/core/jni/com_android_server_CustomSerialService.cpp
文件中,可以看到自定义的JNI代码实现了与Serial接口相关的功能。以下是对该自定义JNI代码的功能进行大致介绍:
getBaudrate()
函数:根据传入的波特率参数,返回对应的波特率常量。该函数用于获取波特率的速率。customserial_init()
函数:初始化自定义Serial服务,输出日志信息表明服务正在启动。customserial_open()
函数:打开指定路径的Serial接口,并返回一个ParcelFileDescriptor
对象。该函数根据传入的路径、波特率和标志位打开Serial接口,并对接口进行配置。customserial_close()
函数:关闭已打开的Serial接口。该函数通过ParcelFileDescriptor
对象获取文件描述符,并关闭该描述符对应的Serial接口。
#include <jni.h> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include <utils/misc.h> #include <utils/Log.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <jni.h> //#include <android/parcel/ParcelFileDescriptor.h> static const char *TAG="serial_port"; #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) namespace android { static speed_t getBaudrate(jint baudrate) { switch(baudrate) { case 0: return B0; case 50: return B50; case 75: return B75; case 110: return B110; case 134: return B134; case 150: return B150; case 200: return B200; case 300: return B300; case 600: return B600; case 1200: return B1200; case 1800: return B1800; case 2400: return B2400; case 4800: return B4800; case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; case 460800: return B460800; case 500000: return B500000; case 576000: return B576000; case 921600: return B921600; case 1000000: return B1000000; case 1152000: return B1152000; case 1500000: return B1500000; case 2000000: return B2000000; case 2500000: return B2500000; case 3000000: return B3000000; case 3500000: return B3500000; case 4000000: return B4000000; default: return -1; } } static void customserial_init(JNIEnv* env) { ALOGI("Custom Serial Service is starting."); } static jobject customserial_open(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) { int fd; speed_t speed; jobject mFileDescriptor; /* Check arguments */ { speed = getBaudrate(baudrate); if (speed == -1) { /* TODO: throw an exception */ LOGE("Invalid baudrate"); return NULL; } } /* Opening device */ { jboolean iscopy; const char *path_utf = env->GetStringUTFChars( path, &iscopy); LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); fd = open(path_utf, O_RDWR | flags); LOGD("open() fd = %d", fd); env->ReleaseStringUTFChars( path, path_utf); if (fd == -1) { /* Throw an exception */ LOGE("Cannot open port"); /* TODO: throw an exception */ return NULL; } } /* Configure device */ { struct termios cfg; LOGD("Configuring serial port"); if (tcgetattr(fd, &cfg)) { LOGE("tcgetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } cfmakeraw(&cfg); cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) { LOGE("tcsetattr() failed"); close(fd); /* TODO: throw an exception */ return NULL; } } /* Create a corresponding file descriptor */ { jclass pfdClass = env->FindClass("android/os/ParcelFileDescriptor"); jmethodID iParcelFileDescriptor = env->GetStaticMethodID(pfdClass, "adoptFd", "(I)Landroid/os/ParcelFileDescriptor;"); mFileDescriptor = env->CallStaticObjectMethod(pfdClass, iParcelFileDescriptor, fd); } return mFileDescriptor; } static void customserial_close(JNIEnv *env, jobject thiz) { jclass SerialPortClass = env->GetObjectClass( thiz); jclass FileDescriptorClass = env->FindClass( "java/io/FileDescriptor"); jfieldID mFdID = env->GetFieldID( SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); jfieldID descriptorID = env->GetFieldID( FileDescriptorClass, "descriptor", "I"); jobject mFd = env->GetObjectField( thiz, mFdID); jint descriptor = env->GetIntField( mFd, descriptorID); LOGD("close(fd = %d)", descriptor); close(descriptor); } static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ {"native_init", "()V", (void*)customserial_init}, {"native_open", "(Ljava/lang/String;II)Landroid/os/ParcelFileDescriptor;", (void*)customserial_open}, {"native_close", "(Landroid/os/ParcelFileDescriptor;)V", (void*)customserial_close}, }; int register_android_server_CustomSerialService(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/server/CustomSerialService", //"android/os/SystemCustomSerial", gMethods, NELEM(gMethods)); } };
系统Serial JNI代码功能介绍
系统的JNI代码实现了与Serial接口相关的功能,并且通过ParcelFileDescriptor
类进行封装。以下是对系统JNI代码的功能进行介绍:
android_server_SerialService_open()
函数:打开指定路径的Serial接口,并返回一个ParcelFileDescriptor
对象。该函数通过系统提供的open()
函数打开Serial接口,并根据返回的文件描述符创建ParcelFileDescriptor
对象。
JNI代码功能区别和原因
主要区别在于自定义代码对波特率的处理以及对串口的配置,而系统代码则相对简单,没有进行波特率转换和串口配置。这些区别可能是基于具体需求的考虑,自定义代码可能需要更加灵活的配置和处理能力。
系统的Serial JNI不能满足需求的情况下,可以通过自定义的JNI代码实现对Serial接口的控制和访问,以满足特定的需求。然后接着新建一个SystemCustomSerial对JNI做调用封装。
service部分
CustomSerialService功能介绍
在frameworks/base/core/java/android/btf/CustomSerialService.java
文件中,我们可以看到对自定义Serial JNI的调用和封装。以下是对该文件的功能进行介绍:
open()
方法:打开串口设备,接收路径、波特率和标志位作为参数,并调用自定义JNI的native_open()
方法打开串口。如果打开成功,则返回一个ParcelFileDescriptor
对象;否则,抛出异常。close()
方法:关闭串口设备,接收一个ParcelFileDescriptor
对象作为参数,并调用自定义JNI的native_close()
方法关闭串口。native_open()
方法:调用自定义JNI的native_open()
方法,接收路径、波特率和标志位作为参数,并返回一个ParcelFileDescriptor
对象。native_close()
方法:调用自定义JNI的native_close()
方法,接收一个ParcelFileDescriptor
对象作为参数。native_init()
方法:调用自定义JNI的native_init()
方法,用于初始化自定义Serial服务。
// ICustomSerialService.aidl import android.os.ParcelFileDescriptor; interface ICustomSerialService { /** * Opens the serial port and returns a ParcelFileDescriptor */ ParcelFileDescriptor open(String path, int baudrate, int flags); /** * Closes a given ParcelFileDescriptor */ void close(in ParcelFileDescriptor pfd); }
package com.android.server; import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; import java.io.IOException; import android.util.Log; import android.btf.ICustomSerialService; public class CustomSerialService extends ICustomSerialService.Stub { private static final String TAG = "CustomSerialService"; //private Context mContext; public CustomSerialService() { try { native_init(); } catch (IOException e) { Log.e(TAG, "SystemCustomSerial is starting.", e); } } // 打开串口设备 @Override public ParcelFileDescriptor open(String path, int baudrate, int flags) throws RemoteException { Slog.d(TAG, "open serial port: " + path + ", baudrate: " + baudrate + ", flags: " + flags); try { return native_open(path, baudrate, flags); } catch (IOException e) { Slog.e(TAG, "open serial port failed", e); throw new RemoteException(e.getMessage()); } } // 关闭串口设备 @Override public void close(ParcelFileDescriptor pfd) throws RemoteException { Slog.d(TAG, "close serial port: " + pfd); try { native_close(pfd); } catch (IOException e) { Slog.e(TAG, "close serial port failed", e); throw new RemoteException(e.getMessage()); } } // 调用JNI中的native_open方法 private native ParcelFileDescriptor native_open(String path, int baudrate, int flags) throws IOException; // 调用JNI中的native_close方法 private native void native_close(ParcelFileDescriptor pfd) throws IOException; private native void native_init() throws IOException; }
manager部分
SystemCustomSerial功能介绍
在frameworks/base/core/java/android/btf/SystemCustomSerial.java
文件中,我们可以看到对自定义Serial JNI的调用和封装。以下是对该文件的功能进行介绍:
sService
成员变量:用于与自定义串口服务进行通信的接口。mPfd
成员变量:串口设备的文件描述符,用于与串口进行数据传输。mFileInputStream
和mFileOutputStream
成员变量:输入流和输出流,用于读取和写入串口数据。SystemCustomSerial()
构造函数:无参构造函数,用于创建SystemCustomSerial
对象。SystemCustomSerial(String path, int baudrate, int flags)
构造函数:带参数的构造函数,用于创建SystemCustomSerial
对象,并打开指定路径的串口。close()
方法:关闭串口和相关资源,包括关闭自定义串口服务、关闭文件描述符和关闭流。getInputStream()
方法:获取串口的输入流。getOutputStream()
方法:获取串口的输出流。
import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.os.RemoteException; import java.io.IOException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.btf.ICustomSerialService; import android.util.Log; public class SystemCustomSerial { private ICustomSerialService sService; // 用于与自定义串口服务进行通信的接口 private ParcelFileDescriptor mPfd; // 串口设备文件描述符 private FileInputStream mFileInputStream; // 输入流,用于读取串口数据 private FileOutputStream mFileOutputStream; // 输出流,用于写入串口数据 private static final String TAG = "SystemCustomSerial"; public SystemCustomSerial() { } public SystemCustomSerial(String path, int baudrate, int flags) throws SecurityException, IOException { sService = ICustomSerialService.Stub.asInterface(ServiceManager.getService("custom_serial")); Log.d(TAG, "SystemCustomSerial :"+sService); try { mPfd = sService.open(path, baudrate, flags); } catch (RemoteException e) { Log.e(TAG, "Failed to open serial port", e); throw new IOException("Failed to open serial port", e); } if (mPfd == null) { Log.e(TAG, "native open returns null"); throw new IOException("Failed to open serial port"); } mFileInputStream = new FileInputStream(mPfd.getFileDescriptor()); mFileOutputStream = new FileOutputStream(mPfd.getFileDescriptor()); } public void close() throws RemoteException, IOException { try { if(mPfd !=null){ sService.close(mPfd); } } catch (RemoteException e) { Log.e(TAG, "Failed to close serial port", e); throw new IOException("Failed to close serial port", e); } try { mPfd.close(); // Also close the ParcelFileDescriptor mFileInputStream.close(); mFileOutputStream.close(); } catch (IOException e) { Log.e(TAG, "Failed to close stream", e); throw new IOException("Failed to close stream", e); } } // 获取串口输入流 public InputStream getInputStream() { return mFileInputStream; } // 获取串口输出流 public OutputStream getOutputStream() { return mFileOutputStream; } }
以上是CustomSerialService
和SystemCustomSerial
类对自定义Serial JNI的调用和封装,通过调用相关方法和操作相关成员变量,实现了对串口的打开、关闭和数据传输等功能。
----------到此为止 只能勉强能用 还不够方便和高效! 咱们继续
SerialPort.java
SerialPort.java
文件封装了对com_android_server_SerialService
的调用,并提供了对串口进行读写操作的功能。以下是对该文件的功能进行介绍:
SerialPort(File device, int baudrate, int flags)
构造函数:通过传入的设备文件、波特率和标志位,打开串口设备。在构造函数中,首先通过反射调用android.os.ServiceManager
的getService()
方法获取custom_serial
服务的IBinder
对象,并将其转换为ICustomSerialService
接口对象。然后调用服务的open()
方法打开串口设备,并获取串口的文件描述符。最后,使用文件描述符创建串口的输入流和输出流。getInputStream()
方法:获取串口的输入流。getOutputStream()
方法:获取串口的输出流。close()
方法:关闭串口设备,包括关闭输入流和输出流。
package android.xxx.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import android.btf.ICustomSerialService; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; public class SerialPort { private static final String TAG = "serial_port"; private ParcelFileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; private ICustomSerialService sService; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { Method method = null; try { method = Class.forName("android.os.ServiceManager").getMethod("getService", String.class); IBinder binder = (IBinder) method.invoke(null, new Object[]{"custom_serial"}); this.sService = ICustomSerialService.Stub.asInterface(binder); if (this.sService == null) { Log.d(TAG, "get service binder2 :" + this.sService ); } } catch (Exception e) { e.printStackTrace(); } try { mFd = sService.open(device.getAbsolutePath(), baudrate, flags); } catch (RemoteException e) { Log.e(TAG, "RemoteException"); e.printStackTrace(); } if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); } Log.e(TAG, "mFd : " + mFd); // use AutoCloseInputStream and AutoCloseOutputStream to wrap file descriptor mFileInputStream = new ParcelFileDescriptor.AutoCloseInputStream(mFd); mFileOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(mFd); } // Getters and setters public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } public void close() { // close input and output streams try { mFileInputStream.close(); mFileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
SerialPortUtil.java
SerialPortUtil.java
文件封装了对SerialPort
类的调用,并提供了一些工具方法用于串口的发送和接收操作。以下是对该文件的功能进行介绍:
mSerialPorts
、mOutputStreams
和mInputStreams
成员变量:用于存储串口设备、输入流和输出流的实例。dev_sendUart(String uartNode, String data, boolean hex)
方法:向指定的串口设备发送数据。根据传入的参数,获取对应的输出流,并将数据写入流中。dev_closeUart(String uartNode)
方法:关闭指定的串口设备。通过指定的串口设备名称,获取对应的串口实例,并关闭该串口。dev_openUart(String uartNode, int baudrate)
方法:打开指定的串口设备,并设置波特率。通过指定的串口设备文件和波特率,创建串口实例,并将实例存储到对应的集合中。dev_receiveUart(String uartNode, DataCallback callback)
方法:接收指定串口设备的数据。根据指定的串口设备名称,获取对应的输入流,并创建一个读取线程来持续读取输入流中的数据,并通过回调函数返回。HexStringTobytes(String inHex)
方法:将十六进制字符串转换为字节数组。bytesToHexString(byte[] bytes)
方法:将字节数组转换为十六进制字符串。DataCallback
接口:用于定义数据接收的回调方法。ReadThread
内部类:用于创建读取线程,持续从输入流中读取数据,并通过回调函数返回。
import android.xxx.util.SerialPort; //注意这个是我们新增的接口 import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.io.*; import java.util.HashMap; import java.util.Map; public class SerialPortUtil { private static Map<String, SerialPort> mSerialPorts = new HashMap<>(); private static Map<String, OutputStream> mOutputStreams = new HashMap<>(); private static Map<String, InputStream> mInputStreams = new HashMap<>(); public SerialPortUtil() { } public static int dev_sendUart(String uartNode, String data, boolean hex) { OutputStream mOutputStream = mOutputStreams.get(uartNode); if (mOutputStream == null) { return -1; } else { try { if (hex) { byte[] bOutArray = HexStringTobytes(data); mOutputStream.write(bOutArray); } else { mOutputStream.write(data.getBytes()); } return 0; } catch (IOException var4) { var4.printStackTrace(); return -1; } } } public static int dev_closeUart(String uartNode) { SerialPort mSerialPort = mSerialPorts.get(uartNode); if (mSerialPort == null) { return -1; } else { mSerialPort.close(); mSerialPorts.remove(uartNode); mInputStreams.remove(uartNode); mOutputStreams.remove(uartNode); return 0; } } public static int dev_openUart(String uartNode, int baudrate) { try { SerialPort mSerialPort = new SerialPort(new File(uartNode), baudrate, 0); mSerialPorts.put(uartNode, mSerialPort); mOutputStreams.put(uartNode, mSerialPort.getOutputStream()); mInputStreams.put(uartNode, mSerialPort.getInputStream()); return 0; } catch (IOException var3) { var3.printStackTrace(); return -1; } } public static void dev_receiveUart(String uartNode, SerialPortUtil.DataCallback callback) { InputStream mInputStream = mInputStreams.get(uartNode); if (mInputStream != null) { SerialPortUtil.ReadThread readThread = new SerialPortUtil.ReadThread(mInputStream, callback); readThread.start(); } } public static byte[] HexStringTobytes(String inHex) { int hexlen = inHex.length(); byte[] result; if (hexlen % 2 == 1) { ++hexlen; result = new byte[hexlen / 2]; inHex = "0" + inHex; } else { result = new byte[hexlen / 2]; } int j = 0; for(int i = 0; i < hexlen; i += 2) { result[j] = (byte)Integer.parseInt(inHex.substring(i, i + 2), 16); ++j; } return result; } public static String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); byte[] var2 = bytes; int var3 = bytes.length; for(int var4 = 0; var4 < var3; ++var4) { byte b = var2[var4]; String hex = Integer.toHexString(b & 255); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } public interface DataCallback { void onDataReceive(byte[] var1, int var2); } private static class ReadThread extends Thread { private InputStream mInputStream; private SerialPortUtil.DataCallback callback; public ReadThread(InputStream inputStream, SerialPortUtil.DataCallback callback) { this.mInputStream = inputStream; this.callback = callback; } public void run() { while(true) { try { if (!this.isInterrupted()) { if (mInputStream == null) { return; } int available = mInputStream.available(); if (available > 0) { byte[] buffer = new byte[available]; int size = mInputStream.read(buffer); if (size > 0) { this.callback.onDataReceive(buffer, size); } } continue; } } catch (Exception var3) { var3.printStackTrace(); } return; } } } }
SerialPortUtil
类封装了对SerialPort
的调用,并提供了更高层次的操作接口,包括打开串口、发送数据、接收数据等。同时,通过提供的工具方法,可以方便地进行十六进制字符串和字节数组之间的转换。
test部分
好了 上面接口已经全部完成 , 接下来我们随便搞个测试程序玩玩
我写了个串口自测的函数 , 同时打开ttyS3和ttyS4 , 因为这2个串口在我设备中是硬件短接的 , 发送字符串的"ABCD" 如果收发都正常则表示这2个串口功能正常。
还是那句话 , 接口写完了 你想把应用写成花都是很简单的事情 , 我们以后不要受限于其他人提供的serial so库了。
/* boolean result1 = test1.testSerialPort("/dev/ttyS3", "/dev/ttyS4", 9600, false, "ABCD"); Log.d("SerialPortTest", "Test 1 result: " + result1); */ public boolean testSerialPort(String senderPort, String receiverPort, int baudrate, boolean hex, String dataToSend) { // 打开发送端口 if (SerialPortUtil.dev_openUart(senderPort, baudrate) != 0) { Log.d(TAG, "无法打开发送端口: " + senderPort); return false; } // 打开接收端口并开始接收数据 if (SerialPortUtil.dev_openUart(receiverPort, baudrate) != 0) { Log.d(TAG, "无法打开接收端口: " + receiverPort); return false; } SerialPortUtil.dev_receiveUart(receiverPort, new SerialPortUtil.DataCallback() { @Override public void onDataReceive(byte[] data, int size) { String receivedData = new String(data, 0, size); if (dataToSend.equals(receivedData)) { Log.d(TAG, "从 " + receiverPort + " 接收到正确的数据: " + receivedData); isDataCorrect = true; } else { Log.d(TAG, "从 " + receiverPort + " 接收到错误的数据: " + receivedData); isDataCorrect = false; } } }); // 开始从发送端口发送数据 new Thread(() -> { try { while (true) { Log.d(TAG, "从 " + senderPort + " 发送 " + dataToSend); SerialPortUtil.dev_sendUart(senderPort, dataToSend, hex); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); return isDataCorrect; }
本博客对Rockchip系列中的UART接口系列的<新增framework系统jni+service+manager接口访问>内容非常丰富。解释了为什么要使用自定义的serial jni。通过自定义可以实现对底层串口设备的控制和操作 实现上层封装可以高效的满足特定需求并提供更高级别的串口通信功能。
写这个功能+调试 , 这个博客耗费不少时间 , 原创内容 欢迎点赞收藏支持 有任何问题 欢迎留言。