8
\$\begingroup\$

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?

\$\endgroup\$
  • 1
    \$\begingroup\$ About half of the PIC18s have anywhere from 128 bytes to 1K of EEPROM built into them. I assume from your question your PIC18 is one of the 50% that does not have EEPROM? \$\endgroup\$ – tcrosley Feb 1 '12 at 23:18
4
\$\begingroup\$

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.

The final #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

\$\endgroup\$
4
\$\begingroup\$

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 write_program_eeprom(...) andread_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
\$\endgroup\$
  • 1
    \$\begingroup\$ Yes, this is exactly what I'm asking for! Can you describe what code attributes you used to pack your S/N into the last few bytes of flash? \$\endgroup\$ – Joel B Feb 2 '12 at 13:30
  • \$\begingroup\$ "Firmware had a routine which would receive the s/n and store it in Flash" write_program_eeprom(...) and read_program_eeprom(...). The EEPROM and Flash are two different things! \$\endgroup\$ – m.Alin Jun 27 '12 at 9:51
  • \$\begingroup\$ @m.Alin That's how the "canned" functions which came with CCS ccompiler were called. They would actually write and read Flash. \$\endgroup\$ – Nick Alexeev Jun 27 '12 at 16:48
2
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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:

  1. 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.
  2. 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)`.
  3. 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.
\$\endgroup\$
  • \$\begingroup\$ All PIC 18F can read their program memory via the table read mechanism. \$\endgroup\$ – Olin Lathrop Feb 2 '12 at 17:58
  • \$\begingroup\$ That is true. Some of the 14-bit parts are able to read program memory as well. Even on PICs which can read program memory, however, the retlw approach is often much faster (on 18-bit PICs, a call to a retlw will take four cycles total; using clrf TBLPTRU/movlw xx/movwf TBLPTRH/movlw xx/movwf TBLPTRL/tblrd *+/movf TABLAT,w would take eight). \$\endgroup\$ – supercat Feb 2 '12 at 19:58
1
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Thanks @Wouter van Ooijen! I'm trying to avoid the "find out where the value is stored" approach, since that will most likely mean the value is relocated on successive compiles and require me (or some sad fellow who comes behind me) to find out where the value is located again, assuredly introducing problems. \$\endgroup\$ – Joel B Feb 1 '12 at 22:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.