Introduction to the method of calling the Bootloader in the system memory from the user code
Preface
We know that any STM32 chip contains a system memory (SystemMemory), which stores the internal boot code Bootloader. Different STM32 models support different communication ports for upgrading code, and you need to refer to the application note AN2606 . However, there is a problem that cannot be avoided, that is, how to enter the System Memory to execute the Bootloader? The usual way is to configure BOOT1 and BOOT0: pull BOOT0 high and pull BOOT1 low (Note: BOOT1 of some models is controlled by the option byte nBOOT1). However, in some products, due to the requirements of appearance, it is often inconvenient to place buttons or jumpers outside to change the level of the BOOT pin. Moreover, users do not want to write IAP code by themselves, which is troublesome. In particular, some products need to use USBDFU for code upgrade, and USB is not used in the product function. Users will feel that if they want to write IAP for a function of code upgrade through USB, they need to be familiar with the USB code, which is troublesome, and these USB codes also occupy the user's program space. For these users, they really hope to be able to call the Bootloader of the System Memory in STM32 regardless of the BOOT pin to complete the code upgrade function.
question
A customer used STM32F411 in the design of its product. Due to the requirements of product appearance, the BOOT pin cannot be controlled externally, and only the USB interface is left outside, so USB DFU is required for upgrading. Moreover, the USB interface is only used for code upgrade and has no other functions. Therefore, the customer does not want to touch the USB code and hopes to directly use the Bootloader in the System Memory for code upgrade.
Research
1. Determine its feasibility
First, open the application note AN2606 "STM32 microcontroller system memory bootmode", turn to the end of section 3.1 Bootloader activation, you can see the following information:
What this means is that the user can execute the Bootloader by jumping from the user code to the system memory. However, before jumping to the Bootloader, several things must be done:
1) Turn off the clocks of all peripherals
2) Disable the PLL used
3) Disable all interrupts
4) Clear all pending interrupt flags
Finally, remove the activation conditions of the Bootloader and generate a hardware reset to execute the user code, or you can directly use the Go command to execute the user code.
So, how to jump from user code to System Memory? This is not difficult. If you have written IAP or read the reference code in the application notes about IAP, such as the application note AN3965 "STM32F40x/STM32F41x in-application programming using theUSART" and its reference code STSW-STM32067, you should know that the IAP startup code executes the user code by resetting the main stack pointer and jumping to the user code. In the same way, as long as you know the address of System Memory, you can also execute the Bootloader from the user code by resetting the main stack pointer and jumping to System Memory. The System Memory address can be obtained from the reference manual. For example, looking at the reference manual RM0383 of STM32F411, you can find the following table:
From the table above, we can know that the System memory address of STM32F411 starts from 0x1FFF0000.
Then many people will ask, my code is very complicated, using many peripherals and opening many interrupts, but to jump to the Bootloader in System Memory, I need to turn off the clocks of all peripherals, turn off the PLL, turn off all interrupts, disable all interrupts, and clear all pending interrupts. This is a very huge task! So, here, we need a simpler thing to complete this huge task. In fact, there is such a simple method - reset! This can be achieved through software reset. However, after the reset, how do you know that we still remember that we want to upgrade the code? This requires another feature of STM32, that is, the backup data registers will retain their values after the software reset, which gives us the opportunity to make a mark before and after the reset.
In this way, after verification, the customer's needs are feasible. The next thing to do is to sort out the ideas.
2. Software Process
Here we use the 32F411EDISCOVERY board to design a reference routine: design a user program to make LED3 flash; when the user button is pressed, an EXTI interrupt is generated, and the backup data register RTC_BKP0R is selected in the interrupt, and the value 0x32F2 is written, and then a software reset is generated; after the software reset, RTC_BKP0R is judged at the beginning of the running code . If its value is not 0x32F2, the user code is run directly. If its value is 0x32F2, it is necessary to jump to the Bootloader for code upgrade, and RTC_BKP0R is cleared before the jump. In this way, after entering the Bootloader, after the customer performs a USB DFU upgrade, he will not mistakenly enter the Bootloader in the future because of a reset that does not require an upgrade code.
Let's look at the software flow chart. First, look at the flow chart of the main program:
Let's look at the flowchart of the EXTI interrupt:
3. Main code
Use the STM32F4Cube library to develop this example. Let's first look at the main function in main.c :
int main(void)
{
/* STM32F4xx HALlibrary initialization:
- Configure theFlash prefetch, Flash preread and Buffer caches
- Systick timer isconfigured by default as source of time base, but user can eventually implement his proper time base source (a general purpose timer for example or other time source), keeping in mind that Time base durationshould be kept 1ms since PPP_TIMEOUT_VALUEs are defined and handled inmilliseconds basis.
- Low LevelInitialization */
HAL_Init();
/* Configure the Systemclock to have a frequency of 100 MHz */
SystemClock_Config();
/* Configure LED3,LED4, LED5 and LED6 */
BSP_LED_Init(LED3);
BSP_LED_Init(LED4);
BSP_LED_Init(LED5);
BSP_LED_Init(LED6);
/* Configure EXTI Line0(connected to PA0 pin) in interrupt mode */
EXTILine0_Config();
/* Infinite loop */
while (1)
{
}
}
The Main function is very simple. It configures the system clock, initializes the LED used, and then configures the EXTI interrupt of the user button, and then enters the main loop. As mentioned earlier, to realize the user's function program for LED3 flashing, we did not see it in the main loop because SysTick is used in the Cube library, so the flashing of LED3 is placed in the interrupt code of SysTick. Check stm32f4xx_it.c, as follows:
void SysTick_Handler(void)
{
HAL_IncTick();
// LED3 Toggle
led_toggle_counter++;
if (led_toggle_counter>= 500)
{
led_toggle_counter =0;
BSP_LED_Toggle(LED3);
}
}
From the comments at the beginning of the main function, we know that before jumping into the main function, the SystemInit function located in system_stm32f4xx.c has already been called and executed in startup_stm32f411xe.s. The SystemInit function mainly performs functions such as initializing FPU, resetting RCC clock registers, and configuring vector tables. Since we want to enter SystemMemory in the most original state, we will jump to System Memory at the beginning of this function, as follows:
void SystemInit(void)
{
/* Check if need to gointo bootloader before configure clock*/
RtcHandle.Instance =RTC;
if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0 ) ==0x32F2)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSE_DIV2);
__HAL_RCC_RTC_ENABLE();
HAL_RTCEx_BKUPWrite(&RtcHandle, RTC_BKP_DR0 ,0x0);
__HAL_RCC_RTC_DISABLE();
HAL_PWR_DisableBkUpAccess();
__HAL_RCC_PWR_CLK_DISABLE();
__set_MSP(*(__IO uint32_t*) 0x1FFF0000);
SysMemBootJump = (void (*)(void)) (*((uint32_t *) 0x1FFF0004));
SysMemBootJump();
while (1);
}
/* FPU settings------------------------------------------------------------*/
#if (__FPU_PRESENT ==1) && (__FPU_USED == 1)
SCB->CPACR |=((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clockconfiguration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |=(uint32_t)0x00000001;
/* Reset CFGR register*/
RCC->CFGR =0x00000000;
/* Reset HSEON, CSSONand PLLON bits */
RCC->CR &=(uint32_t)0xFEF6FFFF;
/* Reset PLLCFGRregister */
RCC->PLLCFGR =0x24003010;
/* Reset HSEBYP bit */
RCC->CR &=(uint32_t)0xFFFBFFFF;
/* Disable allinterrupts */
RCC->CIR =0x00000000;
/* Configure the VectorTable location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR =SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR =FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
It can be seen that RTC_BKP_DR0 is judged at the beginning of the function. If its value is 0x32F2, the write sequence of the backup domain is started first , as described in 5.1.2 Battery backup domain in RM0383:
Then clear RTC_BKP_DR0 to 0 and then turn off the clock that was turned on for this operation.
The initial value of the main stack pointer MSP is located at the offset 0x00 of the vector table, and the reset value is located at the offset 0x04 of the vector table. For STM32F411, when executing the Bootloader in the System Memeory, the initial value of MSP is located at 0x1FFF0000, and the Reset vector is located at 0x1FFF0004. So in the program, use __set_MSP(*(__IO uint32_t*) 0x1FFF0000); to reset the main stack pointer, and then jump to the program address pointed to by 0x1FFF0004 to execute the Bootloader.
Let's look at the EXTI interrupt program in stm32f4xx_it.c:
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY_BUTTON_PIN);
}
And its Callback function in main.c:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin ==KEY_BUTTON_PIN)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_HSE_DIV2);
__HAL_RCC_RTC_ENABLE();
RtcHandle.Instance =RTC;
HAL_RTCEx_BKUPWrite(&RtcHandle, RTC_BKP_DR0 ,0x32F2);
if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0) != 0x32F2)
{
// Write backupdata memory failed
BSP_LED_On(LED5);
while (1) ; // Error
}
else
{
// Write backupdata memory succeeded.
BSP_LED_On(LED6);
HAL_NVIC_SystemReset (); // Software reset for going into bootloader
while (1) ;
}
}
}
When it is determined that the user button is pressed and the user code needs to be upgraded, the access sequence of the backup domain is started first, and the value of RTC_BKP_DR0 is written to 0x32F2. Then it is read back to determine whether the write is successful, which is convenient for debugging. If the write is successful, HAL_NVIC_SystemReset() is called to reset the software. After resetting, you can enter the System Memory.
4. Experiment
Use 32F411EDISCOVERY to do the experiment.
1) First compile the program and download it to the 32F411EDISCOVERY board. You can see that LED3 is flashing.
2) Press the User button, LED3 turns off, and the system enters the Bootloader in System Memory.
3) Open the DfuSeDemo software. There is nothing displayed in AvailableDFU Devices.
4) Plug a USB Micro cable into CN5 of the 32F411EDISCOVERY board. You can see that LED7 lights up and the USB is connected.
5) After the driver is completed, you can check DfuSeDemo again, and AvailableDFU Devices is displayed.
If it is "STM Device in DFU Mode", it means it has been successfully driven and is working normally.
6) After that, it is the normal process of upgrading the code. Click the "Choose" button to select the code to be updated. Here is a demo program of a 32F411EDISCOVERY board. The file 32f411ediscovery.dfu generated by the Dfu file manager software is ready to be imported!
7) Click the " Upgrade" button to upgrade, and select Yes in the pop-up dialog box. The upgrade will be successful.
8) Click "Leave DFU mode" again, the progress bar will show "Successfully left DFU mode!", and you can enter the updated user code. You can see the 4 LED lights scrolling and flashing normally...
Notice
This example is only used to verify its feasibility. In actual application, if there are any imperfections, please improve them by yourself. In addition, there are several points that need attention:
1) This Demo code is written based on STM32Cube_FW_F4_V1.11.0. After decompression, you can put it into \STM32Cube_FW_F4_V1.11.0\Projects\STM32F411E-Discovery\Templates to replace the original source code file, and then you can compile and run it.
2) This program uses key press as a condition to trigger the software code upgrade. Users can modify the trigger conditions according to their own situation, such as pressing multiple keys at the same time, etc.
3) When RTC is used in the user application, the RTC clock source cannot be modified once it is selected unless the backup domain is reset. The description of RCC_BDCR in RM0383 mentions:
4) For how to use Dfu file manager to generate .dfu file, please refer to UM0412 "Getting started withDfuSe USB device firmware upgrade" or practical experience "Using USB DFU to implement IAP function".
5) For the USB DFU protocol used in the Bootloader, please refer to AN3156 “USB DFU protocol used in the STM32 bootloader”.
==================================
1. ST released the PC-based ST MCU selection tool
2. Two topics about USB DFU IAP routine porting
3. Analysis of the problem of no response to USART interrupt reception
4. Introduction to STM32 development ecosystem
5. A video database that STM32 fans must know