Editing PIC .hex files

 In C#, PIC, Windows

… or how to edit a .hex file instead of recompiling it for every option.

I had a product a while back where the customer needed to program it, but we didn’t want them to have to hassle with compiling it for every (10,000+) permutation.  I had a whole build system set up when this was just a few hundred files that would do all the compilations for me (thank you python!).  That was now out of the question.  There were a few hurdles to do this on a PIC, but we achieved it

Outsmarting the Compiler

We use the CCS compiler for our work on the PIC16 processor.  There is a handy compiler tag to tell it where to put your constant variable, #org StartLocation, StopLocation.  Simple right?  right?  Not so fast. If you just define a constant variable, refer to it once when loading it into the actual variable in RAM, the compiler will compile it out in the name of speed.  How nice.  To outsmart the CCS compiler, one had to create a one element array and initialize it as so:

const unsigned int32 SomeConstantVariable[1] = {SOME_VALUE};

Now we have the value stored in flash memory as part of the program.

Overcoming the Architecture

One interesting tidbit of heretofore insignificant trivia is that the PIC16 uses 14-bit words for instructions.  This now becomes significant because we are saving our variable in code memory.  How do we access it?  How does it show up in 14-bit memory?  It shows up as the lowest eight bits of a 14-bit word.  So, our example four-byte unsigned int32 takes up eight bytes of space with every other byte 0x34.  The higher byte is actually an opcode (RETLW) that returns the lower byte.  The CCS compiler does not access the memory location so much as run the code at the location.  As part of this method there is some overhead associated with storing a value.  There is a one word prefix added to the amount of memory required.  Let me show you how this works.  Say we are trying to store the value 0xFEDCBA98 (4,275,878,552 unsigned) into memory.  The value is stored in memory Little Endian style, so the lowest byte is at the lowest address.

Memory Layout
Address (hex) 100 101 102 103 104
Value (hex) 000B 3498 34AB 34CD 34EF

Now that we’ve got that figured out, let’s put it together:

#org 0x0100, 0x0104
const unsigned int32 SomeConstantVariable[1] = {SOME_VALUE};

Notice here that I am stating the length of the array, not the variable index of the array.  I am not assigning a value to array element ‘1’, but initializing an array of size one.

Editing the Hex File

To edit the hex file we need to find the memory location we care about and write to it.  One thing to note is that the memory location listed in the .hex file and the memory location listed in the #org is not the same.  The value in the intel hex file is actually twice the value listed in the #org statement.  Why?  Because the intel hex file is addresses bytes, but the #org addresses 14-bit words.  The intel hex file is a paged file format, so take that into account when looking up addresses.  Now we know the address we need and what to look for in memory, so what’s left?  Making sure we get the CRC on the .hex file correct.

Embrace and Extend or, a custom CRC

At this point we have edited our .hex file and want to load it onto our firmware.  We can do that using the ICD-64U previously mentioned using their handy-dandy command line tool… but it doesn’t like the checksum.  Wait you say, there isn’t a checksum as part of the intel hex file format?  CCS was kind enough to add one for us.  The last line of the file reads: ;CRC=5FA2  CREATED="11-Dec-12 12:13".  The CRC is run on all lines proceeding this line in the file.  The following code, in C#, will generate the CRC for the file:

private ushort CRC16(byte[] inBytes)
    long length = inBytes.Length;
    ushort crc_Dbyte = 0;
    ushort byte_counter = 0;
    byte bit_counter;
    ushort c;

    for (byte_counter = 0; byte_counter < length; byte_counter++)
       if ((inBytes[byte_counter] > 31) && (inBytes[byte_counter] < 127))
          c = (ushort)((inBytes[byte_counter] << 8) & 0xFF00);
          for(bit_counter=0; bit_counter < 8; bit_counter++)
              if (((crc_Dbyte ^ c) & 0x8000) != 0)
                  crc_Dbyte <<= 1;
                  crc_Dbyte ^= 0x1021;
                  crc_Dbyte <<= 1;
              c <<= 1;

   return crc_Dbyte;

There is also an option in the command line tool to ignore the checksum, but it is generally not good form to have warnings thrown at the end user that occur all the time.  If you’re wondering why I chose C# for this code it’s simple.  The programmer runs on windows and it was the easiest way to create it and get it to work.  I don’t want to have to pack python libraries with it, or have them install the GTK framework, or wxWindows.

Closing Remarks

That gives enough information to get you going.  I hope this just saved someone a week of learning the hard way… and a lot of back and forth with CCS tech support.  If you need some help filling in the gaps, I happen to know an engineer who can help.

  • Allen Huffman

    Thanks for this. I was referred here from the CCS forums, after trying a number of CRC formats that did not match. Your contribution is appreciated.

Leave a Comment

Contact Us

We're not around right now. But you can send us an email and we'll get back to you, asap.

Start typing and press Enter to search