Skip to main content

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

Popular posts from this blog

Interface a Rotary Encoder the right way.

So whilst reading my favorite odyssey found here I stumbled onto something I never spotted before. It turns out that the general purpose timers support hardware interfacing with an incremental encoder. This means no more interrupts and no need to increment whatever variable you had. Now all you have to do is get the value from the Count register in the timer. Did I also mention it takes care of figuring out which direction you are turning it? Amazing! Lets talk about it.






Encoder Interface Mode Verbatim from the reference manual of the STM32F103 page 392 or Section 15.3.12  "Encoder interface mode acts simply as an external clock with direction selection. This 
means that the counter just counts continuously between 0 and the auto-reload value in the 
TIMx_ARR register (0 to ARR or ARR down to 0 depending on the direction). So the user 
must configure TIMx_ARR before starting. In the same way, the capture, compare, 
prescaler, trigger output features continue to work as normal."
W…

STM32L0/F0/F3 I2C : Part 3 RX TX via DMA

Most modern microcontroller come with a peripheral called DMA which allows for an even more hands-off approach. The Direct Memory Access controller will get a tutorial of it's own in the future. However, it is so simple to use that I can easily explain the required bits in this tutorial without feeling like I will overwhelm the reader. In this iteration of the I2C series I will cover how to TX and RX date on the peripheral in conjunction with the DMA controller. 

First let me briefly explain in a high level what the DMA controller does. It allows you to transfer data in 3 ways: Peripheral to memoryMemory to peripheralMemory to memory. What does this mean? For example, when you receive data on I2C in your RXDR register, you can have the DMA controller move that data received into an array. At the end of the day your variables are just memory locations, so if you have declared an array it has an address associated with it, this is address is what the DMA considers  "Memory"…

STM32 I2C Does it suck?

Recently I have been annoyed with  bugs in the I2C implementation in ST's F1/L1/F40/F2 series, and  any microcontroller made by ST before 2012. There appears to be, what I can only describe as, a race condition when attempting to receive data in I2C interrupt(low priority)  or polling mode.





Just to be clear a race condition is defined as , verbatim from wiki: "the behavior of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable."
I was humming along trying to make my YouTube tutorials when I arrived to the I2C protocol implementation. I have used I2C with the F1 series plenty of times, however I have never had the need to receive data from a slave. However for the sake of completeness I decided to implement an RXing routine in I2C for my tutorial. Only to find statements like this in the referen…