Custom DFU Bootloader

From Stm32World Wiki
Jump to navigation Jump to search

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):

STM32F411 Flash Sector Layout.png

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:

Bootloader memory regions.png

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:

Application memory regions.png

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 */

Miscellaneous Links