android 串口 android-serialport-api
最近在做Android串口的开发,找到一个开源的串口类android-serialport-api。其主页在这里http://code.google.com/p/android-serialport-api/ ,这里可以下到APK及对源码。
但是下载源码之后发现源码不能直接使用,而且源码结构较为复杂。关于串口的操作不外乎几步:
1.打开串口(及配置串口);
2.读串口;
3.写串口;
4.关闭串口。
android-serialport-api的代码使用了继承等复杂的行为,不容易使初学者很快掌握关于串口的上述4步,所以我特别自己写了一个demo,只有一个activity,其中包含了打开串口,写串口,读串口的操作,对于关闭串口,大家一开就会不明白怎么写了。
http://www.cnblogs.com/CZM-/p/7943572.html
参考此贴,利用NDK编译jni c文件为库并运行
这里是工程结构
首先新建jni包,添加SerialPort.c即可,
/* * Copyright 2009-2011 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 <termios.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <jni.h> #include "SerialPort.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: 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 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(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); 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; } /* * Class: cedric_serial_SerialPort * Method: close * Signature: ()V */ JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close (JNIEnv *env, jobject thiz) { jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); // LOGD("close(fd = %d)", descriptor); close(descriptor); }
接下来在工程的local.properties文件中添加NDK路径(上面下载好的那个NDK),类似其中的SDK路径一样,我的添加后如下:
ndk.dir=/home/cam/Android/Sdk/ndk-bundle
sdk.dir=/home/cam/Android/Sdk
接下来在app module目录下的build.gradle中设置库文件名(生成的so文件名)。找到gradle文件的defaultConfig这项,在里面添加如下内容:
defaultConfig {
ndk {
moduleName "testhello"
ldLibs "log", "z", "m" //添加依赖库文件,因为有log打印等
}
}
新建android_serialport_api包,(包名字必须一模一样,不然需要根据你的包名重新编译产生头文件,并修改c文件)并在此包下新建SerialPort.java
/* * 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. */ package android_serialport_api; import android.util.Log; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.util.Iterator; import java.util.Vector; public class SerialPortFinder { public class Driver { private String mDriverName; private String mDeviceRoot; Vector<File> mDevices = null; public Driver(String name, String root) { mDriverName = name; mDeviceRoot = root; } public Vector<File> getDevices() { if (mDevices == null) { mDevices = new Vector<File>(); File dev = new File("/dev"); File[] files = dev.listFiles(); if(null == files || files.length ==0){ return mDevices; } int i; for (i=0; i<files.length; i++) { if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) { Log.d(TAG, "查找的设备: " + files[i]); mDevices.add(files[i]); } } } return mDevices; } public String getName() { return mDriverName; } } private static final String TAG = "SerialPort"; private Vector<Driver> mDrivers = null; Vector<Driver> getDrivers() throws IOException { if (mDrivers == null) { mDrivers = new Vector<Driver>(); LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers")); String l; while((l = r.readLine()) != null) { String drivername = l.substring(0, 0x15).trim(); String[] w = l.split(" +"); if ((w.length >= 5) && (w[w.length-1].equals("serial"))) { Log.d(TAG, "---串口设备=" + drivername + " on " + w[w.length-4]); mDrivers.add(new Driver(drivername, w[w.length-4])); } } r.close(); } return mDrivers; } public String[] getAllDevices() { Vector<String> devices = new Vector<String>(); // Parse each driver Iterator<Driver> itdriv; try { itdriv = getDrivers().iterator(); while(itdriv.hasNext()) { Driver driver = itdriv.next(); Iterator<File> itdev = driver.getDevices().iterator(); while(itdev.hasNext()) { String device = itdev.next().getName(); String value = String.format("%s (%s)", device, driver.getName()); devices.add(value); } } } catch (IOException e) { e.printStackTrace(); } return devices.toArray(new String[devices.size()]); } public String[] getAllDevicesPath() { Vector<String> devices = new Vector<String>(); Iterator<Driver> itdriv; try { itdriv = getDrivers().iterator(); while(itdriv.hasNext()) { Driver driver = itdriv.next(); Iterator<File> itdev = driver.getDevices().iterator(); while(itdev.hasNext()) { String device = itdev.next().getAbsolutePath(); devices.add(device); } } } catch (IOException e) { e.printStackTrace(); } return devices.toArray(new String[devices.size()]); } }
创建主活动MainActivity.java
package com.otherway.myapplication; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Toast; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android_serialport_api.SerialPort; public class MainActivity extends Activity implements View.OnClickListener { private static final String TAG = "MainActivity"; protected SerialPort mSerialPort; protected InputStream mInputStream; protected OutputStream mOutputStream; private String prot = "/dev/ttySAC2"; private int baudrate = 9600; private static long i = 0; private Toast mToast; Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { } } }; private Thread receiveThread; private Thread sendThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initLayout(); mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT); } /** * 初始化Layout。 */ private void initLayout() { findViewById(R.id.btn_open).setOnClickListener(MainActivity.this); findViewById(R.id.btn_send).setOnClickListener(MainActivity.this); findViewById(R.id.btn_close).setOnClickListener(MainActivity.this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_open: showTip("btn_open"); // 打开 try { mSerialPort = new SerialPort(new File(prot), baudrate, 0); mInputStream = mSerialPort.getInputStream(); mOutputStream = mSerialPort.getOutputStream(); receiveThread(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { Log.e(TAG, "打开失败"); e.printStackTrace(); } break; case R.id.btn_send: showTip("btn_send"); // 发送 sendThread = new Thread() { @Override public void run() { while (true) { try { i++; mOutputStream.write((String.valueOf(i)).getBytes()); Log.i("test", "发送成功:1" + i); Thread.sleep(1); } catch (Exception e) { Log.i("test", "发送失败"); e.printStackTrace(); } } } }; sendThread.start(); break; case R.id.btn_close: showTip("btn_close"); closeSerialPort(); break; default: break; } } private void receiveThread() { // 接收 receiveThread = new Thread() { @Override public void run() { while (true) { int size; try { byte[] buffer = new byte[1024]; if (mInputStream == null) return; size = mInputStream.read(buffer); if (size > 0) { String recinfo = new String(buffer, 0, size); Log.i("test", "接收到串口信息:" + recinfo); // sb = recinfo; // handler.sendEmptyMessage(1); } } catch (IOException e) { e.printStackTrace(); } } } }; receiveThread.start(); } /** * 关闭串口 */ public void closeSerialPort() { if (mSerialPort != null) { mSerialPort.close(); } if (mInputStream != null) { try { mInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (mOutputStream != null) { try { mOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } private void showTip(final String str) { mToast.setText(str); mToast.show(); } }
主界面布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.otherway.myapplication.MainActivity"> <Button android:id="@+id/btn_open" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="100dp" android:text="打开" /> <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/btn_open" android:text="发送" /> <Button android:id="@+id/btn_close" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/btn_send" android:text="关闭" /> </RelativeLayout>
改为你需要的串口端口号及波特率;
测试时将串口rx,tx短接,打开串口后,再点击发送程序会一直循环发送,在调试信息上会打印出串口接受数据;