关于Linux串口的一些小知识
- 串口名称使用
ls -l /dev/ttyS*
一般情况下串口的名称全部在dev下面,如果你没有外插串口卡的话默认是dev下的ttyS*,一般ttyS0对应com1,ttyS1对应com2,当然也不一定是必然的;- 记得看一下串口的权限,是不是rw-rw-rw-,不是的话请运行
chmod 666 /dev/ttyS\*
给予可读写权限。- 检查串口是否可用,可以对串口发送数据比如对com1口,
echo Hello > /dev/ttyS0
。- 串口驱动:
cat /proc/tty/drivers/sw_serial
。- 在PC上查看连接到CubieBoard2的串口设备:
dmesg | grep ttyS*
。
dmesg | grep ttyS*
[ 0.000000] console [tty0] enabled
[ 6.246827] systemd[1]: Created slice system-getty.slice.
[ 13.740764] usb 3-4: ch341-uart converter now attached to ttyUSB0
以下内容均在Android Studio中实现
配置环境
配置JAVA
下载并配置好JAVA(JAVA_HOME、CLASSPATH、PATH),CMD中要能使用javah
命令。
配置gradle
- 在
gradle
目录中找到gradle-wrapper.properties
文件。修改其中配置为
distributionUrl=https://services.gradle.org/distributions/gradle-2.10-all.zip
。gradle必须高于一定的版本,好像网上说NDK支持是后面加进来的。
在菜单中File->Project Structure->Project中修改Gradle version为2.10也可以,改完后OK。
- 项目中的
build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath ‘com.android.tools.build:gradle:2.1.2‘ // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
- NDK
在项目中
local.properties
中配置ndk,ndk应该没什么要求。ndk.dir=D:\\Programs\\Androidsdk\\ndk-bundle sdk.dir=D:\\Programs\\Androidsdk
我用的是AS自动下载配置的,版本为android-ndk-r12d。
工程
新建
- 新建一个类,类名为SerialPort。代码如下:
package com.bilibili.www.serialporttest; 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; import android.util.Log; public class SerialPort { private static final String TAG = "SerialPort"; // JNI static { //指定库名,需要和build.gradle中一致 try { System.loadLibrary("serial_port"); } catch (UnsatisfiedLinkError e) { Log.d(TAG, "Unsatisfied Link error: " + e.toString()); } } private static native FileDescriptor open(String path, int baudrate, int flags); public native void close(); /* * 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, int flags) 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");//获得root 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(); } } Log.d(TAG, "Start!!!!!!!"); mFd = open(device.getAbsolutePath(), baudrate, flags); if (mFd == null) { Log.e(TAG, "native open returns null"); throw new IOException(); }else { mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } } // Getters and setters public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } }
- 打开terminal,输入
cd <你的工程位置>\app\src\main\java
;输入javah -jni <你的包名>.<类名>
。这里是如javah -jni com.bilibili.www.serialporttest.SerialPort
这样的命令,完成之后如果没有问题,会在java目录下出现一个com_bilibili_www_serialporttest_SerialPort.h的文件。 - 在
<你的工程位置>\app\src\main
目录下新建一个jni
目录。
- 打开terminal,输入
修改
- 将com_bilibili_www_serialporttest_SerialPort.h文件放进jni目录。
- 在
jni
目录下建立一个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 "com_bilibili_www_serialporttest_SerialPort.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" #include <android/log.h> #ifdef __cplusplus extern "C" { #endif 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: com_bilibili_www_serialporttest_SerialPort * Method: open * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; */ JNIEXPORT jobject JNICALL Java_com_bilibili_www_serialporttest_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: com_bilibili_www_serialporttest_SerialPort * Method: close * Signature: ()V */ JNIEXPORT void JNICALL Java_com_bilibili_www_serialporttest_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); } #ifdef __cplusplus } #endif
- module里面的build.gradle
import com.android.build.gradle.api.AndroidSourceSet apply plugin: ‘com.android.application‘ android { compileSdkVersion 24 buildToolsVersion "24.0.0" defaultConfig { applicationId "com.bilibili.www.serialporttest" minSdkVersion 16 targetSdkVersion 19 versionCode 1 versionName "1.0" ndk { moduleName = "serial_port" ldLibs "log", "z", "m", "jnigraphics", "android" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘ ndk { moduleName = "serial_port"//模块名,要和类SerialPort中的System.loadLibrary();一致 ldLibs "log", "z", "m", "jnigraphics", "android"//所要链接的库 abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库,目前可有可无。没有的话就生成NDK能生成的所有平台,比如mips } } debug { ndk { moduleName = "serial_port" ldLibs "log", "z", "m", "jnigraphics", "android" abiFilters "armeabi", "armeabi-v7a", "x86" } } } productFlavors { } } dependencies { compile fileTree(include: [‘*.jar‘], dir: ‘libs‘) testCompile ‘junit:junit:4.12‘ compile ‘com.android.support:appcompat-v7:24.0.0‘ }
- MainActivity
没有任何布局上的改变,要用控制台查看串口接受信息。这里我使用了接收,需要的话,可以自行添加发送代码。
package com.bilibili.www.serialporttest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import android.util.Log; import android.os.Message; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final int msgKey = 2; private File mFile; private FileInputStream mInputStream; private SerialPort mSerialPort; private ReadThread mThread; //private StringBuffer mSb; private String mSb; private boolean flag = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { mFile=new File("/dev/ttySAC0"); mSerialPort = new SerialPort(mFile, 115200, 0); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } mInputStream = (FileInputStream) mSerialPort.getInputStream(); //启动线程 mThread = new ReadThread(); mThread.start(); } public static int searchByte(byte[] data, byte value) { int size = data.length; for (int i = 0; i < size; ++i) { if (data[i] == value) { return i; } } return -1; } public class ReadThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); while (!isInterrupted()) { int size,couth = 128; try { Log.d(TAG, "----ReadThread start----"); if (mInputStream == null) { return; } /* 获取可以读取的数据长度 */ /*for(int coutq1 = mInputStream.available(); coutq1 != couth; coutq1 = mInputStream.available()) { try { sleep(10L); couth = mInputStream.available(); sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } }*/ byte[] buffer = new byte[couth]; Log.d(TAG, "----read(buffer)----"); size = mInputStream.read(buffer); Log.d(TAG, "----size----" + String.valueOf(size)); if (size > 0) { Log.d(TAG, "----ReadThread size>0----"); /*mSb = new StringBuffer(); for (int i = 0; i < buffer.length; i++) { mSb.append(buffer[i]); } Log.d(TAG, "Data=" + mSb.toString());*/ mSb=new String(Arrays.copyOfRange(buffer,0,size)); Log.d(TAG, "Data=" + mSb); Message msg = new Message(); msg.what = msgKey; mHandler.sendMessage(msg); } else { Log.d(TAG, "----ReadThread DataisNull----"); } } catch (IOException e) { Log.d(TAG, "----ReadThread printStackTrace----"); e.printStackTrace(); return; } } } } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case msgKey: //Log.e(TAG, mSb.toString()); Log.e(TAG, mSb); break; default: break; } } }; }
编译
- Make Project(Ctrl + F9)
- 在
<你的工程位置>\app\build\intermediates\ndk\debug\lib
目录中,你会发现”armeabi”, “armeabi-v7a”, “x86”三个目录,将他们复制到<你的工程位置>\app\libs
中。
运行
GLHF!
问题与经验
- 编译没有问题,运行时提示tcaddr()函数出现错误,找不到该函数。
没有把”armeabi”, “armeabi-v7a”, “x86”文件夹放进
<你的工程位置>\app\libs
中,所以apk不包含这几个文件夹中的so二进制文件,所以出现找不到函数的情况。 - 能运行,但是串口没有反应。
请检查串口权限,要求other是可读可写的。
时间: 2024-12-16 09:01:20