Skip to main content
Visitor II
September 7, 2018
Solved

Procedures for developing an STM32 bootloader + app

  • September 7, 2018
  • 14 replies
  • 6016 views

Hello there,

I am trying to develop an STM32L4 proof of concept bootloader application. I am basing on this source: https://github.com/akospasztor/stm32-bootloader and Atollic's "Working with bootloaders on Cortex-M devices".

I am trying to do a simple thing: My bootloader should start and jump to a fixed address in flash, under which the regular application lies. The procedure I have done is as follows:

I have programmed the bootloader app to the flash memory (using ST-LINK), ld file contains following flash description:

FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 220K

The total flash memory is 512K.

Then I flashed the application code to the memory (with ST-LINK). Its ld file flash parameter looks as follows:

FLASH (rx) : ORIGIN = 0x8037000, LENGTH = 292K

220 * 1024 = 0x37000, so my starting address for the app is 0x08037000.

220K + 292K = 512K, so the memory sizes look fine.

My jump function looks as follows:

typedef void (*pFunction)(void);
 
/**
 * @brief	Jumps the program counter to the memory location set by \p address.
 * 			Before the jump is performed vector tables are set.
 */
void boot_jump2Address(const uint32_t address)
{
	uint32_t appStack;
	pFunction appEntry;
 
	// get the application stack pointer (1st entry in the app vector table)
	appStack = (uint32_t)*((__IO uint32_t*)address);
 
	// Get the app entry point (2nd entry in the app vector table
	appEntry = (pFunction)*(__IO uint32_t*)(address + 4);
 
	boot_basicClockConfig();
	HAL_RCC_DeInit();
	HAL_DeInit();
 
	SysTick->CTRL = 0;
	SysTick->LOAD = 0;
	SysTick->VAL = 0;
 
	// Reconfigure vector table offset to match the app location
#if (SET_VECTOR_TABLE)
	SCB->VTOR = address;
#endif
 
	__set_MSP(appStack); // Set app stack pointer
	appEntry(); // Start the app
 
	while (1); // never reached
}

I then start to debug the application (not bootloader code). The bootloader starts and it jump to the app code, then my debugger catches the PC and I can debug my application. The problem is that the application crashes. When debugging it, one can see that the line at which it crashes is in the HAL_RCC_OscConfig function generated by CubeMx:

assert_param(IS_RCC_PLLR_VALUE(RCC_OscInitStruct->PLL.PLLR));

This assert is not passed, because the PLLR is visible as 1, instead of 2. 2 is the value set in the code. For some reason its changing itself. But only if I run directly to the breakpoint set at this line- if I go trhough the code line by line until this point, I can see in the debugger view that its 2 correctly. That doesnt matter, because the code crashes later on on similar stages.

My question is- Is there any additional setting I need to modify for my base application in order to place it under different flash address than 0x08000000? I have only changed the linker script (FLASH parameter). But is there anything else?

I would really appreciate all help, as I am out of ideas on how to fix this.

    This topic has been closed for replies.
    Best answer by Tesla DeLorean

    >>Other general gotchs are a) don't disable interrupts on the processor, and b) don't call from interrupt context, this includes HAL call backs.

    This would be using __disable_irq() instead of actually addressing the issue of turning off interrupts at the source. The App likely uses a different memory map, and the linker has given uwTicks/usTickFreq entirely different addresses.

    The "easy" way is to reset the processor and branch directly into the app via an expedited path. The other is to clean up the mess so you don't have any interrupts firing into handlers which have had none of their variables/state defined.

    The test here would be to create a vector table where all entries point to HardFault_Handler, point SCB->VTOR to this for a couple of seconds, and see if the thing outputs fault data. The key here being to have HardFault_Handler to output diagnostic information, and not just a while(1) loop. If you can't determine the source of the interrupt use bisection of the table until it is apparent.

    The b) point above relates to unlooping all context pushes on the stack, where LR points to user space code and doesn't contain magic values swapping what register map the processor is using, and preemption level the processor is running at.

    Look for issues with SysTick, TIM and Watch Dogs.

    You could certainly have an initialization/command-line structure you hand between loader/app, this is especially true where you've created contractual obligations between them. ie clocks and PLL's up, frequencies, Debug USART, etc.

    14 replies

    Visitor II
    September 7, 2018

    Hello Lukasz,

    Try to cancel every initialization you do (or HAL library do) in boot firmware.

    I see you do that but the HAL library can forget some parameters in the DeInit function.

    Compare all registers before Init and after DeInit.

    Also, verify that every interrupts enable are disable because I don't see that in your code.

    Another try is to modify your application software to accept a already clock initialized.

    Best regards.

    Graduate II
    September 7, 2018

    You need to manage SCB->VTOR in the SystemInit() code of the application. I usually fix the #define nonsense ST uses and let the linker fix up the address like it was designed to do.

    extern void *__Vectors; // Keil forks use this symbol, check your startup.s

    void SystemInit (void)

    {   

     /* FPU settings ------------------------------------------------------------*/

     #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)

       SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */

     #endif

     /* Configure the Vector Table ----------------------------------------------*/

       SCB->VTOR = (uint32_t)&__Vectors;

    ...

    }

    You can also decide not to repeat the SystemClock stuff if you've brought this up properly in the loader.

    Visitor II
    September 7, 2018

    Hello guys, thank you for answer,

    From my point of view there are couple places where the address could be changed.

    1. In linker script (I did that already),
    2. In system Init, one can find this:
     /* Configure the Vector Table location add offset address ------------------*/
    #ifdef VECT_TAB_SRAM
     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    #else
     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    #endif

    After one follows The FLASH_BASE define:

    #define FLASH_BASE (0x08000000UL) /*!< FLASH(up to 512 KB) base address */

    Should this define be altered as well? Because I tried to change this define to 0x08037000UL and it help solving my problem. I could try omitting another system clock config in the main program but would this cause the crash?

    Visitor II
    July 28, 2021

    I know this is an old thread, but I just asked a question this may partly help. Is the code above to relocate the ISR vector table depending if the bootloader or main app is running?

    Graduate II
    September 7, 2018

    I'm saying the code you're quoting is dangerous and easily overlooked.

    You shouldn't need to change things in multiple places, it should be sufficient to change the linker script, and everything else works off that address.

    Yes, they expect you to change FLASH_BASE and/or VECT_TAB_OFFSET, I think this could be discarded and a single source file could be used in both loader and app context, reducing the chance of error.

    At the end of the day for interrupts to function in your application, SCB->VTOR == 0x08037000

    Other general gotchs are a) don't disable interrupts on the processor, and b) don't call from interrupt context, this includes HAL call backs.

    Visitor II
    September 7, 2018

    I agree with you on the modification places in 100%. The problem is that certain design patterns are forced by the startup code generated in cubemx. Over the years I have learned to "hack" it, I gues this is doable as well.

    This is however not the reason for the application to not work. I call the jumo from an freertos task, which is not an interrupt rouitine. The only thing I can think of from you post that I have missed is disabling the interrupt, I will check that.

    So to summarize, The FLASH_BASE define should be altered as well? It is used in various places in the code provided by ST.

    Graduate II
    September 7, 2018

    FreeRTOS might however being running in a user state and not a system state. Is it faulting?

    FLASH_BASE is only used for some asserts as far as I can see.

    The ST model would have you modify VECT_TAB_OFFSET to 0x37000

    SCB->VTOR is far simpler in it's implementation, it should ignore the low order 9-bits based on the size of the vector table, ie the NVIC/CMx feeds the low order bits as it initiates a read for the new PC

    Visitor II
    September 8, 2018
    Before jump, in the bootloader code there is no faults. Do you suggest that the scheduler should be stopped and the jump should occur from the main code?
    Tge offset idea makes sense, I will do it like that, thanks.
    So to summarize the VTOR setting, should I not modify it in the vootloader code before jump if I set the offset in the application code?
    Graduate II
    September 8, 2018

    The app code typically sets VTOR pretty early, and is going to overwrite anything the loader does.

    The loader shouldn't have any of it's routines running, think TIM and/or SysTick.

    Visitor II
    September 8, 2018
    So in other words:
    - it doesnt matter raither the loader or app (in system init) set vector table?
    - all interrupts should be turned off before jump?
    Visitor II
    September 9, 2018

    Ok, I still havent solved this issue but I am closer: Before jump, I called __disable_irq() to turn off the global interrupts. Thanks to this, I could move further in my application code. The problem is that all HAL startup routines use HAL tick from sys clock, so the interrupts needs to be on. The problem is that as soon as the interrupts are turned ON, I end up in hard fault hander...

    How to overcome this deadlock? I would really like to avoid the way of initalizing thing like you described @Community member​ at the beggining (that bootloader preconfigures things). At the beginning, I am trying to create a situation in which after the jump from the bootloader, the MCU is in a perfect reset state, just like after the reset. It seems I cannot get to that state. How to overcome the interrupts problem and why is this a problem in the first place...?

    Btw, seems like the global interrupts are turned ON by default in this MCU, never boticed that.

    Would really appreciate further help.