CRC Checksum Algorithms in Hexmate

Last modified by Microchip on 2023/11/09 09:12

Using Hexmate and MPLAB XC8 to verify programming

Checksums can be used to verify that the code programmed into a device is a valid copy of the original image. This page shows you how to use Hexmate to calculate a checksum over a range of addresses in a HEX file and have that value verified in an MPLAB® XC8 project. The examples given will work on any PIC18 device.

A checksum is a small value calculated from and used to represent all the values in a block of data. If that data block is copied, a checksum recalculated from the new block can be compared to the original checksum. Agreement between the two checksums provides some sort of guarantee that the copy is valid. There are many checksum algorithms. More complex algorithms provide a more robust verification but might use too many resources when used in an embedded environment.

Hexmate can be used to calculate the checksum of data stored in a HEX file built by the compiler. This checksum can be embedded into that HEX file and burned into the target device along with the program image. At runtime, the target device can run a similar checksum algorithm over the same data in program memory. If the embedded checksum and the checksum calculated at runtime are the same, the program can assume that it has a valid program image to execute.

Hexmate implements several checksum algorithms, such as simple summation algorithms, the versatile Fletcher algorithms, or the more robust Cyclic Redundancy Check (CRC) algorithms. Hexmate options can be specified from the MPLAB X IDE, so you do not need to run this utility explicitly.

Hexmate can be used on any Intel HEX file produced by any MPLAB XC Compiler; however, not all Microchip PIC® devices can read the entire width of their program memory via a C language pointer or array, so to perform checksum verifications on these devices you might also need to implement a routine (which might need to be written in assembly) to read a block of flash memory to obtain the data over which to calculate the checksum. Other PIC devices have no means at all to read the entire width of their program memory.

Non-reflected CRC Example

In this example, the code in our project must first ensure that the program memory range from 0 to 0xFF inclusive is verified before proceeding. To do this, a 16-bit checksum will be calculated at compile time and stored in the HEX file at locations corresponding to the device addresses 0x100 and 0x101.

We will use a CRC algorithm with the following parameters to calculate the checksum. Note this algorithm is sometimes misidentified as CRC-CCITT.

Algorithm TypeCRC
Checksum width16 bits
Polynomial0x1021
Initial value0xFFFF
Data/result reflectionFalse

Specify the Checksum Command when Building

The MPLAB XC8 command to generate the required checksum is:

—CHECKSUM=0-FF@100,offset=0xFFFF,algorithm=5,width=-2,polynomial=0x1021

Algorithm #5 will select the non-reflected CRC algorithm. The width is specified as 2 bytes (16-bits) and is negative delete to format the result with a little-endian byte order, which is the order used by the compiler to store multi-byte C objects. The offset specifies the initial value, here, 0xFFFF. The “0-FF” string specifies the (HEX) address range over which to calculate the checksum; the “@100” indicates the (HEX) address where to store the checksum in the device.

To use this command in MPLAB X IDE, copy all the arguments to the right of the “=“ into the Checksum field found in XC8 Linker > Additional options, as shown.

xc8 options window used to control the checksum

Verifying the Checksum at Runtime

To help us verify the checksum result, we will first create placeholder objects and typedefs to make the code easier to modify.​

1
2
3
4
5
typedef unsigned char readType;
typedef unsigned int resultType;

extern const readType checksumData[0x100] @ 0x0;
extern const resultType hexmateChecksum @ 0x100;

The typedefs relate to the width of data that we read (8 bits) and the width of the checksum result (16 bits). Typically data is read as bytes, but you should adjust the resultType to match the algorithm you are using, e.g., unsigned char for 8-bit algorithms, unsigned int for 16-bit algorithms, etc.

The checksumData object will be used to represent the block of data over which we will calculate the checksum; the hexmateChecksum object will represent the checksum result that Hexmate will embed into the HEX file, and which will be programmed into the device.

Since both these objects are extern, they do not consume any memory; and since they are defined as const, they will represent program memory addresses.

Here is a code that can implement a non-reflected CRC algorithm.​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define POLYNOMIAL     0x1021

#define WIDTH   (8 * sizeof(resultType))
#define MSb     ((resultType)1 << (WIDTH - 1))

resultType
crc(const readType * data, unsigned n, resultType remainder) {
   unsigned pos;
   unsigned char bitp;

   for (pos = 0; pos != n; pos++) {
        remainder ^= ((resultType)data[pos] << (WIDTH - 8));
       for (bitp = 8; bitp > 0; bitp--) {
           if (remainder & MSb) {
                remainder = (remainder << 1) ^ POLYNOMIAL;
            } else {
                remainder <<= 1;
            }
        }
    }

   return remainder;
}

It is passed a pointer to the first datum, the number of data to process, and the initial value.

Add this routine to your project and call it as soon as possible after main() executes. Compare the value obtained from this routine with that produced by Hexmate. That might look something like this:​

1
2
3
4
5
6
7
8
9
void main(void) {
   if(HexmateChecksum != crc(checksumData,
                   sizeof(checksumData)/sizeof(readType), 0xFFFF))
       // something’s not right, take appropriate action (e.g. break)
       __builtin_software_breakpoint();

   // data verifies okay, continue with the program
   // your code goes here
   

Reflected CRC Example

The crc() function shown in the previous example works on the most significant bit first, i.e., the data bits are considered big-endian. Some CRC algorithms work on the least significant bits first, and you may need to reflect the bit positions of the data to match your algorithm of choice.

The reflected version of the above algorithm can be implemented in several ways. The first is to apply the same polynomial to a reflected copy of the data. The calculated result also needs to be reflected to ensure the correct bit order. The other implementation is to reflect the polynomial and apply that to the original data.

For the next example, we will implement a code that performs a different version of the 16-bit CRC algorithm. Specifically, this algorithm will have the following parameters (see the application note AN752). Note the difference in the reflection specification.

Algorithm TypeCRC (MCRF4XX)
Checksum width16 bits
Polynomial0x1021
Initial value0xFFFF
Data/result reflectionTrue

The MPLAB XC8 command to generate the required checksum is:

—CHECKSUM=0-ff@100,offset=0xffff,algorithm=-5,width=-2,polynomial=0x1021

Algorithm #-5 (note the negative sign) selects the reflected CRC algorithm. The width is specified as 2 bytes (16-bits) and is negative delete to format the result with little-endian byte order. Note, this is the byte order, not the bit order within each byte as dictated by the algorithm. The other arguments are identical to those in the first example.

Here is a code that can implement a reflected CRC algorithm. A function to reflect bits with a specified data size is also provided.​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef unsigned int reflectWidth;

#define POLYNOMIAL     0x1021

#define WIDTH   (8 * sizeof(resultType))
#define MSb     ((resultType)1 << (WIDTH - 1))
#define LSb     (1)

#define REFLECT_DATA(X)       ((readType) reflect((X), sizeof(readType) * 8))
#define REFLECT_REMAINDER(X)  (reflect((X), WIDTH))

// reflect the value passed, assuming nBits of this
// value are significant
reflectWidth
reflect(reflectWidth data, unsigned char nBits)
{
    reflectWidth reflection = 0;
    reflectWidth reflectMask = (reflectWidth)1 << nBits - 1;
   unsigned char bitp;

   for (bitp = 0; bitp != nBits; bitp++) {
       if (data & 0x01) {
            reflection |= reflectMask;
        }
        data >>= 1;
        reflectMask >>= 1;
    }

   return reflection;
}

resultType
crc_reflected_IO(const readType * data, unsigned n, resultType remainder) {
   unsigned pos;
    readType   reflected;
   unsigned char bitp;

   for (pos = 0; pos != n; pos ++) {
        reflected = REFLECT_DATA(data[pos]);
        remainder ^= ((resultType)reflected << (WIDTH - 8));

       for (bitp = 8; bitp > 0; bitp--) {
           if (remainder & MSb) {
                remainder = (remainder << 1) ^ POLYNOMIAL;
            } else {
                remainder <<= 1;
            }
        }
    }
    remainder = REFLECT_REMAINDER(remainder);

   return remainder;
}

You can achieve the same checksum using the following code:​

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
resultType
crc_reflected_poly(const readType * data, unsigned n, resultType remainder) {
   unsigned byte;
   unsigned char bitp;
    resultType rpoly;

    rpoly = reflect(POLYNOMIAL, WIDTH);
   for (byte = 0; byte != n; byte++) {
        remainder ^= data[byte];

       for (bitp = 8; bitp > 0; bitp--) {
           if (remainder & LSb) {
                remainder = (remainder >> 1) ^ rpoly;
            } else {
                remainder >>= 1;
            }
        }
    }

   return remainder;
}

Note that in this code the data and results are not reflected, but the polynomial is. The polynomial reflection was made at runtime using the reflect() function, but this can be pre-calculated. Note also that the direction of the shifts is reversed since it operates in a reverse bit direction.

You could call either of these routines in the same way as we did in the first example.

When implementing a CRC checksum, you need the specifications that have been provided for these two examples, viz.: the checksum width, the polynomial, the initial value, and whether the data and result are reflected. Some algorithms, for example, the CRC-32, require an XOR of the final checksum with a value. This XOR cannot be performed by Hexmate, but you may explicitly XOR the Hexmate checksum. For such algorithms, you will also need to know the value to use for the final XOR.

Back to Top