杰瑞科技汇

Java实现Modbus CRC校验怎么做?

Of course! Here is a comprehensive guide on how to work with Modbus CRC (Cyclic Redundancy Check) in Java.

Java实现Modbus CRC校验怎么做?-图1
(图片来源网络,侵删)

What is Modbus CRC?

Modbus CRC is an error-detecting code used to ensure the integrity of data transmitted over serial communication lines (like RS-485 or RS-232). It's a 16-bit checksum appended to the end of a Modbus message.

  • Purpose: To detect errors (e.g., from electrical noise) that might corrupt the data during transmission.
  • Algorithm: It's based on a polynomial division. The standard polynomial used in Modbus is 0xA001 (or x¹⁶ + x¹⁵ + x² + 1).
  • Result: A 16-bit (2-byte) value. If the calculated CRC of a received message matches the CRC appended to the message, the data is considered valid.

The CRC Calculation Algorithm

The core of the Modbus CRC is a function that takes a byte array and returns the 16-bit checksum. The most common and efficient way to implement this is using a pre-calculated lookup table.

How the Lookup Table Works

  1. Initialization: A 256-byte array (the lookup table) is pre-calculated. Each entry at index i contains the CRC result for a single byte input i.
  2. Calculation: To calculate the CRC for a message:
    • Start with an initial value (usually 0xFFFF).
    • For each byte in the message:
      • Look up a value from the table using the current byte and the high byte of the running CRC.
      • Update the running CRC by XORing it with the value from the table.

Java Implementation

Here are two common ways to implement the CRC calculation in Java: a standard version and a more performant version using a ByteBuffer.

Version 1: Standard Array-Based Implementation

This is the most straightforward and widely used implementation. It's easy to understand and works perfectly for most applications.

Java实现Modbus CRC校验怎么做?-图2
(图片来源网络,侵删)
public class ModbusCRC {
    // The Modbus CRC polynomial: x^16 + x^15 + x^2 + 1 (0xA001)
    private static final int POLYNOMIAL = 0xA001;
    // Precomputed lookup table for faster CRC calculation
    private static final int[] CRC_TABLE = new int[256];
    // Static initializer to populate the lookup table
    static {
        for (int i = 0; i < 256; i++) {
            int crc = i;
            for (int j = 0; j < 8; j++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >>> 1) ^ POLYNOMIAL;
                } else {
                    crc = crc >>> 1;
                }
            }
            CRC_TABLE[i] = crc & 0xFFFF; // Ensure it's a 16-bit value
        }
    }
    /**
     * Calculates the Modbus CRC16 checksum for a given byte array.
     *
     * @param data The byte array to calculate the CRC for.
     * @return The 16-bit CRC checksum as an int.
     */
    public static int calculate(byte[] data) {
        int crc = 0xFFFF; // Initial value
        for (byte b : data) {
            // Get the current byte and XOR it with the high byte of the current CRC
            int tableIndex = (crc ^ b) & 0xFF;
            // Update the CRC by XORing it with the value from the table
            crc = (crc >>> 8) ^ CRC_TABLE[tableIndex];
        }
        return crc & 0xFFFF; // Ensure the result is a 16-bit unsigned int
    }
    /**
     * Calculates the CRC and returns it as a 2-byte little-endian byte array.
     * This is the format typically appended to a Modbus message.
     *
     * @param data The data to check.
     * @return A 2-byte array containing the CRC (Low byte, High byte).
     */
    public static byte[] calculateAsBytes(byte[] data) {
        int crc = calculate(data);
        return new byte[]{
            (byte) (crc & 0xFF), // Low byte
            (byte) ((crc >> 8) & 0xFF) // High byte
        };
    }
    // Main method for demonstration
    public static void main(String[] args) {
        // Example from the Modbus specification
        // Message: 01 04 00 00 00 0A (Read Holding Registers)
        // Expected CRC: D5 CA
        byte[] message = new byte[]{(byte) 0x01, (byte) 0x04, 0x00, 0x00, 0x00, 0x0A};
        int crc = calculate(message);
        System.out.println("Calculated CRC: 0x" + Integer.toHexString(crc).toUpperCase()); // Expected: d5ca
        byte[] crcBytes = calculateAsBytes(message);
        System.out.println("CRC as Bytes: [0x" + Integer.toHexString(crcBytes[0] & 0xFF).toUpperCase() + 
                           ", 0x" + Integer.toHexString(crcBytes[1] & 0xFF).toUpperCase() + "]"); // Expected: [0xCA, 0xD5] -> Little Endian
    }
}

Version 2: Using java.nio.ByteBuffer

This version is more modern and can be slightly more efficient, especially when dealing with streams of data. It avoids creating intermediate byte arrays for the CRC result.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ModbusCRCByteBuffer {
    // Same polynomial and table as before
    private static final int POLYNOMIAL = 0xA001;
    private static final int[] CRC_TABLE = new int[256];
    static {
        for (int i = 0; i < 256; i++) {
            int crc = i;
            for (int j = 0; j < 8; j++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >>> 1) ^ POLYNOMIAL;
                } else {
                    crc = crc >>> 1;
                }
            }
            CRC_TABLE[i] = crc & 0xFFFF;
        }
    }
    /**
     * Calculates the Modbus CRC and appends it to a ByteBuffer.
     * The buffer's position is advanced by 2 bytes.
     *
     * @param buffer The ByteBuffer containing the data. Must be in Little-Endian order.
     */
    public static void appendCRC(ByteBuffer buffer) {
        // Save the original position to calculate the CRC only over the data
        int limit = buffer.limit();
        buffer.limit(buffer.position());
        int crc = 0xFFFF;
        while (buffer.hasRemaining()) {
            byte b = buffer.get();
            int tableIndex = (crc ^ b) & 0xFF;
            crc = (crc >>> 8) ^ CRC_TABLE[tableIndex];
        }
        // Restore the limit and append the CRC bytes in little-endian order
        buffer.limit(limit);
        buffer.put((byte) (crc & 0xFF));
        buffer.put((byte) ((crc >> 8) & 0xFF));
    }
    // Main method for demonstration
    public static void main(String[] args) {
        // Create a ByteBuffer in Little-Endian order
        ByteBuffer buffer = ByteBuffer.allocate(8); // 6 data bytes + 2 CRC bytes
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        // Add the message data
        buffer.put((byte) 0x01);
        buffer.put((byte) 0x04);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x0A);
        // Prepare to read the data for CRC calculation
        buffer.flip(); // Flip to read mode
        // Append the CRC
        appendCRC(buffer);
        // Flip back to read mode to see the final message
        buffer.flip();
        // Print the final message
        System.out.println("Final message with CRC:");
        while (buffer.hasRemaining()) {
            System.out.printf("0x%02X ", buffer.get());
        }
        // Expected output: 0x01 0x04 0x00 0x00 0x00 0x0A 0xCA 0xD5
    }
}

Using a Third-Party Library (Recommended)

For production code, it's often better to use a well-tested and maintained library. A popular choice for Modbus in Java is jamod.

Why use a library?

  • Reliability: It's been tested extensively.
  • Features: Provides full Modbus functionality (TCP, RTU, ASCII), not just CRC.
  • Maintained: Actively updated.

How to use jamod for CRC

  1. Add the dependency to your pom.xml (Maven):

    Java实现Modbus CRC校验怎么做?-图3
    (图片来源网络,侵删)
    <dependency>
        <groupId>com.ghgande</groupId>
        <artifactId>j2mod</artifactId>
        <!-- Check for the latest version on Maven Central -->
        <version>3.2.2</version> 
    </dependency>
  2. Use the CRC utility:

    import com.ghgande.j2mod.modbus.util.CRC16;
    public class JamodCRCExample {
        public static void main(String[] args) {
            // Example message
            byte[] message = new byte[]{(byte) 0x01, (byte) 0x04, 0x00, 0x00, 0x00, 0x0A};
            // Use the CRC16 utility from the jamod library
            int crc = CRC16.calculate(message);
            System.out.println("Calculated CRC with jamod: 0x" + Integer.toHexString(crc).toUpperCase()); // Expected: d5ca
        }
    }

Practical Example: Verifying a Received Message

Here's how you would use the ModbusCRC class in a real-world scenario to validate a message that includes its own CRC.

public class ModbusMessageValidator {
    public static boolean validateMessage(byte[] fullMessage) {
        // A valid message must have at least 2 bytes for the CRC itself
        if (fullMessage.length < 2) {
            return false;
        }
        // 1. Separate the data from the appended CRC
        int dataLength = fullMessage.length - 2;
        byte[] data = new byte[dataLength];
        byte[] receivedCrcBytes = new byte[2];
        System.arraycopy(fullMessage, 0, data, 0, dataLength);
        System.arraycopy(fullMessage, dataLength, receivedCrcBytes, 0, 2);
        // 2. Calculate the CRC for the data part
        int calculatedCrc = ModbusCRC.calculate(data);
        // 3. Convert the received CRC bytes into an int (little-endian)
        int receivedCrc = ((receivedCrcBytes[1] & 0xFF) << 8) | (receivedCrcBytes[0] & 0xFF);
        // 4. Compare the calculated CRC with the received CRC
        System.out.println("Received CRC: 0x" + Integer.toHexString(receivedCrc).toUpperCase());
        System.out.println("Calculated CRC: 0x" + Integer.toHexString(calculatedCrc).toUpperCase());
        return calculatedCrc == receivedCrc;
    }
    public static void main(String[] args) {
        // A valid message: [Data] [CRC_Low] [CRC_High]
        // Message: 01 04 00 00 00 0A CA D5
        byte[] validMessage = new byte[]{
            (byte) 0x01, (byte) 0x04, 0x00, 0x00, 0x00, 0x0A,
            (byte) 0xCA, (byte) 0xD5 // CRC in little-endian
        };
        // An invalid message (one bit flipped in the data)
        byte[] invalidMessage = new byte[]{
            (byte) 0x01, (byte) 0x04, 0x00, 0x00, 0x00, 0x0B, // Last data byte changed to 0x0B
            (byte) 0xCA, (byte) 0xD5 // CRC is still for the original message
        };
        System.out.println("--- Validating Valid Message ---");
        boolean isValid = validateMessage(validMessage);
        System.out.println("Is message valid? " + isValid); // Expected: true
        System.out.println("\n--- Validating Invalid Message ---");
        isValid = validateMessage(invalidMessage);
        System.out.println("Is message valid? " + isValid); // Expected: false
    }
}

This example demonstrates the complete cycle: extracting data, calculating the checksum, and comparing it to the one received to ensure data integrity.

分享:
扫描分享到社交APP
上一篇
下一篇