px-lib  0.9.3
Cross-platform embedded library and documentation for 8/16/32-bit microcontrollers
XMODEM-CRC UART Bootloader

1. Introduction

This is an example of how to implement a UART bootloader on the STM32. A USB-Serial bridge (USB2) is connected to UART1 of the STM32 on the Deluxe Edition of the Hero Board. On the Lite Edition a vertical header (H17) is populated and a separate USB-Serial bridge with +3V3 levels can be used to interface with UART1.

A single press of the RESET button will perform a normal reset and the bootloader will jump to the application. A "double-tap" of the RESET button (press twice within ~ 500 ms) will start an XMODEM reception of a new binary application file.

2. Set up ANSI/VT100 terminal emulator

An ANSI/VT100 terminal emulator that supports XMODEM-CRC, for example Tera Term can be used to send the new BIN file. Tera Term can auto detect which variant of XMODEM protocol to use (checksum, CRC or 1k), but it can be preset in the TERATERM.INI file:

Setup > Setup directory...
tera_term_setup01.png

Search for "xmodem" and modify the "XmodemOpt" setting to "crc":

; XMODEM option (checksum/crc/1k)
XmodemOpt=crc
; Binary flag for XMODEM Receive and ZMODEM Send (on/off)
XmodemBin=on

While you are modifying TERATERM.INI, you can also change the default BAUD from 9600 to 115200:

;     Baud rate
BaudRate=115200

After modifying and saving TERATERM.INI, close and reopen Tera Term for the new configuration to take effect.

Tera Term must now be set up to use the correct communication settings:

Setup > Serial port...
tera_term_setup02.png

3. Initialize XMODEM transfer on ANSI/VT100 terminal emulator

The XMODEM transfer must be initialised on Tera Term first:

File > Transfer > XMODEM > Send...
tera_term_xmodem_send01.png

Select the BIN file to use for the transfer:

tera_term_xmodem_send02.png

Tera Term will now wait for the XMODEM transfer to start:

tera_term_xmodem_send03.png

4. Start XMODEM reception on bootloader

"Double-tap" the RESET button. The bootloader will send the character 'C' to start reception of the new BIN file.

It will send a 'C' 4 times (1 second apart) and if a valid XMODEM data packet is not received, it will time out and jump to the application. This avoids getting stuck in the bootloader if the RESET button is "double-tapped" accidentally.

tera_term_xmodem_transfer.gif
XMODEM transfer of new file

5. Building an application for upload to bootloader

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 application's Makefile and linker script has been set up to create a BIN file for upload to the bootloader when the 'make build=release-boot' command is executed:

px-lib\boards\arm\stm32\px_hero\examples\gpio>make build=release-boot

-------- begin (RELEASE FOR BOOT) --------
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]
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Compiling C: src/main.c
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/px_hero -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

...    

Creating BIN load file for Flash: BUILD_RELEASE_BOOT/gpio.bin
arm-none-eabi-objcopy -O binary BUILD_RELEASE_BOOT/gpio.elf BUILD_RELEASE_BOOT/gpio.bin

...

Size:
arm-none-eabi-size BUILD_RELEASE_BOOT/gpio.elf
   text    data     bss     dec     hex filename
   1376      12    2076    3464     d88 BUILD_RELEASE_BOOT/gpio.elf
--------  end (RELEASE FOR BOOT) ---------

Inspect the output of the build process. The start and end banner will include the text "RELEASE FOR BOOT"

6. Technical details

The bootloader is only ~2.5k in size, but 16k is reserved to leave enough space for more complex bootloaders, for example USB UF2 Bootloader. The start address of the application is set in the bootloader's main.c (0x4000 = 16384 = 16k):

35 /// Start address of app in FLASH
36 #define MAIN_APP_ADR_START (FLASH_BASE + 0x00004000)
37 /// End address of app in FLASH (out of bounds)
38 #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 start up file system_stm32l0xx.c has also 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]

Location:

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

The linker script px-lib/boards/arm/stm32/px_hero/stm32l072xb.ld 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, for example px-lib/boards/arm/stm32/px_hero/examples/gpio/BUILD_RELEASE_BOOT/gpio.lss, 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 BIN file programmed in Flash, before it will jump to the start address of the application:

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