Editing PIC .hex files
… 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:
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.
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:
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:
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 | 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; } else 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.
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.