Custom DFU Bootloader
All STM32 MCUs have a built-in bootloader which will allow reflashing. The Boot0 pin is used to toggle between "normal" boot and this bootloader. Unfortunately this process can not be controlled without manipulating the Boot0 pin, so if we want to control this from our application, we will have to implement our own bootloader.
Video
Overall Architecture
All STM32 MCUs (indeed all ARM processors) follows the Von Neumann architecture. This essentially - in this context - means that the instruction and data memory share the same address space. Since STM32 processors are 32-bit processors and since 32-bit addresses can address 4GB (4,294,967,296 bytes), sharing the address space between instruction and data memory is not a problem.
On a STM32F411 the flash layout is as follows (from Reference Manual):
On STM32s, programs are stored in on-chip flash memory. Different STM32s have different amount of flash memory, but common for all is that it is mapped in the address space starting from 0x08000000.
Start Addr (incl.) | End Addr (not incl.) | Size (hex) | Size (kB) | ||
---|---|---|---|---|---|
End | --> | 0x8060000 | 0x807FFFF | 0x20000 | 128 kB |
0x8040000 | 0x804FFFF | 0x20000 | 128 kB | ||
0x8020000 | 0x803FFFF | 0x20000 | 128 kB | ||
0x8010000 | 0x801FFFF | 0x10000 | 64 kB | ||
0x800C000 | 0x800FFFF | 0x4000 | 16 kB | ||
0x8080000 | 0x800BFFF | 0x4000 | 16 kB | ||
0x8004000 | 0x8007FFF | 0x4000 | 16 kB | ||
Begin | --> | 0x8000000 | 0x8003FFF | 0x4000 | 16 kB |
Under normal reset (Boot0 not activated), the MCU will jump to address 0x8000000 and begin execution from there.
Boot flag
In order for this to actually work, we will have to be able to store a flag between MCU starts. Unfortunately (actually it is not that unfortunate and indeed quite deliberate) the region of RAM used for static variables are all reset to 0 at startup. The RAM in the MCU is however not physically cleared by the MCU, so all the RAM outside this particular region - which is usually used for the heap and the stack, can in fact be used.
It is of course very very naughty to mess with the stack outside it's normal usage, however, since we will restart the MCU immediately we can do this relatively safe.
First we define the "value" of our bootflag:
#define DFU_BOOT_FLAG 0xDEADBEEF
The actual boot flag can be declared like this:
extern int _estacj; uint32_t *dfu_boot_flag;
And initialized at the beginning of main like this:
/* USER CODE BEGIN 1 */ // We handle all this before the HAL Libraries have been initialized. dfu_boot_flag = (uint32_t*) (&_estack - 100); // set in linker script /* USER CODE END 1 */
This will have to be done in both the bootloader and the application.
Linker Script
The memory regions are defined in the linker script, which in turn is generated by STM32CubeMX on project creation (but never updated after this). For the STM32F411 that will look like this:
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K }
In order to prevent the bootloader and the application to overlap, the Linker Script will need to be modified for both the bootloader and the application. If we want to use the first 64kB of flash for the bootloader, and the remaining for the application like this:
Start Addr (incl.) | End Addr (not incl.) | Size (hex) | Size (kB) | Usage | ||
---|---|---|---|---|---|---|
End | --> | 0x8060000 | 0x807FFFF | 0x20000 | 128 kB | Application (448 kB) |
0x8040000 | 0x804FFFF | 0x20000 | 128 kB | |||
0x8020000 | 0x803FFFF | 0x20000 | 128 kB | |||
0x8010000 | 0x801FFFF | 0x10000 | 64 kB | |||
0x800C000 | 0x800FFFF | 0x4000 | 16 kB | Bootloader (64 kB) | ||
0x8080000 | 0x800BFFF | 0x4000 | 16 kB | |||
0x8004000 | 0x8007FFF | 0x4000 | 16 kB | |||
Begin | --> | 0x8000000 | 0x8003FFF | 0x4000 | 16 kB |
The linker scripts will be like the following.
Bootloader
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K }
Application
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K FLASH (rx) : ORIGIN = 0x8010000, LENGTH = 448K }
Bootloader
The bootloader will reside in the beginning of the flash and will always be called first.
It will use the bootflag to determine if it should execute the bootloader code or simply jump to the main application. We can do this test in the beginning of the main function before clocks, interrupts and peripherals are initialised (at this point only the stack has been initialised), like this:
/* USER CODE BEGIN 1 */ // We handle all this before the HAL Libraries have been initialized. dfu_boot_flag = (uint32_t*) (&_estack - 100); // set in linker script if (*dfu_boot_flag != DFU_BOOT_FLAG) { /* Test if user code is programmed starting from address 0x08008000 */ if (((*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD) & 0x2FFC0000) == 0x20000000) { /* Jump to user application */ JumpAddress = *(__IO uint32_t*) (USBD_DFU_APP_DEFAULT_ADD + 4); JumpToApplication = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) USBD_DFU_APP_DEFAULT_ADD); JumpToApplication(); } } *dfu_boot_flag = 0; // So next boot won't be affected /* USER CODE END 1 */
If the test fail (boot flag != 0xDEADBEEF) the bootloader will continue to execute and the actual application is ignored.
The resulting memory layout of the bootloader will be:
Application
Since the bootloader is occupying the first 64 kB of the flash, the application will need to be relocated to a higher memory address. There are two steps involved in this.
First the linker script will need to change:
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K FLASH (rx) : ORIGIN = 0x8010000, LENGTH = 448K }
It is also necessary to make a small change to 'system_stm32f4xx.c':
/*!< Uncomment the following line if you need to relocate the vector table anywhere in Flash or Sram, else the vector table is kept at the automatic remap of boot address selected */ #define USER_VECT_TAB_ADDRESS #define FLASH_BASE 0x8010000
And that is it - building the application will now show the following memory regions:
DFU Functionality
File usbd_dfu_if.h
/* USER CODE BEGIN EXPORTED_DEFINES */ #define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */ #define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */ #define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */ #define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */ #define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */ #define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */ #define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */ #define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */ /* USER CODE END EXPORTED_DEFINES */