cancel
Showing results for 
Search instead for 
Did you mean: 

What are option bytes in STM32 and how do I use them?

B.Montanari
ST Employee

What are option bytes in STM32 and how do I use them?

All STM32 have options and even though the functionalities may vary among the different families and series, they are all meant to allow the user a way to customize the general settings of the microcontroller. Option Bytes are mostly used to pre-configure the system on chip before starting the Cortex®-M and user code. They are automatically loaded after a Power-On reset or on request by setting the OBL_LAUNCH bit in the FLASH_CR register. Here are a few example settings that are typically available: read-out protection level (RDP), watchdog settings during low power modes, boot configuration modes, brown out threshold level and many more related to security, such as proprietary code read protection (PCROP) and write protection areas (WRP). As you can imagine, these options can change how the STM32 behaves during power on, execution, and even when to power off.
The option bytes reside in a different memory region from the user Flash memory, and can be accessed in different modes and times, including:
  • Regular programming phase with either SWD or JTAG (whenever available) by using a programming tool.
  • Using the system bootloader with the available interface (USART, SPI, I2C, CAN, USB, etc.)
  • During code execution / run time based on your own firmware implementation.

This article will focus on the latter as it will create a small code example aimed for the STM32G071 using the NUCLEO-G071RB board. It shows the steps needed to program the option bytes using the HAL API and some tips and tricks to prevent issues, but don’t worry, these steps can be easily tailored to any other STM32 device.
As the first step, the usual recommendation is to check the reference manual (RM0444 in this case) to get acquainted with all the possible option bytes and settings. The option bytes section in our documentation is always in the FLASH chapter, with a dedicated subchapter for it, below is a table from RM0444 showing the address and bit names:
590.png
One might notice from the above table that the address increases in a 2 words (8 bytes) ratio at a time, this is because most of the option bytes have a complementary word that is verified to ensure the proper programming. In the case of the STM32G0x1, it is represented as shown here:
591.png
Alright, now that we now where to look for information in the documentation and how the option bytes roughly are organized in the memory region, we should look into how we can program them.
After a reset, such as power on reset or just a simple NRST pin reset, the options related bits of the FLASH control register are write protected. To run any operation on the option bytes page, the option lock bit must be cleared. The following sequence is used to unlock this register:
1. Unlock the FLASH_CR with the LOCK clearing sequence
2. Write OPTKEY1 of the FLASH option key register
3. Write OPTKEY2 of the FLASH option key register
Be aware that any incorrect sequence locks up the Flash memory option registers until the next system reset. In the case of a wrong key sequence, a bus error is detected and a Hard Fault interrupt is generated. Once the unlock sequence is performed, we can write the desired values in the FLASH option byte registers – as the process is completed, we should check the FLASH busy flag before issuing the option start command. Any modification of the value of one option is automatically performed by erasing user option byte pages first, and then programming all the option bytes with the values contained in the Flash memory option registers. The complementary values are automatically computed and written into the complemented option bytes upon setting the OPTSTRT bit.
After the busy bit is cleared by hardware, all new options are updated in the Flash memory, but not applied to the system. An interesting fact is that if you perform a read from the option registers, they’ll still returns the last loaded option byte values, the new options have effect on the system only after they are loaded. There are two ways to load the option bytes:
– when OBL_LAUNCH bit of the FLASH control register (FLASH_CR) is set
– after a power reset (BOR reset or exit from Standby/Shutdown modes); here it is important to notice that it must be a power reset; a software reset or simply toggling the NRST line won’t load the option bytes
Before we move to the firmware implementation of the theory, there is a precaution we must consider. Upon an option byte programming failure (for any reason, such as loss of power or a reset during the option byte change sequence), mismatched values of the option bytes are loaded after reset. Those mismatched values force a secure configuration that might permanently lock the device depending on the STM32 series. To prevent this, only program the option bytes in a safe environment – safe supply, no pending watchdog, and clean reset line. To try to ensure this condition, in our firmware implementation we’ll check the voltage level on the MCU before moving forward.
Firmware Implementation:
For this code, as mentioned before, the board used is the NUCLEO-G071RB and it is set using the default hardware allocation, which means that the LED, button keys and USART (115200/8/N/1) pins are used. On top of these peripherals, the ADC and IWDG are also added – the application code is explained below and all of it should reside in the main.c file
As a general guideline, the firmware application will start the clocks and peripherals except the IWDG:
int main(void)
{
  /* USER CODE BEGIN 1 */
  FLASH_OBProgramInitTypeDef OptionsBytesStruct;
  uint16_t adc_value;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  HAL_FLASHEx_EnableDebugger();
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
Then, the code will configure the ADC to read the voltage reference and apply a simple check if the voltage is above a certain level, if it is, then it will move forward. This portion of the code is just a pseudo implementation:
/* USER CODE BEGIN 2 */
  // ADC enable - to measure Vref.
  // Vref is internally connected to Vin[13]. Vref is used to calculate the Vpower level
  // and use that information to disable Option Bytes update.
  // ADC runs on IN
  RCC->CCIPR |= 0x80000000; // Select ADC clock = HSI16.
  RCC->APBENR2 |= 0x00100000; // Enable ADC clock
  ADC->CCR |= 0x00400000; // Enable Vref - set BEFORE enabling ADC
  ADC1->CR |= 0x000000001; // Enable ADC
  while ((ADC1->ISR & 0x00000001) != 0x00000001)
  { // While ADC is NOT ready = loop.....
    ;
  }
  ADC1->SMPR = 0x00000077; // Sample time (160.5 cycles)
  ADC1->CHSELR |= 0x00002000; // Select Channel 13 (Vref)
  ADC1->CR |= 0x00000004; // Start conversion
  // Here we are about to make a decision whether to update the Option Byte
  // Based on the Power Supply Voltage
  while ((ADC1->ISR & 0x00000004) != 0x00000004)
  { // While ADC conversion is NOT completed loop.....
    ;
  }
  adc_value = ADC1->DR; // Read ADC
  if (adc_value > 1712) // 1712 for Vss ~2.9V
  { // Vss too low
    // Progress indicator: ADC Error: Vss too low.
    // Skip the update!
  }
Once the basic features are passed, the firmware checks and prints any flags that were previously set and ensures that all reset and clock control flags are cleared.
  if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST))
  {
	  printf("IWDG flag\r\n");
  }
  if (__HAL_RCC_GET_FLAG(RCC_FLAG_OBLRST))
  {
	  printf("OBL flag\r\n");
  }
  __HAL_RCC_CLEAR_RESET_FLAGS();
  printf("clear all RCC flags\r\n");
The next step is to verify the RDP level and, in case it is set to level 1, the code waits for the blue button to be pressed and released before progressing to the RDP regression.
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct);
  if(OptionsBytesStruct.RDPLevel == OB_RDP_LEVEL_1)
  {
	  printf("RDP LVL1 \r\n");
	  printf("wait BT1 to be pressed\r\n");
	  while(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_SET)
	  {
		  ;
	  }
	  printf("wait BT1 to be released\r\n");
	  while(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_RESET)
	  {
		  ;
	  }
	  while(HAL_FLASH_Unlock() != HAL_OK)
	  {
		  printf("Waiting Flash Unlock\r\n");
	  }
	  while(HAL_FLASH_OB_Unlock() != HAL_OK)
	  {
		  printf("Waiting OB Unlock\r\n");
	  }
	  printf("RDP regression LV0 init\r\n");
	  RDP_Regression();
  }
In case it is not RDP level 1, the code will also enter in the main loop and wait for the key to be pressed to execute a code that changes a few option bytes, including the RDP to level 1 and the nBOOT selection.
/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  printf("\033[96mPress BT1 to change Option Bytes\033[0m \r\n");
	  if(HAL_GPIO_ReadPin(BT1_GPIO_Port, BT1_Pin) == GPIO_PIN_RESET)
	  {
		  printf("BT1 pressed\r\n");
		  while(HAL_FLASH_Unlock() != HAL_OK)
		  {
			  printf("Waiting Flash Unlock\r\n");
		  }
		  while(HAL_FLASH_OB_Unlock() != HAL_OK)
		  {
			  printf("Waiting OB Unlock\r\n");
		  }
		  OptionsBytesStruct.OptionType = OPTIONBYTE_USER | OPTIONBYTE_RDP ; //Configure USER and RDP
		  OptionsBytesStruct.USERType = OB_USER_nBOOT_SEL;
		  OptionsBytesStruct.USERConfig = OB_BOOT0_FROM_PIN;//Set to boot from pin (UART)
		  OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_1;
		  while(HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK)
		  {
			  printf("Waiting OB Program\r\n");
		  }
		  printf("OB Boot0 is now Boot From Pin and RDP 1\r\n");
		  SET_BIT(FLASH->CR, FLASH_CR_OPTSTRT);
		  while((FLASH->SR & FLASH_SR_BSY1) != 0)
		  {
			  ;
		  }
To ensure the programming and load of the option bytes the two methods are implemented -- the OBL_LAUNCH and also the IWDG with standby entry, which wakes up due to the IWDG and forces a power reset, so both methods are shown for demonstration purposes.
  		  printf("OBLauch\r\n");
		  MX_IWDG_Init();
		  while( HAL_FLASH_OB_Launch()!= HAL_OK)
		  {
			  printf("OBLauch Failed..retry with IWDG and StandBy Mode\r\n");
			  HAL_PWR_EnterSTANDBYMode();
		  }
	  }
A simple LED and print messages are also present to guide the process if the key is not pressed, executing in the main loop as well
void ToggleLED(void)
{
	if(HAL_GPIO_ReadPin(LED_GREEN_GPIO_Port, LED_GREEN_Pin))
	{
		printf("\033[5;32mLED\033[0m \r\n");
		HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
	}
	else
	{
		printf("\033[1;32mLED\033[0m \r\n");
		HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
	}
	HAL_Delay(2000);
}
An additional detail is how the RDP works, so please refer to this image for a quick explanation:
592.png
As we can see, upon an RDP regression from level 1 to level 0, a mass erase is performed, so we need to take this into account when implementing the RDP_Regression function, because if we happen to leave it in the FLASH, the function will cease to exist before its completion and it wouldn’t work as expected, this is the reason we’ll place this function in RAM
void __attribute__((__section__(".RamFunc"))) RDP_Regression(void)
{
	__disable_irq();
	printf("Mass Erase Start\r\n");

    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
    FLASH->OPTKEYR = FLASH_OPTKEY1;
    FLASH->OPTKEYR = FLASH_OPTKEY2;
	/* Force readout protection level 0 */
	FLASH->OPTR = OPTION_BYTE_TARGET_VALUE;
	FLASH->CR |= FLASH_CR_OPTSTRT;
	while((FLASH->SR & FLASH_SR_BSY1) != 0)
	{
		;
	}
    /* Force OB Load */
    FLASH->CR |= FLASH_CR_OBL_LAUNCH;
}
Conclusion:
The options bytes are a key factor in the end product, as it gives a great set of customizations to ensure your microcontroller will behave as you want, including one of the most used features of the option bytes, the read protection, which allows you to lock your STM32 against unwanted writes and reads.
With some precautions taken, the option bytes can be programmed at any given circumstance, but it is important to ensure an adequate condition before updating them.
Hope you enjoyed this article!
 
Comments
JMrow.1
Associate

How does one get at option bytes setting via the STM32Cube_Prog CLI ?

浚钱.1
Associate III

�?次选项字节首先在用户存储区域�?�动时;�?�设我�?置的字节高低级�?置字节都改�?了系统引导加载程�?的起始地�?�,�?能回退,怎么办?

浚钱.1
Associate III

�?次选项字节首先在用户内存中�?�动时;�?�设我将�?置字节的选项字节高低电平更改为系统引导加载程�?的起始地�?�,但无法回退,如何处�?�呢?

B.Montanari
ST Employee

Hi @JMrow.1​ ,

These are the usual steps:

  1. Open the command prompt, here you can use the cmd.exe or powershell (assuming you are in a Windows OS platform)
  2. Issue the directory change "cd "C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin"
  3. If you want all the option bytes to be dumped, call the STM32Cube_Prog.exe like this "STM32_Programmer_CLI.exe -c port=swd –ob displ" , assuming we are using the SWD interface
  4. To program the option bytes you can use a similar approach, where my recommendation is to configure only the option bytes you'll change. Here is a snippet for that: "stm32_programmer_cli.exe -q -c port=SWD -rdu -ob BOOT_LOCK=0 -ob WRP1A_END=0x00 -ob WRP1A_STRT=0x3f -ob SEC_SIZE=0x0". As you can see, I'm calling and making changes to the BOOT_LOCK, WRP1A_END, STRT and SEC_SIZE. The "-ob" is how you invoke the option byte action and the "-rdu" in the begging is just to set the RDP to level 0 in case it was set to level 1 - please note that this will cause a mass erase on the flash memory

You can find all acronym in the STM32CubeProg user manual> https://www.st.com/resource/en/user_manual/um2237-stm32cubeprogrammer-software-description-stmicroelectronics.pdf

Version history
Last update:
‎2022-06-22 05:58 AM
Updated by: