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;@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {// 发送串口信息sendThread = new Thread() {@Overridepublic 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() {@Overridepublic void onClick(View v) {closeSerialPort();}});}private void receiveThread() {// 接收串口信息receiveThread = new Thread() {@Overridepublic 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/suString 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 setterspublic 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 <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);
}

4、Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
TARGET_PLATFORM := android-3
LOCAL_MODULE    := serial_port
LOCAL_SRC_FILES := SerialPort.c
LOCAL_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开发板之串口开发相关推荐

  1. 迅为linux下串口,迅为iMX6UL开发板多路串口开发平台接口详解

    原标题:迅为iMX6UL开发板多路串口开发平台接口详解 iMX6UL开发板 核心板参数 尺寸:38mm*42mm CPU:iMX6UL 主频528MHz ARM Cortex-A7架构 单核 内存:5 ...

  2. 迅为iMX6UL开发板多路串口开发平台接口详解

    iMX6UL开发板 核心板参数 尺寸 38mm*42mm CPU iMX6UL 主频528MHz   ARM Cortex-A7架构 单核 内存 512MDDR 存储 8G EMMC 工作电压 5V电 ...

  3. 迅为linux下串口,迅为iMX6UL开发板多路串口开发板接口详解

    迅为iMX6UL开发板 一.底板硬件描述: 1.POWER电源接口 电源输入为5V/2A+,给核心板提供5V电源,给底板供电. 原理图部分如下图所示. 电源接口位置如下图所示. 2.SWITCH电源开 ...

  4. Android开发串口通信之开发板的串口通信

    本人最近刚开始学习android,学习大概将近一个月.学着学着突然想做个小东西出来.因为android前面的学习主要是UI 界面的学习,就想着做一个通信的串口 来实现app与外部的数据传输.通过界面的 ...

  5. 烧写android到开发板,烧写开发板_RZMars的技术博客_51CTO博客

    ###################################################################################### 主机操作: 写u-boot ...

  6. 安卓开发板之串口通信,通过modbus Rtu协议控制下位机

    安卓开发板之串口通信,通过modbus Rtu协议控制下位机 1.环境准备 2.编写串口操作核心类 3.编写测试类 前言:因为公司最近有个人脸识别门禁的项目,这个项目主要业务是实现远程人脸注册,管理员 ...

  7. 【转载】嵌入式开发板通过串口与PC互相传送文件

    嵌入式开发板通过串口与PC互相传送文件 2017年03月24日 15:55:17 缘客_ql 阅读数:6961 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn ...

  8. 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十八)串口编程

    文章目录 一. 串口应用程序编程介绍 1.串口的作用 2. 本套视频特别说明 二. 硬件知识_UART硬件介绍 1. 串口的硬件介绍 2. 串口的参数 3. 串口电平 4. 串口内部结构 三. TTY ...

  9. 乐鑫Esp32学习之旅 安信可 ESP32-Cam 摄像头开发板二次开发 C SDK编程,拍照图片通过有线串口传到上位机PC端。(附带设备端+PC端源码)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 系列一:ESP32系列模组基础学习系列笔记 1. 爬坑学习新旅程,虚 ...

最新文章

  1. 观点|基础模型产业发展路在何方?李飞飞等共话基础模型未来趋势
  2. 使用CSS3悬停效果打造不同的页面版式
  3. 人工智能与模式识别 --中国计算机学会推荐国际学术刊物
  4. counterfactual
  5. jpa取出mysql数组_java读取数据库数据,并将数据存入数组返回
  6. java中如何调用dal接口案例_关于Java:接口的目的
  7. [leedcode 215] Kth Largest Element in an Array
  8. 小米笔记本写代码真香,包邮送一台!
  9. 全民加速节:解读CDN的应用场景与产品价值
  10. 2022年全新美观的春节倒计时代码
  11. 深度学习笔记(26) 卷积神经网络
  12. windows rt运行android,Move from Android to WinRT
  13. Oracle数据库学习
  14. java 中异步消息通知,ActivityMQ的基本使用
  15. c 易语言dll 循环,易语言的Dll命令及程序集知识点
  16. 如何自学成为设计师_不会自学,你永远只能是个三流设计师
  17. 【linux】系统压力模拟工具stress
  18. linux上查看gpu卡型_如何检查Linux上安装了哪个GPU
  19. The last dimension of the inputs to `Dense` should be defined. Found `None`.
  20. IP地址为 140.111.0.0 的B类网络,若要切割为9个子网,而且都要 连上Internet,请问子网掩码设为

热门文章

  1. 青岛农业大学海都学院计算机专业怎么样,青岛农业大学海都学院就业率怎么样...
  2. 国内贵金属市场白银独涨
  3. 艾迪康申请在港上市:新冠贡献超20亿元收入,凯雷为控股股东
  4. 如何利用python抓取免费的IP资源、并测试http代理是否可用
  5. 《Adobe Photoshop CS4中文版经典教程》—第1课1.4节在Photoshop中还原操作
  6. vue安装webpack时出现npm ERR! JSON.parse Failed to parse json
  7. 开源密码管理器 KeeWeb
  8. eoLinker chrome插件离线版安装
  9. 二维数组旋转90度、180度、270度
  10. git enter passphrase for key