Implement the Xmodem protocol using java

  • 2020-05-24 05:32:43
  • OfStack

1. Introduction

Xmodem is an asynchronous file transfer protocol widely used in serial communication. It is divided into two protocols: Xmodem (using 128-byte data blocks) and 1k-Xmodem (using 1024-byte data blocks of 1k bytes).
This paper implements the Xmodem protocol of 128 byte data blocks, which adopts CRC16 verification. When applied in the project, the sending end and the receiving end can modify their protocols according to specific conditions.
If you don't know much about serial communication, you can read my blog about using Java for serial communication.

2. Implement

In the process of debugging with embedded students, it was found that the sending end sent data too fast, resulting in the receiving end could not process it, so a sub-thread was opened in the send method to process the data sending logic, which was convenient to add delay processing.
In the receive method, sending C means checking with CRC.


public class Xmodem {

 //  start 
 private final byte SOH = 0x01;
 //  The end of the 
 private final byte EOT = 0x04;
 //  The reply 
 private final byte ACK = 0x06;
 //  The retransmission 
 private final byte NAK = 0x15;
 //  Unconditional termination 
 private final byte CAN = 0x18;

 //  In order to 128 Data is transmitted in the form of byte blocks 
 private final int SECTOR_SIZE = 128;
 //  Maximum error (no reply) number of packets 
 private final int MAX_ERRORS = 10;

 //  Input stream for reading serial port data 
 private InputStream inputStream;
 //  Output stream for sending serial port data 
 private OutputStream outputStream;

 public Xmodem(InputStream inputStream, OutputStream outputStream) {
 this.inputStream = inputStream;
 this.outputStream = outputStream;
 }

 /**
 *  To send data 
 * 
 * @param filePath
 *   The file path 
 */
 public void send(final String filePath) {
 new Thread() {
  public void run() {
  try {
   //  Error packets 
   int errorCount;
   //  Package number 
   byte blockNumber = 0x01;
   //  The checksum 
   int checkSum;
   //  The number of bytes read into the buffer 
   int nbytes;
   //  Initializes the data buffer 
   byte[] sector = new byte[SECTOR_SIZE];
   //  Read file initialization 
   DataInputStream inputStream = new DataInputStream(
    new FileInputStream(filePath));

   while ((nbytes = inputStream.read(sector)) > 0) {
   //  If the last 1 Packet data less than 128 Two bytes, so 0xff A filling 
   if (nbytes < SECTOR_SIZE) {
    for (int i = nbytes; i < SECTOR_SIZE; i++) {
    sector[i] = (byte) 0xff;
    }
   }

   //  with 1 Packet data is sent at most 10 time 
   errorCount = 0;
   while (errorCount < MAX_ERRORS) {
    //  Set of packages 
    //  Control characters  +  Package number  +  The inverse of the package number  +  data  +  The checksum 
    putData(SOH);
    putData(blockNumber);
    putData(~blockNumber);
    checkSum = CRC16.calc(sector) & 0x00ffff;
    putChar(sector, (short) checkSum);
    outputStream.flush();

    //  Get response data 
    byte data = getData();
    //  If you receive response data, jump out of the loop and send it 1 Packet data 
    //  No reply received, error number of packets +1 , continue to resend 
    if (data == ACK) {
    break;
    } else {
    ++errorCount;
    }
   }
   //  The package number is self-increasing 
   blockNumber = (byte) ((++blockNumber) % 256);
   }

   //  When all data is sent, the end of sending is identified 
   boolean isAck = false;
   while (!isAck) {
   putData(EOT);
   isAck = getData() == ACK;
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
  };
 }.start();
 }

 /**
 *  Receive data 
 * 
 * @param filePath
 *   The file path 
 * @return  Whether the reception is completed 
 * @throws IOException
 *   abnormal 
 */
 public boolean receive(String filePath) throws Exception {
 //  Error packets 
 int errorCount = 0;
 //  Package number 
 byte blocknumber = 0x01;
 //  data 
 byte data;
 //  The checksum 
 int checkSum;
 //  Initializes the data buffer 
 byte[] sector = new byte[SECTOR_SIZE];
 //  Write file initialization 
 DataOutputStream outputStream = new DataOutputStream(
  new FileOutputStream(filePath));

 //  Send characters C . CRC Way to check 
 putData((byte) 0x43);

 while (true) {
  if (errorCount > MAX_ERRORS) {
  outputStream.close();
  return false;
  }

  //  Get response data 
  data = getData();
  if (data != EOT) {
  try {
   //  Determine whether the received start id is 
   if (data != SOH) {
   errorCount++;
   continue;
   }

   //  Gets the package number 
   data = getData();
   //  Determine if the package number is correct 
   if (data != blocknumber) {
   errorCount++;
   continue;
   }

   //  Gets the inverse of the package number 
   byte _blocknumber = (byte) ~getData();
   //  Determine whether the inverse of the package number is correct 
   if (data != _blocknumber) {
   errorCount++;
   continue;
   }

   //  To get the data 
   for (int i = 0; i < SECTOR_SIZE; i++) {
   sector[i] = getData();
   }

   //  Get the checksum 
   checkSum = (getData() & 0xff) << 8;
   checkSum |= (getData() & 0xff);
   //  Determine if the checksum is correct 
   int crc = CRC16.calc(sector);
   if (crc != checkSum) {
   errorCount++;
   continue;
   }

   //  Send a reply 
   putData(ACK);
   //  The package number is self-increasing 
   blocknumber++;
   //  Write data locally 
   outputStream.write(sector);
   //  Error packets return to zero 
   errorCount = 0;

  } catch (Exception e) {
   e.printStackTrace();

  } finally {
   //  If an error is sent, a retransmission id is sent 
   if (errorCount != 0) {
   putData(NAK);
   }
  }
  } else {
  break;
  }
 }

 //  Turn off the output stream 
 outputStream.close();
 //  Send a reply 
 putData(ACK);

 return true;
 }

 /**
 *  To get the data 
 * 
 * @return  data 
 * @throws IOException
 *   abnormal 
 */
 private byte getData() throws IOException {
 return (byte) inputStream.read();
 }

 /**
 *  To send data 
 * 
 * @param data
 *   data 
 * @throws IOException
 *   abnormal 
 */
 private void putData(int data) throws IOException {
 outputStream.write((byte) data);
 }

 /**
 *  To send data 
 * 
 * @param data
 *   data 
 * @param checkSum
 *   The checksum 
 * @throws IOException
 *   abnormal 
 */
 private void putChar(byte[] data, short checkSum) throws IOException {
 ByteBuffer bb = ByteBuffer.allocate(data.length + 2).order(
  ByteOrder.BIG_ENDIAN);
 bb.put(data);
 bb.putShort(checkSum);
 outputStream.write(bb.array());
 }
}

CRC16 check algorithm, the use of the table method.


public class CRC16 {

 private static final char crctable[] = { 0x0000, 0x1021, 0x2042, 0x3063,
  0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
  0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252,
  0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
  0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
  0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509,
  0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630,
  0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
  0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7,
  0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af,
  0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
  0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e,
  0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
  0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
  0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4,
  0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc,
  0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
  0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
  0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da,
  0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
  0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589,
  0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481,
  0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
  0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0,
  0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f,
  0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
  0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e,
  0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16,
  0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
  0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45,
  0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c,
  0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
  0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };

 public static char calc(byte[] bytes) {
 char crc = 0x0000;
 for (byte b : bytes) {
  crc = (char) ((crc << 8) ^ crctable[((crc >> 8) ^ b) & 0x00ff]);
 }
 return (char) (crc);
 }
}

Use 3.


// serialPort Is a serial port object 
Xmodem xmodem = new Xmodem(serialPort.getInputStream(),serialPort.getOutputStream());
// filePath Is the file path 
// ./bin/xxx.bin
xmodem.send(filePath);

4. Write at the end

Complete code download


Related articles: