2
\$\begingroup\$

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().
\$\endgroup\$
5
  • 2
    \$\begingroup\$ A desperate measure, but one that will definitely work and avoid all these battles, is to set a magic value in RAM or the RTC backup registers, reboot to your own code, see that flag before any setup (ie, ideally in assembly before C/C++ init) and fixup the stack pointer and jump to the bootloader right away. I'd not try to debug through this, just get it right by design and see that it works by result. \$\endgroup\$ Dec 14, 2018 at 3:34
  • \$\begingroup\$ @ChrisStratton: this is actually the only reliable way. It is moreover recommended by ST - or at least the guru of the now defunct forums cited by OP. It is finally how I did and it works just like it should. So you should really post your comment as an answer. \$\endgroup\$
    – user72833
    Dec 14, 2018 at 5:45
  • 1
    \$\begingroup\$ Does the bootloader use interrupts? Try maybe changing the VTOR to point to the bootloader before jumping. \$\endgroup\$
    – filo
    Dec 14, 2018 at 7:11
  • \$\begingroup\$ No luck with the reset, ST support has me testing if chip errata from the F042 listed in AN2606 possibly applies to the L011 as well. "Due to empty check mechanism present on this product, it is not possible to jump from user code to system bootloader. Such jump will result in a jump back to user flash space." \$\endgroup\$
    – Joe Baker
    Dec 20, 2018 at 20:12
  • \$\begingroup\$ If you enable instruction stepping, you should be able to continue stepping through the the bootloader ROM, using the disassembled instructions as a guideline. I've done this before for the F042 using python GDB scripting to trace all register values and memory read/writes to see how the bootloader decides to jump back to user flash: gist.github.com/devanlai/f9636f68c57fde2c5da912231f03c47e \$\endgroup\$
    – Devan
    Dec 24, 2018 at 19:13

2 Answers 2

0
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ The question code is reading the start vector from the boot ROM's vector table, not jumping to the start of it. \$\endgroup\$ Jan 16, 2019 at 18:19
0
\$\begingroup\$

This turned out to be undocumented hardware errata with the STM32L011. Everything I was doing in the code was correct. I ended up having to redesign my board to use a STM32L031 instead.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ For future readers who find this answer, are you able to edit the answer to give any details about the the undocumented hardware errata? \$\endgroup\$ Oct 29 at 9:44
  • 1
    \$\begingroup\$ It was documented on other models at the time, which was part of what tipped me off to it. Let me pull up the ref manuals to find the exact verbiage and part numbers again. The short version is that this method of jumping to bootloader simply does not work on the L011. \$\endgroup\$
    – Joe Baker
    Oct 30 at 17:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.