Currently I have a serial number hard-coded in my firmware for a design I'm working with. The firmware can read and report back the serial number. That works fine for what I need. The trouble is that each new serial number requires me to change my code and recompile. This is cumbersome when there are a lot of units to be built, has the possibly to introduce errors, and is all-around bad practice. The serial numbers are given to me and the hardware design is set in stone, so I can't add any features in hardware to serialize the units (EEPROM/Silicon ID Chip/Pull-Ups). What I would like to do is locate the serial number at a fixed address, compile the code once, then edit that address in the compiled HEX file for each new serial number. The number is referenced in several places, so ideally, I want to define & locate it once, then reference that "variable" everywhere else in my code. Does anyone know how to locate constant data at a specific addressable memory location of my choosing, using the C18 Compiler? Is there a better way anyone can suggest?
Specifically to resolve the question of binding variables to specific addresses in flash memory on the PIC18 with the C18 compiler, please reference the section "Pragmas" in the hlpC18ug.chm in the doc directory where the compiler is installed.
To do this you need to define a new "section" in memory and bind it to a start address so
#pragma romdata serial_no_section=0x1700
This creates a new section called "serial_no_section" that starts at address 0x1700 in flash (program) memory (because we defined "romdata" in the #pragma).
Directly after the #pragma line, define your variable(s) so:
#pragma romdata serial_no_section=0x1700 const rom int mySerialNumber = 0x1234; #pragma romdata
Now you have 0x12 at address 0x1700 and 0x34 at address 0x1701 in memory (because PIC18 uses the little-endian model). The "const rom" ensure that the compiler knows that this is a const variable type, and that the variable lies in "rom" memory and thus need to be accessed via table read instructions.
#pragma romdata statement ensures that any following variable declarations are linked to default memory sections as the linker sees fits rather than following on in the "serial_no_section" section.
Now all code can simply reference the variable "mySerialNumber", and you know exactly what address the serial number can be found at in memory.
Editing the HEX code can be a bit challenging as you need to calculate the checksum for each line you edit. I am working on a C++ class to decode and encode Intel HEX files which should make this easier, but it is not finished yet. Decoding files works, encoding again is not yet implemented. The project (if you are interested) is here https://github.com/codinghead/Intel-HEX-Class
Hope this helps
I've done the serial number (s/n for short) in a way similar to what Joel is describing. I was using PIC18F4620 and CCS compiler. The location of s/n in Flash memory was forces to the last 4 bytes. Since I was using only 80% of Flash, my compiler and linker wouldn't write executable code in the last 4 bytes.
Then I had 2 alternative ways for actually writing s/n into individual units:
- CCS in-circuit debugger (ICD) had a feature which allowed to manipulate any location inside Flash. It was a useful hack. In later revisions they have removed it, unfortunately.
- PIC had a serial link to a PC. s/n was uploaded through it. Firmware had a routine which would receive the s/n and store it in Flash.
Reply to Joel's comment
Don't know about C18, but CCS compiler comes with library functions
read_program_eeprom(...) . Here's what they look like in assembly.
.................... write_program_eeprom(i_ADDR, iWord); EF84: BSF FD0.6 EF86: CLRF FF8 EF88: MOVLW 7F EF8A: MOVWF FF7 EF8C: MOVLW F0 EF8E: MOVWF FF6 EF90: MOVLB 0 EF92: BRA EF24 EF94: MOVLW F0 EF96: MOVWF FF6 EF98: MOVFF 490,FF5 EF9C: TBLWT*+ EF9E: MOVFF 491,FF5 EFA2: TBLWT* EFA4: BCF FA6.6 EFA6: BSF FA6.4 EFA8: RCALL EF3C EFAA: RCALL EF3C EFAC: CLRF FF8 EFAE: CLRF FF8
.................... iWord = read_program_eeprom(i_ADDR); EF60: CLRF FF8 EF62: MOVLW 7F EF64: MOVWF FF7 EF66: MOVLW F0 EF68: MOVWF FF6 EF6A: TBLRD*+ EF6C: MOVF FF5,W EF6E: TBLRD* EF70: MOVFF FF5,03 EF74: CLRF FF8 EF76: MOVLB 4 EF78: MOVWF x90 EF7A: MOVFF 03,491
I have done this a few times. Usually I define a firmware info area in a fixed location in program memory, then write a program that makes a serialized HEX file from the template HEX file. These are all easy things to do.
In production, you run the serialize program once after all the tests have passed. It makes the temporary HEX file with the unique serial number, that gets programmed into the PIC, then the temporary HEX file deleted.
I would not let the location be relocatable, then have to find it. That can change every build as the linker moves things around. I have done that for very small PICs like the 10F series where these constants are part of MOVLW instructions. In those cases I have it read the MAP file on the fly to determine where those locations are. I have MPLINK MAP file parsing code in a library just for that purpose.
To put something in a fixed location define a segment at a fixed address. The linker will place such absolute segments first, then the relocatable ones around it. Don't forget to use CODE_PACK instead of just CODE on a PIC 18, else you'll be dealing with whole instuction words instead of individual bytes. For example (just typed in, not run past the assemlber):
.fwinfo code_pack h'1000' ;firmware info area at fixed known address db h'FFFFFFFF' ;serial number, filled in by production program db fwtype ;type ID of this firmware db fwver ;version number db fwseq ;build sequence number
I would suggest storing the serial number in a fixed address. Depending upon your compiler/linker and the part in question, there are a few approaches you could take:
- Define a relocatable section which will contain the serial number, use a #pragma directive in your code to force the serial number into that section, and force the address of that section within the link spec.
- For parts that can read code memory directly, exclude an area of memory from the area the linker is allowed to use (i.e. tell it the part is e.g. four bytes smaller than it really is), and then read the serial number in code using something like `((unsigned long const *)0x3FFC)`.
- For parts that can't read code memory directly, you may be able to put "RETLW" instructions at some fixed address, and then convince the linker that there are callable functions which return 'byte' at those addresses. Then one would say e.g. "out_hex(ser_byte0()); out_hex(ser_byte1()); out_hex(ser_byte2()); to output the bytes of the serial number.
I would do the opposite: compile and link the code, then find out where the value is stored from the linker file. You might need to locate the variable explicitly in a segment that is mapped to flash.
Do did not ask for this, but the PC software I provide for my Wisp648 programmer has the ability to read a .hex file, modify a specific location, and write the .hex file back (to the same or another file). No need to have my programmer present. Source is available (in Python), license allows all use: www.voti.nl/xwisp Might be handy once you solved your primary problem.