How to change the Read Out Protection on STM32F1?
How to change the Read Out Protection on STM32F1
- Introduction
STM32F1 microcontrollers offer three levels of read-out 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's important to note that level 2 should only be considered for the final product, not during development stages.At level 0, no read-out protection is enabled, and 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 read-out protection to level 0 either via system bootloader mode or 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 will remain 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 will not be erased in this process.In this article we will showcase this functionality using the firmware to modify it. You will be able to configure any other option bytes using this method as well, but the focus will be on read out protection.
- Microcontroller Configuration
First, let’s create a STM32CubeIDE project for the STM32F1 series and then add our logic. After opening the software, click on File -> New -> STM32 Project:After the menu loads, search for STM32F103RB, you can easily locate it with the corresponding Nucleo board and press ‘next’.Name your project (avoid using special characters and spaces) and click finish with the default settings.Now we will setup the necessary microcontroller peripherals, including the SWD interface, USART, IWDG 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 SysTickNext, we will configure USART2, which is connected on this Nucleo board to the on-board ST-LINK. With the help of ST-Link’s virtual COM port, we will then use this link to relay messages. Go to Connectivity > USART2 and set it to Asynchronous mode. The default basic parameters under configuration tab should work, but feel free to adjust it to your preferences, just remember that this will be used later when configuring your terminal.For the button pin and LED, we’ll 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]’. As for the LED, we will 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]’.The last peripheral we will use is the Independent Watchdog (IWDG). Set it up in the System Core > IWDG by simply checking the ‘Activated box’, no need to adjust the time base here as the default will work just fine.Before generating the code, we must choose to not initialize the watchdog, otherwise it will keep resetting the microcontroller unless we first add some code to continuously refresh it. For this, go to Project Manager > Advanced Settings and check the Do Not Generate Function Call box for the MX_IWDG_Init line.It is now possible to either click on the Device configuration tool code generation icon in the top bar or press Alt+K. This will generate a basic project with all the configuration we selected using the GUI.
- 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 will 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 will create the RDP regression function in such a way that it runs from the microcontroller’s RAM instead of Flash
void __attribute__((__section__(".RamFunc"))) RDP_Regression(void)
{
FLASH_OBProgramInitTypeDef OptionsBytesStruct;
__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
MX_IWDG_Init(); // Initialize the watchdog timer
/* Force readout protection level 0 and remove the write protection*/
OptionsBytesStruct.OptionType = OPTIONBYTE_RDP ; //Configure the RDP
OptionsBytesStruct.RDPLevel = OB_RDP_LEVEL_0; // Set the read protection level to 0
while(HAL_FLASHEx_OBProgram(&OptionsBytesStruct) != HAL_OK) // Write the option byte until it succeeds
/* Force OB Load */
__NVIC_SystemReset(); // Reset the system
}
Still under the same comment section, 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 will 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 2 and END 2 sections. The idea here is to check the RCC flags and most importantly the option bytes current configuration regarding the RDP level.
Printf(“\033[0m”];
/* Check if the Independent Watchdog (IWDG) flag is set and print a message if it is */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST))
{
printf(“IWDG flag\r\n”);
}
/* Check if the Software Reset (SFTRST) flag is set and print a message if it is */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST))
{
printf(“Software reset flag\r\n”);
}
/* Clear all RCC reset flags */
__HAL_RCC_CLEAR_RESET_FLAGS();
printf(“clear all RCC flags\r\n”);
HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct); // Get the current Options Bytes configuration for the Flash memory
After the RDP level check, the small decision branch happens, if it’s at level 1 the code will blink the led at a 500ms rate until the button is pressed. Upon button press, it will start the regression process by unlocking the flash memory and the Option bytes, and finally 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.
// 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)
{
;
}
// Loop until flash memory is unlocked
while(HAL_FLASH_Unlock() != HAL_OK)
{
printf("Waiting Flash Unlock\r\n");
}
// Loop until options bytes are unlocked
while(HAL_FLASH_OB_Unlock() != HAL_OK)
{
printf("Waiting OB Unlock\r\n");
}
printf("RDP regression LV0 init\r\n");
// Call RDP_Regression() function
RDP_Regression();
}
In the while loop, more precisely inside the USER CODE BEGIN 3 label, the code checks the button state 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 and the microcontroller is reset.
printf("\033[96mPress BT1 to change Option Bytes\033[0m \r\n");
if(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) != GPIO_PIN_SET)
{
printf("BT1 pressed, please release it to proceed\r\n");
while(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) != GPIO_PIN_SET);
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");
MX_IWDG_Init();
HAL_FLASH_OB_Launch();
while(1)
{
//fail safe in case things go bananas
printf("OBLaunch Failed..retry with IWDG and StandBy Mode\r\n");
HAL_PWR_EnterSTANDBYMode();
}
}
else
{
ToggleLED(2000);
}
- 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:After this, the NUCLEO board must be unplugged from the computer’s USB port and the CN2 jumpers removed from the ST-Link as shown:Now the board must be plugged back in, which will power up the board and restart program execution. Note: The reason behind removing the ST-Link jumpers is to prevent the debug connection by the ST-Link (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:Now press and hold the blue button until the following message appears: Release the blue button to change the RDP level to 1. The MCU will reset itself and display the new RDP level message with a faster LED blink rate.Press and release the blue button again to restore the RDP level to 0, causing a mass erase of the MCU.At this point the Flash memory is automatically mass erased. Warning: This may take up to 10 seconds, if you reset the microcontroller before the FLASH memory is fully erased the RDP byte will stay locked at Level 1 and your program will be deleted, so 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 displIf the RDP regression worked, you’ll see a message like the following: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:In order to see the RDP protection taking place, you can re-do 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 read-out protection and after the execution of this command your microcontroller will halt completely and recover only after a power cycle.To further explore, you can try to check the Flash contents again using the command ‘STM32_Programmer_cli.exe -c port=swd mode=HOTPLUG -r32 0x08000000 1000’, but this time you’ll receive an error message as the microcontroller has protected the Flash content, and again the micro will halt until it is power cycled.Note: You can follow the same process to set RDP to level 2, but 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 https://www.st.com/content/ccc/resource/technical/document/application_note/27/38/37/58/c2/8c/40/07/DM00161366.pdf/files/DM00161366.pdf/jcr:content/translations/en.DM00161366.pdf)) because RDP level 2 will disable the built-in System Bootloader.
- Conclusion
In this article you learned how to configure the option bytes for the STM32F1 series, more specifically the Read-Out Protection feature, allowing your application to activate the FLASH protection and even revert to a state of no protection whenever necessary.