Android硬件通信之 串口通信

2023-05-16

一,串口介绍

1.1 串口简介

串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口;

串行接口(SerialInterface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢;

1.2 串口使用场景

串口是一种用于android开发板对硬件设备通信的一种协议,通过发送某种指令控制硬件设备,通常用于物联网设备的信息传输,比如切割器,打印机,ATM吐卡机、IC/ID卡读卡等。

1.3 波特率

波特率表示串口传输速率,用来衡量数据传输的快慢,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。波特率与距离成反比,波特率越大传输距离相应的就越短;

1.4 数据位

这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息;

1.5 停止位

用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢;

1.6 校验位

在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位;

1.7 串口地址

不同操作系统的串口地址,Android是基于Linux的所以一般情况下使用Android系统的设备串口地址为/dev/ttyS0;

  • /dev的串口包括:虚拟串口,真实串口,USB转串口
  • 真实串口:/dev/tty0..tty1这个一般为机器自带COM口
  • 虚拟串口:/dev/ttyS1...ttyS2...ttyS3...均为虚拟console,同样可以作为输入输出口
  • USB转串口:/dev/tty/USB0

二 Android中串口的实践

2.1 由于串口底层需要调用C代码,所以需要用jni来进行C交互,下面是全部的C代码,以及JNI调用

2.1 SerialPort.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class android_serialport_SerialPort */

#ifndef _Included_android_serialport_SerialPort
#define _Included_android_serialport_SerialPort
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     android_serialport_SerialPort
 * Method:    open
 * Signature: (Ljava/lang/String;IIIII)Ljava/io/FileDescriptor;
 */
JNIEXPORT jobject JNICALL Java_android_serialport_SerialPort_open
  (JNIEnv *, jobject, jstring, jint, jint, jint, jint, jint);

/*
 * Class:     android_serialport_SerialPort
 * Method:    close
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_android_serialport_SerialPort_close
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
/* Header for class android_serialport_SerialPort_Builder */

#ifndef _Included_android_serialport_SerialPort_Builder
#define _Included_android_serialport_SerialPort_Builder
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

2.2 SerialPort.c

#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_serialport_SerialPort_open
        (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint dataBits, jint parity,
         jint stopBits,
         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);


        cfg.c_cflag &= ~CSIZE;
        switch (dataBits) {
            case 5:
                cfg.c_cflag |= CS5;    //使用5位数据位
                break;
            case 6:
                cfg.c_cflag |= CS6;    //使用6位数据位
                break;
            case 7:
                cfg.c_cflag |= CS7;    //使用7位数据位
                break;
            case 8:
                cfg.c_cflag |= CS8;    //使用8位数据位
                break;
            default:
                cfg.c_cflag |= CS8;
                break;
        }

        switch (parity) {
            case 0:
                cfg.c_cflag &= ~PARENB;    //无奇偶校验
                break;
            case 1:
                cfg.c_cflag |= (PARODD | PARENB);   //奇校验
                break;
            case 2:
                cfg.c_iflag &= ~(IGNPAR | PARMRK); // 偶校验
                cfg.c_iflag |= INPCK;
                cfg.c_cflag |= PARENB;
                cfg.c_cflag &= ~PARODD;
                break;
            default:
                cfg.c_cflag &= ~PARENB;
                break;
        }

        switch (stopBits) {
            case 1:
                cfg.c_cflag &= ~CSTOPB;    //1位停止位
                break;
            case 2:
                cfg.c_cflag |= CSTOPB;    //2位停止位
                break;
            default:
                cfg.c_cflag &= ~CSTOPB;    //1位停止位
                break;
        }

        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_serialport_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);
}

2.3 gen_SerialPort_h.sh 生成java的文件目录

#!/bin/sh
javah -o SerialPort.h -jni -classpath ../java android.serialport.SerialPort

2.4 SerialPort.java jni对应的java文件

/*
 * 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;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.DataOutputStream;
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 final class SerialPort {

    private static final String TAG = "SerialPort";

    public static final String DEFAULT_SU_PATH = "/system/bin/su";

    private static String sSuPath = DEFAULT_SU_PATH;
    private File device;
    private int baudrate;
    private int dataBits;
    private int parity;
    private int stopBits;
    private int flags;

    /**
     * Set the su binary path, the default su binary path is {@link #DEFAULT_SU_PATH}
     *
     * @param suPath su binary path
     */
    public static void setSuPath(@Nullable String suPath) {
        if (suPath == null) {
            return;
        }
        sSuPath = suPath;
    }

    /**
     * Get the su binary path
     *
     * @return
     */
    @NonNull
    public static String getSuPath() {
        return sSuPath;
    }

    /*
     * 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 dataBits 数据位;默认8,可选值为5~8
     * @param parity   奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
     * @param stopBits 停止位;默认1;1:1位停止位;2:2位停止位
     * @param flags    默认0
     * @throws SecurityException
     * @throws IOException
     */
    public SerialPort(@NonNull File device, int baudrate, int dataBits, int parity, int stopBits,
                      int flags) throws SecurityException, IOException {


        this.device = device;
        this.baudrate = baudrate;
        this.dataBits = dataBits;
        this.parity = parity;
        this.stopBits = stopBits;
        this.flags = flags;

        /* Check access permission */
        Log.e(TAG, "SerialPort: canRead" + device.canRead());
        if (!device.canRead() || !device.canWrite()) {
            try {
                /* Missing read/write permission, trying to chmod the file */
                Process su;
                su = Runtime.getRuntime().exec(sSuPath);
                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();
            }
        }

        mFd = open(device.getAbsolutePath(), baudrate, dataBits, parity, stopBits, flags);
        if (mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        }
        mFileInputStream = new FileInputStream(mFd);
        mFileOutputStream = new FileOutputStream(mFd);


    }

    /**
     * 串口,默认的8n1
     *
     * @param device   串口设备文件
     * @param baudrate 波特率
     * @throws SecurityException
     * @throws IOException
     */
    public SerialPort(@NonNull File device, int baudrate) throws SecurityException, IOException {
        this(device, baudrate, 8, 0, 1, 0);
    }

    /**
     * 串口
     *
     * @param device   串口设备文件
     * @param baudrate 波特率
     * @param dataBits 数据位;默认8,可选值为5~8
     * @param parity   奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
     * @param stopBits 停止位;默认1;1:1位停止位;2:2位停止位
     * @throws SecurityException
     * @throws IOException
     */
    public SerialPort(@NonNull File device, int baudrate, int dataBits, int parity, int stopBits)
            throws SecurityException, IOException {
        this(device, baudrate, dataBits, parity, stopBits, 0);
    }

    // Getters and setters
    @NonNull
    public InputStream getInputStream() {
        return mFileInputStream;
    }

    @NonNull
    public OutputStream getOutputStream() {
        return mFileOutputStream;
    }

    /**
     * 串口设备文件
     */
    @NonNull
    public File getDevice() {
        return device;
    }

    /**
     * 波特率
     */
    public int getBaudrate() {
        return baudrate;
    }

    /**
     * 数据位;默认8,可选值为5~8
     */
    public int getDataBits() {
        return dataBits;
    }

    /**
     * 奇偶校验;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
     */
    public int getParity() {
        return parity;
    }

    /**
     * 停止位;默认1;1:1位停止位;2:2位停止位
     */
    public int getStopBits() {
        return stopBits;
    }

    public int getFlags() {
        return flags;
    }

    // JNI
    private native FileDescriptor open(String absolutePath, int baudrate, int dataBits, int parity,
                                       int stopBits, int flags);

    public native void close();

    /**
     * 关闭流和串口,已经try-catch
     */
    public void tryClose() {
        try {
            mFileInputStream.close();
        } catch (IOException e) {
            //e.printStackTrace();
        }

        try {
            mFileOutputStream.close();
        } catch (IOException e) {
            //e.printStackTrace();
        }

        try {
            close();
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }

    static {
        System.loadLibrary("serial_port");
    }

    public static Builder newBuilder(File device, int baudrate) {
        return new Builder(device, baudrate);
    }

    public static Builder newBuilder(String devicePath, int baudrate) {
        return new Builder(devicePath, baudrate);
    }

    public final static class Builder {

        private File device;
        private int baudrate;
        private int dataBits = 8;
        private int parity = 0;
        private int stopBits = 1;
        private int flags = 0;

        private Builder(File device, int baudrate) {
            this.device = device;
            this.baudrate = baudrate;
        }

        private Builder(String devicePath, int baudrate) {
            this(new File(devicePath), baudrate);
        }

        /**
         * 数据位
         *
         * @param dataBits 默认8,可选值为5~8
         * @return
         */
        public Builder dataBits(int dataBits) {
            this.dataBits = dataBits;
            return this;
        }

        /**
         * 校验位
         *
         * @param parity 0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
         * @return
         */
        public Builder parity(int parity) {
            this.parity = parity;
            return this;
        }

        /**
         * 停止位
         *
         * @param stopBits 默认1;1:1位停止位;2:2位停止位
         * @return
         */
        public Builder stopBits(int stopBits) {
            this.stopBits = stopBits;
            return this;
        }

        /**
         * 标志
         *
         * @param flags 默认0
         * @return
         */
        public Builder flags(int flags) {
            this.flags = flags;
            return this;
        }

        /**
         * 打开并返回串口
         *
         * @return
         * @throws SecurityException
         * @throws IOException
         */
        public SerialPort build() throws SecurityException, IOException {
            return new SerialPort(device, baudrate, dataBits, parity, stopBits, flags);
        }
    }


    public static void checkFilePermission(File file) {
        Log.e(TAG, "canRead: " + file.canRead());
        Log.e(TAG, "canWrite: " + file.canWrite());
        if (!file.canRead() || !file.canWrite()) {
            try {
                /* Missing read/write permission, trying to chmod the file */
                Process su;
                su = Runtime.getRuntime().exec(sSuPath);
                String cmd = "chmod 7777 " + file.getAbsolutePath() + "\n" + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                Log.e(TAG, "checkFilePermission: " + file.getAbsolutePath());
                if ((su.waitFor() != 0) || !file.canRead() || !file.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "checkFilePermission: Exception:" + e.getMessage());
                throw new SecurityException();
            }
        }
    }


    //隐藏系统导航栏
    public void hideBottomNavation() {
        chmod("mount -o remount -w /system");
        chmod("chmod 777 /system");
        chmod("echo qemu.hw.mainkeys=1 >> /system/build.prop");
    }

    public void chmod(String instruct) {
        try {
            Process process = null;
            DataOutputStream os = null;
            process = Runtime.getRuntime().exec("su");
            os = new DataOutputStream(process.getOutputStream());
            os.writeBytes(instruct);
            os.flush();
            os.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

2.5 CMakeLists.txt,编译c或c++程序的规则文件

Cmake在Jni那篇讲过,这个地方在讲下

CMake是一个可以跨平台的编译工具,可以用简单的语句来描述所有平台的编译过程。他能够输出各种各样的 makefile 或者工程文件。和make与makefile类似,我们在使用CMake时同样也需要一个文件来提供规则,这个文件就是CMakeLists

使用CMake编写跨平台工程的流程如下:

(1)编写源文件

(2)编写CMakeLists.txt

(3)由CMake根据CMakeLists.txt来生成相应的makefile文件

(4)使用make并根据makefile调用gcc来生成相应的可执行文件。

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             serial_port

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/SerialPort.c )
             
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       serial_port

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

2.5 调用串口-连接,并获取输入输出流

Runnable serialConnectRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    if (mSerialPort == null) {
                        mSerialPort = new SerialPort(new File(path), baudrate);
                        mOutputStream = mSerialPort.getOutputStream();
                        mInputStream = mSerialPort.getInputStream();                    
                     }
                } catch (SecurityException e) {
                    ToastUtil.showToast(App.getInstance(), "You do not have read/write permission to the serial port.");
                } catch (IOException e) {
                    ToastUtil.showToast(App.getInstance(), "The serial port can not be opened for an unknown reason.");
                } catch (InvalidParameterException e) {
                    ToastUtil.showToast(App.getInstance(), "Please configure your serial port first.");
                }
                //Serial结束
            }
        };

 2.6 读取串口消息

 private class ReadThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                int size;
                try {
                    byte[] buffer = new byte[512];
                    if (mInputStream == null) return;
                    size = mInputStream.read(buffer);
                    if (size > 0) {
                        String mReception=new String(buffer, 0, size);
                        String msg = mReception.toString().trim();
                        Log.e(TAG, "接收短消息:" + msg);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

2.7 发送串口指令

 private class WriteRunnable implements Runnable {
        @Override
        public void run() {
            try {
                String cmd="KZMT;";
                Log.e(TAG, "发送短消息:" + cmd);
                mOutputStream.write(cmd.getBytes());
                mOutputStream.flush();
            } catch (IOException e) {

            }
        }
    }

2.8 断开关闭串口

/**
 * 关闭串口连接
 */
public void closeSerialPortStream() {
	try {
		if (mOutputStream != null) {
			mOutputStream.close();
			mOutputStream = null;
		}
		if (mInputStream != null) {
			mInputStream.close();
			mInputStream = null;
		}
		if (mSerialPort != null) {
			mSerialPort.close();
			mSerialPort = null;
		}
	  
	} catch (Exception e) {
		e.printStackTrace();
	}
}

三 google官方串口工具类

3.1 除了上面自己编程C底层文件,也可以直接用google官方的串口工具SDK(android-serialport-api),Github串口Demo地址:https://github.com/licheedev/Android-SerialPort-API

3.2 依赖:

allprojects {
    repositories {
        ...
        jcenter()
        mavenCentral() // since 2.1.3
    }
}

dependencies {
        implementation 'com.licheedev:android-serialport:2.1.3'
}

3.3 使用

// 默认8N1(8数据位、无校验位、1停止位)
// Default 8N1 (8 data bits, no parity bit, 1 stop bit)
SerialPort serialPort = new SerialPort(path, baudrate);

// 可选配置数据位、校验位、停止位 - 7E2(7数据位、偶校验、2停止位)
// or with builder (with optional configurations) - 7E2 (7 data bits, even parity, 2 stop bits)
SerialPort serialPort = SerialPort 
    .newBuilder(path, baudrate)
// 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
// Check bit; 0: no check bit (NONE, default); 1: odd check bit (ODD); 2: even check bit (EVEN)
//    .parity(2) 
// 数据位,默认8;可选值为5~8
// Data bit, default 8; optional value is 5~8
//    .dataBits(7) 
// 停止位,默认1;1:1位停止位;2:2位停止位
// Stop bit, default 1; 1:1 stop bit; 2: 2 stop bit
//    .stopBits(2) 
    .build();
    
// read/write to serial port - needs to be in different thread!
InputStream in = serialPort.getInputStream();
OutputStream out = serialPort.getOutputStream();

// close
serialPort.tryClose();

四 总结

串口通讯使用到进程、Linux指令、JNI等,但本质最终还是获得一个输入输出流去进行读写操作;

串口通讯对于Android开发者来说,仅需关注如何连接、操作(发送指令)、读取数据。

大部分的物联网通信本质上都是获取io流,通过io流进行数据的传输和读取,比如蓝牙,wifi等,只不过蓝牙,wifi是通过Socket协议维持一个长连接进行通信 

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

Android硬件通信之 串口通信 的相关文章

随机推荐

  • [LeetCode]1138. Alphabet Board Path(第147周周赛)(模拟)

    On an alphabet board we start at position 0 0 corresponding to character board 0 0 Here board 61 34 abcde 34 34 fghij 34
  • 简洁高效的一套iOS列表框架GYTableViewController

    之前做项目一直使用自己封装TableView框架 xff0c 最近把他整理了下 不要脸的 开放出来 如果您用的比较爽的话请点击右上角的star关注下 xff0c 也可以随时发送issues吐槽给我 xff0c 我会随时发现并解决 githu
  • 通过切换源解决Vscode 安装go-outline等插件失败

    vscode编辑go程序 xff0c 提示安装go outline xff0c install不成功 xff1a 解决办法 1 切换代理源 xff1a 开启代理设置 go env w GO111MODULE 61 on 设置代理源 go e
  • linux用java -jar启动jar包缓慢问题

    1 首先查看linux服务器hostname 命令 xff1a less etc hostname 2 和下图展示是否一致 xff0c 如果一致 xff0c 直接跳转第4步 3 不一致时 xff0c 需要修改成一致 xff1a 修改命令 x
  • 使用Go语言编写命令行实用程序

    当今的计算机环境中 xff0c 命令行界面仍然是一种常用的工具 在一些特定的应用场景中 xff0c 如服务器管理 网络管理等 xff0c 命令行工具非常有用 在Go语言中 xff0c 我们可以很容易地编写命令行实用程序 xff0c 以帮助我
  • Anaconda报NotWritableError错时解决的方法

    Anaconda报NotWritableError错时解决的方法 出现的问题 最近在windows平台下使用Anaconda软件时遇到的这个问题 xff0c 百度了好久也没有找到这个错误的解决方法 xff0c 后面自己认真的看了下报错提示才
  • iOS UIBezierPath贝赛尔曲线详解

    UIBezierPath是在画图 xff0c 定制动画轨迹中都有应用 UIBezierPath主要用来绘制矢量图形 xff0c 它是基于Core Graphics对CGPathRef数据类型和path绘图属性的一个封装 xff0c 所以是需
  • C++字符串格式化的几种方式

    使用snprintf格式化字符串使用boost format格式化字符串使用stringstream格式化字符串 具体示例 使用snprintf格式化字符串 span class token macro property span clas
  • wampserver的安装与配置的详细过程:

    首先是百度网盘里wampserver exe的地址 xff1a 链接 xff1a https pan baidu com s 1NyqBb7CjSC6wDOhQQ34BUw 提取码 xff1a ntjn 安装过程 xff1a 下载好后 xf
  • 用paramiko包对远程服务器操作时报错paramiko.buffered_pipe.pipeTimeout

    最近在做服务端的一些自动化测试 xff0c 用到了python下的paramiko这个包连接远程服务器并执行一些操作 当我向服务器发送了一些命令 xff0c 准备接收返回结果的时候 xff0c 也就是用到了recv这个方法的时候会报错par
  • python 字符串里面提取变量的方法

    1 字符串拼接 比如单引号和2个 43 号里面的就是变量 2 占位符 S 有多个变量在后面逗号分隔即可
  • 树莓派Raspbian更新源(Debian)| 完整解决步骤

    转载自 xff1a 树莓派更新源更换 树莓派小无相系列 型号 xff1a 树莓派 3b 43 系统 xff1a Raspbian系统 xff08 Debian xff09 本质 xff1a 常见更新教程是更新 xff1a etc apt s
  • 【ABAQUS】hypermesh如何导出CEL网格到Abaqus

    Abaqus的CEL具备流固耦合分析能力 xff0c 有些朋友喜欢用hm进行网格划分 xff0c 但hm划分的欧拉体是无法直接导入到AbaqusCAE界面的 xff0c 本文提供导入方法如下 xff0c 希望对遇到问题的朋友有所帮助 第1步
  • 编译原理和技术 Lab 1 Lexical Analysis

    Lab 1 Lexical Analysis 1 Goal You are given A public repository of a incomplete project on Gitlab The URL is http 210 45
  • mybatis 的xml文件中调用java的方法

    1 使用场景 最近在做项目开发时 xff0c 遇到一个很棘手的问题 xff0c 前端传的搜索条件 xff0c 不能简单的作为查询条件 xff0c 直接传给后端 xff0c 而需要处理之后才可以作为条件 xff0c 拼接到sql中去 2 解决
  • 好用的工具分享

    1 比较好用的远程控制软件parsec Connect to Work or Games from Anywhere Parsec https parsec app 2 比较好用的JSON内容对比工具 JSON Compare Best J
  • mysql 将数据库表字段全部转为小写

    需求场景 xff1a 最近在开发项目时 xff0c 需要创建一张表 xff0c 发现别的库 xff0c 已经有人创建了 xff0c 于是便把建表语言拿过来 xff0c 直接建表 xff0c 但是有一件不爽的事 xff0c 原来创建表时 xf
  • python中 文件明明存在,但是os.path.exists 返回False

    文件明明存在 xff0c 但是用os path exists xff08 xff09 返回False xff0c 通过试验发现 xff0c 路径或者文件名中还有中文就会返回fasle xff0c 所以文件夹的名称或者文件名最好不要含有中文
  • redhat7.4安装ansible

    ansible是什么就不做介绍了 xff0c 网上一大堆 xff0c 在线安装也比较简单 xff0c 因为特别原因 xff0c 我们的服务器全部在内网运行 xff0c 所以必须采用离线安装 1 ansible安装方式有 源码 pip yum
  • Android硬件通信之 串口通信

    一 xff0c 串口介绍 1 1 串口简介 串行接口简称串口 xff0c 也称串行通信接口或串行通讯接口 xff08 通常指COM接口 xff09 xff0c 是采用串行通信方式的扩展接口 xff1b 串行接口 xff08 SerialIn