Search code examples
c#crc16ppphdl-coder

CRC-16 and/or Frame Check Sequence


I have an incoming packet that reads 7E0302403F387E from a serial port.

start and end flag is 7E, FCS/CRC is 3F38 and the data is 030240. The FCS is calculated per the algorithm specified in RFC 1662. https://www.rfc-editor.org/rfc/rfc1662#appendix-A

This is the code I'm using to generate the FCS for 030240:

static byte[] HexToBytes(string input)
    {
        byte[] result = new byte[input.Length / 2];
        for (int i = 0; i < result.Length; i++)
        {
            result[i] = Convert.ToByte(input.Substring(2 * i, 2), 16);
        }
        return result;
    }


    public static class Crc16
    {
        const ushort polynomial = 0x8408;
        static readonly ushort[] table = new ushort[256];

        public static ushort ComputeChecksum(byte[] bytes)
        {
            ushort crc = 0;
            for (int i = 0; i < bytes.Length; ++i)
            {
                byte index = (byte)(crc ^ bytes[i]);
                crc = (ushort)((crc >> 8) ^ table[index]);
            }
            return crc;
        }

        static Crc16()
        {
            ushort value;
            ushort temp;
            for (ushort i = 0; i < table.Length; ++i)
            {
                value = 0;
                temp = i;
                for (byte j = 0; j < 8; ++j)
                {
                    if (((value ^ temp) & 0x0001) != 0)
                    {
                        value = (ushort)((value >> 1) ^ polynomial);
                    }
                    else
                    {
                        value >>= 1;
                    }
                    temp >>= 1;
                }
                table[i] = value;
            }
        }
    }

This is how I call it:

//string input = "00 03 00 02 04 00";
string input = "030240";
var bytes = HexToBytes(input);
string hexe = Crc16.ComputeChecksum(bytes).ToString("x2");

I should get an FCS of 3F38 but instead I'm getting 9ED0. What am I doing wrong?

Edit 1:

I'm reading ~\0\u0003\0\u0002\u0004\0?8~ from the serial port. I am able to convert that into either 7E-03-02-40-3F-38-7E or 7e-5c-30-5c-75-30-30-30-33-5c-30-5c-75-30-30-30-32-5c-75-30-30-30-34-5c-30-3f-38-7e. The first is too short and the second is too long. I should be getting 10 bytes. Any tips?

Edit 2:

I am using ASCII_To_Hex to convert ~\0\u0003\0\u0002\u0004\0?8~ to 7E-03-02-40-3F-38-7E

public string ASCII_To_Hex(string ASCII)
    {
        char[] charValues = dataIN.ToCharArray();
        string hexOutput = "";
        foreach (char _eachChar in charValues)
        {
            // Get the integral value of the character.
            int value = Convert.ToInt32(_eachChar);
            // Convert the decimal value to a hexadecimal value in string form.
            hexOutput += String.Format("{0:X}", value);
            // to make output as your eg 
            //  hexOutput +=" "+ String.Format("{0:X}", value);

        }
        return hexOutput;
    }

What changes can I make here to correctly decipher the incoming packet?

Edit 3:

I made the following changes based on Mark's suggestion:

public static class Crc16
    {
        const ushort polynomial = 0x1021;

        static readonly ushort[] table = new ushort[256];

        public static ushort ComputeChecksum(byte[] bytes)
        {
            ushort crc = 0xffff;
            for (int i = 0; i < bytes.Length; ++i)
            {
                byte index = (byte)(crc ^ bytes[i]);
                crc = (ushort)((crc >> 8) ^ table[index]);
            }
            return (ushort)~crc;
        }

        static Crc16()
        {
            ushort value;
            ushort temp;
            for (ushort i = 0; i < table.Length; ++i)
            {
                value = 0;
                temp = i;
                for (byte j = 0; j < 8; ++j)
                {
                    if (((value ^ temp) & 0x0001) != 0)
                    {
                        value = (ushort)((value >> 1) ^ polynomial);
                    }
                    else
                    {
                        value >>= 1;
                    }
                    temp >>= 1;
                }
                table[i] = value;
            }
        }
    }

This is how I'm calling it:

string hex = "00-03-00-02-04-00";
        hex = hex.Replace("-", "");
        var bytes = HexToBytes(hex);
        string hexe = Crc16.ComputeChecksum(bytes).ToString("X2");

I'm getting FO93 instead of 389B


Solution

  • Based on the linked appendix, the initial value of the CRC is 0xffff, not 0, and you need to exclusive-or the result with 0xffff to get the FCS to put in the packet.

    However that still doesn't work for your example packet. I can only guess that your example packet was not correctly extracted or identified.

    Update:

    From the OP comment below and some deduction, the actual data in the message is "~\x00\x03\x00\x02\x04\x00\x9b\x38~". The OP left out three zero bytes for some reason, and the OP printed it in a way that obscured one byte as a question mark.

    Now the CRC calculates correctly as 0x389b, stored in the message little-endian as 0x9b 0x38. To get that, you must apply my comments above on the initial value and final exclusive-or.