Android开发板之串口开发
Android开发板之串口开发
简介
首先描述一下我的应用项目,它是一个简单的智能盒子,主要内容:是通过Android开发板上的串口进行数据的读取操作,一块android开发板外接一个Arduino,再接一个传感器,当传感器上返回数据后在Arduino上进行编程处理,转换成Android程序想要的数据,再通过串口通信输入到Android程序中进行响应操作。
那说完用途,接下来说说这个具体的Android开发,Android的串口编程,在网上我们可以找到开源项目android-serialport-api,这个在github下载到:https://github.com/cepr/android-serialport-api(GITHUB的地址),有兴趣的可以下载源码做做研究。
由于android-serialport-api的代码的复杂,对于初学者不是很适合,所以我将串口开发的几个主要涉及到的知识点抽取出来,使大家易懂容易上手。
精华集锦:关于Android串口,简单的总结出主要的四部曲:打开串口,串口输入,串口输出,关闭串口。而串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位,其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1,校验位是为了减少误差的会根据奇、偶进行补位操作,下面具体的和我一起来分解我的项目:
一、项目配置
首先,看一下项目结构:创建了一个jni和jniLibs的两个包文件夹,在AndroidStudio 下SO文件需要放在jniLibs下,将MK和C一些文件放在jni下。如图:
再就是,在main/Java下,创建android_serialport_api包,放入SerialPort.java文件,这个包名必须的是和jni包下的SerialPort.c中的函数名同步的,(这样做是为了保持与C函数中的方法名同步,如果不这样做你需要根据你自己建立的包的名字来更改SerialPort.c中的函数名,其实无所谓了)。
二、代码讲解
1、程序的入口activity类:
在这个类中,通过设置按钮配置,打开按钮打开串口,发送按钮点击向串口输出数据,实现了在串口TX和RX短接的情况下,模拟串口的读写数据。
package com.xkdx.serial_test;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.text.TextUtils;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;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 { protected SerialPort mSerialPort; protected InputStream mInputStream; protected OutputStream mOutputStream; private TextView text; private String prot = "ttyS0";//串口号(具体的根据自己的串口号来配置) private int baudrate = 9600;//波特率(可自行设定) private static int i = 0; private StringBuilder sb; Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { text.setText(text.getText().toString().trim()+sb.toString()); } } }; private Thread receiveThread; private Thread sendThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sb = new StringBuilder(); text = (TextView) findViewById(R.id.text_receive); //设置按钮事件 Button btn_set = (Button) findViewById(R.id.btn_set); btn_set.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EditText et_prot = (EditText) findViewById(R.id.et_prot); EditText et_num = (EditText) findViewById(R.id.et_num); prot = TextUtils.isEmpty(et_prot.getText().toString().trim()) ? "ttyS0" : et_prot.getText().toString().trim(); baudrate = Integer.parseInt(TextUtils.isEmpty(et_num.getText() .toString().trim()) ? "9600" : et_num.getText() .toString().trim()); } }); //打开按钮事件 Button btn_open = (Button) findViewById(R.id.btn_open); btn_open.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 配置并打开串口 try { mSerialPort = new SerialPort(new File("/dev/" + prot), baudrate, 0); mInputStream = mSerialPort.getInputStream(); mOutputStream = mSerialPort.getOutputStream(); receiveThread(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { Log.i("test", "打开失败"); e.printStackTrace(); } } }); //发送按钮事件 Button btn_send = (Button) findViewById(R.id.btn_send); btn_send.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 发送串口信息 sendThread = new Thread() { @Override public void run() { while (true) { try { i++; mOutputStream.write(("1").getBytes()); Log.i("test", "发送成功:1" + i); Thread.sleep(1000); } catch (Exception e) { Log.i("test", "发送失败"); e.printStackTrace(); } } } }; sendThread.start(); } }); //关闭按钮事件 Button btn_close = (Button) findViewById(R.id.btn_receive); btn_receive.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { closeSerialPort(); } }); } 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.append(recinfo).append(","); 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(); } } }}
2、在整个项目中SerialPort.Java是最重要的类,在这个类中完成了对串口权限的修改,通过jni调用底层C函数,实现串口的一系列操作。代码:
package android_serialport_api;import android.util.Log;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"; private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { //检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限 if (!device.canRead() || !device.canWrite()) { try { //通过挂在到linux的方式,修改文件的操作权限 Process su = Runtime.getRuntime().exec("/system/bin/su"); //一般的都是/system/bin/su路径,有的也是/system/xbin/su String cmd = "chmod 777 " + 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, flags); 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(调用java本地接口,实现串口的打开和关闭) /** * @param path 串口设备的据对路径 * @param baudrate 波特率 * @param flags 校验位 */ private native static FileDescriptor open(String path, int baudrate, int flags); public native void close(); static {//加载jni下的C文件库 System.loadLibrary("serial_port"); }}
3、再来看看SerialPort.c文件,在这个C文件中,主要实现了Open()和Close()方法的实现。
/* * 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 #include #include #include #include #include #include #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, "" , "()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);}
4、Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)TARGET_PLATFORM := android-3LOCAL_MODULE := serial_portLOCAL_SRC_FILES := SerialPort.cLOCAL_LDLIBS := -lloginclude $(BUILD_SHARED_LIBRARY)
如果你要修改so文件的名称,请修改LOCAL_MODULE:= serial_port,主要的就是这些,还有其他的类就不一 一说了,打开一看就明白。
调试和总结:
在调试中如果大家没有串口硬件的话可以使用PC机+模拟器完成调试实验。具体操作方法:
1、打开cmd进入到android开发的sdk目录下的tools文件夹下;
2、执行命令emulator @(你自己的模拟器的名字) -qemu -serial COM3(COM3是你的PC的一个串口通过命令挂载到你的模拟器上,当然你也可以是COM1跟你电脑对应);例:我的模拟器叫123,我给模拟器挂载的电脑上的串口是COM3,则执行:emulator @123 -qemu -serial COM3
这样你会看到模拟器已经启动,这时候你将程序部署上运行即可。
如果用程序打开串口,提示没有读写权限。应该在命令提示符下用linux命令赋予读写的权限: 进入shell:adb shell 进入设备目录:#cd dev 修改权限:#chmod 777 ttyS2即可。(这个我已经写入程序内,无需在添加,如果LOG日志打印显示无权限,那就只能你手动在执行一边,再解说以下这个进入shell,这个是adb shell,你的命令执行的目录下必须有adb shell.exe多的不解释了)
Demo截图:
下载地址:seriail_test串口测试代码
Demo运行操作说明:由于只是为测试程序的串口收发通过性,所以将串口的TX和DX短接,然后实现自发自收的功能。主要操作:首先你将串口的TX和DX短接后,在APP里面输入端口号和波特率,点击设置按钮,如果不进行设置,则默认端口为ttyS0,波特率为9600设置完后,点击打开按钮,去设置和打开串口,再去点击发送按钮,程序就进行自己发送信息了,默认内容为1;最后点击关闭则关闭串口,释放流对象。(Demo只是为测试代码的通过性,如果按照我描述的步骤走,不会有BUG,测试已通过)。
希望我的文章对你有一定的帮助,要是有实在解决不了的问题,请留言给我!如果你有什么好的分享也请留言给我!
更多相关文章
- Android第十八课 VS源码文件导入Android工程 中文乱码
- Android 主题之安装的APK主题文件
- android中图片的三级cache策略(内存、文件、网络)之三:文件缓存策略
- Android用户界面设计:基本按钮
- Android 4.0 Launcher2源码分析——Laucher界面元素分解(主布局文
- 读懂Android (1):使用Android内部的DownloadProvider下载文件,并
- Android学习路线(二十八)保存文件