px-lib  0.8.3 dev
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers
USB UF2 Bootloader

1. Introduction

This bootloader implements the UF2 bootloader communication layer over USB MSC (Mass Storage Class). It emulates (fakes) a USB removable flash drive with a FAT16 file system to provide info when reading files and to write a new application to FLASH if the file is properly formatted using the UF2 tool.

Read these pages for an excellent introduction:

A single press of the RESET button will perform a normal reset and the bootloader will jump to the application after a 1/2 second timeout. A "double-tap" of the RESET button (press twice within 0.5 s) will start the UF2 bootloader: the micro will enumerate over USB and appear as a removable drive with the volume label "HERO-BOOT". The LED will blink continously to indicate that it is in UF2 boot mode.

The bootloader will ignore a UF2 file with the wrong Family ID. See PX_UF2_CFG_FAMILY_ID

2. How to upload a new application

2.1 Connect USB1 to PC

Apologies for stating the obvious, but the board must be connected via the USB1 connector to your PC ;)

2.2 Activate UF2 bootloader

uf2_boot_activate.png
  1. Give the RESET button a "double-tap" (press twice within 0.5 s). The board should appear as a removable drive with "HERO-BOOT" as the volume label.
  2. "CURRENT.UF2" can be copied to get the current FLASH content of the microcontroller (in UF2 format).
  3. "INDEX.HTM" can be opened in a browser. It will redirect the browser to the board's online documentation.
  4. "INFO_UF2.TXT" is a text file that can be opened. It contains info for development environments to detect which board is connected.

The LED will blink continuously to indicate that it is in UF2 boot mode.

2.3 Copy UF2 file to drive

uf2_copy_file.png
  1. Locate the UF2 application file, for example:

    px-lib/boards/arm/stm32/piconomix_hero_board/examples/gpio/BUILD_RELEASE_BOOT/gpio.uf2

  2. With the left mouse button, click on the file and (keeping the button pressed) drag the file to the drive.
  3. Drop the file on the drive by releasing the left mouse button.

That's it! If the file is very big, the LED will dim while the file is being copied. It's actually flashing very fast as each block of FLASH is written.

After the file is written to the micro's FLASH, the application will start automatically.

3. Building an application for upload

The UF2 bootloader occupies the first 16k of FLASH. The application that would normally start at the first address in FLASH must be moved to a higher address to accomodate the bootloader which is executed first.

The application Makefile and linker script have been set up to make the creation of a UF2 file for upload super simple.

For a gentle introduction to Make, see 7.2 How to understand and modify Makefiles

Execute 'make build=release-boot':

1 boards\arm\stm32\piconomix_hero_board\examples\gpio>make build=release-boot
2 
3 -------- begin (RELEASE FOR BOOT) --------
4 arm-none-eabi-gcc.exe (GNU Tools for Arm Embedded Processors 7-2017-q4-major) 7.2.1 20170904 (release) [ARM/embedded-7-branch revision 255204]
5 Copyright (C) 2017 Free Software Foundation, Inc.
6 This is free software; see the source for copying conditions. There is NO
7 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 
9 
10 Compiling C: src/main.c
11 arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m0plus -gdwarf-2 -funsigned-char -funsigned-bitfields -fshort-enums -Wall -Wstrict-prototypes -std=gnu99 -ffunction-sections -fdata-sections --specs=nano.specs -Os -DSTM32L072xx -DUSE_FULL_LL_DRIVER -DVECT_TAB_OFFSET=0x4000 -Wa,-adhlns=BUILD_RELEASE_BOOT/main.lst -I. -Icfg -Ires -Isrc -I../../../../../../boards/arm/stm32/piconomix_hero_board -I../../../../../.. -I../../../../../../arch/arm/stm32 -I../../../../../../common -I../../../../../../comms -I../../../../../../data -I../../../../../../devices/comms -I../../../../../../devices/display -I../../../../../../devices/general -I../../../../../../devices/mem -I../../../../../../devices/rtc -I../../../../../../devices/sensor -I../../../../../../gfx -I../../../../../../gfx/fonts -I../../../../../../gfx/images -I../../../../../../utils -I../../../../../../libs/ChaN_FatFs -I../../../../../../libs/STM32Cube/L0/Drivers/CMSIS/Device/ST/STM32L0xx/Include -I../../../../../../libs/STM32Cube/L0/Drivers/CMSIS/Include -I../../../../../../libs/STM32Cube/L0/Drivers/STM32L0xx_HAL_Driver/Inc -I../../../../../../libs/STM32Cube/L0/Drivers/STM32L0xx_HAL_Driver/Legacy -I../../../../../../libs/STM32Cube_USB/Class/CDC/Inc -I../../../../../../libs/STM32Cube_USB/Core/Inc -MMD -MP -MF BUILD_RELEASE_BOOT/main.d src/main.c -o BUILD_RELEASE_BOOT/main.o
12 
13 ...
14 
15 Creating BIN load file for Flash: BUILD_RELEASE_BOOT/gpio.bin
16 arm-none-eabi-objcopy -O binary BUILD_RELEASE_BOOT/gpio.elf BUILD_RELEASE_BOOT/gpio.bin
17 
18 Creating UF2 load file for Flash: BUILD_RELEASE_BOOT/gpio.uf2
19 python ../../../../../../tools/uf2conv.py BUILD_RELEASE_BOOT/gpio.bin -c -b 0x4000 -f 0xe892273c -o BUILD_RELEASE_BOOT/gpio.uf2
20 Converting to uf2, output size: 3072, start address: 0x4000
21 Wrote 3072 bytes to BUILD_RELEASE_BOOT/gpio.uf2.
22 
23 ...
24 
25 Size:
26 arm-none-eabi-size BUILD_RELEASE_BOOT/gpio.elf
27  text data bss dec hex filename
28  1432 12 2076 3520 dc0 BUILD_RELEASE_BOOT/gpio.elf
29 -------- end (RELEASE FOR BOOT) ---------
  • Line 3 displays the start banner text "RELEASE FOR BOOT" to indicate that it is building a RELEASE version (no debug info) suitable for upload via the bootloader.
  • Line 16 extracts a raw binary application file from the built ELF file
  • Line 19 uses the Python UF2 utility to create a UF2 file from the BIN file
  • The *.uf2 file will be located in the BUILD_RELEASE_BOOT sub directory.

Observe on line 19 that the FLASH offset is specified with -b 0x4000 and the UF2 Family ID is specified with -f 0xe892273c.

4. Technical details

The UF2 bootloader is ~13.5k in size and occupies the first 16k (0x00000000 to 0x00003FFF). The start address of the application is set in the bootloader's main.c (0x4000 = 16384 = 16k):

38 /// Start address of app in FLASH
39 #define MAIN_APP_ADR_START (FLASH_BASE + 0x00004000)
40 /// End address of app in FLASH (out of bounds)
41 #define MAIN_APP_ADR_END (FLASH_BASE + 0x00020000)

The application would normally start at the first address in FLASH, but it must be moved to a higher address to accomodate the bootloader which is executed first. The start address is set in the application's Makefile with the BOOTLOADER_SIZE symbol:

# (1b) Offset from start of FLASH to reserve space for bootloader (release for bootloader build)
BOOTLOADER_SIZE = 0x4000

The template px-lib/arch/arm/stm32/MakeRules.mk uses this symbol to set the Vector Table Offset Register to the correct value in system_stm32l0xx.c and also to specify the offset for the linker script:

# Allocate space for bootloader in release for boot build (if specified)
#
# VTOR register (Vector Table Offset Register) must be set to correct value in:
# libs/STM32Cube/L0/Drivers/CMSIS/Device/ST/STM32L0xx/Source/Templates/system_stm32l0xx.c
#
# FLASH_OFFSET must be specified in linker script.
ifeq ($(BUILD), release_boot)    
    ifndef BOOTLOADER_SIZE
        $(error "BOOTLOADER_SIZE not defined")
    endif    
    CDEFS   += VECT_TAB_OFFSET=$(BOOTLOADER_SIZE)
    LDFLAGS += -Wl,--defsym,FLASH_OFFSET=$(BOOTLOADER_SIZE)
endif

The startup file system_stm32l0xx.c is located here:

px-lib/libs/STM32Cube/L0/Drivers/CMSIS/Device/ST/STM32L0xx/Source/Templates/system_stm32l0xx.c

It has been modified to allow VECT_TAB_OFFSET to be overriden externally:

// piconomix [mod start] - Allow VECT_TAB_OFFSET to be overridable with compiler switches
#ifndef VECT_TAB_OFFSET
#define VECT_TAB_OFFSET  0x00U /*!< Vector Table base offset field.
                                   This value must be a multiple of 0x100. */
#endif
// piconomix [mod end]

The linker script is located here:

px-lib/boards/arm/stm32/piconomix_hero_board/stm32l072xb.ld

It allows the start address to be changed with the FLASH_OFFSET symbol:

/*
 *  Describe the location and size of blocks of memory in the target
 */
FLASH_OFFSET = DEFINED(FLASH_OFFSET)? FLASH_OFFSET : 0;
FLASH_ORIGIN = 0x08000000 + FLASH_OFFSET;
FLASH_LENGTH = 128k - FLASH_OFFSET;

The generated LSS file is located here:

px-lib/boards/arm/stm32/piconomix_hero_board/examples/gpio/BUILD_RELEASE_BOOT/gpio.lss

It can be inspected to verify that the application has been moved to the right address:

BUILD_RELEASE_BOOT/gpio.elf:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .isr_vector   000000c0  08004000  08004000  00004000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .init         0000000c  080040c0  080040c0  000040c0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .text         0000049c  080040cc  080040cc  000040cc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

".isr_vector" is the first section and contains the vector table. The VMA / LMA (Virtual / Load Memory Address) are both set to 0x08004000.

The bootloader will perform a number of sanity checks on the UF2 file programmed in FLASH, before it will jump to the start address of the application:

83 static void main_exe_app(void)
84 {
85  uint32_t sp_adr; // app's stack pointer value
86  uint32_t pc_adr; // app's reset address value
87 
88  // Does vector table at start of app have valid stack pointer value?
89  sp_adr = *(uint32_t *)MAIN_APP_ADR_START;
90  if( (sp_adr < MAIN_SRAM_ADR_START) || (sp_adr > MAIN_SRAM_ADR_END) )
91  {
92  // No. Return to bootloader
93  return;
94  }
95  // Does vector table at start of app have valid reset address value?
96  pc_adr = *(uint32_t *)(MAIN_APP_ADR_START + 4);
97  if( (pc_adr < MAIN_APP_ADR_START) || (pc_adr >= MAIN_APP_ADR_END) )
98  {
99  // No. Return to bootloader
100  return;
101  }
102  // Reset SRAM command so that bootloader will be executed again after reset
103  main_magic = 0;
104  // Rebase the stack pointer
105  __set_MSP(sp_adr);
106  // Rebase vector table to start of app (even though app may do it too)
107  SCB->VTOR = MAIN_APP_ADR_START;
108  // Jump to application reset address
109  __asm__ __volatile__("bx %0\n\t"::"r"(pc_adr));
110 }