Android 串口开发记录

一、导入

在我们的认识中Android系统是手机系统,它的物理接口一般只有usb host接口(之前的手机还有耳机接口,不过近几年取消了)。但其实安卓支持各种各样的工业接口,如HDMI、usb、网口、串口等等。本文将来说一下在安卓下开发串口通信。

二、串口是什么?

串行接口简称串口,也称​​串行通信​​接口或​​串行通讯接口​​(通常指​​COM接口​​),是采用串行通信方式的​​扩展接口​​。串行​​接口​​ (Serial Interface)是指数据一位一位地顺序传送。其特点是​​通信线路​​简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。

三.安卓串口Demo开发

这里使用的是谷歌开源的一个项目​​https://github.com/cepr/android-serialport-api​​ 我这里以AS项目为例做一个简单的demo。

1、下载该项目,找到android-serialport-api文件夹中project文件夹里的libs文件夹。我们后面需要使用这个libs文件夹里的so库。 如图所示:

2、创建AS工程,在mian目录下创建jniLibs目录,并将刚刚找到的libs文件夹中的三个文件拷贝进去。

3、在app下的build.gradle添加如下配置,用以适配不同架构。

ndk {
            //选择要添加的对应cpu类型的.so库。
            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
        }

上面我们添加了三中类型的libserial_port.so库,所以build.gradle添加配置也是这三个。

4、创建包名:android_serialport_api,并把SerialPort.java拷贝进去,SerialPort.java已经封装了串口的打开、配置、读写和关闭串口的方法,我们直接调用即可。

以下是串口操作类SerialPort.java

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";
	public FileDescriptor mFd;
	private FileInputStream mFileInputStream;
	private FileOutputStream mFileOutputStream;

	public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

		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本地接口,实现串口的打开和关闭)
	private native static FileDescriptor open(String path, int baudrate, int flags);
	public native void close();

	static {//加载jni下的C文件库
		Log.d(TAG, "本地库加载中");
		System.loadLibrary("serial_port");
	}
}

以及使用串口的工具类SerialPortUtils.java

package android_serialport_api;

import android.util.Log;
import android.widget.Toast;


import com.example.androidserialport.DIYToast;
import com.example.androidserialport.Data;
import com.example.androidserialport.MainActivity;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;

/**
 * 串口监听工具
 * Created by sam on 2018/3/31.
 */
public class SerialPortUtils {

    private final String TAG = "SerialPortUtils";
    private String path = "/dev/ttyS1";//串口名,安卓下一般为ttys1,ttys2... 而windows下则为com1,com2,com3... 我们平时用的usb口其实也是一个串口,所以用usb转232转换器就可以在电脑上使用串口了
    private int baudrate = 9600;//波特率
    public boolean serialPortStatus = false; //是否打开串口标志
    public String data_;
    public boolean threadStatus; //线程状态,为了安全终止线程

    public SerialPort serialPort = null;
    public InputStream inputStream = null;
    public OutputStream outputStream = null;


    /**
     * 打开串口
     * @return serialPort串口对象
     */
    public SerialPort openSerialPort(){
        try {
            serialPort = new SerialPort(new File(path),baudrate,0);
            this.serialPortStatus = true;
            threadStatus = false; //线程状态

            //获取打开的串口中的输入输出流,以便于串口数据的收发
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();

            new ReadThread().start(); //开始线程监控是否有数据要接收
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "openSerialPort: 打开串口异常:" + e.toString());
            DIYToast.ShowToast(MainActivity.context,"openSerialPort: 打开串口异常:" + e.toString());
            return serialPort;
        }
        Log.d(TAG, "openSerialPort: 打开串口");
        DIYToast.ShowToast(MainActivity.context,"openSerialPort: 打开串口");
        return serialPort;
    }

    /**
     * 关闭串口
     */
    public void closeSerialPort(){
        try {
            inputStream.close();
            outputStream.close();

            this.serialPortStatus = false;
            this.threadStatus = true; //线程状态
            serialPort.close();
        } catch (Exception e) {
            Log.e(TAG, "closeSerialPort: 关闭串口异常:"+e.toString());
            DIYToast.ShowToast(MainActivity.context,"closeSerialPort: 关闭串口异常:"+e.toString());
            return;
        }
        Log.d(TAG, "closeSerialPort: 关闭串口成功");
        DIYToast.ShowToast(MainActivity.context,"closeSerialPort: 关闭串口成功");
    }

    /**
     * 发送串口指令
     * @param data
     */
    public void sendSerialPort(byte[] data){
        //Log.d(TAG, "sendSerialPort: 发送数据");

        try {
            if (data.length > 0) {
                outputStream.write(data);

                //注意:有些工控机上需要以\n或者\r来表示数据发送完毕,所以在write完毕后要多输出个\r,这个看个人情况
                //outputStream.write('\n');
                //outputStream.write('\r'+'\n');

                outputStream.flush();
                //Log.d(TAG, "sendSerialPort: 串口数据发送成功");
            }
        } catch (IOException e) {
            Log.e(TAG, "sendSerialPort: 串口数据发送失败:"+e.toString());
        }

    }

    /**
     * 因为这里是阻塞的,所以单开一线程来读数据
     */
    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            //判断进程是否在运行,更安全的结束进程
            while (!threadStatus){
                //Log.d(TAG, "监听打卡...");
                //64   1024
                byte[] buffer = new byte[16];
                int size; //读取数据的大小
                try {
                    size = inputStream.read(buffer);
                    if (size > 0){
                        Log.d(TAG, "接收到数据:" + new String(buffer));
                        Data.test=String.valueOf(new String(buffer));
                        //注意,多数情况下使用串口的单片机都是使用十六井控进行输送,所以这里可能还需要把buffer转换为十六进制
                    }
                } catch (IOException e) {
                    Log.e(TAG, "run: 数据读取异常:" +e.toString());
                }
            }

        }
    }

    /**
     * Utility class to convert a byte array to a hexadecimal string.
     *
     * @param bytes Bytes to convert
     * @return String, containing hexadecimal representation.
     */
    public static String ByteArrayToHexString(byte[] bytes) {
        final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
        char[] hexChars = new char[bytes.length * 2];
        int v;
        for ( int j = 0; j < bytes.length; j++ ) {
            v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    /**
     * 将byte[]转为各种进制的字符串
     * @param bytes byte[]
     * @param radix 基数可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制
     * @return 转换后的字符串
     */
    public static String binary(byte[] bytes, int radix){
        return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
    }

}

我将通过串口的工具类SerialPortUtils来实现安卓串口通信具体功能。以下是我的主界面代码以及布局。

主界面代码

package com.example.androidserialport;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import android_serialport_api.SerialPortUtils;

public class MainActivity extends AppCompatActivity {
    private Button btn_open,btn_close,btn_send;
    private TextView tv1;
    private SerialPortUtils S;
    public static Context context;
    private EditText et_number;
    private Handler handler = new Handler();
    private Runnable runnable;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context=this;
        btn_open = findViewById(R.id.btn_open);
        btn_close = findViewById(R.id.btn_close);
        btn_send = findViewById(R.id.btn_send);
        tv1 = findViewById(R.id.textView);
        et_number = findViewById(R.id.et_number);
        S = new SerialPortUtils();
        //打开串口
        btn_open.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                S.openSerialPort();

            }
        });
        //关闭串口
        btn_close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                S.closeSerialPort();
            }
        });
        //发送数据
        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int number = Integer.parseInt(et_number.getText().toString());
                byte[] bytes = {(byte) number};
                S.sendSerialPort(bytes);
            }
        });
        //接收数据
        runnable = new Runnable() {
            @Override
            public void run() {
                tv1.setText(Data.test);
                handler.postDelayed(runnable,1000);
            }
        };
        handler.post(runnable);
    }
}

简单的布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_open"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开串口"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.179"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.704" />

    <Button
        android:id="@+id/btn_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="关闭串口"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.803"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.704" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textSize="55dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.367"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.23" />

    <EditText
        android:id="@+id/et_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.529"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.232" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="接收数据"
        android:textSize="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.389" />

</androidx.constraintlayout.widget.ConstraintLayout>

到这里基本就结束了,成功实现了安卓下的串口通信。贴张图吧。

这里使用的是虚拟串口,具体方法将在下一篇文章中讲解。

最后直接附上所需so库。

​so库下载​icon-default.png?t=N7T8https://wwsc.lanzouy.com/izxy712ulxsb

  • 61
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值