android串口通信——android-serialport-api

2023-05-16

一.串口通信原理

串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。
波特率:这是一个衡量符号传输速率的参数。

二、android-serialport-api

主要介绍一下内容:

1.android-serialport-api简单介绍
2.硬件地址和波特率的获取
3.发送和数据和接收数据

1.android-serialport-api简单介绍

android-serialport-api下有两个主要的类

参数    说明
SerialPort    获取串口的类(其实就是获取输入输出流)
SerialPortFinder    获取硬件地址的类

1.1 SerialPort的介绍

1.1.1调用的顺序如下:

SerialPort构造方法 –> jni open –>c open

 

需要一个操作串口的对象
创建SerialPort对象
JNI(使用c语言的open函数打开文件)
操作的文件对象,波特率,文件操作权限。
调用jni的open方法创建对象。
返回文件的描述(FileDescriptor)
返回需要的串口对象。

1.1.2 SerialPort的构造函数

public SerialPort(File device, int baudrate, int flags)

参数    说明
device    要操作的文件对象
baudrate    波特率
flags     文件操作的标志

1.1.3 C语言中的open函数

c语言中open函数是用来打开一个文件的。具体可以看这里C语言open()函数:打开文件函数

int open(const char * pathname, int flags);

参数 说明
pathname    pathname 指向欲打开的文件路径字符串
flags    文件的打开打开方式: O_RDONLY 以只读方式打开文件O_WRONLY 以只写方式打开文件O_RDWR 以可读写方式打开文件

返回值:若所有欲核查的权限都通过了检查则返回0 值, 表示成功, 只要有一个权限被禁止则返回-1.

SerialPort类

package android_serialport_api;

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;
/**
 1. 串口类
 2. @author qiwenming
 3.  */
public class SerialPort {

    private static final String TAG = "SerialPort";

    /*
     * Do not remove or rename the field mFd: it is used by native method close();
     */
    private FileDescriptor mFd;//文件描述
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    /**
     *获得一个窗口 
     * @param device 设备
     * @param baudrate 波特率
     * @param flags 标志
     * @throws SecurityException
     * @throws IOException
     */
    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");
                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();
            }
        }
        //打开设备,这里面调用jni 的open方法
        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 FileInputStream getInputStream() {
        return mFileInputStream;
    }

    public FileOutputStream getOutputStream() {
        return mFileOutputStream;
    }

    //============== JNI=========================================
    /**
     * 打开串口设备的方法
     * @param path 设备的绝对路径
     * @param baudrate 波特率
     * @param flags 标志
     * @return
     */
    private native static FileDescriptor open(String path, int baudrate, int flags);
    //关闭设备
    public native void close();
    //加载库文件
    static {
        System.loadLibrary("serial_port");
    }
}

SerialPort.c 中open函数

/*
 * 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)
{
    //变量定义,参数检查
    ......


    /* 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);
        //使用c语言的open函数打开文件
        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;
        }
    }

    //设备的一些设置
    ......

    /* Create a corresponding file descriptor */
    //创建我们需要的 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;
}

2.发送和数据和接收数据

2.1数据的发送

数据的发送很简单,通过SerialPort对象的getOutputStream()方法获取到输出流,然后把数据写入到这个流中就行了。

      .....
      mOutputStream = mSerialPort.getOutputStream();

      // 把字符指令转为字节
        byte[] text = StringUtils.hexStringToBytes(sendData.commandStr);
        try {
            mOutputStream.write(text);
            mOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

2.2 数据的接收

数据的接收,通过SerialPort对象的getInputStream()方法获取到输入流,然后读流的就行了。不过一般会开一个线程去读数据

......
try {
            mSerialPort = mApplication.getSerialPort();
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();

            /* Create a receiving thread */
            mReadThread = new ReadThread();
            mReadThread.start();
        } catch (SecurityException e) {
            DisplayError(R.string.error_security);
        } catch (IOException e) {
            DisplayError(R.string.error_unknown);
        } catch (InvalidParameterException e) {
            DisplayError(R.string.error_configuration);
        }
....
/**
*读取流中的数据
*/
private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            while(!isInterrupted()) {
                int size;
                try {
                    byte[] buffer = new byte[64];
                    if (mInputStream == null) return;
                    size = mInputStream.read(buffer);
                    if (size > 0) {
                        onDataReceived(buffer, size);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }


具体的话可以看项目中的SerialPortActivity这个类,但是这个只是一个例子而已,真正在使用中,我们要根据自己的要求来修改,后面的blog中会介绍。

3.硬件地址和波特率的获取

我们都知道要操作一个串口设备,我们需要知道它的地址和波特率。其中波特率的话是一般根据硬件的说明来设定的,所以这个的话,我们只需要看说明就行了,当然在选择的波特率的时候,我们可以提供一些值来做选择。

3.1 波特率

我们可以创建一个集合来存储波特率,用到的时候来选择就行。

/**
 * @author qiwenming
 * @creation 2015-6-18 下午4:10:00
 * @instruction 波特率的集合
 */
public class BaudRateListConstants {

    private static List<Integer> list = new ArrayList<Integer>();
    public static List<Integer> getBaudRateList(){
        if(list.size()<=0){
            list.add(50);
            list.add(75);
            list.add(110);
            list.add(134);
            list.add(150);
            list.add(200);
            list.add(300);
            list.add(600);
            list.add(1200);
            list.add(1800);
            list.add(2400);
            list.add(4800);
            list.add(9600);
            list.add(38400);
            list.add(57600);
            list.add(115200);
            list.add(230400);
            list.add(460800);
            list.add(500000);
            list.add(576000);
            list.add(921600);
            list.add(1000000);
            list.add(1152000);
            list.add(1500000);
            list.add(2000000);
            list.add(2500000);
            list.add(3000000);
            list.add(3500000);
            list.add(4000000);
        }
        return list;
    }
}

3.2 硬件地址的获取

其实硬件地址,也是一个集合来存储着,用得使用,指定硬件地址。我们说andorid-serialport-api中主要的java文件有两个,其中SerialPort我们已经说了,剩下的SerialPortFinder就是获取硬件地址的类。
获取硬件地址的基本步骤:

 

读取/proc/tty/drivers文件
获取到基本地址,然后读取/dev下的所有文件,文件的开始地址与基础地址匹配的就是串口设备,文件的绝对路径地址存入到集合中。
串口设备的真实地址集合

或者如下:

开始
读取/proc/tty/drivers文件
获取到基本地址,然后读取/dev下的所有文件, 文件的开始地址与基础地址匹配的就是串口设备, 文件的绝对路径地址存入到集合中
串口设备的真实地址集合
结束


3.2.1读取 /proc/tty/drivers 文件中的内容

读取 /proc/tty/drivers 文件中的内容,判断哪些是串口设备的基础地址(通过serail这个关键字来判断)。我们可以看到drivers的文件内容如下(外设不同,文件内容不同)

drivers文件

 流程图如下:

1.在读取文件的内容的时候,是一行一行的读取的。(如下面的那条数据)
2.读取到一行数据的时候,窃取第一串字符为文件名。(如下面的那条数据,文件名为:serial)
3.按照” +”(一个或者多个空格)分割一行数据。(如下面的数据分割后的结果是: w[0]=”serial”,w[1]=”/dev/ttyS”,w[2]=”4”,w[3]=”64-67”,w[4]=”serial”)
4.判断上面分割的字符数组的长度是否大于等于5,并且最后一个字符串是不是”serial”,满足条件传递filename到3.2.2中执行 3.2.2 。不满足读取下一行,直到读取完成。

serial               /dev/ttyS       4 64-67 serial


3.2.2通过获取到基础地址,去查找真正的串口设备地址。把地址集合存起来。

流程图如下:

下面是/dev 目录的切图(部分)

这里写图片描述

1.获取/dev下的所有文件,得到一个文件数组files。
2.循环files,等到files[i]。
3.获取files[i]的绝度路径,判断这个绝对路径是不是以3.2.1中传递过来的基础地址filename开头。(例如:我们传递过来的是基础地址是:/dev/ttyS,那么我们这里满足条件的就是:/dev/ttyS0,/dev/ttyS1,/dev/ttyS2,/dev/ttyS3,这四个设备就是串口设备)
4.满足3说明是串口设备,保存到集合中,不满足执行5。
5.判读是否已经循环结束。结束返回集合,没有结束执行2。

SerialPortFinder

/*
 * 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 java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Iterator;
import java.util.Vector;

import android.util.Log;

/**
 * 串口通讯的 寻检器
 * 
 * @author qiwenming
 * 
 */
public class SerialPortFinder {

    /**
     * 设备类
     * 
     * @author qiwenming
     */
    public class Driver {
        public Driver(String name, String root) {
            mDriverName = name;
            mDeviceRoot = root;
        }

        private String mDriverName;//设备名称
        private String mDeviceRoot;//设备根节点
        Vector<File> mDevices = null;//设备集合

        /**
         * 获取设备集合 这个是特定类型的设备 比如USB等
         * @return
         */
        public Vector<File> getDevices() {
            //如果设备已经添加过,就不添加了,否则就必须添加
            if (mDevices == null) {//如果
                mDevices = new Vector<File>();
                File dev = new File("/dev");
                //获取 /dev 下的设备
                File[] files = dev.listFiles();
                int i;
                for (i = 0; i < files.length; i++) {
                    //这里是拿文件的路径和我们 传递进来的路径进行对比,看看开头是不是我们传递捡来的  ,传递进来的函数是 外部类 getDrivers()
                    //比如 我们传递进来的文件路径是:  /dev/ttyUSB 那么我们获取道的文件的绝对路径 如:/dev/ttyUSB1 /dev/ttyUSB2
                    //就满足下面这个条件 ,就会加到集合中去
                    if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
                        Log.d(TAG, "Found new device: " + files[i]);
                        mDevices.add(files[i]);
                    }
                }
            }
            return mDevices;
        }

        public String getName() {
            return mDriverName;
        }
    }

    private static final String TAG = "SerialPort";

    private Vector<Driver> mDrivers = null;

    /**
     * 获取设备  
     * @return
     * @throws IOException
     * 其实就是读取 /proc/tty/drivers 这个文件
     * drivers中有设备的地址的总地址添加到一个集合中
     */
    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) {
                // Issue 3:
                // Since driver name may contain spaces, we do not extract
                // driver name with split()
                //看下面这个会发现 原来我们的 这个第一字符到第二个字符之间是21个,这也就是为什么这里面要用的 0x15 这个原因了 
                //dev/tty             /dev/tty        5       0 system:/dev/tty
                String drivername = l.substring(0, 0x15).trim();//其实就是获取第一个非空格字符串
                String[] w = l.split(" +");//这里是正则表达式:" +" 表示有一个或者多个空格
                if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
                    Log.d(TAG, "Found new driver " + drivername + " on "
                            + w[w.length - 4]);
                    mDrivers.add(new Driver(drivername, w[w.length - 4]));
                }
            }
            r.close();//关闭流
        }
        return mDrivers;
    }

    /**
     * 得到所有的设备的名称
     * @return
     */
    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()]);
    }

    /**
     * 获取道设备的路径
     * @return
     */
    public String[] getAllDevicesPath() {
        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().getAbsolutePath();
                    devices.add(device);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return devices.toArray(new String[devices.size()]);
    }
}

————————————————

转载于:https://blog.csdn.net/qiwenmingshiwo/article/details/49557889

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android串口通信——android-serialport-api 的相关文章

随机推荐

  • python类的实例化

    在 Python 中 xff0c 类的实例化是通过在类名后面加上圆括号的方式来创建一个类的实例 例如 xff0c 如果有一个名为 34 MyClass 34 的类 xff0c 可以通过如下方式创建一个该类的实例 xff1a span cla
  • 如何提取abaqus的位移和其坐标

    坐标 每次在 Abaqus 中运行作业时 xff0c 都会在工作文件夹中创建一个 job name INP 文件 该文件包含节点编号和原始坐标 xff0c 如附图所示 INP 文件可以用任何文本编辑器 xff08 word notepad
  • C语言怎么打印出枚举类型

    在 C 语言中 xff0c 你可以使用 printf 函数来打印枚举类型 首先 xff0c 你需要在枚举类型定义的时候为每一个枚举值赋予一个字符串的别名 xff1a span class hljs built in enum span sp
  • Android android_id, deviceId, oaid的区别

    一 ANDROID ID 1 ANDROID ID是设备首次启动时由系统随机生成的一串64位的十六进制数字 2 获取方式 String ANDROID ID span class token operator 61 span Setting
  • Android设备唯一标识(AndroidID,OAID等 )

    一 ID 体系 xff1a 你只是一串代码 想要了解 OAID xff0c 我们首先需要明白 ID 体系 xff1a 想要追踪一个用户就必须先找到用户 xff0c 在这个过程中 xff0c 标识符 xff08 ID xff09 就像我们的另
  • 如何使用tkinter中的pack使按钮在主窗体正中间

    使用pack 方法可以将按钮放置在主窗体正中间 xff0c 可以使用以下代码 xff1a btn pack expand 61 True fill 61 39 both 39 xff0c 其中expand 61 True表示按钮可以填充整个
  • python实例化的几种表示方式

    Python 中实例化的几种表示方式包括 通过类名直接实例化 如果定义了一个类 34 Person 34 可以使用 34 person 61 Person 34 来实例化该类 通过 super 函数实例化 可以使用 super 函数来实例化
  • 查看git项目工程的地址

    1 xff0c cmd控制台输入 git remote v 命令 首先 xff0c 进入到项目工程目录 xff1a 其次 xff0c 从该目录地址中 xff0c 进入cmd控制台 xff1a 然后 xff0c 输入git remote v
  • 使用Android Studio打包生成Jar包的方法(亲测可用)

    首先我们来说下打成jar包的分类 xff1a 1 application应用打成jar包 2 内库打成jar包 不管是那种打包jar包都得变成内库 xff0c 所以当一种方法讲解了 打jar包之前得让你的项目成内库 xff1a 如何变成内库
  • Android引包出现Duplicate class com.xxx.xxx found in modules错误

    方法一 xff1a 改依赖库的版本 其实就是依赖的包版本不一样 xff0c 产生了冲突 xff0c 把所有依赖的库改成版本一致的就可以了 注意 xff1a 如果有多个library的引用 xff0c 并且各自引入了 so库文件 xff0c
  • Android实现视频播放的3种实现方式

    Android提供了常见的视频的编码 解码机制 使用Android自带的MediaPlayer MediaController等类可以很方便的实现视频播放的功能 支持的视频格式有MP4和3GP等 这些多媒体数据可以来自于Android应用的
  • java:单例模式的五种实现方式

    1 概述 java单例模式是一种常见的设计模式 单例模式有以下特点 xff1a 1 单例类只能有一个实例 xff1b 2 单例类必须自己创建自己的唯一实例 xff1b 3 单例类必须给所有其他对象提供这一实例 xff1b 2 优缺点 优点
  • 如何反编译pyc即pyinstaller出的exe

    有位大佬的代码 https span class token operator span span class token comment blog csdn net qfcy article details 113245876 改pyin
  • Ubuntu 22.04 为 Jellyfin 配置代理

    问题背景 最近捡了一个 1L 小主机 目前装上了 Ubuntu 22 04 打算把这个小东西当作家庭服务器用起来 玩这个当然少不了搭建一套私有影音系统啦 于是在网上找解决方案 自然而然地搭起了一套 Jellyfin 环境 然后就开始踩坑了
  • Undefined symbols for architecture arm64解决方案

    在iOS开发中经常遇到的一个错误是Undefined symbols for architecture arm64 xff0c 这个错误表示工程某些地方不支持arm64指令集 那我们应该怎么解决这个问题了 我们不仅要解决这个问题 更要了解出
  • AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher

    今天使用OkHttp的时候遇到一个坑 xff0c 加了句log后就报错 Log d TAG 34 onResponse 34 43 response body string String resp 61 response body stri
  • Android 关机广播和重启广播的监听

    关于如何在app应用中如何监听系统关机的信息 xff0c 并且做相应的逻辑处理 xff0c 比如存储某些数据等操作 我们都知道开机完成的时候有开机广播 xff0c 那么关机的时候有没有关机广播呢 xff1f 那么Android官方的回答是有
  • 稳定获取Android设备唯一码(UUID)的解决方案

    最近做的一个项目中需要用到Android设备唯一码 xff08 UUID xff09 来标识一台设备 xff0c Android中设备唯一码有很多 xff0c 如 xff1a MAC地址 IMEI号 xff08 DeviceId xff09
  • Android消息推送MQTT实战

    1 前言 年初做了一款Android TV 应用 xff0c 用到了MQTT 主要实现的是类似一些景区利用大屏幕实时显示景点人数 xff0c 超过人数就不允许进入 即利用闸机设备监控到进景区的游客 xff0c 然后通过MQTT将消息发送给大
  • android串口通信——android-serialport-api

    一 串口通信原理 串口通信 xff08 Serial Communications xff09 的概念非常简单 xff0c 串口按位 xff08 bit xff09 发送和接收字节 尽管比按字节 xff08 byte xff09 的并行通信