cancel
Showing results for 
Search instead for 
Did you mean: 

Procedures for developing an STM32 bootloader + app

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.

15 REPLIES 15

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.

As a test case, I did the following thing:

  • I have changed the VECT_TAB_OFFSET to 0x37000 like you suggested in the main application,
  • In the bootloader app, I made the jump at the very beggining of the main code. The function consisted only those lines:
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);
 
	__set_MSP(appStack); // Set app stack pointer
	appEntry(); // Start the app
 
}

And called like this:

boot_jump2Address(0x8037000);

In this case the application is correctly started and works. This proves that there are some old Bootloader initializations that block the possibility of correct passing the control from bootloader to the app... I am starting to think about leaving some kind of a cookie for mysalfe in form of a voltage state on a pin in order to be able to jump using a system reset but this would be a really dirty work around... Is there really now easy way of making the MCU go to the default state with all peripherals in HAL without making a reset?

>>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.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Ok, I have understood you incorrectly before. I thought that by saying "a) don't disable interrupts on the processor" you meant that the problem could lie in the fact that I have not disabled them, not the way around, sorry.

Ok, this is clear and I think I know what to do after your suggestions. The branching after reset seems amlot easier to implement and maintain for me at the moment. Otherwise I would need to track every single interrupt source and turn it off, just like you said. I need to think it over and go through the code to see how much interrupts there could be. Thank you for help.

In case anyone struggles with something similar, this is how I solved the problem using the tips obtained here:

Instead of jumping to the new address in the end of the bootloader, I set the jump address in a RTC backup register and then loop forever waiting for watchdog to reset the MCU. After the restart, the program starts again in the bootloader code. At the very beggining (only after HAL_Init) I check either there is anything written in the RTC backup register. If its != 0, means the bootloader left a message for itself. I save the address, clear that RTC backup register and jump to the read address. This way reset clears all my peripherals.

I only need to turn off the systick timer before jump and turn on the watchdog, in case there is no usable code under jump address. If thats the case, the watchdog will reset the MCU and I will endup in bootloader again (but wont jump, since RTC backup register is cleared).

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?