Android利用CMake进行JNI串口操作
利用CMake进行JNI串口操作
-
- 三方应用的操作串口方式
- 系统应用操作串口的方式
- 串口设备
三方应用的操作串口方式
由于三方应用无法通过系统的串口API操作串口,需要借助JNI访问系统串口节点。
步骤如下:
新建Android Native LIbrary,生成CMakeLists.txt和SerialPort.cpp。
CMakeLists.txt如下:
# For more information about using CMake with Android Studio, read the# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.18.1)# Declares and names the project.project("serialport")# Creates and names a library, sets it as either STATIC# or SHARED, and provides the relative paths to its source code.# You can define multiple libraries, and CMake builds them for you.# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library. serialport # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). SerialPort.cpp)# Searches for a specified prebuilt library and stores the path as a# variable. Because CMake includes system libraries in the search path by# default, you only need to specify the name of the public NDK library# you want to add. CMake verifies that the library exists before# completing its build.find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log)# Specifies libraries CMake should link to your target library. You# can link multiple libraries, such as libraries you define in this# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library. serialport # Links the target library to the log library # included in the NDK. ${log-lib})
编写串口的c++代码,操作系统API的方式,SerialPort.cpp代码如下:
#include #include #include #include #include #include #include #include #include "android/log.h"static const char *TAG = "serial_port";jmethodID FindMethod(JNIEnv *env, jclass klass, const char *name, const char *signature);jfieldID FindField(JNIEnv *pEnv, jclass klass, const char *name, const char *desc);#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)static speed_t getBandRate(jint bandRate) { switch (bandRate) { 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; }}extern "C" JNIEXPORT jint JNICALL Java_com_example_serialport_SerialPort_open (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint flags) { int fd; speed_t speed; jobject fileDescriptor; /* Check arguments */ { speed = getBandRate(baudrate); if (speed == -1) { return -1; } } /* Opening device */ { jboolean is_copy; const char *path_utf = (*env).GetStringUTFChars(path, &is_copy); 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"); return -1; } } /* Configure device */ { struct termios cfg{}; LOGD("Configuring serial port"); if (tcgetattr(fd, &cfg)) { close(fd); return -1; } cfmakeraw(&cfg); cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) { close(fd); return -1; } } /* Create a corresponding file descriptor */ { jclass fileDescriptorClass = (*env).FindClass("java/io/FileDescriptor"); jmethodID fileDescriptorInitMethod = FindMethod(env, fileDescriptorClass, "", "()V"); jfieldID fileDescriptorDescriptorField = FindField(env, fileDescriptorClass, "descriptor", "I"); fileDescriptor = (*env).NewObject(fileDescriptorClass, fileDescriptorInitMethod); (*env).SetIntField(fileDescriptor, fileDescriptorDescriptorField, (jint) fd); } return fd;}jmethodID FindMethod(JNIEnv *env, jclass klass, const char *name, const char *signature) { jmethodID result = env->GetMethodID(klass, name, signature); if (result == nullptr) { abort(); } return result;}jfieldID FindField(JNIEnv *env, jclass klass, const char *name, const char *desc) { jfieldID result = env->GetFieldID(klass, name, desc); if (result == nullptr) { abort(); } return result;}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_close (JNIEnv *env, jobject thiz, jint fd) { close(fd);}extern "C" JNIEXPORT jint JNICALL Java_com_example_serialport_SerialPort_readArray (JNIEnv *env, jobject thiz, jint fd, jbyteArray buffer, jint length) { auto *buf = (jbyte *) malloc(length); if (!buf) { LOGE("jniThrowException java/lang/OutOfMemoryError"); return -1; } int ret = read(fd, buf, length); if (ret > 0) { // copy data from native buffer to Java buffer env->SetByteArrayRegion(buffer, 0, ret, buf); } free(buf); if (ret < 0) LOGE("jniThrowException java/io/IOException"); return ret;}extern "C" JNIEXPORT jint JNICALL Java_com_example_serialport_SerialPort_readDirect (JNIEnv *env, jobject thiz, jint fd, jobject buffer, jint length) { auto *buf = (jbyte *) env->GetDirectBufferAddress(buffer); if (!buf) { LOGE("ByteBuffer not direct: jniThrowException java/lang/IllegalArgumentException"); return -1; } int ret = read(fd, buf, length); if (ret < 0) LOGE("jniThrowException java/io/IOException"); return ret;}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_writeArray (JNIEnv *env, jobject thiz, jint fd, jbyteArray byteArray, jint length) { auto *buf = (jbyte *) malloc(length); if (!buf) { LOGE("jniThrowException java/lang/OutOfMemoryError"); } env->GetByteArrayRegion(byteArray, 0, length, buf); jint ret = write(fd, buf, length); free(buf); if (ret < 0) LOGE("jniThrowException java/io/IOException");}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_writeDirect (JNIEnv *env, jobject thiz, jint fd, jobject buffer, jint length) { auto *buf = (jbyte *) env->GetDirectBufferAddress(buffer); if (!buf) { LOGE("ByteBuffer not direct: jniThrowException java/lang/IllegalArgumentException"); return; } int ret = write(fd, buf, length); if (ret < 0) LOGE("jniThrowException java/io/IOException");}
在java依赖c++库,这里用的是JNI静态注册System.loadLibrary
,定义Native方法,与c++代码中对应,SerialPort.java代码如下:
package com.example.serialport;import java.io.IOException;import java.nio.ByteBuffer;public class SerialPort { static { System.loadLibrary("serialport"); } private int fd = -1; public void openSerialPort(String path, int bandRate, int flags) { fd = open(path, bandRate, flags); } public void closeSerialPort() { close(fd); } public int read(ByteBuffer buffer) throws IOException { if (fd < 0) { return -1; } if (buffer.isDirect()) { return readDirect(fd, buffer, buffer.remaining()); } else if (buffer.hasArray()) { return readArray(fd, buffer.array(), buffer.remaining()); } else { throw new IllegalArgumentException("buffer is not direct and has no array"); } } public void write(ByteBuffer buffer, int length) throws IOException { if (fd < 0) { return; } if (buffer.isDirect()) { writeDirect(fd, buffer, length); } else { if (!buffer.hasArray()) { throw new IllegalArgumentException("buffer is not direct and has no array"); } writeArray(fd, buffer.array(), length); } } /** * A native method that is implemented by the 'serialport' native library, * which is packaged with this application. */ private native int open(String path, int bandRate, int flags); private native void close(int fd); private native int readArray(int fd, byte[] buffer, int flags) throws IOException; private native int readDirect(int fd, ByteBuffer buffer, int flags) throws IOException; private native void writeArray(int fd, byte[] buffer, int flags) throws IOException; private native void writeDirect(int fd, ByteBuffer buffer, int flags) throws IOException;}
接下来对SerialPort的封装,SerialPortManager继承Thread,同时需要检查串口节点的读写权限。代码如下:
package com.example.serialport;import android.util.Log;import java.io.File;import java.io.IOException;import java.nio.ByteBuffer;public class SerialPortManager extends Thread { private static final String TAG = SerialPortManager.class.getSimpleName(); private static final int DATA_BUFFER_SIZE = 512; //最大字节长度 private final SerialPort mSerialPort; private final ByteBuffer mInputBuffer; private final ByteBuffer mOutputBuffer; private SerialPortDataListener mSerialPortDataListener; public SerialPortManager() { mSerialPort = new SerialPort(); mInputBuffer = ByteBuffer.allocate(DATA_BUFFER_SIZE); mOutputBuffer = ByteBuffer.allocate(DATA_BUFFER_SIZE); } public void openSerialPort(String name, int speed) { if (name != null && mSerialPort != null && checkAccessPermission(name)) { mSerialPort.openSerialPort(name, speed, 0); } } /* Check access permission */ private boolean checkAccessPermission(String name) { File device = new File(name); if (!device.canRead() || !device.canWrite()) { try { /* Missing read/write permission, trying to chmod the file */ Process su; su = Runtime.getRuntime().exec("/system/bin/su"); String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n"; su.getOutputStream().write(cmd.getBytes()); if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { throw new SecurityException(); } } catch (Exception e) { e.printStackTrace(); throw new SecurityException(); } } return true; } public void closeSerialPort() { if (mSerialPort != null) { mSerialPort.closeSerialPort(); } } @Override public void run() { int ret = 0; byte[] buffer; while (ret >= 0 && mSerialPort != null) { try { mInputBuffer.clear(); ret = mSerialPort.read(mInputBuffer); } catch (IOException e) { e.printStackTrace(); break; } if (ret > 0 && mSerialPortDataListener != null) { buffer = new byte[ret]; mInputBuffer.get(buffer, 0, ret); mSerialPortDataListener.onDataRead(buffer); } } } public void writeData(byte[] data) { if (mSerialPort != null && data.length > 0 && data.length <= DATA_BUFFER_SIZE) { try { mOutputBuffer.clear(); mOutputBuffer.put(data); mSerialPort.write(mOutputBuffer, data.length); } catch (IOException e) { e.printStackTrace(); } } else { Log.e(TAG, "writeDataToRemote failed"); } } public void setSerialPortDataListener(SerialPortDataListener listener) { if (listener != null) { this.start(); mSerialPortDataListener = listener; } } public interface SerialPortDataListener { void onDataRead(byte[] data); }}
可通过DATA_BUFFER_SIZE配置最大支持的缓冲字节长度。通过setSerialPortDataListener
设置监听串口读到的数据。
完成以上步骤后就可以通过SerialPortManager读写串口了。
系统应用操作串口的方式
系统应用可以访问到framework隐藏的API,可通过串口相关的API进行串口操作。主要有android.hardware.SerialManager
和android.hardware.SerialPort。
SerialManager serialManager = (SerialManager)context.getSystemService(Context.SERIAL_SERVICE);SerialPort serialPort = serialManager.openSerialPort(name, speed);
操作方式而上述三方应用的方式类似。
相关源码地址:
/frameworks/base/core/java/android/hardware/SerialPort.java
/frameworks/base/core/jni/android_hardware_SerialPort.cpp
/frameworks/base/core/java/android/hardware/SerialManager.java
/frameworks/base/services/core/java/com/android/server/SerialService.java
/frameworks/base/services/core/jni/com_android_server_SerialService.cpp
/libnativehelper/JNIHelp.cpp
串口设备
1.串口分为Tx和Rx,Tx代表发送,Rx代表接收。相对客户端所处的设备连接的另一个串口设备而言,客户端的Tx对应远端设备的Rx,Rx对应远端设备的Tx,这样两边设备形成收发回路来进行通信。
2.串口设备间连接需要考虑共地的问题,否则会造成一端接收异常等现象。
3.不同硬件设备所支持的串口读写字节的最大长度不一样,需要考虑是否出现断帧的现象。
4.串口设备节点一般以/dev/tty***格式,例如/dev/ttyMT2、/devttyS0。
5.串口波特率BandRate需要两端设备相匹配,否则会出现乱码,所支持的波特率见上述getBandRate
方法。