cancel
Showing results for 
Search instead for 
Did you mean: 

How to program STM32 Option Bytes with the HAL API

MCU Support TD
ST Employee
Although it is possible to program the option bytes through a debugger with a tool like STM32CubeProgrammer, there are many times where it is necessary or helpful to program the options bytes in the runtime of the application. The STM32 HAL libraries provide an API for programming and checking the option bytes from application code. The purpose of this article is to show proper usage of the HAL option bytes API for multiple use cases.  ​​​​​​​

1. User option bytes

1.1 Modifying A Single User option

Here is an example of programming the User option bytes. In this case, the function below clears the nBOOT_SEL option bit. It uses HAL status typedefs for error checking the function.
 
HAL_StatusTypeDef ClearnBootSel()
{
	FLASH_OBProgramInitTypeDef OB;
	HAL_FLASHEx_OBGetConfig(&OB);

	/* OB.USERConfig returns the FLASH_OPTR register */
	// Use it to check if OB programming is necessary
	if (OB.USERConfig & FLASH_OPTR_nBOOT_SEL)
	{

		  HAL_FLASH_Unlock();
		  HAL_FLASH_OB_Unlock();

		  OB.OptionType = OPTIONBYTE_USER;
		  OB.USERType = OB_USER_nBOOT_SEL;
		  OB.USERConfig = OB_BOOT0_FROM_PIN;

		  if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
		  {
			  HAL_FLASH_OB_Lock();
			  HAL_FLASH_Lock();
			  return HAL_ERROR;
		  }

		  HAL_FLASH_OB_Launch();

		  /* We should not make it past the Launch, so lock
		   * flash memory and return an error from function
		   */
		  HAL_FLASH_OB_Lock();
		  HAL_FLASH_Lock();
		  return HAL_ERROR;
	}

	return HAL_OK;
}

The FLASH_OBProgramInitTypeDef struct contains all of the necessary information to program and read the option bytes. Using OB.USERConfig, you can check the value of the OPTR register, which will show you the current value of the user option bytes. You will always want to check if the option bytes are already in the desired configuration before needlessly unlocking the flash memory and option bytes. Another thing to note is that the HAL_FLASH_OB_Launch() causes a system reset, and therefore will never return. This is the reason a HAL_ERROR is.
Returned after the launch function, as it should never be reached.

1.2 Modifying Multiple User Options simultaneously

It is also possible to program multiple USER option bytes simultaneously. This can be done by combining the defines for the option bytes API. Let us say, for example, you wanted to modify nRST_STOP, nRST_STDBY, and nRST_SHDW to be 1, 0, and 1 respectively: 
 
HAL_StatusTypeDef modifynRST()
{
	FLASH_OBProgramInitTypeDef OB;
	HAL_FLASHEx_OBGetConfig(&OB);

						                                // check if
	if ( !(OB.USERConfig & FLASH_OPTR_nRST_STOP)  ||    // nRST_STOP is cleared   or
		  (OB.USERConfig & FLASH_OPTR_nRST_STDBY) ||	// nRST_STDBY is set      or
		 !(OB.USERConfig & FLASH_OPTR_nRST_SHDW)    )   // nRST_SHDW is cleared
	{

		  HAL_FLASH_Unlock();
		  HAL_FLASH_OB_Unlock();

		  OB.OptionType = OPTIONBYTE_USER;
		  OB.USERType = OB_USER_nRST_STOP | OB_USER_nRST_STDBY | OB_USER_nRST_SHDW;
		  OB.USERConfig = OB_STOP_NORST | OB_STANDBY_RST | OB_SHUTDOWN_NORST;

		  if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
		  {
			  HAL_FLASH_OB_Lock();
			  HAL_FLASH_Lock();
			  return HAL_ERROR;
		  }

		  HAL_FLASH_OB_Launch();

		  /* We should not make it past the Launch, so lock
		   * flash memory and return an error from function
		   */
		  HAL_FLASH_OB_Lock();
		  HAL_FLASH_Lock();
		  return HAL_ERROR;
	}

	return HAL_OK;
}

First, we check if any one of the desired options is configured incorrectly. Then, make OB.USERType the combination of user options you want to program, and OB.USERConfig the combination of values for those options.
 

2. Setting RDP Level

Modifying the RDP level is similar to modifying the USER options, but it a bit simpler since the API struct provides members that cater to programming RDP specifically:
 
HAL_StatusTypeDef SetRDPLevel1()
{
	FLASH_OBProgramInitTypeDef OB;
	HAL_FLASHEx_OBGetConfig(&OB);

	if (OB.RDPLevel != OB_RDP_LEVEL_1)
	{

		  HAL_FLASH_Unlock();
		  HAL_FLASH_OB_Unlock();

		  OB.OptionType = OPTIONBYTE_RDP;
		  OB.RDPLevel = OB_RDP_LEVEL_1;

		  if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
		  {
			  HAL_FLASH_OB_Lock();
			  HAL_FLASH_Lock();
			  return HAL_ERROR;
		  }

		  HAL_FLASH_OB_Launch();

		  /* We should not make it past the Launch, so lock
		   * flash memory and return an error from function
		   */
		  HAL_FLASH_OB_Lock();
		  HAL_FLASH_Lock();
		  return HAL_ERROR;
	}

	return HAL_OK;
}
 

3. Working with WRP

3.1 Zone Based WRP

On some MCU series, such as the G and L series, for example, the WRP is set as a zone between two flash memory pages (WRP zone inclusive of the starting and ending flash memory pages). Here is an example of getting the current protections on such a device:
void getWriteProtections(uint8_t *ZoneA_Start, uint8_t *ZoneA_End, uint8_t *ZoneB_Start, uint8_t *ZoneB_End)
{
	FLASH_OBProgramInitTypeDef OB;

	OB.WRPArea = OB_WRPAREA_ZONE_A;
	HAL_FLASHEx_OBGetConfig(&OB);

	*ZoneA_Start = 	OB.WRPStartOffset;
	*ZoneA_End 	= 	OB.WRPEndOffset;

	OB.WRPArea = OB_WRPAREA_ZONE_B;
	HAL_FLASHEx_OBGetConfig(&OB);

	*ZoneB_Start = 	OB.WRPStartOffset;
	*ZoneB_End 	= 	OB.WRPEndOffset;
}

The write protection start and end values are in terms of flash memory page numbers. For instance, ZoneA_Start = 0 and ZoneA_End = 4 means that the first 5 pages are write protected. Having these values can be helpful for understanding what the current write protections are, or if it is activated at all. When the start is larger than the end, it means that there is no write protections active for that zone.
  uint8_t a_beg, a_end, b_beg, b_end;
  getWriteProtections(&a_beg, &a_end, &b_beg, &b_end);
  
  if ( (a_beg > a_end) && (b_beg > b_end) )
  {
	  // No write protections active
  }

Here is an example function of setting the WRP:
HAL_StatusTypeDef setWRP(uint8_t A_Start, uint8_t A_End, uint8_t B_Start, uint8_t B_End)
{
	FLASH_OBProgramInitTypeDef OB;

	HAL_FLASH_Unlock();
	HAL_FLASH_OB_Unlock();

	OB.OptionType = OPTIONBYTE_WRP;

	OB.WRPArea = OB_WRPAREA_ZONE_A;
	OB.WRPStartOffset = A_Start;
	OB.WRPEndOffset   = A_End;

	if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
	{
	  HAL_FLASH_OB_Lock();
	  HAL_FLASH_Lock();
	  return HAL_ERROR;
	}

	OB.WRPArea = OB_WRPAREA_ZONE_B;
	OB.WRPStartOffset = B_Start;
	OB.WRPEndOffset   = B_End;

	if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
	{
	  HAL_FLASH_OB_Lock();
	  HAL_FLASH_Lock();
	  return HAL_ERROR;
	}

	HAL_FLASH_OB_Launch();

	/* We should not make it past the Launch, so lock
	* flash memory and return an error from function
	*/
	HAL_FLASH_OB_Lock();
	HAL_FLASH_Lock();
	return HAL_ERROR;
}

3.2 Sector Based WRP

Some parts have WRP that is enabled or disabled on a sector-by-sector basis rather than being defined as a range between two sectors, such as with the H7 series. Here is an example of changing the WRP on the H743:
 

HAL_StatusTypeDef ChangeWRP(uint32_t bank_num, uint32_t state, uint32_t sectors)
{
	FLASH_OBProgramInitTypeDef OB;
	OB.OptionType = OPTIONBYTE_WRP;
	OB.WRPState = state;
	OB.Banks = bank_num;
	OB.WRPSector = sectors;

	HAL_FLASH_Unlock();
	HAL_FLASH_OB_Unlock();
	if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
	{
	  HAL_FLASH_OB_Lock();
	  HAL_FLASH_Lock();
	  return HAL_ERROR;
	}

	HAL_FLASH_OB_Launch();

	// Should not be reached
	HAL_FLASH_OB_Lock();
	HAL_FLASH_Lock();
	return HAL_ERROR;

}

...
...
...

// Example of enabling WRP on sectors 1, 3, 5 of Bank 1.
uint32_t sectors = OB_WRP_SECTOR_1 | OB_WRP_SECTOR_3 | OB_WRP_SECTOR_5;
ChangeWRP(FLASH_BANK_1, OB_WRPSTATE_ENABLE, sectors);

4. Working with PCROP 

Working with PCROP will be similar to working with WRP, but the struct presents the protections as addresses in the option byte struct. Here is an example of reading the current PCROP values:
void getPCROP(uint32_t *ZoneA_Start, uint32_t *ZoneA_End, uint32_t *ZoneB_Start, uint32_t *ZoneB_End)
{
	FLASH_OBProgramInitTypeDef OB;
	HAL_FLASHEx_OBGetConfig(&OB);

	*ZoneA_Start = 	OB.PCROP1AStartAddr;
	*ZoneA_End 	= 	OB.PCROP1AEndAddr;
	*ZoneB_Start = 	OB.PCROP1BStartAddr;
	*ZoneB_End 	= 	OB.PCROP1BEndAddr;
}
Just like with WRP, you can use the PCROP addresses to know if it is disabled. If the start address is greater than the end address for that zone, PCROP is disabled. Unlike the WRP API however, the PCROP values are given by the struct as the actual addresses, even though the PCROP option bytes themselves represent the address as an offset of lower granularity blocks.
Here is an example function of setting the PCROP:
HAL_StatusTypeDef setPCROP(uint32_t A_Start, uint32_t A_End, uint32_t B_Start, uint32_t B_End)
{
    FLASH_OBProgramInitTypeDef OB;

    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();

    OB.OptionType = OPTIONBYTE_PCROP;
    OB.PCROPConfig = OB_PCROP_ZONE_A | OB_PCROP_ZONE_B;

    OB.PCROP1AStartAddr = A_Start;
    OB.PCROP1AEndAddr   = A_End;
    OB.PCROP1BStartAddr = B_Start;
    OB.PCROP1BEndAddr   = B_End;

    if ( HAL_FLASHEx_OBProgram(&OB) != HAL_OK )
    {
      HAL_FLASH_OB_Lock();
      HAL_FLASH_Lock();
      return HAL_ERROR;
    }


    HAL_FLASH_OB_Launch();

    /* We should not make it past the Launch, so lock
    * flash memory and return an error from function
    */
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    return HAL_ERROR;
}

Even though the example above does not do so, a suggested best practice that your function checks if the address is valid given the size of the flash memory for your particular MCU, since the HAL OB Program function does not bound check the input.
As stated above, the granularity of PCROP is larger than a single address, but despite this, the address passed to the API will accept any single address.  As a consequence, inside the option bytes API the address is truncated given the granularity of the MCU's PCROP:
1323.png
ropbase is the beginning of flash memory: 0x8000000. For example, assuming 512 Byte granularity of the PCROP, let's use the setPCROP() function from above with some arbitrary values:
  uint32_t ZoneA_beg = 0x8000473;
  uint32_t ZoneA_end = 0x8000891;
  uint32_t ZoneB_beg = 0x8000c35;
  uint32_t ZoneB_end = 0x8000f42;

  setPCROP(ZoneA_beg, ZoneA_end, ZoneB_beg, ZoneB_end);

Here is what occurs during the truncation:
// right shifted by 9 because 512 = 1 << 9
0x8000473 - 0x8000000 = 0x473 >> 9 = 2 
0x8000891 - 0x8000000 = 0x891 >> 9 = 4 
0x8000c35 - 0x8000000 = 0xc35 >> 9 = 6 
0x8000f42 - 0x8000000 = 0xf42 >> 9 = 7 

This yields the 512 Byte block offsets that go into the PCROP registers, which correspond to these actual protection zones in terms of address space:
 
//512 bytes = 0x200

ZoneA_Start = 0x8000000 + (0x200 * 2) = 0x8000400

// Whole subsector is protected
ZoneA_End = 0x8000000 + (0x200 * 4) = 0x8000800, end of that sector is 0x80009FF

ZoneB_Start = 0x8000000 + (0x200 * 6) = 0x8000C00

// Whole subsector is protected
ZoneB_End = 0x8000000 + (0x200 * 7) = 0x8000E00, end of that sector is 0x8000FFF

Overall:
Zone A = 0x8000400 to 0x80009FF (inclusive)
Zone B = 0x8000C00 to 0x8000FFF (inclusive)
 

5. Conclusion

Although this article does not touch on every possible use case for the option bytes, these examples serve as a starting point to help you get a feel for using the HAL option bytes API. Depending on your particular MCU, there could be small differences in option byte implementation. For more details on the option bytes, always refer to the datasheet and reference manual of the part. If you want to learn more about the option bytes generally, please refer to this article:
https://community.st.com/s/article/What-Are-Option-Bytes-In-Stm32-And-How-Do-I-Use-Them
 
Comments
AWack
Associate III

Hi,

I´m trying to program the 3 bits for booting options (STM32G0B1) with the HAL functions in the above manual. Unfortunately I can´t find the possible options like OB_USER_nBOOT_SEL. I searched the whole project, but coudn´t found any #define of this. Where are these constants defined?

Best regards,

Achim

BPrac.1
Associate II

Hi! Im trying to implement these method, but i have a lot of problems. Also on clean MCU(G0B1)

 

https://community.st.com/t5/stm32-mcus-products/problem-with-option-bytes-set-from-program-level-g0b1/td-p/616562

sireevenkat1
Senior

Hi @MCU Support TD ,

Can I use option bytes to store the data(eg: Serial number, values storage etc).is It possible if so please share the available resource link/ example for reference. 

FYR:I am using STM32G491RE 

Thanks

Version history
Last update:
‎2022-11-29 08:23 AM
Updated by: