Implement the Xmodem protocol using java


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