Electrical – STM32L011 jump to bootloader from user code

bootloaderfirmwarestm32

I'm trying to make it so that my STM32L011 can jump from user code to the ST bootloader that allows flash to be reprogrammed over USART2. This is hypothetically the same question as STM32F091 Jump to Bootloader from application, but I'm not having any luck with the peripheral-disable fixes from that question. ST redid their forums and broke all of the old links to threads so I'm having an awful time finding clues as to other possible issues and I'm pretty stumped as to where to look next.

My jump function is as follows (modified from the above question for my application and using the ST LL libraries). My application code uses the LPUART on PA2/PA3, which should then be usable by USART2 for the bootloader. A command is received on the LPUART in user application code, which calls the jump function. The application code acknowledges the command properly per my serial protocol, but then the USART bootloader does not respond to any flasher utilities. They send the intitial 0x7F byte per AN2606 but the bootloader doesn't send the ACK back.

#define STM32L01_SYSTEM_MEMORY  0x1FF00000

void jump_to_bootloader()
{
    // Disable global interrupts
    __disable_irq();

    // Disable interrupt requests from peripherals
    NVIC_DisableIRQ(SysTick_IRQn);
    NVIC_DisableIRQ(LPUART1_IRQn);
    NVIC_DisableIRQ(EXTI0_1_IRQn);
    NVIC_DisableIRQ(DMA1_Channel2_3_IRQn);
    NVIC_DisableIRQ(I2C1_IRQn);

    typedef void (*pFunction)(void);
    volatile uint32_t addr = STM32L01_SYSTEM_MEMORY;
    uint32_t jumpaddr = *(__IO uint32_t*) (STM32L01_SYSTEM_MEMORY + 4);
    pFunction jump_to_application;

    // Application-specific shutdown of HSE and switch to MSI
    clock_HSE_to_MSI();

    // Reset ALL peripherals
    LL_AHB1_GRP1_ForceReset(LL_AHB1_GRP1_PERIPH_ALL);
    LL_APB1_GRP1_ForceReset(LL_APB1_GRP1_PERIPH_ALL);
    LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_ALL);
    LL_mDelay(5);

    LL_AHB1_GRP1_ReleaseReset(LL_AHB1_GRP1_PERIPH_ALL);
    LL_APB1_GRP1_ReleaseReset(LL_APB1_GRP1_PERIPH_ALL);
    LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_ALL);
    LL_mDelay(5);

    // Fully reset RCC to power-up state
    LL_RCC_DeInit();

    // Zero out SysTick
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;


    // Remap memory to system flash
    SYSCFG->CFGR1 = 0x01;

    jump_to_application = (pFunction)jumpaddr;

    /* Initialize user application's Stack Pointer */
    __set_MSP(*(__IO uint32_t*) addr);


    jump_to_application();
}

I have ruled out these problems:

  1. The control pins on my MAX3221 RS232 transceiver (NEN, NFORCEOFF, FORCEON) are in the proper enabled states, so if USART2 is talking I should see something on my logic analyzer that's attached to the RS232 side. Unfortunately, the need to drive GPIO pins at boot time to set these control pins means that I'm not able to do the "call my jump function at the start of main()" test.
  2. The jump_to_bootloader() function does fully execute (no hangs on clock switch etc). I see one of the MAX3221 control pins go high for about 160us before going back low, which I have confirmed happens after any point in the function that I could toggle it like that myself. I'm operating under the assumption that this occurs as part of the GPIO peripheral being initialized again.
  3. I've tried various permutations of including the memory barrier instruction calls included in the referenced question above to no effect. I haven't found reference to those being included in any other similar code online.
  4. I'm resetting all peripherals to ensure that I'm not having the same problem as the above question. This should in theory negate the need to try calling the jump function at the start of main(). I'm also specifically disabling interrupt requests used in my application to be safe.
  5. I never see the chip reset pin go low, so I don't suspect anything with the BOOT0 pin at this time. The pin is held low with a pulldown resistor, so if a reset were occurring, then BOOT0 would cause the chip to boot back to user code in flash. The chip option bytes are set as below:

Option bytes

Working theories:

  1. Something could be going wrong with where I'm jumping. As far as I can tell I have the correct address from AN2606. I'm having trouble confirming this when looking at the registers with a debugger. After stepping all the way through jump_to_bootloader(), the code appears to hang, at least according to the debugger… I don't fully understand if I can still trust the debugger after jumping out of my user code. The SP and MSP registers show SRAM adresses (0x20000000 range) and the PC is in flash (0x80000000 start).

Before __set_MSP():

Register values

After stepping to the end of the jump function (debugger step buttons no longer enabled):
Register values 2

If I then click suspend execution on the debugger, it brings me back to an Eclipse-generated breakpoint at the start of main(). I don't understand how this could be the case as my application code verifiably no longer functions correctly, both with and without the debugger present.

  1. Something is causing a reset that I don't see on the hardware pin for some reason, which is then causing a reboot into user flash. This feels like a red herring, but I'm suspicious given the way that the debugger brought me back to the start of main().

Best Answer

You could try jumping to 0x1FF00FFE instead of 0x1FF00000. (embedded bootloader address according to AN2606 Rev 35, p 26)

My initial comment was completely wrong. But according to the comments above, the topic starter found what was wrong. Because of the empty check mechanism presented on L011 device you can't jump to the system memory from user code and stay there. I encountered similar problems on my L07x device with dual banking mechanism (similar issue https://stackoverflow.com/questions/42020893/stm32l073rz-rev-z-iap-jump-to-bootloader-system-memory). It turned out that the bootloader is jumping back to user code no matter what as long as there is a valid code in one of the banks. Of course, it is possible to bypass it by jumping to the address after the empty check, but it is difficult to find the correct address and there is no guarantee that STM won't change that address in the next printing of the chip.