STM32 TIPS: Unique Device ID & Flash Size



Here is a random little post for you guys. Down at the bottom of your reference manual just before the ending sections usually reserved for debug support and revision history there is the "Device Electronic Signature" section. Its provides you with some device specific addresses where you will find a 96-bit unique device ID as well as the flash memory size for your device. Check out the code below to get an idea of how to read these values.




Memory Size Register

The first thing we do is head over to the Reference manual page 825 for STM32L0 and page 1072 for STM32F1. This is where the Device ID and Flash Memory Size resister addresses are given. Observe below I have highlighted the physical address of this read-only register which yields the flash size of the device.
The next step is to make an pointer directly to this address, it must be at-least a 16 bit integer given the size of the register. We can use the  stdint.h  library to make an integer exactly that wide, or just use a regular int, whatever floats your boat.


/*************************|   TIP : 1 Memory Size Register   |**************************
 *  STM32L0 : 0x1FF8 007C  Page 826 of RM0377
 *  STM32F1 : 0x1FFF F7E0  Page 1072 of RM008  */
uint16_t (*flash_size) = (uint16_t*)(0x1FF8007C ); // address is specific to  STM32L0

To print this via something like UART which accepts 8 bit  data we must print the 16 bit number in two bytes. The simple uart_send( ) command below simply sends 1 byte of data. The important thing to take from the following two lines is that we shift the number pointed to by the pointer 8 times to the right , then we AND it with 0xFF to extract the 1s and 0s.  This gives us the top 8 bits and the following line does the same thing except without the shifting so it yields the lower 8 bits. 


uart2_send((*flash_size>>8) & 0xFF);
uart2_send((*flash_size) & 0xFF);

96-bit Unique Device ID

The following magic number we will extract is a 96-bit unique device ID. According to the datasheet "this unique device identifier provides a reference number which is unique for any device in any context. These bits can never be changed by the user"
Further more the datasheet states:
The unique device identifier is ideally suited:
  • for use as serial numbers
  • for use as security keys in order to increase the security of code in Flash memory while using and combining this unique ID with software cryptographic primitives and protocols before programming the internal Flash memory
  • to activate secure boot processes, etc.
The number is spread across three 32-bit registers. Each byte and sometimes word telling us something different about the chip.


  • Bits 0 - 23 : Lot number
  • Bits 24 -31 : Silicon wafer number
  • Bits 32 -63 :  Another lot number
  • Bits 64 -95 : Unique ID bits
You might think that the unique ID bits are the important part but the lot numbers and wafer numbers add even more uniqueness. 
The first register has a base address of 0x1FF80050 for the STM32L0 and the second address is the same with an offset of 0x04 , the third one has an offset of 0x14. We must make some variables with these addresses and their offsets. The STM32F1 has slightly different offsets so make sure to whip open that datasheet. 


/*************************|   TIP : 2 Unique Device ID 96 bit   |***********************
*  STM32L0 : 0x1FF8 0050  Page 826 of RM0377
*  STM32F1 : 0x1FFF F7E8  Page 1072 of RM008  */
uint32_t (*unique_id_1) = (uint32_t*)(0x1FF80050 ); // BASE address
uint32_t (*unique_id_2) = (uint32_t*)(0x1FF80054 ); // BASE address + 0x04 offset
uint32_t (*unique_id_3) = (uint32_t*)(0x1FF80064 ); // BASE address + 0x14 0ffset

Since these are 32-bit wide registers we need to take the same approach as before by extracting the bits in chunks of 8-bits.


/*************************|   TIP : 2 Unique Device ID 96 bit   |*****************************/
uart2_send((*unique_id_3>>24) & 0xFF);
uart2_send((*unique_id_3>>16) & 0xFF);
uart2_send((*unique_id_3>>8) & 0xFF);
uart2_send((*unique_id_3) & 0xFF);

uart2_send((*unique_id_2>>24) & 0xFF);
uart2_send((*unique_id_2>>16) & 0xFF);
uart2_send((*unique_id_2>>8) & 0xFF);
uart2_send((*unique_id_2) & 0xFF);

uart2_send((*unique_id_1>>24) & 0xFF);
uart2_send((*unique_id_1>>16) & 0xFF);
uart2_send((*unique_id_1>>8) & 0xFF);
uart2_send((*unique_id_1) & 0xFF);

 Below you can find all the code which was made using Atollic True Studio, if you are using Keil, you only need to change the chose the right alternate function macros. I am not sure why TrueStudio and Keil define them differently, they seem to be using different versions of the header file.




/* Includes */
#include <stddef.h>
#include  "stm32l0xx.h"
#include "stdlib.h"
#include "stdbool.h"
/* Private macro */

/* Private variables */
uint32_t msTICKS = 0;
uint8_t bytes_received = 0;
uint8_t data_buffer[100];
bool SENDCOMPLETE = false;
/* Private function prototypes */

/* Private function prototypes */
void SysTick_Handler(void);
void delayMs(uint32_t ms);
void setClockTo32Mhz(void);
void init_uart2(void);
void USART2_IRQHandler(void);
void uart2_send(uint8_t data);
void init_stuff(void);
int main(void)
{
 init_stuff();

 /*************************|   TIP : 1 Memory Size Register   |*****************************
  *  STM32L0 : 0x1FF8 007C  Page 826 of RM0377
  *  STM32F1 : 0x1FFF F7E0  Page 1072 of RM008  */
 uint16_t (*flash_size) = (uint16_t*)(0x1FF8007C ); // address is specific to  STM32L0


 /*************************|   TIP : 2 Unique Device ID 96 bit   |*****************************
 *  STM32L0 : 0x1FF8 0050  Page 826 of RM0377
 *  STM32F1 : 0x1FFF F7E8  Page 1072 of RM008  */
 uint32_t (*unique_id_1) = (uint32_t*)(0x1FF80050 ); // BASE address
 uint32_t (*unique_id_2) = (uint32_t*)(0x1FF80054 ); // BASE address + 0x04 offset
 uint32_t (*unique_id_3) = (uint32_t*)(0x1FF80064 ); // BASE address + 0x14 0ffset

 while (1)
 {
  /*************************|   TIP : 1 Memory Size Register   |*****************************/
  uart2_send((*flash_size>>8) & 0xFF);
  uart2_send((*flash_size) & 0xFF);
  delayMs(500);

/*************************|   TIP : 2 Unique Device ID 96 bit   |*****************************/
uart2_send((*unique_id_3>>24) & 0xFF);
uart2_send((*unique_id_3>>16) & 0xFF);
uart2_send((*unique_id_3>>8) & 0xFF);
uart2_send((*unique_id_3) & 0xFF);

uart2_send((*unique_id_2>>24) & 0xFF);
uart2_send((*unique_id_2>>16) & 0xFF);
uart2_send((*unique_id_2>>8) & 0xFF);
uart2_send((*unique_id_2) & 0xFF);

uart2_send((*unique_id_1>>24) & 0xFF);
uart2_send((*unique_id_1>>16) & 0xFF);
uart2_send((*unique_id_1>>8) & 0xFF);
uart2_send((*unique_id_1) & 0xFF);

  delayMs(5000);


 }
 return 0;
}

void init_stuff(void)
{
 //*************************|   System/Systick   |*****************************
 setClockTo32Mhz();
 SystemCoreClockUpdate();
 SysTick_Config(SystemCoreClock / 1000);
 //*************************|   UART CODE   |*****************************
 init_uart2();
 NVIC_EnableIRQ(USART2_IRQn);
 //*************************|   debug led   |*****************************
 RCC->IOPENR |= RCC_IOPENR_GPIOBEN;
 GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODE3_1)) | GPIO_MODER_MODE3_0;


}


void USART2_IRQHandler(void)
{
 // get status register and check what generated the interrupt
 volatile const uint32_t STATUS = USART2->ISR;

 if ((STATUS & USART_ISR_TC)) // transmit complete
 {
  //clear interrupt flag
  USART2->ICR |= USART_ICR_TCCF;
  SENDCOMPLETE = true ;

  if (1) //if there is more data to send do so
  {

  }

 }
 if ((STATUS & USART_ISR_RXNE)) //send received data to buffer
 {
  /*
   * put the data received at the current count of
   * variable bytes_received and the variable itself
   */
  data_buffer[bytes_received++] = USART2->RDR;

 }

}

void init_uart2(void)
{
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
 RCC->APB1ENR |= RCC_APB1ENR_USART2EN;

 // PA2 and PA3 to Alternate Function Mode
 GPIOA->MODER = ( GPIOA->MODER & ~(GPIO_MODER_MODE2_0))
   | (GPIO_MODER_MODE2_1);

 GPIOA->MODER = ( GPIOA->MODER & ~(GPIO_MODER_MODE15_0))
   | (GPIO_MODER_MODE15_1);

 //Select the specific Alternate function
 GPIOA->AFR[0] |= 4 << GPIO_AFRL_AFSEL2_Pos; //true studio
 GPIOA->AFR[1] |= 4 << GPIO_AFRH_AFSEL15_Pos; //true stdio

 //GPIOA->AFR[0] |= 4 << GPIO_AFRL_AFRL2_Pos; // Keil
 //GPIOA->AFR[1] |= 4 << GPIO_AFRH_AFRH7_Pos; // Keil

 // Baudrate = clk_Frq / BRR ===>  32Mhz / 9600 = 0xD05
 USART2->BRR = 0xD05; //160000 / 96;
 // Enable RX_NE interrupt and TXE interrupt, enable UART, RECEIVE , TRANSMIT COMPLETE
 USART2->CR1 = USART_CR1_TE | USART_CR1_UE | USART_CR1_RXNEIE | USART_CR1_RE
   | USART_CR1_TCIE;
 //USART2->CR1 = USART_CR1_TE | USART_CR1_UE | USART_CR1_RE;
}

void uart2_send(uint8_t data)
{

 SENDCOMPLETE = false;
 USART2->TDR = data;
 while (SENDCOMPLETE == false) // will be true onces interrupt is handled in ISR
  ;

}
void delayMs(uint32_t ms)
{
 msTICKS = 0;
 while (msTICKS < ms)
  ;
}
void SysTick_Handler(void)
{
 msTICKS++;
}

void setClockTo32Mhz(void)
{

 //adjust flash latency
 FLASH->ACR |= FLASH_ACR_LATENCY;
 while ((FLASH->ACR & FLASH_ACR_LATENCY) == 0)
  ; //wait for latency set flag

 //set voltage scaling to range 1
 PWR->CR |= PWR_CR_VOS_0;
 PWR->CR &= ~(PWR_CR_VOS_1);
 while (((PWR->CSR) & (PWR_CSR_VOSF)) == 1)
  ; //wait for voltage to settle

 //turn on HSE external, HSE bypass and security
 RCC->CR |= RCC_CR_CSSHSEON | RCC_CR_HSEBYP | RCC_CR_HSEON;
 while (((RCC->CR) & RCC_CR_HSERDY) == 0)
  ; //wait for the HSE to be ready

 //reset and configure pll mull and div settings, and PLL source
 RCC->CFGR = ((RCC->CFGR & ~(RCC_CFGR_PLLDIV | RCC_CFGR_PLLMUL))
   | RCC_CFGR_PLLDIV2 | RCC_CFGR_PLLMUL8 | RCC_CFGR_PLLSRC_HSE);
 while ((RCC->CR & RCC_CR_PLLRDY) == 1)
  ;

 //turn on PLL , wait for ready
 RCC->CR |= RCC_CR_PLLON;
 while (((RCC->CR) & RCC_CR_PLLRDY) == 0)
  ; // wait for pll to ready

 //set PLL as system clock
 RCC->CFGR |= RCC_CFGR_SW_PLL;
 while (((RCC->CFGR) & (RCC_CFGR_SWS_PLL)) != RCC_CFGR_SWS_PLL)
  ;
}

Comments

  1. Hi Eddie, I tried reading the unique ID on my STM32F107VCT6 device however I received a message that I could not access the memory at that location, any ideas?

    The 96-bit unique device identifier provides a reference number which is unique for any
    device and in any context. These bits can never be altered by the user.
    The 96-bit unique device identifier can also be read in single bytes/half-words/words in
    different ways and then be concatenated using a custom algorithm.
    Base address: 0x1FFF F7E8
    Address offset: 0x00


    uint32_t (*unique_id_1) = (uint32_t*)(0x1FF8F7E8 ); // BASE address
    uint32_t (*unique_id_2) = (uint32_t*)(0x1FF8F7E8 + 0x4 ); // BASE address + 0x04 offset
    uint32_t (*unique_id_3) = (uint32_t*)(0x1FF8F7E8 + 0x14 ); // BASE address + 0x14 0ffset


    Failed to execute MI command:
    -data-evaluate-expression *(unique_id_1)
    Error message from debugger back end:
    Cannot access memory at address 0x1ff8f7e8

    Cheers,
    Paul

    ReplyDelete
  2. You need to check the datasheet of the exact MCU your using to get the address to use.

    ReplyDelete
  3. void ID_to_hex_Array(uint32_t id, char *hex_array) {
    for (int i = 0; i < 8; i++) {
    uint8_t byte = (id >> (28 - i * 4)) & 0x0F;
    hex_array[i] = (byte < 10) ? ('0' + byte) : ('A' + byte - 10);
    }
    hex_array[8] = '\0';
    }
    void Read_ID_stm(void){
    varIP.IDstm[0] = *(uint32_t*)0x1FFFF7E8;
    varIP.IDstm[1] = *(uint32_t*)0x1FFFF7EC;
    varIP.IDstm[2] = *(uint32_t*)0x1FFFF7F0;
    #define tmpArerayMax 9 //
    char tmpArray_0[tmpArerayMax]={0}; ID_to_hex_Array(varIP.IDstm[0], tmpArray_0);
    strcpy(varIP.hexArray_IDstm, tmpArray_0);
    char tmpArray_1[tmpArerayMax]={0}; ID_to_hex_Array(varIP.IDstm[1], tmpArray_1);
    strcat(varIP.hexArray_IDstm, tmpArray_1);
    char tmpArray_2[tmpArerayMax]={0}; ID_to_hex_Array(varIP.IDstm[2], tmpArray_2);
    strcat(varIP.hexArray_IDstm, tmpArray_2);

    }// END END END END END END END END END END END END END END END END END END END END END END END END END END END END

    ReplyDelete
Share your comments with me

Archive

Contact Form

Send