Android and serial port communication - data packet processing

foreword

This article is the fifth article of Android serial communication. Originally this article was not in the plan, but I encountered this problem in the project recently. I just took this opportunity to write an article. While deepening my understanding, I also let everyone know that data may be subcontracted during serial communication. understood.

In fact, I have heard about the possibility of subcontracting in serial port communication, but I have never encountered it in actual use, or to be precise, although I have encountered it, I have not specifically dealt with it:

Subcontract? Isn’t it because the transmitted data is incomplete, so I will lose this data and wait for a complete data.

Or, the previous use is very small amount of data, and the data read at a time is only 1 byte, so it is rare for the data package to be incomplete.

What is subcontracting?

Strictly speaking, there is no concept of subcontracting.

Because of the characteristics of serial port communication, it does not know or cannot know what the so-called "packet" is. It only knows that you have given it data, and it will send the data as much as possible.

Because the serial communication uses streaming transmission, that is to say, all data is sent and read in the form of streams, and there is no so-called "packet" concept.

The so-called "packet" is just a "packet" that we artificially specify the length of data or the format of data that meets the requirements at the application layer.

In order to minimize the number of requests during communication, when processing the data stream, it usually reads as much data as possible, and then caches it (the so-called buffered data) until it reaches a certain size or exceeds a certain size. Time has not read new data.

For example, we artificially stipulate that a data packet is 10 bytes, and when PLC or other serial device sends, the data of these 10 bytes will be sent out continuously. However, when Android devices or other hosts are receiving, due to the reasons mentioned above, they may read 4 bytes of data first, and then read 6 bytes of data. In other words, the complete data we need will not be read in one read, but will be split into different "packets", which is the so-called "packet":

1.gif

How to deal with subcontracting?

In fact, the answer is on the surface of the mystery. After a simple explanation of the reasons for subcontracting above, I believe that everyone has their own answer to how to solve the problem of subcontracting.

The core principle of subcontracting is very simple to say. It is nothing more than taking the complete data package we need from the data that has been read multiple times, and then combining it into the complete data package we need.

The question is, how should we know which packet the read data belongs to? How do we know whether the data packet is complete?

This depends on the protocol we define when using serial communication.

Generally speaking, in order to solve the problem of subcontracting, the methods we commonly use to define protocols are as follows:

  1. All data is specified as fixed length.
  2. Specify a termination character for a complete data, reading this character indicates that the current data packet is complete.
  3. A character is added before each data packet to indicate the length of the subsequent data packet.

fixed packet length

Fixed data length means that we stipulate that the length of the data packet sent in each communication is a fixed length. If the actual length is less than the specified length, some special characters such as are used to \0fill the remaining length.

For this situation, it is very easy to deal with, as long as we judge the length of the read data every time we read the data, if the data length does not reach the fixed length that meets the requirements, it is considered that the read data is incomplete, and we continue to read until The data length conforms to:

val resultByte = mutableListOf<Byte>()
private fun getFullData(count: Int = 0, dataSize: Int = 20): ByteArray {
    
    
    val buffer = ByteArray(1024)
    val readLen = usbSerialPort.read(buffer, 2000)
    for (i in 0 until readLen) {
    
    
        resultByte.add(buffer[i])
    }
    
    // 判断数据长度是否符合
    return if (resultByte.size == dataSize) {
    
    
        resultByte.toByteArray()
    } else {
    
    
        if (count < 10) {
    
    
            getFullData(count + 1, dataSize)
        }
        else {
    
    
            // 超时
            return ByteArray(0)
        }
    }
}

But this method also has an obvious disadvantage, that is, the usage scenarios are very limited, and it is only suitable for the scenario where the master sends a request and the slave responds, because if the slave is sending data continuously, the master may be in the When reading in a certain period of time, or polling and reading all the time, it is unreliable to judge only by the data length, because we cannot ensure that the data of the specified length we read must be the same complete data, and it is possible to refer to The last data or the next data is mixed, and once the wrong reading is made, it means that the data read every time in the future will be wrong.

add terminator

In order to solve the limitations caused by the above method, we can add an end symbol to each frame of data. Generally speaking, we will specify \r\nCRLF ( 0x0D 0x0A) as the end symbol.

Therefore, when we read data, we will read in a loop until the end symbol is read, then we think that this reading is over and a complete data packet has been obtained:

val resultByte = mutableListOf<Byte>()
private fun getFullData(): ByteArray {
    
    
    var isFindEnd = false

    while (!isFindEnd) {
    
    
        val buffer = ByteArray(1024)
        val readLen = usbSerialPort.read(buffer, 2000)
        if (readLen != 0) {
    
    
            for (i in 0 until readLen) {
    
    
                resultByte.add(buffer[i])
            }
            if (buffer[readLen - 1] == 0x0A.toByte() && buffer.getOrNull(readLen - 2) == 0x0D.toByte()) {
    
    
                isFindEnd = true
            }
        }
    }

    return resultByte.toByteArray()
}

But this method obviously also has a flaw, that is, if it is a single interval read or the first read data during polling, it may also be incomplete data.

Because although we have read the end symbol, it does not mean that what we read this time is the complete data. Maybe there is still data before us that we have not read.

However, this method can ensure that only the first read data may be incomplete during polling, but subsequent data is complete.

If it is only read at a single interval, there is no guarantee that the complete data will be read.

Increase the packet length at the beginning

Similar to adding a terminator, we can also add a special character at the beginning of the packet, followed by a specified length (1byte) character to specify the length of the next packet.

In this way, we can first search for the start symbol when parsing. After finding it, we think that a new data packet has started, and then read the next 1 byte characters to get the length of the data packet. Next, follow this specification Length, read in a loop until the length matches.

The specific reading method is actually a combination of the above two methods, so I will not post the code here.

best case

The most convenient way to solve data packetization is of course to include fixed data headers, fixed data tails, and even fixed data lengths in the data.

For example, if a certain temperature sensor sends a data packet with a fixed length of 10 bits and a terminator CRLF, and there are only three situations at the beginning of the data packet, , , (0x2B -0x2D +0x20 ), then we can receive data write this:

val resultByte = mutableListOf<Byte>()
val READ_WAIT_MILLIS = 2000
private fun getFullData(count: Int = 0, dataSize: Int = 14): ByteArray {
    
    
    var isFindStar = false
    var isFindEnd = false
    while (!isFindStar) {
    
     // 查找帧头
        val buffer = ByteArray(1024)
        val readLen = usbSerialPort.read(buffer, READ_WAIT_MILLIS)
        if (readLen != 0) {
    
    
            if (buffer.first() == 0x2B.toByte() || buffer.first() == 0x2D.toByte() || buffer.first() == 0x20.toByte()) {
    
    
                isFindStar = true
                for (i in 0 until readLen) {
    
     // 有帧头,把这次结果存入
                    resultByte.add(buffer[i])
                }
            }
        }
    }

    while (!isFindEnd) {
    
     // 查找帧尾
        val buffer = ByteArray(1024)
        val readLen = usbSerialPort.read(buffer, READ_WAIT_MILLIS)
        if (readLen != 0) {
    
    
            for (i in 0 until readLen) {
    
     // 先把结果存入
                resultByte.add(buffer[i])
            }
            if (buffer[readLen - 1] == 0x0A.toByte() && buffer.getOrNull(readLen - 2) == 0x0D.toByte()) {
    
     // 是帧尾, 结束查找
                isFindEnd = true
            }
        }
    }


    // 判断数据长度是否符合
    return if (resultByte.size == dataSize) {
    
    
        resultByte.toByteArray()
    } else {
    
    
        if (count < 10) {
    
    
            getFullData(count + 1, dataSize)
        }
        else {
    
    
            return ByteArray(0)
        }
    }

What about sticky bags?

Above we only talked about subcontracting, but in actual use, there may be sticky packages.

Sticky packets, as the name implies, are different data packets mixed together in one read.

If you want to solve the problem of sticky packets, it is also very simple. Similar to solving subcontracting, we also need to provide a way to distinguish different data packets when defining the protocol, so that we can parse according to the protocol.

Summarize

In fact, it is not difficult to solve the sub-packet or sticky package in serial communication. The main problem is that each hardware device manufacturer or sensor manufacturer defines a set of communication protocols in serial communication. "Actually, there is no sign that can distinguish different data packets, which will cause us to be unable to parse the data packets normally when accessing these devices.

But it does not mean that there is no way to analyze, but we need to analyze the specific situation, such as temperature sensor, although the communication protocol does not give information such as data head, data tail, data length, etc., but in fact it returns the data format almost They are all fixed, we just need to parse according to this fixed format.

Guess you like

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