Android串口通讯RS485发送和接收数据

最近有个需求是这样子的,客户购买了我们这边的室内可视分机,
客户DIY自己的软件,我们这边需要提供RS485串口通讯demo演示以及sdk集成。
大致需求是这样子,话不多说,往下看。

一、SDK部分

1、拷贝libxxx.so文件到armeabi-v7a目录下

拷贝.so库(用来进行485串口通讯的)到,libs/armeabi-v7a目录下,.so文件名称根据自己项目具体的功能模块命名名称。
拷贝so库

2、删除res目录下所有的资源文件

删除所有资源文件

3、build.gradle(Module:serialport485)

apply plugin: 'com.android.library'

android {
    
    
    compileSdkVersion 26

    defaultConfig {
    
    
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
        ndk {
    
    
            // 设置支持的SO库架构
            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
    }

    buildTypes {
    
    
        release {
    
    
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
    
    
        main {
    
    
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    
    
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    configurations.all {
    
    
        resolutionStrategy.force 'com.android.support:support-annotations:25.3.1'
    }
}

4、serialport485的Library库,AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.leecore.serialport" />

5、串口通讯实现

串口通讯类,静态块加载.so文件,打开/关闭串口,获取输入流/输出流,定义JNI封装接口等等。

/*
 * 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 com.leecore.serialport;

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;

/**
 * @description: 串口通讯
 * @version: v1.0
 * @author: yeyl
 * @date: 2020/12/22 18:46
 * @history:
 */
public class SerialPort {
    
    

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

    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 {
    
    
        /* 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) {
    
    
                throw new SecurityException();
            }
        }
        mFd = serialPortOpen(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;
    }

    public void close() {
    
    
        try {
    
    
            mFileInputStream.close();
        } catch (IOException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
    
    
            mFileOutputStream.close();
        } catch (IOException e) {
    
    
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        serialPortClose();
    }

    /**
     * 打开串口
     *
     * @param path     设备路径
     * @param baudrate 波特率
     * @param flags    标志位
     */
    private native FileDescriptor serialPortOpen(String path, int baudrate, int flags);

    /**
     * 串口关闭
     */
    private native void serialPortClose();

}

6、打包成aar文件依赖方式

编译aar方式一:
编译aar方式一
编译aar方式二:
编译aar方式二
aar文件无debug和release版本之分,使用哪个都是可以的,编译aar结果:
编译aar结果

二、DEMO部分

1、拷贝serialport485.aar文件到libs目录下

serialport485.aar文件名称是被修改后的
拷贝aar文件

2、build.gradle(Module:app)

依赖aar方式一:

apply plugin: 'com.android.application'

android {
    
    
    compileSdkVersion 26
    defaultConfig {
    
    
        applicationId "com.xxx.phone.demo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk {
    
    
            // 设置支持的SO库架构
            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
    }
    buildTypes {
    
    
        release {
    
    
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
    
    
        main {
    
    
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    
    
    implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    configurations.all {
    
    
        resolutionStrategy.force 'com.android.support:support-annotations:25.3.1'
    }
}

依赖aar方式二:

apply plugin: 'com.android.application'

android {
    
    
    ...//此处省略代码
    repositories {
    
    
        flatDir {
    
    
            dirs 'libs'
        }
    }
}

dependencies {
    
    
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation(name: 'serialport485', ext: 'aar')
    ...//此处省略代码
}

3、MainActivity.java

比如打开/关闭串口API、读取/写入数据API等,串口通讯485接口说明,都已在此类中实现了。

package com.xxx.phone.demo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import com.leecore.serialport.SerialPort;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;

/**
 * @description: 串口通讯485,打开/关闭串口、读取/写入数据演示
 * @version: v1.0
 * @author: yeyl
 * @date: 2020/12/23 17:20
 * @history:
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    

    private SerialPort mSerialPort;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    /**
     * 波特率
     */
    private static final int BAUDRATE = 9600;
    /**
     * 串口路径
     */
    private static final String UART_PATH = "/dev/uart_dev/UART_485";
    /**
     * 读取到的数据
     */
    private TextView mTvReadData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvReadData = findViewById(R.id.tv_main_read_data);
        findViewById(R.id.btn_main_open).setOnClickListener(this);
        findViewById(R.id.btn_main_close).setOnClickListener(this);
        findViewById(R.id.btn_main_read_data).setOnClickListener(this);
        findViewById(R.id.btn_main_write_data).setOnClickListener(this);
    }

    /**
     * 字节数组转16进制字符串
     *
     * @param buf
     * @return
     */
    public static String bytesToHexString(byte[] buf) {
    
    
        final String HEX = "0123456789ABCDEF";
        if (buf == null) {
    
    
            return "";
        }
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
    
    
            result.append(HEX.charAt((buf[i] >> 4) & 0x0f)).append(HEX.charAt(buf[i] & 0x0f));
        }
        return result.toString();
    }

    /**
     * 读取数据请求
     */
    private void readDataRequest() {
    
    
        if (mInputStream == null) {
    
    
            return;
        }
        try {
    
    
            if (mInputStream.available() > 0) {
    
    
                byte[] response = new byte[mInputStream.available()];
                mInputStream.read(response);
                mTvReadData.setText(bytesToHexString(response));
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 写入数据请求
     */
    private void writeDataRequest() {
    
    
        if (mOutputStream == null) {
    
    
            return;
        }
        byte[] data = new byte[3];
        data[0] = 97;
        data[1] = 98;
        data[2] = 99;
        try {
    
    
            mOutputStream.write(data);
            mOutputStream.flush();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()) {
    
    
            case R.id.btn_main_open:
                init(UART_PATH, BAUDRATE);
                break;
            case R.id.btn_main_close:
                closeSerialPort();
                break;
            case R.id.btn_main_read_data:
                readDataRequest();
                break;
            case R.id.btn_main_write_data:
                writeDataRequest();
                break;
            default:
                break;
        }
    }

    /**
     * 初始化串口
     *
     * @param uartPath 串口路径
     * @param baudrate 波特率
     */
    public void init(String uartPath, int baudrate) {
    
    
        try {
    
    
            mSerialPort = getSerialPort(uartPath, baudrate);
            mInputStream = mSerialPort.getInputStream();
            mOutputStream = mSerialPort.getOutputStream();
        } catch (SecurityException e) {
    
    
            mSerialPort = null;
        } catch (IOException e) {
    
    
            mSerialPort = null;
        } catch (InvalidParameterException e) {
    
    
        }
    }

    /**
     * 根据设备串口路径以及波特率设置串口
     *
     * @param path     串口路径
     * @param baudrate 波特率
     * @return 打开的串口
     * @throws SecurityException
     * @throws IOException
     * @throws InvalidParameterException
     */
    private SerialPort getSerialPort(String path, int baudrate) throws SecurityException, IOException, InvalidParameterException {
    
    
        if (mSerialPort == null) {
    
    
            /* Check parameters */
            if ((path.length() == 0) || (baudrate == -1)) {
    
    
                throw new InvalidParameterException();
            }
            /* Open the serial port */
            mSerialPort = new SerialPort(new File(path), baudrate, 0);
        }
        return mSerialPort;
    }

    /**
     * 关闭串口
     */
    public void closeSerialPort() {
    
    
        if (mSerialPort != null) {
    
    
            mSerialPort.close();
            mSerialPort = null;
        }
        mInputStream = null;
        mOutputStream = null;
    }
}

4、activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.xxx.phone.demo.MainActivity">

    <Button
        android:id="@+id/btn_main_open"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开串口" />

    <Button
        android:id="@+id/btn_main_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="关闭串口" />

    <Button
        android:id="@+id/btn_main_read_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="读取数据" />

    <Button
        android:id="@+id/btn_main_write_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="写入数据" />

    <TextView
        android:id="@+id/tv_main_read_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp" />

</LinearLayout>

三、串口调试部分

1、demo截图

demo截图

2、串口调试软件截图

写入(发送)数据:
写入(发送)数据
读取(接收)数据:
串口调试软件输入数据
串口调试软件输入97 98 99数据完,点击手动发送,接着回到Modbus485Demo界面中点击读取数据,界面左下角显示979899数据,验证结果是正确的。
点击读取数据,显示数据

猜你喜欢

转载自blog.csdn.net/discode/article/details/115262165