1

I have a very small bootloader sitting in front of the main firmware running on a custom-designed board based around the STM32F405VGT chip. It has a fairly minimally modified startup.s and linker files for both applications. The primary application runs fine when loaded into the root of the FLASH memory, but does not launch from the bootloader.

When stepping through the code, as soon as it tries to launch the app, the program ends up in the WWDG_IRQHandler, which is aliased to the Default_Handler and just sits and spins in the infinite loop (WWDG is disabled for the bootloader).

Bootloader Code:

    uint32_t addr = 0x08010000;

    /* Get the application stack pointer (First entry in the application vector table) */
    uint32_t appStack = (uint32_t) *((__IO uint32_t*) addr);

    /* Get the application entry point (Second entry in the application vector table) */
    ApplicationEntryPoint entryPoint = (ApplicationEntryPoint)*((__IO uint32_t*)(addr + sizeof(uint32_t)));

    /* would expect the value of entryPoint to be 0x802bc9c based on the values in the .map file as well as the actual values downloaded from the image using openocd.  Instead, it comes back as 0x802bc9d, not sure if this is related to THUMB code */

    /* Reconfigure vector table offset register to match the application location */
    SCB->VTOR = addr;

    /* Set the application stack pointer */
    __set_MSP(appStack);

    /* Start the application */
    entryPoint();

Here is the .ld file for the application:

/* Include memory map */
/* Uncomment this section to use the real memory map */
MEMORY
{
    BOOTLOADER   (rx)    : ORIGIN = 0x08000000, LENGTH =  32K
    USER_PROPS   (rw)    : ORIGIN = 0x08008000, LENGTH =  16K
    SYS_PROPS    (r)     : ORIGIN = 0x0800C000, LENGTH =  16K
    APP_CODE     (rx)    : ORIGIN = 0x08010000, LENGTH = 448K
    SWAP         (rx)    : ORIGIN = 0x08070000, LENGTH = 384K

    RAM          (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
    BOOT_RAM     (xrw)   : ORIGIN = 0x2001E000, LENGTH =   8K
    CCMRAM       (rw)    : ORIGIN = 0x10000000, LENGTH =  64K
}

/* Uncomment this section to load directly into root memory */
/*
MEMORY
{
  APP_CODE (rx)      : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 128K
  MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
  CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 64K
}
*/

/* Highest address of the user mode stack */
_estack = 0x20020000;    /* end of 128K RAM */

/* Entry Point */
ENTRY(Reset_Handler)



/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size  = 0x000;      /* required amount of heap (none)  */
_Min_Stack_Size = 0x400;      /* required amount of stack */

SECTIONS
{


  /* The startup code goes first into EEPROM */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >APP_CODE


  /* The program code and other data goes into EEPROM */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >APP_CODE

  /* Constant data goes into EEPROM */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >APP_CODE

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >APP_CODE
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >APP_CODE
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >APP_CODE


  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> APP_CODE

  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}

The bootloader .ld is identical, except all references to APP_CODE are replaced with BOOTLOADER

Here is the startup.s file for the bootloader. The startup.s file for the application is identical, except Boot_Reset_Handler is called Reset_Handler instead :

.syntax unified
  .cpu cortex-m4
  .fpu softvfp
  .thumb

.global  g_pfnVectors
.global  Default_Handler

/* start address for the initialization values of the .data section. 
defined in linker script */
.word  _sidata
/* start address for the .data section. defined in linker script */  
.word  _sdata
/* end address for the .data section. defined in linker script */
.word  _edata
/* start address for the .bss section. defined in linker script */
.word  _sbss
/* end address for the .bss section. defined in linker script */
.word  _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */

/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called. 
 * @param  None
 * @retval : None
*/

    .section  .text.Boot_Reset_Handler
  .weak  Boot_Reset_Handler
  .type  Boot_Reset_Handler, %function
Boot_Reset_Handler:  
  ldr   sp, =_estack     /* set stack pointer */

/* Copy the data segment initializers from flash to SRAM */  
  movs  r1, #0
  b  LoopCopyDataInit

CopyDataInit:
  ldr  r3, =_sidata
  ldr  r3, [r3, r1]
  str  r3, [r0, r1]
  adds  r1, r1, #4

LoopCopyDataInit:
  ldr  r0, =_sdata
  ldr  r3, =_edata
  adds  r2, r0, r1
  cmp  r2, r3
  bcc  CopyDataInit
  ldr  r2, =_sbss
  b  LoopFillZerobss
/* Zero fill the bss segment. */  
FillZerobss:
  movs  r3, #0
  str  r3, [r2], #4

LoopFillZerobss:
  ldr  r3, = _ebss
  cmp  r2, r3
  bcc  FillZerobss

/* Call the clock system intitialization function.*/
  bl  SystemInit   
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl  main
  bx  lr    
.size  Boot_Reset_Handler, .-Boot_Reset_Handler

/**
 * @brief  This is the code that gets called when the processor receives an 
 *         unexpected interrupt.  This simply enters an infinite loop, preserving
 *         the system state for examination by a debugger.
 * @param  None     
 * @retval None       
*/
    .section  .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
  b  Infinite_Loop
  .size  Default_Handler, .-Default_Handler

   .section  .isr_vector,"a",%progbits
  .type  g_pfnVectors, %object
  .size  g_pfnVectors, .-g_pfnVectors



g_pfnVectors:
  .word  _estack
  .word  Boot_Reset_Handler

  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler
  .word  0
  .word  0
  .word  0
  .word  0
  .word  SVC_Handler
  .word  DebugMon_Handler
  .word  0
  .word  PendSV_Handler
  .word  SysTick_Handler
  ...

I want to point out that this is not a duplicate of Bootloader for Cortex M4 - Jump to loaded Application although the problem seems similar, the author of that post did not adequately explain how the problem was resolved.

Everything is built using standard gcc tools for embedded development.

drz
  • 962
  • 7
  • 22
  • what does the disassembly around the actual jump/call look like? – old_timer Jun 01 '17 at 19:45
  • and the disassembly of the vector table for the application. – old_timer Jun 01 '17 at 19:46
  • 2
    Are you linking with CMSIS and/or a file named similar to `system_stm32f4xx.c`? That file may have a function called `SystemInit()` which sets `SCB->VTOR` to a different value from what you expect. You may need to edit that file and set the value of `VECT_TAB_OFFSET` for your expectations. – kkrambo Jun 01 '17 at 20:11
  • @kkrambo you were more-or-less correct. There is a similar function, but not in the same file, that does reset the vector table. Once I removed that call, it works as expected. Thank you. – drz Jun 01 '17 at 20:30
  • And what is that function?! – cricardol Jul 26 '18 at 14:49

1 Answers1

0

I have used the following approach on various STM32 Cortex-M3 and M4 parts:

Given the following in-line assembly function:

__asm void boot_jump( uint32_t address )
{
   LDR SP, [R0]       ;Load new stack pointer address
   LDR PC, [R0, #4]   ;Load new program counter address
}

The bootloader switches to the application image thus:

// Switch off core clock before switching vector table
SysTick->CTRL = 0 ;

// Switch off any other enabled interrupts too
...

// Switch vector table
SCB->VTOR = APPLICATION_START_ADDR ;

//Jump to start address
boot_jump( APPLICATION_START_ADDR ) ;

Where APPLICATION_START_ADDR is the base address of the application area (addr in your example); this address is the start of the application's vector table, which starts with the initial stack pointer and reset vector, the boot_jump() function loads these into the SP and PC registers to start the application as if it had been started at reset. The application's reset vector contains the application's execution start address.

The obvious difference between this and your solution is the disabling of any interrupt generators before switching the vector table. You may of course not be using any interrupts in the bootloader.

Clifford
  • 88,407
  • 13
  • 85
  • 165