Google官方庫android-serialport-api
GitHub:android-serialport-api
此庫版本太舊了,而且還不是AS工程,也不支持奇偶檢驗、數據位和停止位設定。
想要這個庫支持奇偶檢驗、數據位和停止位設定也很簡單
- 修改SerialPort.h和SerialPort.c兩個文件
/////////////////////////SerialPort.h/////////////////////////
/*
* Class: android_serialport_api_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
(JNIEnv *, jclass, jstring, jint, jint, jint, jint);
/////////////////////////SerialPort.c/////////////////////////
/*
* Class: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint parity, jint dataBits, jint stopBits)
{
int fd;
int flags;
speed_t speed;
jobject mFileDescriptor;
flags = 0;
/* Check arguments */
{
speed = getBaudrate(baudrate);
if (speed == -1) {
/* TODO: throw an exception */
LOGE("Invalid baudrate");
return NULL;
}
if (parity < 0 || parity > 2) {
LOGE("Invalid parity");
return NULL;
}
if (dataBits < 5 || dataBits > 8) {
LOGE("Invalid dataBits");
return NULL;
}
if (stopBits < 1 || stopBits > 2) {
LOGE("Invalid stopBit");
return NULL;
}
}
/* Opening device */
{
jboolean iscopy;
const char *path_utf = (*env)->GetStringUTFChars(env, 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(env, 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);
/* More attribute set */
switch (parity) {
case 0:
cfg.c_cflag &= ~PARENB; //無奇偶校驗
break;
case 1:
cfg.c_cflag |= (PARODD | PARENB); //奇校驗
break;
case 2:
cfg.c_iflag &= ~(IGNPAR | PARMRK); // 偶校驗
cfg.c_iflag |= INPCK;
cfg.c_cflag |= PARENB;
cfg.c_cflag &= ~PARODD;
break;
default:
cfg.c_cflag &= ~PARENB;
break;
}
switch (dataBits) {
case 5: cfg.c_cflag |= CS5; break;
case 6: cfg.c_cflag |= CS6; break;
case 7: cfg.c_cflag |= CS7; break;
case 8: cfg.c_cflag |= CS8; break;
default: cfg.c_cflag |= CS8; break;
}
switch (stopBits) {
case 1: cfg.c_cflag &= ~CSTOPB; break;
case 2: cfg.c_cflag |= CSTOPB; break;
default:cfg.c_cflag &= ~CSTOPB; break;
}
if (tcsetattr(fd, TCSANOW, &cfg))
{
LOGE("tcsetattr() failed");
close(fd);
/* TODO: throw an exception */
return NULL;
}
}
/* Create a corresponding file descriptor */
{
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
}
return mFileDescriptor;
}
- 修改SerialPort.java文件,將原來的構造方法修改成下面的
/**
* 串口配置
*@param device 串口設備文件
*@param baudrate 波特率
*@param parity 奇偶校驗,0 None, 1 Odd, 2 Even
*@param dataBits 數據位,5 - 8
*@param stopBits 停止位,1 或 2
*/
public SerialPort(File device, int baudrate, int parity, int dataBits, int stopBits) throws SecurityException, IOException {
/* Check access permission */
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();
}
}
mFd = open(device.getAbsolutePath(), baudrate, parity, dataBits, stopBits);
if (mFd == null) {
Log.e(TAG, "native open returns null");
throw new IOException();
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}
還有JNI的open方法
private native static FileDescriptor open(String path, int baudrate, int parity, int dataBits, int stopBits);
如何使用呢?
我用的是Android Studio 3.5,在新建項目的時候,是看不到Include C++ Support這個選項的,但也無妨,無論是新建的項目還是已經現有的項目都可以按照下面的步驟完成配置。
- 檢查ndk配置
NDK(Native Develop Kit),Android NDK 是一套允許您使用原生代碼語言(例如C和C++) 實現部分應用的工具集。在開發某些類型應用時,這有助於您重複使用C/C++語言編寫的代碼庫。
如果還沒有下載Android NDK的可以藉助Android Studio下載或者網上找資源下載,比如官方網站https://developer.android.google.cn/ndk/downloads/。哪種方式方便快捷,就可以用哪種。
Android Studio找到Settings,或者使用快捷鍵Ctrl + Alt + S。搜索Android SDK,找到SDK Tools,最下面就是NDK的版本信息,勾選上之後,點擊Apply,最後點OK。
NDK下載完成,按照上面的步驟配置好NDK路徑。
- 將整個工程目錄切換至Project視圖,默認是Android視圖。找到src/main,右鍵main文件夾,選擇New,找到Folder,選擇JNI Folder
JNI(Java Native Interface),即Java本地接口,JNI是Java調用Native 語言的一種特性。通過JNI可以使得Java與C/C++機型交互。即可以在Java代碼中調用C/C++等語言的代碼或者在C/C++代碼中調用Java代碼。
將官方庫android-serialport-api下載下來,找到jni文件夾,將裏面所有的文件複製到上面新建的jni文件夾裏。如果想要支持奇偶檢驗、數據位和停止位設定,按照我上面的代碼修改就可以實現。
- 配置app/build.gradle
android {
...
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
}
4.最後一步,在src/main/java根目錄下,新建一個文件夾android_serialport_api,名字千萬不要改哦,因爲這個名字鏈接着這個api庫,改變之後,java代碼無法調用它,會報錯的。
新建一個SerialPort類或者將官方原來的SerialPort.java文件複製過去,如果有需要,也可以將SerialPortFinder.java這個文件複製過去,這個是查找串口驅動文件路徑。
public class SerialPort {
private static final String TAG = "SerialPort";
/*
* Do not remove or rename the field mFd: it is used by native method close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
/**
* 串口配置
*@param device 串口設備文件
*@param baudrate 波特率
*@param parity 奇偶校驗,0 None, 1 Odd, 2 Even
*@param dataBits 數據位,5 - 8
*@param stopBits 停止位,1 或 2
*/
public SerialPort(File device, int baudrate, int parity, int dataBits, int stopBits) throws SecurityException, IOException {
/* Check access permission */
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();
}
}
mFd = open(device.getAbsolutePath(), baudrate, parity, dataBits, stopBits);
if (mFd == null) {
Log.e(TAG, "native open returns null");
throw new IOException();
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}
// Getters and setters
public InputStream getInputStream() {
return mFileInputStream;
}
public OutputStream getOutputStream() {
return mFileOutputStream;
}
// JNI
private native static FileDescriptor open(String path, int baudrate, int parity, int dataBits, int stopBits);
public native void close();
static {
System.loadLibrary("serial_port");
}
}
到這裏,Google官方庫android-serialport-api基本配置完成了,剩下的就是寫工具類進行調用了,實現打開串口、監聽數據、發送數據和關閉串口了。
如果不知道如何下手的,我下面寫了SerialPortHelper,可以參考參考下。
public abstract class SerialPortHelper
{
private SerialPort mSerialPort;
private OutputStream mOutputStream;
private InputStream mInputStream;
private ReadThread mReadThread;
private boolean _isOpen = false;
//串口配置
private String sPort;
private int iBaudRate;
private int parity = 0; //默認無校驗
private int dataBits = 8;//默認數據位8位
private int stopBits = 1;//默認停止位1位
public SerialPortHelper(String sPort, int iBaudRate) {
this.sPort = sPort;
this.iBaudRate = iBaudRate;
}
public SerialPortHelper(String sPort, int iBaudRate, int parity, int dataBits, int stopBits) {
this.sPort = sPort;
this.iBaudRate = iBaudRate;
this.parity = parity;
this.dataBits = dataBits;
this.stopBits = stopBits;
}
public void open() throws SecurityException, IOException {
this.mSerialPort = new SerialPort(new File(sPort), iBaudRate, parity, dataBits, stopBits);
this.mOutputStream = this.mSerialPort.getOutputStream();
this.mInputStream = this.mSerialPort.getInputStream();
this.mReadThread = new ReadThread();
this.mReadThread.start();
this._isOpen = true;
}
public void close() {
if (this.mReadThread != null) {
this.mReadThread.interrupt();
}
if (this.mSerialPort != null) {
this.mSerialPort.close();
this.mSerialPort = null;
}
this._isOpen = false;
}
public void send(byte[] bOutArray) {
try {
this.mOutputStream.write(bOutArray);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendHex(String sHex) {
byte[] bOutArray = ByteUtil.HexToByteArr(sHex);
send(bOutArray);
}
public void sendTxt(String sTxt) {
byte[] bOutArray = sTxt.getBytes();
send(bOutArray);
}
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
while (!isInterrupted()) {
try {
if (SerialPortHelper.this.mInputStream == null) {
return;
}
int available = SerialPortHelper.this.mInputStream.available();
if (available > 0) {
byte[] buffer = new byte[available];
int size = SerialPortHelper.this.mInputStream.read(buffer);
if (size > 0) {
SerialPortHelper.this.onDataReceived(buffer, size);
}
} else {
SystemClock.sleep(50);
}
} catch (Throwable e) {
Log.e("error", e.getMessage());
return;
}
}
}
}
public boolean isOpen() {
return this._isOpen;
}
protected abstract void onDataReceived(byte[] buffer, int size);
}
初始化串口
private SerialPortHelper mSerialPortHelper;
mSerialPortHelper = new SerialPortHelper("dev/ttyS4", 9600) {
@Override
protected void onDataReceived(byte[] buffer, int size) {
//todo 業務處理,解析接收的數據
}
};
打開串口
mSerialPortHelper.open();
發送數據
mSerialPortHelper.send(byte[]/Hex String/Txt String);
關閉串口
mSerialPortHelper.close();
如果覺得上面的步驟比較繁瑣,開發效率不高的話,可以試試別人造的輪子。
快速使用Android串口