cancel
Showing results for 
Search instead for 
Did you mean: 

Bootloader for STM32F0 not initializing main stack pointer without printf() call

NSharp
Associate II

I have created a bootloader for my STM32F0 chip, and it will boot my firmware just fine, given one condition. I have followed the advice of many here, and I think I've gotten it working the way it is intended to work (set the ROM starting addresses and lengths, remap the vector table to SRAM, set SRAM at 0x00000000, etc.). However, I have run into a strange problem in my code that is baffling me. Using Keil v5 and ARMCLANG v6.14, if it matters.

If I turn off my debug (using EventRecorder to service printf()), I end up in the HardFault_Handler() every time. If I place one printf() that dumps the stack pointer address, everything works perfectly.

By way of example:

void jumpRoutine(void)
{
    // Disable global interrupts
    __disable_irq();
 
    // Disable all peripheral interrupts
    HAL_NVIC_DisableIRQ(SysTick_IRQn);
    HAL_NVIC_DisableIRQ(WWDG_IRQn);
    .
    .
    .
    HAL_NVIC_DisableIRQ(USB_IRQn);
 
    uint32_t start_addr = (uint32_t)0x08002000;
    uint32_t start_addr_ref = (uint32_t)&start_addr;
    uint32_t *p_start_addr = (uint32_t *)start_addr_ref;
 
    printf("\nInitializing 0x%08x\n", start_addr_ref);  <<------  LINE HAS TO BE HERE TO WORK!
 
    __set_MSP(*p_start_addr);
    void (*pApp)(void) = (void (*)(void))(*p_start_addr + 4);
 
    // Jump to firmware
    pApp();
}

Any explanation as to why this might happen? I've tried changing the variables to be volatile, but that had no effect (didn't think it would, but I tried it). I have disassembled the code, and it is very different between the version with no printf() and the version with printf(). The code size shrinks by about 50% (6KiB to 4KiB) with no printf() included as well.

Could this be timing-related? There's not much to this code, aside from the CubeMX-generated init code and my jump routine.

6 REPLIES 6

It relates to how the compiler manages the stack frame, and you changing the stack pointer.

I wouldn't do it this way, too much to screw up. And who enables the interrupt on the other side?

Better solution is that the code you're sending it too changes the SP once it lands, ie that Reset_Handle implicitly does a LDR SP, =__initial_sp OR _estack

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

@Community member​ -- thank you for your quick response.

My firmware (on the other side) remaps the vector table, explicitly enables the global interrupts, and then initializes the HAL-based code (which initializes any required interrupts for the code).

So, would I just avoid the __setMSP() call in the bootloader, then? I thought the SP and the RH must be set before the jump to my application? Every example I have seen shows this pattern, even the ST IAP example:

    /* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
    if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
    {
        /* Jump to user application */
        JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
        Jump_To_Application = (pFunction) JumpAddress;
      
        /* Initialize user application's Stack Pointer */
        __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
      
        /* Jump to application */
        Jump_To_Application();
    }

In the end, is it just a race condition where I am slowing things down enough with the printf() to make the stack pointer get initialized?

Again, really appreciate your time and help in understanding this process.

You're already calling from a viable stack, changing the MSP in C code is not advisable. This type of control transfer/task switch is better orchestrated in assembler, where there is no doubt about the instructions and sequence of events.

I don't think it is a race condition, but rather how the compiler/optimizer deal with a function that calls another function (ie relates to LR, SP, R12), and how it nests the scope, and you switching things around. For example, it pushed things onto a different stack, then recovering them from some new random location.

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

OK, @Community member​, I think I understand now. I moved this jump code out of a function and into the main() body. It now works without the printf(). It was the stack traversal through the additional function causing the original issue.

I did remove the call to __setMSP(), and to my surprise, it worked just fine. I assume that by re-mapping the vector table immediately in my application and then setting 0x00000000 to SRAM, I am effectively setting the main stack pointer in my application?

Thanks for all your help!

Piranha
Chief II

And what happens if you put __DSB() immediately before the jump?

Anyway I recommend another safer and simpler approach described in my post here:

https://community.st.com/s/question/0D50X0000AFpTmUSQV/using-nvicsystemreset-in-bootloaderapplication-jumps

NSharp
Associate II

@Piranha​  I have not tried the call to __DSB(), but I do not have the SCB->VTOR register on the STM32F0 (Cortex-M0 based). And, I am no longer setting the main stack pointer (MSP). From your example, this seems to be a wait for completion of setting the SCB->VTOR and MSP; but, it shouldn't apply in my situation anymore.

I read through your recommended bootloader scenario, and I can see the resiliency benefits. However, does this solution require the presence of the SCB->VTOR register?

I will try taking the principles presented there (simplicity, same code paths for all scenarios, just flipping a "switch" [RAM value] to determine the jump, and using NVIC_SystemReset() to start the system). I am still stuck trying to remap the vector table into SRAM, though, due to the lack of SCB->VTOR.