Android SerialPort

modbus

Posted by LXG on December 17, 2021

Android-SerialPort-API-Github

Modbus4Android-Github

SerialWorker-Github

RS485

RS485通讯指南

定义

RS-485(目前称为EIA/TIA-485)是通信物理层的标准接口,一种信号传输方式, OSI(开放系统互连)模型的第一级。创建 RS-485 是为了扩展 RS-232 接口的物理功能。

串行 EIA-485 连接是使用两根或三根电线的电缆完成的:一根数据线、一根带反转数据的电线,通常还有一根零线(接地,0 V)

这里的主要思想是通过两根电线传输一个信号。当一根电线传输原始信号时,另一根电线传输其反向副本。这种传输方法提供了对共模干扰的高抵抗力。用作传输线的双绞线可以是屏蔽或非屏蔽的。

rs485.webp

传输距离和速率

1200 米是 RS-485 通信中的最大电缆长度,可以达到100 kbits/s

通讯协议

在 RS485 通信协议中,命令由定义为主站的节点发送。连接到主站的所有其他节点通过 RS485 端口接收数据。根据发送的信息,线路上的零个或多个节点响应主站。

RS-485接口的主要优点是:

  • 通过一对双绞线进行双向数据交换
  • 支持连接到同一条线路的多个收发器,即创建网络的能力
  • 通讯线长
  • 高传输速度

RS485 与 RS232

协议 RS232 RS485
协议类型 双工 半双工
信号类型 不平衡 均衡
设备数量 1 个发射器和 1 个接收器 多达 32 个发射器和 43 个接收器
最大数据传输 19.2Kbps 15 米 10Mbps 15 米
最大电缆长度 约 15.25 米,19.2Kbps 大约 1220 米,100 Kbps
输出电流 500mA 250mA
最小输入电压 +/- 3V 0.2V 差分

SerialPort


public final class SerialPort {

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

    // dev/ttyS3
    private final File device;
    // 9600
    private final int baudrate;
    private final int dataBits;
    private final int parity;
    private final int stopBits;
    private final int flags;

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

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

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

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


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

    public native void close();

}

SerialPortActivity


public abstract class SerialPortActivity extends Activity {

    protected SerialPort mSerialPort;
    protected OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            mSerialPort = 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);
        }
    }

    @Override
    protected void onDestroy() {
        if (mReadThread != null) mReadThread.interrupt();
        mApplication.closeSerialPort();
        mSerialPort = null;
        super.onDestroy();
    }

    public SerialPort getSerialPort()
        throws SecurityException, IOException, InvalidParameterException {
        if (mSerialPort == null) {
            SerialPort serialPort = SerialPort //
                .newBuilder("dev/ttyS3", 9600) // 串口地址地址,波特率
                .parity(2) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN)
                .dataBits(7) // 数据位,默认8;可选值为5~8
                .stopBits(2) // 停止位,默认1;1:1位停止位;2:2位停止位
                .build();

            mSerialPort = serialPort;
        }
        return mSerialPort;
    }

    public void closeSerialPort() {
        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;
        }
    }

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

    protected abstract void onDataReceived(final byte[] buffer, final int size);

}

ConsoleActivity


public class ConsoleActivity extends SerialPortActivity {

    EditText mReception;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.console);

        mReception = findViewById(R.id.EditTextReception);

        EditText Emission = findViewById(R.id.EditTextEmission);
        Emission.setOnEditorActionListener(new OnEditorActionListener() {
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                int i;
                CharSequence t = v.getText();
                char[] text = new char[t.length()];
                for (i = 0; i < t.length(); i++) {
                    text[i] = t.charAt(i);
                }
                try {
                    mOutputStream.write(new String(text).getBytes());
                    mOutputStream.write('\n');
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return false;
            }
        });
    }

    @Override
    protected void onDataReceived(final byte[] buffer, final int size) {
        runOnUiThread(() -> {
            if (mReception != null) {
                mReception.append(new String(buffer, 0, size));
            }
        });
    }
}

JNI

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

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

Modbus

modbus-rtu-guide

Modbus是一种被工业电子设备广泛使用的串行通信协议。在 Modbus 中,在主站(主机)和从站(基于 COM 的设备)* 之间建立连接。Modbus 有助于访问设备的配置并读取测量值。

数据交换由主机发起。主机可以自行将其 RS-485 驱动程序切换到传输模式,而其他 RS485 驱动程序(从机)工作在接收模式。为了让从设备通过通信线路应答主机,“主设备”向它发送一个特殊命令,该命令使目标设备有权将其驱动程序切换到传输模式一段时间

Modbus 是用于设备相互交互的最简单的协议之一。同时对于设备制造商来说很容易实现,这是它盛行的主要原因,同时对于一个工程师、程序员来说却很难,因为它把所有实现的困难都推到了他的肩上。最终的解决方案,需要他处理寄存器和变量的多页表、它们的地址、各种读写和数据转换功能。

Modbus RTU 采用二进制编码和 CRC 错误检查。这些选择是为了提高效率,也是 RTU 模式在工业环境中最常用的主要原因。您可能已经猜到,Modbus ASCII 在发送消息时使用 ASCII 字符。

Modbus 串行通信参数

MODBUS规约模式:RTU模式。

  • 传输速率:2400 bps,4800bps,9600bps,19200bps。
  • 串行口通讯数据格式:1 个起始位, 8 个数据位, 无校验位, 1个停止位。
  • 通讯介质:推荐采用0.5mm的双绞线,不带屏蔽层。(原因是如果使用屏蔽双绞线,但现场接地处理不好反而影响通讯质量)。
  • 应答时间:小于4.5个byte传输时间(帧间隔最小时间) + 10ms。

Modbus 报文

modbus_protocal

  • 地址:取值范围是0-247,如果是0,就是主站广播报文;如果是1-247,则有可能是主站请求或者从站应答。
  • 功能码:也就是报文命令,代表主站对从站的操作,读或者写
  • 数据:数据字段,主请求报文,从应答报文会有所差异。也就是说假设抓取总线报文,如何区分是主站请求还是从站应答,则需要通过数据字段进行区分了。
  • CRC校验:采样CRC16,16位循环冗余校验。

常用功能码

0x03:读取保持寄存器 0x06:写单个寄存器 0x10:写多个寄存器

读取保持寄存器

16位地址的寻址空间为64Kb (2^64)

主机发送Modbus RTU 报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 寄存器地址 寄存器数量 CRC校验
Modbus RTU 01 03 00 01 00 01 D5 CA
Modbus RTU 11 03 00 6B 00 03 D5 CA

从站设备返回Modbus RTU报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 字节数 数据内容 CRC校验
Modbus RTU 01 03 02 00 09 78 42
Modbus RTU 11 03 06 02 2B 00 00 00 64 78 42

写单个寄存器

主机发送Modbus RTU/TCP报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 寄存器地址 数据内容 CRC校验
Modbus RTU 0B 06 00 1D 01 2C 19 2B
Modbus RTU 11 06 00 01 02 2B 19 2B

从站设备返回Modbus RTU/TCP报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 寄存器地址 数据内容 CRC校验
Modbus RTU 0B 06 00 1D 01 2C 19 2B
Modbus RTU 11 06 00 01 02 2B 19 2B

写多个寄存器

主机发送Modbus RTU/TCP报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 寄存器地址 寄存器数量 字节数 数据内容 CRC校验
Modbus RTU 0A 10 00 1E 00 02 04 00 00 75 30 70 8F
Modbus RTU 11 10 00 01 00 02 04 02 2B 00 01 70 8F

从站设备返回Modbus RTU/TCP报文如下:

Modbus协议类型 MBAP报文头 地址码 功能码 寄存器地址 寄存器数量 CRC校验
Modbus RTU 0A 10 00 1E 00 02 20 B5
Modbus RTU 11 10 00 01 00 02 20 B5

modbus4j-java

modbus4j

由 Infinite Automation Systems 和 Serotonin Software 用 Ja​​va 编写的 Modbus 协议的高性能和易于使用的实现。支持 ASCII、RTU、TCP 和 UDP 作为从站或主站传输,自动请求分区和响应数据类型解析。

  • 主机Master及其子类:主机的入口,数据流的起点和终点。
  • 数据端口类StreamTransport:负责数据的写入和读出。
  • Modbus消息类ModbusMessage及其子类:支持Modbus定义的各种方法(FunctionCode)
  • 收发数据控制类MessageControl:支持 timeout、retries,默认200ms,1次。
  • 收发等待室WaitingRoom:负责同步收发逻辑。
  • 输出Request消息类:OutgoingRequestMessage 及其子类。
  • 收到Response消息类:IncomingResponseMessage 及其子类。
  • 解析类MessageParser:负责解析收到的消息。
  • 协议数据类型定义:DataType
  • 协议功能码定义:FunctionCode
  • 协议寄存器范围:RegisterRange

modbus

modbus 仿真工具

www.modbustools.com

Modbus Slave 用于在 32 个窗口中模拟多达 32 个从设备!使用此仿真工具加速您的 PLC 编程

Modbus4Android


public class MainActivity extends BaseActivity {

    private void openDevice() {

        if (ModbusManager.get().isModbusOpened()) {
            ModbusManager.get().closeModbusMaster();
            updateDeviceSwitchButton();
            return;
        }

        ModbusParam param;

        if (mMode == MODE_SERIAL) {
            // 串口
            String path = mDevicePaths[mDeviceIndex];
            int baudrate = mBaudrates[mBaudrateIndex];

            mDeviceConfig.updateSerialConfig(path, baudrate);
            param = SerialParam.create(path, baudrate) // 串口地址和波特率
                .setDataBits(mDataBits) // 数据位
                .setParity(mParity) // 校验位
                .setStopBits(mStopBits) // 停止位
                .setTimeout(1000).setRetries(0); // 不重试
        } else {
            // TCP
            String host = mEtHost.getText().toString().trim();
            int port = 0;
            try {
                port = Integer.parseInt(mEtPort.getText().toString().trim());
            } catch (NumberFormatException e) {
                //e.printStackTrace();
            }
            param = TcpParam.create(host, port)
                .setTimeout(1000)
                .setRetries(0)
                .setEncapsulated(false)
                .setKeepAlive(true);
        }

        ModbusManager.get().closeModbusMaster();
        ModbusManager.get().init(param, new ModbusCallback<ModbusMaster>() {
            @Override
            public void onSuccess(ModbusMaster modbusMaster) {
                showOneToast("打开成功");
            }

            @Override
            public void onFailure(Throwable tr) {
                showOneToast("打开失败," + tr);
            }

            @Override
            public void onFinally() {
                updateDeviceSwitchButton();
            }
        });
    }


    private void send03() {

        if (checkSlave() && checkOffset() && checkAmount()) {
            ModbusManager.get()
                .readHoldingRegisters(mSalveId, mOffset, mAmount,
                    new ModbusCallback<ReadHoldingRegistersResponse>() {
                        @Override
                        public void onSuccess(
                            ReadHoldingRegistersResponse readHoldingRegistersResponse) {
                            byte[] data = readHoldingRegistersResponse.getData();
                            appendText("F03读取:" + ByteUtil.bytes2HexStr(data) + "\n");
                        }

                        @Override
                        public void onFailure(Throwable tr) {
                            appendError("F03", tr);
                        }

                        @Override
                        public void onFinally() {

                        }
                    });
        }
    }


    private void send06() {
        if (checkSlave() && checkOffset() && checkRegValue()) {

            ModbusManager.get()
                .writeSingleRegister(mSalveId, mOffset, mRegValue,
                    new ModbusCallback<WriteRegisterResponse>() {
                        @Override
                        public void onSuccess(WriteRegisterResponse writeRegisterResponse) {
                            appendText("F06写入成功\n");
                        }

                        @Override
                        public void onFailure(Throwable tr) {
                            appendError("F06", tr);
                        }

                        @Override
                        public void onFinally() {

                        }
                    });
        }
    }

}

ModbusWorker


/**
 * ModbusWorker实现,实现了初始化modbus,并增加了线圈、离散量输入、寄存器的读写方法
 */
public class ModbusWorker implements IModbusWorker {

    private final ExecutorService mRequestExecutor;

    protected ModbusMaster mModbusMaster;
    private long mSendTime;
    private long mSendIntervalTime;

    public ModbusWorker() {

        // modbus请求用的单一线程池
        mRequestExecutor = Executors.newSingleThreadExecutor();
    }

    /**
     * 初始化modbus
     *
     * @param param
     * @param callback
     */
    @Override
    public void init(final ModbusParam param, final ModbusCallback<ModbusMaster> callback) {

        rxInit(param).observeOn(AndroidSchedulers.mainThread()).doFinally(new Action() {
            @Override
            public void run() throws Exception {
                try {
                    callback.onFinally();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).subscribe(new Observer<ModbusMaster>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(ModbusMaster modbusMaster) {
                try {
                    callback.onSuccess(modbusMaster);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onError(Throwable e) {
                try {
                    callback.onFailure(e);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }

            @Override
            public void onComplete() {

            }
        });
    }

    /**
     * 初始化modbus
     *
     * @param param
     * @return
     */
    @Override
    public Observable<ModbusMaster> rxInit(final ModbusParam param) {
        return getRxObservable(callableInit(param)).subscribeOn(Schedulers.io());
    }

    /**
     * 初始化的Callable
     *
     * @param param
     * @return
     */
    @NonNull
    private Callable<ModbusMaster> callableInit(final ModbusParam param) {
        return new Callable<ModbusMaster>() {
            @Override
            public ModbusMaster call() throws Exception {

                // 重置发送时间
                mSendTime = 0;

                if (mModbusMaster != null) {
                    mModbusMaster.destroy();
                    mModbusMaster = null;
                }

                ModbusMaster master = param.createModbusMaster();

                try {
                    if (master == null) {
                        throw new ModbusInitException("Invalid ModbusParam!");
                    }
                    master.init();
                } catch (ModbusInitException e) {

                    Log.w(TAG, "ModbusMaster init failed", e);

                    if (master != null) {
                        master.destroy();
                    }
                    // 再抛出异常
                    throw e;
                }

                mModbusMaster = master;
                return master;
            }
        };
    }

}

SerialParam


public class SerialParam implements ModbusParam<SerialParam> {

    /**
     * 串口设备
     */
    private String serialDevice;
    /**
     * 串口波特率
     */
    private int baudRate;
    /**
     * 超时
     */
    private int timeout = DEFAULT_TIMEOUT;
    /**
     * 重试
     */
    private int retries = 2;
    /**
     * 数据位
     */
    private int dataBits = 8;
    /**
     * 校验位
     */
    private int parity = 0;
    /**
     * 停止位
     */
    private int stopBits = 1;

    private SerialParam() {
    }

    public static SerialParam create(String serialDevice, int baudRate) {
        SerialParam param = new SerialParam();
        param.serialDevice = serialDevice;
        param.baudRate = baudRate;
        return param;
    }

    @Override
    public ModbusMaster createModbusMaster() {

        ModbusFactory modbusFactory = new ModbusFactory();

        SerialPortWrapper wrapper =
            new AndroidSerialPortWrapper(getSerialDevice(), getBaudRate(), getDataBits(),
                getParity(), getStopBits());

        ModbusMaster master = modbusFactory.createRtuMaster(wrapper);
        master.setRetries(getRetries());
        master.setTimeout(getTimeout());

        return master;
    }

}

AndroidSerialPortWrapper


/**
 * modbus的Android串口实现
 */
public class AndroidSerialPortWrapper implements SerialPortWrapper {

    private final String mDevice;
    private final int mBaudRate;
    private final int mDataBits;
    private final int mParity;
    private final int mStopBits;

    private BufferedInputStream mInputStream;
    private BufferedOutputStream mOutputStream;
    private SerialPort mSerialPort;

    public AndroidSerialPortWrapper(String device, int baudRate, int dataBits, int parity,
        int stopBits) {
        mDevice = device;
        mBaudRate = baudRate;
        mDataBits = dataBits;
        mParity = parity;
        mStopBits = stopBits;
    }

    @Override
    public void close() throws Exception {

        if (mInputStream != null) {
            try {
                mInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mInputStream = null;
            }
        }

        if (mOutputStream != null) {

            try {
                mOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mOutputStream = null;
            }
        }

        if (mSerialPort != null) {
            try {
                mSerialPort.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void open() throws Exception {

        mSerialPort = SerialPort //
            .newBuilder(mDevice, mBaudRate)
            .parity(mParity)
            .dataBits(mDataBits)
            .stopBits(mStopBits)
            .build();
        
        mInputStream = new BufferedInputStream(mSerialPort.getInputStream());
        mOutputStream = new BufferedOutputStream(mSerialPort.getOutputStream());
    }

}

RtuMaster


public class RtuMaster extends SerialMaster {

    // Runtime fields.
    private MessageControl conn;
    
    /**
     * <p>Constructor for RtuMaster.</p>
     * 
     * Default to validating the slave id in responses
     * 
     * @param wrapper a {@link com.serotonin.modbus4j.serial.SerialPortWrapper} object.
     */
    public RtuMaster(SerialPortWrapper wrapper) {
        super(wrapper, true);
    }

    @Override
    public void init() throws ModbusInitException {
        super.init();

        RtuMessageParser rtuMessageParser = new RtuMessageParser(true);
        conn = getMessageControl();
        try {
            conn.start(transport, rtuMessageParser, null, new SerialWaitingRoomKeyFactory());
            if (getePoll() == null)
                ((StreamTransport) transport).start("Modbus RTU master");
        }
        catch (IOException e) {
            throw new ModbusInitException(e);
        }
        initialized = true;
    }

    @Override
    public ModbusResponse sendImpl(ModbusRequest request) throws ModbusTransportException {
        // Wrap the modbus request in an rtu request.
        RtuMessageRequest rtuRequest = new RtuMessageRequest(request);

        // Send the request to get the response.
        RtuMessageResponse rtuResponse;
        try {
            rtuResponse = (RtuMessageResponse) conn.send(rtuRequest);
            if (rtuResponse == null)
                return null;
            return rtuResponse.getModbusResponse();
        }
        catch (Exception e) {
            throw new ModbusTransportException(e, request.getSlaveId());
        }
        finally {
            
        }
    }

}

SerialMaster


abstract public class SerialMaster extends ModbusMaster {

	// Runtime fields.
    protected SerialPortWrapper wrapper;
    protected Transport transport;

    public SerialMaster(SerialPortWrapper wrapper, boolean validateResponse) {
        this.wrapper = wrapper;
        this.validateResponse = validateResponse;
    }

    /** {@inheritDoc} */
    @Override
    public void init() throws ModbusInitException {
        try {
            
        	this.wrapper.open();
            
            if (getePoll() != null)
                transport = new EpollStreamTransport(wrapper.getInputStream(), wrapper.getOutputStream(),
                        getePoll());
            else
                transport = new StreamTransport(wrapper.getInputStream(), wrapper.getOutputStream());
        }
        catch (Exception e) {
            throw new ModbusInitException(e);
        }
    }

}

MessageControl


public class MessageControl implements DataConsumer {

    private Transport transport;
    private MessageParser messageParser;

    public void start(Transport transport, MessageParser messageParser, RequestHandler handler,
            WaitingRoomKeyFactory waitingRoomKeyFactory) throws IOException {
        this.transport = transport;
        this.messageParser = messageParser;
        this.requestHandler = handler;
        this.waitingRoomKeyFactory = waitingRoomKeyFactory;
        waitingRoom.setKeyFactory(waitingRoomKeyFactory);
        transport.setConsumer(this);
    }

    public IncomingResponseMessage send(OutgoingRequestMessage request, int timeout, int retries) throws IOException {
        byte[] data = request.getMessageData();
        if (DEBUG||ModbusConfig.isEnalbeSendLog())
            System.out.println("MessagingControl.send: " + StreamUtils.dumpHex(data));

        IncomingResponseMessage response = null;

        if (request.expectsResponse()) {
            WaitingRoomKey key = waitingRoomKeyFactory.createWaitingRoomKey(request);

            // Enter the waiting room
            waitingRoom.enter(key);

            try {
                do {
                    // Send the request.
                    write(data);

                    // Wait for the response.
                    response = waitingRoom.getResponse(key, timeout);

                    if (DEBUG && response == null)
                        System.out.println("Timeout waiting for response");
                }
                while (response == null && retries-- > 0);
            }
            finally {
                // Leave the waiting room.
                waitingRoom.leave(key);
            }

            if (response == null)
                throw new TimeoutException("request=" + request);
        }
        else
            write(data);

        return response;
    }

}