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