Search code examples
c#ccrccrc16psoc

How to calculate CRC16-CCITT/KERMIT in both C# and C


I am working on calculating a CRC16-CCITT/KERMIT so that I can check data integrity on transmissions of 64-byte data packets between a C# Winforms Application and a microcontroller (PSoC5LP/Arm Cortex-M3). I'm close, but just not quite getting the CRC calculation to line up between the two and having a tough time figuring out why. The example data packet I am calculating the CRC for is:

02 03 01 02 03 04 05 07 08 09 0A 0B 00 00 06 0E 0C 0D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

The CRC for this packet comes out to 0x4D8C in both my Winforms application as well as for this online CRC calculator

Since these line up, I'm assuming the calculation in the C# calculation is legit. Regardless, here's the code. Pulled from this page with the only change being the I hardcoded the polynomial (0x8408):

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;
        }
    }
}

Now the gotcha is getting a working CRC16-CCITT/KERMIT calculator function for the microcontroller that will generate the same CRC for this packet of data.

Here's what I currently have (pulled from this answer):

uint16_t crc16k(uint16_t crc, uint8_t *mem, uint8_t len) {
    uint8_t *data = mem;

    if (data == NULL){
        return 0;
    }

    crc = ~crc;
    crc &= 0xffff;

    while (len--) {
        crc ^= *data++;
        for (uint8_t k = 0; k < 8; k++)
            crc = crc & 1 ? (crc >> 1) ^ 0x8408 : crc >> 1;
    }

    crc ^= 0xFFFF;
    return crc;
}

I am calling this as such:

uint16_t crc_calc = crc16k(0x0000, message_in, 64);

Here's where I'm getting some funnies. that data packet is 64-bytes, but in actuality (for this packet), only the first 29 bytes are data I am using. The rest is just padding out to meet 64 bytes. When I calculate the CRC on the WinForms side, it looks like it is using all 64 bytes, including the padding. When I do the same on the microcontroller side, I get a result of 0xE918. What's weird is if I limit the length parameter to just the 29 bytes I'm interested in, I get 0x4C8B, which is perilously close to the 0x4D8C I'm looking for. I also noticed that in the online calculator I used, it claims the XOR on the output should be 0x0000. The C function I am using XORs 0xFFFF on the output. Changing this to 0x0000 (and processing all 64 bytes) gives me 0x16E7.

So I'm not sure where the problem lies. This is the first time I've worked with a CRC so I could be missing something obvious. Any ideas?

Thank you in advance for the help! Let me know if there's any additional information I should provide.


Solution

  • Of course I get it less than 10 minutes after posting the question (isn't that always how it goes?)

    The C code I thought was for CRC-16/KERMIT looks to actually be CRC-16/X-25. I think I got confused because the question I took the code from the answer of was asking about KERMIT, but the answer says it's X-25.

    Removing the bitwise invert of crc at the start of the function:

    crc = ~crc;
    

    AS WELL AS removing

    crc ^= 0xFFFF;  
    

    This leaves me with:

    uint16_t crc16k(uint16_t crc, uint8_t *mem, uint8_t len) {
        uint8_t *data = mem;
    
        if (data == NULL){
            return 0;
        }
    
        while (len--) {
            crc ^= *data++;
            for (uint8_t k = 0; k < 8; k++)
                crc = crc & 1 ? (crc >> 1) ^ 0x8408 : crc >> 1;
        }
    
        return crc;
    }
    

    This seems to be working and the CRC matches up between my Winforms app and the PSoC5. I suppose the "near match" I had earlier was just coincidence the numbers were similar? I'd love to hear an explanation if someone has one.