1. Introduction
Xmodem is an asynchronous file transfer protocol widely used in serial communication, divided into two protocols: Xmodem (using 128-byte data blocks) and 1k-Xmodem (using 1024-byte data blocks, i.e. 1k-byte data blocks) protocols.
This article implements the Xmodem protocol of 128-byte data blocks, which adopts CRC16 verification. When applied in the project, the sending and receiving end can modify the agreement between both parties according to the specific circumstances.
If you don't know much about serial communication, you can read the blog I wrote to use Java to implement serial communication.
2. Implementation
During the debugging process with embedded students, it was found that the sending side sent data too fast, which caused the receiving side to not be able to process it. Therefore, a child thread was opened in the send method to process the data send logic, which facilitates the addition of delay processing.
In the receiving method, sending C means verification in CRC.
public class Xmodem { // Start private final byte SOH = 0x01; // End private final byte EOT = 0x04; // Answer private final byte ACK = 0x06; // Retransmit private final byte NAK = 0x15; // Unconditional end private final byte CAN = 0x18; // Transfer data in 128 byte blocks private final int SECTOR_SIZE = 128; // Maximum error (no response) number of packets private final int MAX_ERRORS = 10; // Input stream, used to read serial port data private InputStream inputStream; // Output stream, used to send serial port data private OutputStream outputStream; public Xmodem(InputStream inputStream, OutputStream outputStream) { this.inputStream = inputStream; this.outputStream = outputStream; } /** * Send data* * @param filePath * File path*/ public void send(final String filePath) { new Thread() { public void run() { try { // Number of error packets int errorCount; // Package number byte blockNumber = 0x01; // Checksum int checkSum; // Number of bytes read to the buffer int nbytes; // Initialize the data buffer byte[] section = new byte[SECTOR_SIZE]; // Read file initialization DataInputStream inputStream = new DataInputStream( new FileInputStream(filePath)); while ((nbytes = inputStream.read(sector)) > 0) { // If the last packet of data is less than 128 bytes, fill it with 0xff if (nbytes < SECTOR_SIZE) { for (int i = nbytes; i < SECTOR_SIZE; i++) { sector[i] = (byte) 0xff; } } // The same packet of data is sent up to 10 times errorCount = 0; while (errorCount < MAX_ERRORS) { // Group packet// Control characters + packet number + Inverse code of packet number + Data + Checksum putData(SOH); putData(blockNumber); putData(~blockNumber); checkSum = CRC16.calc(sector) & 0x00ffff; putChar(sector, (short) checkSum); outputStream.flush(); // Get answer data byte data = getData(); // If the answer is received, the next packet of data will jump out and send the next packet of data // No response is received, the number of packets incorrectly +1, continue to resend if (data == ACK) { break; } else { ++errorCount; } } // The packet number is increased by blockNumber = (byte) ((++blockNumber) % 256); } // After all data is sent, the send end flag boolean isAck = false; while (!isAck) { putData(EOT); isAck = getData() == ACK; } } catch (Exception e) { e.printStackTrace(); } }; }.start(); } /** * Receive data* * @param filePath * File path* @return Whether the reception is completed* @throws IOException * Exception*/ public boolean receive(String filePath) throws Exception { // Error packet int errorCount = 0; // Package number byte blocknumber = 0x01; // Data byte data; // Checksum int checkSum; // Initialize the data buffer byte[] section = new byte[SECTOR_SIZE]; // Write to file to initialize DataOutputStream outputStream = new DataOutputStream( new FileOutputStream(filePath)); // Send character C and CRC verification putData((byte) 0x43); while (true) { if (errorCount > MAX_ERRORS) { outputStream.close(); return false; } // Get reply data data = getData(); if (data != EOT) { try { // Determine whether the received start identifier if (data != SOH) { errorCount++; continue; } // Get the packet serial number data = getData(); // Determine whether the packet serial number is correct if (data != blocknumber) { errorCount++; continue; } // Get the inverse code of the packet serial number byte _blocknumber = (byte) ~getData(); // Determine whether the inverse code of the packet serial number is correct if (data != _blocknumber) { errorCount++; continue; } // 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 whether the checksum is correct int crc = CRC16.calc(sector); if (crc != checkSum) { errorCount++; continue; } // Send the reply putData(ACK); // The packet number is incremented by blocknumber++; // Write data to the local outputStream.write(sector); // ErrorCount is reset to zero = 0; } catch (Exception e) { e.printStackTrace(); } finally { // If an error occurs, send the retransmission identifier if (errorCount != 0) { putData(NAK); } } } else { break; } } // Close the output stream outputStream.close(); // Send the reply putData(ACK); return true; } /** * Get data* * @return data* @throws IOException * Exception*/ private byte getData() throws IOException { return (byte) inputStream.read(); } /** * Send data* * @param data * Data* @throws IOException * Exception*/ private void putData(int data) throws IOException { outputStream.write((byte) data); } /** * Send data* * @param data * Data* @param checkSum * Checksum* @throws IOException * Exception*/ 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()); }}The CRC16 verification algorithm uses the table lookup 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); }}3. Use
// serialPort is the serial port object Xmodem xmodem = new Xmodem(serialPort.getInputStream(), serialPort.getOutputStream());// filePath is the file path // ./bin/xxx.binxmodem.send(filePath);
4. Write at the end
Complete code download
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.