unique id on atmega328pb

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone,

 

I am looking for how to read the unique hardware id on the atmega328pb chips.  I found in the datasheet in section 32.8.10 some stuff that suggests it can be done, however what they describe seems to require inline assembler.  I am used to using  C and I have come a bit stuck as to how to use this.   From what I found searching, the Z-pointer is registers R30 and R31, but I am not sure of the order of the word.  I am also not certain how to perform the LPM from within C.

 

Can anyone guide or help me please ?

 

Thanks,

 

Steed.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steed wrote:
I am looking for how to read the unique hardware id on the atmega328pb chips.
The code below is untested but it compiles using avr-gcc and the generated code looks correct.  The primary component is the macro READ_SIG_BYTE() which takes an index value as a parameter and returns the signature row byte at that index.  Two helper macros are used to save/restore the interrupt enable state.  The test stub in main() reads the 10 serial number bytes from the signature row and stores them in RAM.

#include <stdint.h>
#include <avr/io.h>

// macro to disable interrupts and return the previous status flags
#define DISABLE_INT()                            \
({                                               \
    uint8_t flag;                                \
    __asm__ __volatile__                         \
    (                                            \
        "in %0, 0x3f"               "\n\t"       \
        "cli"                       "\n\t"       \
        : "=r" (flag)                            \
        :                                        \
        : "memory"                               \
    );                                           \
    flag;                                        \
})

// macro to restore the previous interrupt enable state
#define RESTORE_INT(flag)                        \
({                                               \
    __asm__ __volatile__                         \
    (                                            \
        "out 0x3f, %0"               "\n\t"      \
        :                                        \
        : "d" (flag)                             \
        : "memory"                               \
    );                                           \
})

// macro to read a byte from the "signature row" given its index
#define READ_SIG_BYTE(idx)                       \
({                                               \
    uint8_t val = _BV(SPMEN) | _BV(SIGRD);       \
    uint8_t *sigPtr = (uint8_t *)(uint16_t)idx;  \
    uint8_t stat = DISABLE_INT();                \
    __asm__                                      \
    (                                            \
        "out %2, %0"        "\n\t"               \
        "lpm %0, Z"         "\n\t"               \
        : "=r" (val)                             \
        : "z" (sigPtr),                          \
          "I" (_SFR_IO_ADDR(SPMCSR)),            \
          "0" (val)                              \
    );                                           \
    RESTORE_INT(stat);                           \
    val;                                         \
})

#define SERIAL_LEN                  10
#define SERIAL_START_IDX            0x0e

uint8_t serialNum[SERIAL_LEN];

int
main(void)
{
	// read the 10 serial number bytes
    uint8_t idx;
    for (idx = 0; idx < SERIAL_LEN; idx++)
        serialNum[(uint16_t)idx] = READ_SIG_BYTE(idx + SERIAL_START_IDX);

    while (1)
        ;
}

Edit: fixed a typo in the code.

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

Last Edited: Fri. Dec 18, 2015 - 04:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should be able to just use the <avr/boot.h> facilities.   This would involve zero gobbledygook.  

 

Untested.   I will dig out my ATmega168PB XMINI.

 

David.

 

Edit.  I amended an Arduino sketch:

 

#include <avr/boot.h>

void print_val(char *msg, uint8_t val)
{
    Serial.print(msg);
    Serial.println(val, HEX);
}

void setup(void)
{

    Serial.begin(9600);
    while (!Serial) ;
    print_val("lockb = 0x", boot_lock_fuse_bits_get(1));
    print_val("ext fuse = 0x", boot_lock_fuse_bits_get(2));
    print_val("high fuse = 0x", boot_lock_fuse_bits_get(3));
    print_val("low fuse = 0x", boot_lock_fuse_bits_get(0));
#define SIGRD 5
#if defined(SIGRD) || defined(RSIG)
    Serial.print("Signature : ");
    for (uint8_t i = 0; i < 5; i += 2) {
        Serial.print(" 0x");
        Serial.print(boot_signature_byte_get(i), HEX);
    }
    Serial.println();

    Serial.print("Serial Number : ");
    for (uint8_t i = 14; i < 24; i += 1) {
        Serial.print(" 0x");
        Serial.print(boot_signature_byte_get(i), HEX);
    }
    Serial.println();
#endif
}

void loop(void)
{
}

which gave this output:

lockb = 0xFF
ext fuse = 0xFF
high fuse = 0x97
low fuse = 0xE0
Signature :  0x1E 0x94 0x15
Serial Number :  0x6E 0x75 0x6E 0x6B 0x77 0x6F 0xFF 0x3 0x21 0x16

 

Last Edited: Fri. Dec 18, 2015 - 05:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Or, if you prefer not to use inline assembly, place the following code in a .S file and add the appropriate prototype to your C code or an include file.  Again, the code is untested.

#include <avr/io.h>


    .section .text

//--------------------------------------------------------------------

/*
 ** readSigByte
 *
 * Read a byte from the signature row at a given index.
 *
 * On entry: r24 indicates the index of the signature row to return
 *
 * On exit:  r24 contains requested data
 *
 * C-callable: uint8_t readSigByte(uint8_t idx)
 *
 * Modifies: r24, r25, r30, r31
 *
 */
    .global readSigByte
readSigByte:
#define byteIdx     r24
#define stat        r25

    // disable interrupts, saving the current state
    in        stat, _SFR_IO_ADDR(SREG)
    cli

    // load the corresponding address into r31:30
    mov        r30, byteIdx
    ldi        r31, 0

    // read the signature byte
    ldi        byteIdx, _BV(SPMEN) | _BV(SIGRD)
    out        _SFR_IO_ADDR(SPMCSR), byteIdx
    lpm        byteIdx, Z

    // restore the previous interrupt enable state
    out        _SFR_IO_ADDR(SREG), stat
    ret

#undef byteIdx
#undef stat

//--------------------------------------------------------------------

    .end

 

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

You should be able to just use the <avr/boot.h> facilities. 

 

Indeed, "boot_signature_byte_get" has been brought up before...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I ran the sketch on a regular Uno.   And got this:

lockb = 0xFF
ext fuse = 0xFD
high fuse = 0xD6
low fuse = 0xFF
Signature :  0x1E 0x95 0xF
Serial Number :  0x4A 0x39 0x33 0x34 0x33 0x30 0xFF 0x16 0x3 0x9

Of course,  I have no idea whether the 328P has a unique number.   It is not documented.

Likewise,   the 168PB does not document it.

 

I do not have a 328PB,   but you can run this sketch yourself.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I know you are using a M328.

 

I believe the Xmegas do have unique "ID" numbers on each individual chip, (actually die number and location, IIRC).

The smallest Xmega, the Xmega E5 series , isn't much different from the M328.

 

JC

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Response off my Arduino (SMT clone)

 

lockb = 0xCF
ext fuse = 0xFD
high fuse = 0xDE
low fuse = 0xFF
Signature :  0x1E 0x95 0xF
Serial Number :  0x59 0x34 0x37 0x31 0x37 0x37 0xFF 0x3 0x1C 0x16

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Indeed, "boot_signature_byte_get" has been brought up before...
The definition of boot_signature_byte_get() in the avr-gcc v3.5.0 distribution is:

#define boot_signature_byte_get(addr) \
(__extension__({                      \
      uint8_t __result;                         \
      __asm__ __volatile__                      \
      (                                         \
        "sts %1, %2\n\t"                        \
        "lpm %0, Z" "\n\t"                      \
        : "=r" (__result)                       \
        : "i" (_SFR_MEM_ADDR(__SPM_REG)),       \
          "r" ((uint8_t)(__BOOT_SIGROW_READ)),  \
          "z" ((uint16_t)(addr))                \
      );                                        \
      __result;                                 \
}))

Clearly, interrupts are not explicitly disabled for the sts-lpm sequence so if interrupts happen to be enabled and an interrupt is serviced between the two instructions the code will fail the three cycle requirement given in the datasheet.

 

mega328PB Datasheet wrote:
When an LPM instruction is executed within three CPU cycles after the SPMCSR.SIGRD and SPMCSR.SPMEN are set, the signature byte value will be loaded in the destination register.

 

Other devices that support SIGRD have the same three cycle requirement.  At the least, the documentation for boot_signature_byte_get() should mention that interrupts must be disabled before invoking it.

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steed wrote:
From what I found searching, the Z-pointer is registers R30 and R31, but I am not sure of the order of the word. I am also not certain how to perform the LPM from within C.

 

LOL -- Did OP >>say<< that GCC is being used?!?  ;)

 

Anyway, as it turns out I can trick CodeVision into using an appropriate sequence, as I know it will [always?] tend to use Z for straightforward pointer work.  But the below isn't bulletproof, so even in CV I'd write a little inline ASM fragment (and include I-bit handling).

 

#include <io.h>
flash unsigned char * ptr;
register unsigned char scratch;
register unsigned char myspmcsr;
void main(void)
{
    myspmcsr =  (1<<SELFPRGEN) | (1<<5); //SIGRD);
    ptr = (flash void *) 1;
    SPMCSR = myspmcsr;
    scratch = *ptr;

    while (1)
    {
    }
}


                 ;0000 0007     myspmcsr =  (1<<SELFPRGEN) | (1<<5); //SIGRD);
00003d e2e1      	LDI  R30,LOW(33)
00003e 2e3e      	MOV  R3,R30
                 ;0000 0008     ptr = (flash void *) 1;
00003f e0e1      	LDI  R30,LOW(1)
000040 e0f0      	LDI  R31,HIGH(1)
000041 93e0 0108 	STS  _ptr,R30
000043 93f0 0109 	STS  _ptr+1,R31
                 ;0000 0009     SPMCSR = myspmcsr;
000045 be37      	OUT  0x37,R3
                 ;0000 000A     scratch = *ptr;
000046 9024      	LPM  R2,Z

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Everyone,

 

Thanks for your help.  I eventually went with the boot_signature_byte_get in boot.h.  It works perfectly for what I needed.

 

Thanks,

 

Steed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steed wrote:
I eventually went with the boot_signature_byte_get in boot.h.  It works perfectly for what I needed.
But did you see my note about interrupts?

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

I had missed that, but thank you for pointing it out. I don't think it will be a problem as I read the unique id into ram before I enable interrupts.  However if I ever need to do this later on I will be sure to disable interrupts first.

 

Thanks,

 

Steed.