cancel
Showing results for 
Search instead for 
Did you mean: 

How to change the readout protection on STM32F4

B.Montanari
ST Employee

How to change the readout protection on STM32F4?

Introduction

STM32F4 microcontrollers offer three levels of readout protection: level 0 (no protection), level 1 (Flash memory, backup SRAM, and backup registers protected), and level 2 (same as level 1, but with permanent protection by locking the option bytes). It is important to note that level 2 should only be considered for the final product, not during the development stages.


At level 0, no readout protection is enabled. All read and write operations on the flash memory and backup SRAM are possible in all boot configurations. Option bytes on the microcontroller can also be changed.


Level 1 sets read protection for the flash memory. Access to protected memories is only allowed when booting from user flash memory. Otherwise, a system hard fault is generated, blocking all code execution until the next power-on reset. Option bytes are still configurable at this level, making it possible to revert the readout protection to level 0 either via system bootloader mode or a regular SWD/JTAG interface.


Level 2 provides the same protection as level 1, but with permanent protection by locking the option bytes. Once level 2 protection is set, it cannot be undone, and the microcontroller remains permanently protected, the system bootloader is also unreachable in this mode.
Regressing from level 1 to level 0 protection causes a mass erase of the flash memory and backup SRAM. Only option bytes are not erased in this process.
In this article, we showcase this functionality using the firmware to modify it. You are able to configure any other option bytes using this method as well, but the focus is on readout protection.

1. Microcontroller configuration

First, let us create a STM32CubeIDE project for the STM32F4 series and then add our logic.
 After opening the software, click on File -> New -> STM32 Project:
355.png
After the menu loads, search for STM32F411RE, you can easily locate it with the corresponding Nucleo board and press ‘next’.
356.png
Name your project (avoid using special characters and spaces) and click finish with the default settings.
357.png
Now, we will set up the necessary microcontroller peripherals, including the SWD interface, USART, and GPIO:
To setup the microcontroller’s SWD, go to System Core > SYS and enable the Debug Serial Wire. We can use the default Timebase source as SysTick

358.png
Next, we configure USART2, which is connected on this Nucleo board to the on-board STLINK. With the help of STLINK’s virtual COM port, we can then use this link to relay messages. Go to Connectivity > USART2 and set it to Asynchronous mode. The default basic parameters under the configuration tab should work, but feel free to adjust it to your preferences. Just remember that this is used later when configuring your terminal.
359.png
For the button pin and LED, we will use the hardware available on the Nucleo board:  PC13 is connected to the push button, and we can configure it by clicking it and selecting ‘EXTI_13’. After that, setup a user label for the button doing the following: System Core > GPIO > PC13 and under User Label type ‘B1 [Blue PushButton]’. 
360.png
As for the LED, we use the PA5, as it is connected to the onboard Green LED. On the dropdown menu, select GPIO_Output, and similarly to the button pin, go to System Core > GPIO > PA5 and under User Label type ‘LD2 [Green Led]’.
361.png
It is now possible to click on the Device configuration tool code generation icon in the top bar, or press Alt+K. This generates a basic project with all the configurations that we selected using the GUI.
362.png

2. Coding

With the base code ready, it is time to start adding the application code. First include the stdio.h library in the private includes section so we can easily print to the vcom terminal.

#include "stdio.h"


In the private function prototypes, we create a function to allow us to transmit to the USART simply using the printf function.

int __io_putchar(int ch)
{
      HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 100);
      return ch;
}

Under the USER CODE BEGIN 0 section, we create the regression function in a way that it runs from the microcontroller’s RAM instead of flash.

void __attribute__((__section__(".RamFunc"))) RDP_Regression(void) {
      __disable_irq(); // Disable interrupts
      printf("Mass Erase Start\r\n");

      HAL_FLASH_Unlock();  // Unlock the flash memory
      HAL_FLASH_OB_Unlock(); // Unlock the option bytes

      *(__IO uint8_t*) OPTCR_BYTE1_ADDRESS = OB_RDP_LEVEL_0;

      HAL_FLASH_OB_Launch();
}

Add the simple function to blink the on-board LED at a given time interval.

void ToggleLED(uint16_t n) // Function for toggling the on-board LED
{
      if(HAL_GPIO_ReadPin(LD2_GPIO_Port, LD2_Pin))
      {
             printf("\033[5;32mLED\033[0m \r\n");
             HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
      }
      else
      {
             printf("\033[1;32mLED\033[0m \r\n");
             HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
      }
      HAL_Delay(n);
}


Next, inside the main function and under the USER CODE BEGIN 1 label we define a structure to edit the option bytes.
FLASH_OBProgramInitTypeDef OptionsBytesStruct; //Define a structure to store the Options Bytes configuration for the Flash memory
The next code portion should be placed between the USER CODE BEGIN 3 and END 3. After the RDP level check, the small decision branch happens. If it is at level 1, then the code will blink the LED at a 500ms rate until the button is pressed. Upon button press, it starts the regression process by unlocking the flash memory and the Option bytes. Finally, it will call the RDP_Regression function, which was added earlier and executed from RAM memory.  It is important to remember that upon RDP regression, a full mass erase is performed.
 

printf("\033[0m");
      HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct); // Get the current Options Bytes configuration for the Flash memory

      // Check if the RDP level is set to Level 1
      if (OptionsBytesStruct.RDPLevel == OB_RDP_LEVEL_1) {
             printf("RDP LVL1 \r\n");
             printf("wait BT1 to be pressed\r\n");
             // Loop until button BT1 is pressed
             while (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) != GPIO_PIN_RESET) {
                   ToggleLED(500);
             }
             printf("wait BT1 to be released\r\n");
             // Loop until button BT1 is released
             while (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) != GPIO_PIN_SET) {
                   ;
             }
             printf("RDP regression LV0 init\r\n");
             // Call RDP_Regression() function
            RDP_Regression();
      }


Then the code performs a check every 2 seconds to decide when the microcontroller’s RDP level should be set to 1. While it waits for a button press and release, the code blinks the LED at a 2s rate. When the button is released, the RDP level is set to 1.

//if RDP is set to 0, wait for a button press to set it to level 1
             printf("\033[96mPress BT1 to change Option Bytes\033[0m \r\n");
             if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) != GPIO_PIN_RESET) {
                   printf("BT1 pressed, please release it to proceed\r\n");
                   while (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_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");
                   }

                   OptionsBytesStruct.OptionType = OPTIONBYTE_RDP; //Configure the RDP
                   OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_1;
                   while (HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK) {
                          printf("Waiting OB Program\r\n");
                   }
                   printf("RDP will now be set to level 1\r\n");
                   printf("OBLaunch\r\n");

                   HAL_FLASH_OB_Launch();
             } else {
                   ToggleLED(2000);
             }

      }
      /* USER CODE END 3 */


3. Testing

To test this code, we must follow a specific flow. First you should program the Nucleo board with the code created. After building, click on the green button on the STM32CubeIDE, highlighted below:
363.png
After this, the Nucleo board must be disconnected from the USB’s computer and the CN2 jumpers removed from the STLINK as shown:
364.png
Now, the board must be plugged back in, which powers up the board and restart program execution.
Note: The reason behind removing the STLINK jumpers is to prevent the debug connection by the STLINK (which automatically happens at board power-up) from interfering with the RDP entry/exit.
To confirm this,you can open a terminal, such as Tera Term and configure it to match the USART2 configuration we setup earlier. Just to recall, it was: 115200/8/N/1. The terminal should display the following:
365.png
Now, press, and hold the blue button until the following message appears:
366.png
Release the blue button to change the RDP level to 1. The MCU resets itself and display the new RDP level message with a faster LED blink rate.
367.png
Press and release the blue button again to restore the RDP level to 0, causing a mass erase of the MCU.
368.png
At this point, the flash memory is automatically mass erased.


Warning: This may take up to 20 seconds, if you reset the microcontroller before the flash memory is fully erased, the RDP byte stays locked at Level 1 and your program will be deleted. It is very important so stand still while the flash is being mass erased.


To confirm the RDP regression, fit the CN2 jumpers back in (no need to power cycle) and use the STM32CubeProg (GUI or CLI) to read the memory. Here it is demonstrated using the CLI to read the option byte.
Open your windows command prompt and type the following commands as shown
cd C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin
STM32_Programmer_CLI.exe -c port=swd -ob displ

369.png
If the RDP regression worked, you will see a message like the following:
370.png
By using the command ‘STM32_Programmer_cli.exe -c port=swd mode=HOTPLUG -r32 0x08000000 1000’ you can see the contents of the flash memory, which as we expected, are all FFFF FFFF:
371.png
To see the RDP protection taking place, you can redo the steps (reprogram your code using the CubeIDE, remove CN2 jumpers, power cycle, press the button to set RDP level 1) and fit the CN2 jumpers after it was set to level 1. Now, you can reissue the option byte command ‘STM32_Programmer_CLI.exe -c port=swd -ob displ’ to view the RDP status. Be aware that this will trigger the readout protection and after the execution of this command your microcontroller will halt completely and recover only after a power cycle.
372.png
To explore further, you can try to check the flash contents again using the command ‘STM32_Programmer_cli.exe -c port=swd mode=HOTPLUG -r32 0x08000000 1000’. This time you will receive an error message as the microcontroller has protected the flash content, and again the microcontroller halts until it is power cycled.
373.png
Note: You can follow the same process to set RDP to level 2. Keep in mind that this action is irreversible and should only be done at your final production. If you need to update your firmware after RDP level 2, you should first implement your own bootloader (see IAP bootloader example> STM32 in-application programming (IAP) using the USART - Application note). This is because RDP level 2 will disable the built-in system bootloader.


4. Conclusion

In this article, you learned how to configure the option bytes for the STM32F4 series, more specifically the readout protection feature. This allows your application to activate the flash protection and even revert to a state of no protection whenever necessary.
 

Version history
Last update:
‎2023-11-17 05:43 AM
Updated by: