This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

HOWTO Adding unique identifier to CC2540/CC2541 (editing hex after compiling)

I wanted to add a unique 16-byte identifier to each and every CC2540-based unit I produce. One way to do this is to generate a hex through the typical compilation process and then customize the hexfile per device prior to device programming.

I spent some time figuring out how to do this and thought I would record my steps here for other people. There are numerous caveats which I won't address and instead will describe my solution.

To accomplish this I decided to create a reserved location in the hex file that I could overwrite with the unique identifier via script. This post describes how to do this.

1. I edited the link file to reserve a 16-byte space in code.

I updated my project to point to a new custom link file (in IAR, right-click on Project name > Options... > Linker > Config)

First observe that I am using a (somewhat modified) version of the hal_sleep module. The hal_sleep module reserves an 8-byte partition at the end of the 32K CODE space like so:

-D_SLEEP_CODE_SPACE_START=(_CODE_END-7)
-D_SLEEP_CODE_SPACE_END=(_CODE_END)
-Z(CODE)SLEEP_CODE=_SLEEP_CODE_SPACE_START-_SLEEP_CODE_SPACE_END

A brief explanation here: _CODE_END is 0x7FFF. _SLEEP_CODE_SPACE_START is (0x7FFF - 7) = 0x7FF8. This effectively reserves the last 8 addresses for "sleep code". The mechanics of sleep are not relevant here. I then placed my 16-byte space just before that:

-D_SERIAL_NUM_SPACE_END=(_SLEEP_CODE_SPACE_START-1)
-D_SERIAL_NUM_SPACE_START=(_SERIAL_NUM_SPACE_END-F) 
-Z(CODE)SERIAL_NUM=_SERIAL_NUM_SPACE_START-_SERIAL_NUM_SPACE_END

This creates a 16-byte space starting at _SERIAL_NUM_SPACE_START computed as (((0x7FFF - 7) - 1) - 0xF) = 0x7FE8.

It is important to note that this places this reserved 16-byte space in bank 0, the root bank. Addressing this code later will be made much simpler because of this fact. In addition, the serial number will not be overwritten in a OAD update. This is a good thing.

2. I create a static const array using the SERIAL_NUM reserved address space.

//IN IAR, this is accomplished witha a #pragma directive
#pragma location = "SERIAL_NUM"
static const uint8 s_dropletUUID[DROPLET_ID_MAX_LENGTH] = {'U','N','P','R','O','G','R','A','M','M','E','D',' ',' ',' ',' '};

This tells the compiler where to locate this read-only data. I could have used string initialization here, but for debugging reasons I wanted to remove the '\0' padding.

3. Compile and link as normal, generating a hexfile that contains the "UNPROGRAMMED" string in the hexfile at location 0x7FE8.

4. Modify the hexfile via Python script.

There are two approaches here: One could do a simple string search in the hexfile, modify the relevant bytes, and recompute the checksum per the Intel hex file format but this is error-prone. By going through the process of confining the variable to a particular address we remove the guesswork.

First, it's helpful to familiarize yourself with the Intel Hex file format here: en.wikipedia.org/.../Intel_HEX

I downloaded and installed Alexander Belchenko's excellent "IntelHex" Python library: pypi.python.org/.../1.1

I wrote a simple script to rewrite the 16 bytes at the location _SERIAL_NUM_SPACE_START, i.e, 0x7FE8. Here is a snippet:

import argparse
from intelhex import IntelHex

fname = 'droplet.hex'
serialNumber = 'R1'
index=0
padChar = ' '

ih = IntelHex()
ih.fromfile(fname, format='hex')
ih.padding = 0x00

#we know where our format string is in code
for addr in range(0x7FE8, 0x7FE8+16):
if index >= len(serialNumber):
#my pad character
char = padChar
else:
char = serialNumber[index]

ih[addr]= ord(char)
index = index+1

#don't write start address
ih.write_hex_file('out.hex', False)

5. Checked my work and verified the correct bytes were being changed.

Be careful when using standard text comparison tools to examine your hexfiles. The hexfile format is very flexible and can write the same data different ways. Mr. Belchenko suggested people use his helpful hex diff script hexdiff.py.

6. Fixed the C code to copy the static const string in to RAM at runtime.

Last I realized that to do anything useful with the serial number (report it over BLE, perhaps) I needed to be able to copy the data from code in to RAM. This is not as simple as a memcpy. It is essentially a flash read, and if you look in the hal_flash code you realize you need to compute the page and the address offset. Despair not! Although copying from *arbitrary* code in to RAM is a pain, copying from bank 0 (the root bank), is actually trivial. One can simply abuse HalFlashRead() to do the work for you, realizing that the page and offset can simply be 0 and casting the address of s_dropletUUID to a uint16.

Recall:

#pragma location = "SERIAL_NUM"
static const uint8 s_dropletUUID[DROPLET_ID_MAX_LENGTH] = {'U','N','P','R','O','G','R','A','M','M','E','D',' ',' ',' ',' '};

Then later in the executing code, to retrieve the serial number:

char* localArray[DROPLET_ID_MAX_LENGTH];
HalFlashRead(0, (uint16) s_dropletUUID, localArray, DROPLET_ID_MAX_LENGTH);

NB: Make sure your code works both with the "unprogrammed" string and a valid substituted serial number. My code didn't work after rewriting the ID string and I assumed there was some error in IntelHex but it was actually because my serial numbers aren't null terminated (which is intentional, but needs to be handled correctly).

I am no expert so if this approach is bad I would welcome other forum members to provide further guidance.