Android and serial port communication - modbus

foreword

In the previous two articles, we explained the basics of serial ports and the method of using serial port communication in Android. If you haven’t read the previous articles, it is recommended to read them first, otherwise you may not understand what this article is about. some of the content.

In fact, in practical applications, we rarely use serial communication directly, and generally use Modbus.

Because as I said in the previous article, if we directly use the serial port to communicate, we need to customize the data layer protocol, or simply send a byte number directly for communication, which is obviously inconvenient and not safe.

For example, a problem I mentioned in the previous article, the protocol defined by the driver version manufacturer I used did not define the data length (or attach the data length to the data), nor did it define the stop symbol, which would lead to "stained packets" " or "subcontracting" situation, it is not easy to distinguish data.

And the custom protocol also needs to parse and process the data by itself, which is not so convenient to use.

Therefore, after trying to use the serial port communication directly, our company finally decided to give up the direct use of the serial port communication and use Modbus communication instead.

This article is an extension of a series of articles, we will explain the basics of Modbus and how to use Modbus in Android.

Some of the charts in this article are from the references marked at the end of the article

Modbs Basics

Introduction

Modbus is an application layer message transmission protocol, released by Modicon in 1979, it is a protocol developed to solve PLC communication.

Because Modbus is open source, has no copyright requirements, is easy to deploy and maintain, and has strong reliability, Modbus has become the de facto industry standard for communication protocols in the industrial field, and is now a common connection method between industrial electronic devices.

Since Modbus only defines the message protocol of the application layer, it can use serial port (RS232, RS485) and Ethernet as the physical layer interface.

Modbus is divided into three transmission modes: RTU, ASII, TCP.

When using Modbus, the transmission mode must be the same for all devices.

RTU uses binary data transmission, ASCII uses ASCII character transmission.

RTU and ASCII modes are supported when using a serial connection.

TCP mode is supported when using an Ethernet connection.

Because the focus of this series of articles is to explain the serial communication, we will not explain more about the TCP mode. At the same time, because the ASCII mode is relatively rare in current practical applications, we generally use the RTU mode. Therefore, we will focus on Modbus RTU. If you are interested in other transmission modes, you can read the documents in Reference 4.

Let me explain the difference between Modbus and RS232 and RS485.

RS232 and RS485 define the physical layer standard, that is, the wiring method, the level level, the data transmission method, etc.

Modbus is an application layer protocol, which defines the format in which the data transmitted from the above physical layer should be parsed.

Modbus RTU

When using the serial port as the physical layer protocol, RS485 is usually used.

As we said in the first article, RS485 supports one master and multiple slaves to connect multiple devices at the same time, so Modbus using RS485 also supports multiple device connections. Under standard load conditions, a master can connect up to 32 slaves. And when connecting devices, only daisy chain connections can be used, star networks cannot be used:

1.png

In addition, Modbus is a request/response protocol, that is, only after the master station (host) sends a request to the slave station, the slave station responds with data to the master station, and the slave station cannot directly send data to the master station actively.

Repository Data Model

Four different data models are defined in Modbus, as follows:

name type of data access type illustrate
discrete input single bit read only The I/O system provides
Coil single bit read and write Can be rewritten by application
input register word (word, 16bit) read only The I/O system provides
holding register word (word, 16bit) read and write Can be rewritten by application

Among them, the coil and discrete input can also be called output coil and input coil.

Their data length is one bit, that is, it can only represent 1 or 0, and it is a Boolean type data in the program. For Android programmers, you may wonder what a coil is. In fact, the two models are called coils because Modbus is a protocol written for PLC communication, and some physical devices (such as relays) in PLC have only two states. : Off and on (that is, 0 or 1, or Boolean's false and true), the state switching of these physical devices is generally achieved by relying on the power on/off of the coil, so this type of The data is called a coil.

The input registers and holding registers can also be referred to as input registers and output registers.

Their data length is a word, that is, 16 bits, 2 bytes, which can be regarded as an Int type in the program.

Obviously, there must be more than one available data block for different data models in the same device. Theoretically, each data model can define a maximum of 65536 data blocks.

Therefore, the addresses of each data model are defined as follows:

data model address range
Coil 00001-09999
discrete input 10001-19999
input register 30001-39999
holding register 40001-49999

It can be seen that although we said that each model theoretically supports 65536 data blocks, in actual use, each data model generally only defines a maximum of 10,000 data blocks.

Modbus allows four different data models to be stored in different data blocks, so that different data can be read using different function codes (the function codes will be described below):

2.png

At the same time, Modbus can also map different data models into the same data block, so that different function codes may read the same data:

3.png

function code

In the previous section we introduced the storage area data model, so how do we read data from different data models? In other words, how to distinguish different data models in Modbus?

At this time, the function code should be used.

Three types of function codes are defined in Modbus:

  • Public function codes: standard public general function codes defined by the Modbus organization, including defined and reserved function codes
  • User-defined function codes: Users can customize the function codes they need, and the range is between 65-72 and 100-110 (both decimal).
  • Reserved function codes: Function codes used in traditional equipment of some companies are invalid for public function codes.

4.png

The public function codes define the following types:

5.png

And we generally use the following:

6.png

It can be seen that there are 8 function codes that we commonly use. In fact, if you look closely, you can see that it is just reading all data models; as well as writable data models and writing single/writing multiple permutations and combinations.

When reading data, all data models support reading only one or multiple data at the same time, and all use the same function code.

Writing data also supports writing only a single data and writing multiple data at the same time, but the function codes for writing a single and writing multiple data are separate.

Careful readers may have discovered why all the register addresses in the table are the same. This is because the PLC addresses in the above table use absolute addresses, which are generally used in documents or programs.

The register address of the actual device uses a relative address. Since we have distinguished different data blocks through function codes, in order to save the byte occupation during transmission, we can directly use relative addresses (if absolute addresses are used, the current number of bytes is not enough to represent all addresses).

master/slave

As mentioned above, Modbus using the serial port is a master-slave protocol. That is, at the same time, only one master node and one or more slave nodes are connected on the same serial bus.

Modbus communication is always initiated by the master node, and the slave nodes respond. And the child nodes will not communicate with each other.

In Modbus, the master node does not have an address, and each child node has its own unique address (1-247), usually called a slave station address.

There are two ways for the master node to send requests: unicast mode and broadcast mode.

7.png

In unicast mode, the master station (master node) sends a request with the address of the slave station (child node) to all devices currently connected, but only the slave station whose address matches the slave station will respond to the request and return data. Other devices will not respond and will not perform any operations (the request message will be discarded directly after reading the address does not match). In this mode, two messages are generated: a request message from the master station and a response message from the slave station.

In the broadcast mode, all slave stations will not send a response message to the master station, but will perform the requested operation, and the request of the master station will be sent to all slave stations. Broadcast mode is generally used for writing data. At this time, the slave station address in the request message sent by the master station is 0, which means broadcast.

Data Frame

A Modbus RTU message frame consists of 4 parts:

8-bit slave station address + 8-bit function code + maximum 252*8-bit data + 16-bit error check

8.png

The error checking method usually used in RTU is CRC checking (familiar? CRC appears again)

I don’t know if you have noticed that the function code here uses 2 bytes, but when the function code was introduced above, the maximum is obviously only 127, so where does the remaining half go?

In the Modbus definition, if the slave can correctly process the master’s request, the function code in the returned message will be the same as the function code requested by the master. If an error occurs and the request cannot be processed correctly, the slave will return the function code of the message. The code will be the function code whose highest bit is 1, namely 128-255.

The data bits have different data content and length in different function codes, host requests and slave responses. For example, if a coil is requested to be read, the content of the data bits is: 2 bytes of data indicate the start address of the read coil + 2 words The section data represents the number of coils to read.

At this time, the slave will return the data according to the number of coils requested to be read, and the data format is: 1 byte indicates the number of bytes of data + N bytes indicates that the coil status data has been read. If the read coil status data is not an 8-bit integer, it will be filled with 0 to make it a multiple of 8 bits.

9.png

Data bits can be empty in some cases.

Here is a complete example of a data frame (example from reference 1).

We have a slave station that is a temperature and humidity sensor, and the slave station address is 1. It will write the collected humidity into the 40001 block of the holding register; write the temperature into the 40002 block of the holding register. At this point we send a read holding register request to get its temperature and humidity information.

Then, the request message of the host is:

0103040146013B5A59

Separately disassemble this data frame as:

01: slave station address

03: Function code, read holding register

00 00 : The starting register address for reading (corresponding to the relative address of 40001)

00 02 : The length of the register to be read (here means to read two registers continuously)

C4 0B: CRC check code

After the slave machine receives the request, the response message is:

0103040146013B5A59

Dismantling data:

01: slave station address

03: Function code, read holding register

04: The byte length of the read data (here means 4 bytes)

01 46 01 3B: The read data, the first two bytes are humidity (converted to 326 in decimal, or 32.6%), and the last two bytes are temperature (315 in decimal, or 31.5 degrees Celsius)

5A 59: CRC check code

Here is a word, don't worry about why the read temperature and humidity value should be divided by 10 to get the actual value, because this is defined by the temperature and humidity sensor manufacturer.

Using Modbus in Android

After the above introduction, I believe everyone has a general understanding of Modbus.

So, how to use Modbus in Android? This shouldn't be a problem if you understand the basics of Modbus, and the previous two articles roughly.

The core idea is to open the serial port by using the android-serialport-api or the USB Host method introduced in the previous article, and obtain the input and output streams, and then encapsulate or parse the data according to the Modbus protocol standard when sending and receiving data.

How to open the serial port and obtain the input and output streams has been introduced in the previous article, so what needs to be solved now is how to encapsulate/parse the data.

Of course, you can write one yourself according to the Modbus standard document.

Alternatively, you can directly use ready-made third-party libraries without reinventing the wheel.

Here we can use modbus4j , but as can be seen from its name, this is a java library. Fortunately, we only need to use its parsing and packaging functions, so it can still be used in Android.

modbus4j

According to the old rules, you need to introduce dependencies before using modbus4j:

// 添加仓库地址
repositories {
    
    
	...
	maven {
    
     url 'https://jitpack.io' }
}

……

// 添加依赖
implementation 'com.github.MangoAutomation:modbus4j:3.1.0'

Then before using it officially, we need to create a new class inherited from SerialPortWrapperto implement the serial port function on Android:

class AndroidWrapper : SerialPortWrapper {
    
    
    // 关闭串口
    override fun close() {
    
    
        TODO("Not yet implemented")
    }

    // 打开串口
    override fun open() {
    
    
        TODO("Not yet implemented")
    }

    // 获取输入流
    override fun getInputStream(): InputStream {
    
    
        TODO("Not yet implemented")
    }

    // 获取输出流
    override fun getOutputStream(): OutputStream {
    
    
        TODO("Not yet implemented")
    }

    // 获取波特率
    override fun getBaudRate(): Int {
    
    
        TODO("Not yet implemented")
    }

    // 获取数据位
    override fun getDataBits(): Int {
    
    
        TODO("Not yet implemented")
    }

    // 获取停止位
    override fun getStopBits(): Int {
    
    
        TODO("Not yet implemented")
    }

    // 获取校验位
    override fun getParity(): Int {
    
    
        TODO("Not yet implemented")
    }
}

Rewrite the above methods in our newly created class to provide several parameters required for serial communication.

Then, initialize modbus4j and send messages:

val modbusFactory = ModbusFactory()

val wrapper: SerialPortWrapper = AndroidWrapper()

// 创建管理对象
val master = modbusFactory.createRtuMaster(wrapper)
    
// 发送消息
val request = ……
val response = master.send(request) // requst 为要发送的数据,response 为接收到的响应数据

The above is the simple method of using modbus4j. If students don’t even want to complete the serial communication by themselves, they can also use this library Modbus4Android. This library is based on android -serialport-api and the above modbus4j encapsulates a ready-to-use Modbus on Android library.

However, it uses android-serialport-api to realize serial port communication. If you need to use USB Host, you may still need to package a library yourself. (I will also package one when I find a suitable test device)

Moreover, this library uses RxJava. If you don't like RxJava, you have to encapsulate one yourself. In fact, it is not difficult to encapsulate it. You can change it based on this library.

Modbus4Android

The first step in using this library is still to import dependencies:

// 添加远程仓库
repositories {
    
    
   maven {
    
     url 'https://jitpack.io' }
}

……

// 添加依赖
dependencies {
    
    
   implementation 'com.github.licheedev:Modbus4Android:2.0.2'
}

Next, for convenience and to avoid repeated initialization, we can create a global singleton instance ModbusManager:

class ModbusManager : ModbusWorker() {
    
    



    /**
     * 释放整个ModbusManager,单例会被置null
     */
    @Synchronized
    override fun release() {
    
    
        super.release()
        sInstance = null
    }

    companion object {
    
    
        @Volatile
        private var sInstance: ModbusManager? = null
        fun getInstance(): ModbusManager {
    
    
            var manager = sInstance
            if (manager == null) {
    
    
                synchronized(ModbusManager::class.java) {
    
    
                    manager = sInstance
                    if (manager == null) {
    
    
                        manager = ModbusManager()
                        sInstance = manager
                    }
                }
            }
            return manager!!
        }
    }
}

Then initialize the serial port connection:

private fun initConnect(): Boolean {
    
    
    Log.i(TAG, "initConnect: 开始初始化连接 Modbus\nconfig=$config")

    val param = SerialParam
        .create(config.serialPath, config.serialRate) // 串口地址和波特率
        .setDataBits(config.serialDataBits) // 数据位
        .setParity(config.serialParity) // 校验位
        .setStopBits(config.serialStopBits) // 停止位
        .setTimeout(config.serialTimeout)  //超时时间
        .setRetries(config.serialRetries) // 重试次数

    try {
    
    
        // 初始化前先关闭,避免串口已经被打开过
        ModbusManager.getInstance().closeModbusMaster()
        val modbusMaster = ModbusManager.getInstance().syncInit(param)
        return true
        // 初始化(打开串口)成功
    } catch (e: ModbusInitException) {
    
    
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: InterruptedException) {
    
    
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ExecutionException) {
    
    
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ModbusTransportException) {
    
    
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ModbusRespException) {
    
    
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    }
    return false
}

After completing the above steps, we can start sending requests and receiving data.

Here we still take reading coil data as an example, we can use synchronous request:

val slaveId = 1 // 从站地址
val start = 00001 // 读取的起始位置
val len = 1 // 需要读取的长度

val response = ModbusManager.getInstance().syncReadCoil(slaveId, start, len)

Among them response is the response data information.

In addition, we can also use asynchronous reading:

ModbusManager.getInstance().readCoil(slaveId, start, len, object : ModbusCallback<ReadCoilsResponse> {
    
    
    override fun onSuccess(response: ReadCoilsResponse?) {
    
    
        // 请求成功,收到回复为 response
    }
    override fun onFailure(tr: Throwable?) {
    
    
        // 请求失败
    }
    override fun onFinally() {
    
    
        // 请求完成
    }
})

All read methods supported by the library are as follows:

10.png

All write data methods are as follows:

11.png

Summarize

In this article, we introduced Modbus, an application layer protocol that is likely to come into contact with when using serial port communication in Android, and explained how to use Modbus in Android, and introduced a few others that I think are easier to use. third-party libraries.

Since then, the content of serial communication on Android is almost the same.

The next article depends on the situation and writes about the principles and algorithm implementation of each verification method or the pit dug above to use USB Host to implement Modbus. Maybe this series will end here, haha.

References

  1. This lesson will take you through the Modbus communication protocol
  2. Quickly understand the Modbus communication protocol in 6 minutes!
  3. Modbus
  4. modbus_proto_cn.pdf

Guess you like

Origin blog.csdn.net/sinat_17133389/article/details/130788945