Android开发之串口通信:AndroidSerialPort

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发之串口通信:AndroidSerialPort相关的知识,希望对你有一定的参考价值。

参考技术A android 串口通信,基于 谷歌官方android-serialport-api 编译

项目github地址: https://github.com/AIlll/AndroidSerialPort

读取数据时很可能会遇到分包的情况,即不能一次性读取正确的完整的数据

解决办法:可以在读取到数据时,让读取数据的线程sleep一段时间,等待数据全部接收完,再一次性读取出来。这样应该可以避免大部分的分包情况

只接收一条数据的情况下,以上方法可以应对数据分包,数据量多的情况下需要考虑是否会因为sleep导致接收多条数据,可以根据通信协议核对包头包尾等参数。

打开串口时,会检测读写权限,当没有权限时,会尝试对其进行提权

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 <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    := -llog

include $(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开发之串口通信:AndroidSerialPort的主要内容,如果未能解决你的问题,请参考以下文章

Android UART 串口通信

Android串口开发与SerialPort工具封装

求助,求大神,android与串口通信

Android USB转串口通信开发基本流程

Qt串口通信开发之QSerialPort模块

RISC-V MCU开发教程之与Matlab的串口通信