Android串口通信:串口讀寫實例

在Android串口通信:基本知識梳理(http://gqdy365.iteye.com/admin/blogs/2188846)的基礎上,我結合我項目中使用串口的實例,進行總結;

Android使用jni直接進行串口設備的讀寫網上已經有開源項目了,本文是基於網上的開源項目在實際項目中的使用做的調整和優化;
Google串口開源項目見:https://code.google.com/p/android-serialport-api/

下面是我項目中的相關代碼及介紹:

1、SerialPort.cpp

/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <stdio.h>
#include <jni.h>
#include <assert.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/log.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)

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;
}
}

/*
* Class: cedric_serial_SerialPort
* Method: open
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT jobject JNICALL native_open(JNIEnv *env, jobject thiz, jstring path,jint baudrate) {
int fd;
speed_t speed;
jobject mFileDescriptor;

LOGD("init native Check arguments");
/* Check arguments */
{
speed = getBaudrate(baudrate);
if (speed == -1) {
/* TODO: throw an exception */
LOGE("Invalid baudrate");
return NULL;
}
}

LOGD("init native Opening device!");
/* Opening device */
{
jboolean iscopy;
const char *path_utf = env->GetStringUTFChars(path, &iscopy);
LOGD("Opening serial port %s", path_utf);
// fd = open(path_utf, O_RDWR | O_DIRECT | O_SYNC);
fd = open(path_utf, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
LOGD("open() fd = %d", fd);
env->ReleaseStringUTFChars(path, path_utf);
if (fd == -1) {
/* Throw an exception */
LOGE("Cannot open port %d",baudrate);
/* TODO: throw an exception */
return NULL;
}
}

LOGD("init native Configure device!");
/* Configure device */
{
struct termios cfg;
if (tcgetattr(fd, &cfg)) {
LOGE("Configure device tcgetattr() failed 1");
close(fd);
return NULL;
}

cfmakeraw(&cfg);
cfsetispeed(&cfg, speed);
cfsetospeed(&cfg, speed);

if (tcsetattr(fd, TCSANOW, &cfg)) {
LOGE("Configure device tcsetattr() failed 2");
close(fd);
/* TODO: throw an exception */
return NULL;
}
}

/* Create a corresponding file descriptor */
{
jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor,"<init>", "()V");
jfieldID descriptorID = env->GetFieldID(cFileDescriptor,"descriptor", "I");
mFileDescriptor = env->NewObject(cFileDescriptor,iFileDescriptor);
env->SetIntField(mFileDescriptor, descriptorID, (jint) fd);
}

return mFileDescriptor;
}

/*
* Class: cedric_serial_SerialPort
* Method: close
* Signature: ()V
*/
JNIEXPORT jint JNICALL native_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);
return 1;
}

static JNINativeMethod gMethods[] = {
{ "open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;",(void*) native_open },
{ "close", "()I",(void*) native_close },
};

/*
* 爲某一個類註冊本地方法
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}

return JNI_TRUE;
}

/*
* 爲所有類註冊本地方法
*/
static int registerNatives(JNIEnv* env) {
const char* kClassName = "com/jerome/serialport/SerialPort"; //指定要註冊的類
return registerNativeMethods(env, kClassName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0]));
}

/*
* System.loadLibrary("lib")時調用
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);

if (!registerNatives(env)) { //註冊
return -1;
}
//成功
result = JNI_VERSION_1_4;

return result;
}



在編譯時注意修改const char* kClassName = "com/jerome/serialport/SerialPort";爲你Java層與jni對應得包名;

2、Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

TARGET_PLATFORM := android-3
LOCAL_MODULE := serial_port
LOCAL_SRC_FILES := SerialPort.cpp
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)


如果要修改生成so文件的名稱,請修改LOCAL_MODULE := serial_port

3、SerialPort.java

package com.jerome.serialport;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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;

public SerialPort(File device, int baudrate) throws SecurityException, IOException {
mFd = open(device.getAbsolutePath(), baudrate);
if (mFd == null) {
throw new IOException();
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}

public InputStream getInputStream() {
return mFileInputStream;
}

public OutputStream getOutputStream() {
return mFileOutputStream;
}

private native FileDescriptor open(String path, int baudrate);
public native int close();

static {
System.loadLibrary("serial_port");
}
}


4、SerialPortUtil.java


package com.jerome.serialport;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

/**
* 串口操作類
*
* @author Jerome
*
*/
public class SerialPortUtil {
private String TAG = SerialPortUtil.class.getSimpleName();
private SerialPort mSerialPort;
private OutputStream mOutputStream;
private InputStream mInputStream;
private ReadThread mReadThread;
private String path = "/dev/ttyMT1";
private int baudrate = 115200;
private static SerialPortUtil portUtil;
private OnDataReceiveListener onDataReceiveListener = null;
private boolean isStop = false;

public interface OnDataReceiveListener {
public void onDataReceive(byte[] buffer, int size);
}

public void setOnDataReceiveListener(
OnDataReceiveListener dataReceiveListener) {
onDataReceiveListener = dataReceiveListener;
}

public static SerialPortUtil getInstance() {
if (null == portUtil) {
portUtil = new SerialPortUtil();
portUtil.onCreate();
}
return portUtil;
}

/**
* 初始化串口信息
*/
public void onCreate() {
try {
mSerialPort = new SerialPort(new File(path), baudrate);
mOutputStream = mSerialPort.getOutputStream();
mInputStream = mSerialPort.getInputStream();

mReadThread = new ReadThread();
isStop = false;
mReadThread.start();
} catch (Exception e) {
e.printStackTrace();
}
initBle();
}

/**
* 發送指令到串口
*
* @param cmd
* @return
*/
public boolean sendCmds(String cmd) {
boolean result = true;
byte[] mBuffer = (cmd+"\r\n").getBytes();
//注意:我得項目中需要在每次發送後面加\r\n,大家根據項目項目做修改,也可以去掉,直接發送mBuffer
try {
if (mOutputStream != null) {
mOutputStream.write(mBuffer);
} else {
result = false;
}
} catch (IOException e) {
e.printStackTrace();
result = false;
}
return result;
}

public boolean sendBuffer(byte[] mBuffer) {
boolean result = true;
String tail = "\r\n";
byte[] tailBuffer = tail.getBytes();
byte[] mBufferTemp = new byte[mBuffer.length+tailBuffer.length];
System.arraycopy(mBuffer, 0, mBufferTemp, 0, mBuffer.length);
System.arraycopy(tailBuffer, 0, mBufferTemp, mBuffer.length, tailBuffer.length);
//注意:我得項目中需要在每次發送後面加\r\n,大家根據項目項目做修改,也可以去掉,直接發送mBuffer
try {
if (mOutputStream != null) {
mOutputStream.write(mBufferTemp);
} else {
result = false;
}
} catch (IOException e) {
e.printStackTrace();
result = false;
}
return result;
}

private class ReadThread extends Thread {

@Override
public void run() {
super.run();
while (!isStop && !isInterrupted()) {
int size;
try {
if (mInputStream == null)
return;
byte[] buffer = new byte[512];
size = mInputStream.read(buffer);
if (size > 0) {
if(MyLog.isDyeLevel()){
MyLog.log(TAG, MyLog.DYE_LOG_LEVEL, "length is:"+size+",data is:"+new String(buffer, 0, size));
}
if (null != onDataReceiveListener) {
onDataReceiveListener.onDataReceive(buffer, size);
}
}
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
return;
}
}
}
}

/**
* 關閉串口
*/
public void closeSerialPort() {
sendShellCommond1();
isStop = true;
if (mReadThread != null) {
mReadThread.interrupt();
}
if (mSerialPort != null) {
mSerialPort.close();
}
}

}


5、使用方法:
a、配置ndk開發環境,具體百度一下;
b、工程根目錄下新建jni文件夾,將Android.mk和SerialPort.cpp放進去;
c、ndk中進入jni目錄,編譯生成so文件,默認so生成在libs/armeabi下;
d、新建com.jerom.serialport目錄,將SerialPort和SerialPortUtil放進去;
f、在你要使用的地方初始化SerialPortUtil,實現回調接口OnDataReceiveListener即可接受數據;

[b]
總結:
1、串口發送實質就是向串口設備(類似於文件操作)寫入字節流,串口讀取也是一樣;
2、主要jni與Java native得對應;
[/b]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章