Skip to main content
Graduate
October 10, 2025
Solved

STM32F7 Bootloader: Application Jump Fails for Large Firmware (Works for Small Images)

  • October 10, 2025
  • 4 replies
  • 559 views

Hi all,

I’m working on a custom bootloader for STM32F7, which performs OTA updates using TCP/IP (Ethernet and/or WINC3400 WiFi). The bootloader writes the new firmware to flash, then attempts to jump to the application using the standard sequence: cleaning and disabling caches, setting VTOR, setting MSP, and jumping to the application's reset handler.

The Problem:

  • If the application binary is small, everything works: the bootloader jumps and the app runs as expected.
  • If the application binary is larger (for example, over X KB/MB), the bootloader appears to complete the OTA and jump, but the application does not start (no output, no main runs, or immediate lockup/hard fault).

What I Have Tried:

  • Verified that the full image is written to flash (checked total bytes and flash content matches binary).
  • Cleaned and disabled D/I caches before jump.
  • Set VTOR and MSP to the application address.
  • Disabled all interrupts, deinitialized all peripherals, shut down the WINC3400, and reset all related GPIOs/SPI.
  • When flashing the same large binary directly (not via bootloader), it runs perfectly.

Questions:

  1. Has anyone else experienced an issue where large application binaries fail to run after a bootloader jump, but small binaries work fine on STM32F7?
  2. Are there any known size limits, alignment, or memory configuration pitfalls (stack, heap, vector table, linker script) that can cause this?
  3. Is there any difference in how the bootloader or application should handle cache, MPU, or peripheral reset for large versus small images?

Any advice, references, or similar experiences would be greatly appreciated!

    This topic has been closed for replies.
    Best answer by MM..1

    Hard to say, this is your design. My crystal ball say SPI from WIFI corrupt your data... Try close this ticket with jump over ethernet always work ? And create new ticket...

    4 replies

    Super User
    October 10, 2025

    Show your code and/or follow existing examples of how to jump.

    Don't jump from within an interrupt. Don't disable interrupts without enabling.

     

    Small vs large program size is not the issue here. The jump mechanic is the same.

    MADHU2Author
    Graduate
    October 11, 2025

    Hi @TDK and @MM..1 ,
    Here is my current bootloader sequence for jumping to the application (after OTA update):

    static void goto_application(void)
    {
     printf("Boot from Application\r\n");
     /* Each vector position contains an address to the boot loader entry point */
     	// Disable all interrupts
     	__disable_irq();
    
     //	 Disable Systick timer
     	SysTick->CTRL = 0;
    
     	// Set the clock to the default state
     	HAL_RCC_DeInit();
    
     	// Clear Interrupt Enable Register & Interrupt Pending Register
     	for (i=0;i<5;i++)
     	{
     		NVIC->ICER[i]=0xFFFFFFFF;
     		NVIC->ICPR[i]=0xFFFFFFFF;
     	}
    
     	// Re-enable all interrupts
     	__enable_irq();
     void (*app_reset_handler)(void) = (void*)(*((volatile uint32_t*) (0x08100000 + 4U )));
     __set_MSP(*(volatile uint32_t*) 0x08100000);
     // Turn OFF the Green Led to tell the user that Bootloader is not running
     HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET ); //Green LED OFF
    
     /* Reset the Clock */
     HAL_RCC_DeInit();
     HAL_DeInit();
     SysTick->CTRL = 0;
     SysTick->LOAD = 0;
     SysTick->VAL = 0;
     SCB->VTOR = 0x08100000;
     /* Jump to application */
     app_reset_handler(); //call the app reset handler
    }

    I have also verified that both the reset handler address and the stack pointer address are correct after both Ethernet and WiFi OTA updates—they point to valid locations in flash and RAM, respectively.

    This sequence works perfectly with Ethernet/TCP OTA updates: the bootloader jumps and the app runs every time. But only for small-sized firmware; if it exceeds 256KB, it cannot jump.
    However, when I use the same TCP update sequence over WiFi (with the WINC3400 module), the application does not start after the update, even though the firmware is written and the jump logic is the same.

    If there’s anything missing or any subtle points I should handle (especially regarding peripheral state, EXTI/IRQ, or caches), please let me know.
    Any advice or pointers would be appreciated—thank you!

     

    Super User
    October 11, 2025

    Are you jumping from within an interrupt?

    Code seems okay. Although it would be better to de-initialize peripherals. Could be getting an unexpected interrupt when the application re-initializes them.

     

    Graduate II
    October 10, 2025

    I mean issue isnt size or fw based, but bootloader . I preffer jump to app on first main lines based on some marker, then after update simply reset and no jump.

    Graduate
    October 11, 2025

    Check if the code generated uses the stack pointer for variable access (look at the disassembly output). If the reset handler address is stored on the stack and then the MSP is reloaded, your code will not jump to the app entry point.

    ST Employee
    October 12, 2025

    If your bootloader configures the MPU, make sure to disable or reset it before jumping to the application. 

    MADHU2Author
    Graduate
    October 29, 2025

    hi @Shirley.Ye ,

     

    Yes, I have done this, but I am still unable to jump the application. I am not sure what the main reason is; below I am reuploading my jump sequence. In the current situation, the application runs only on the first two updates using WiFi TCP. After that, no other application runs; only the first application is run, which has a small size and was uploaded first. Why?

    /// Define application start address (change if needed)
    #define APP_START_ADDRESS 0x08040000U // Application start address (sector 5)
    typedef void (*pFunction)(void);
    
    void goto_application(void)
    {
    
     uint32_t appStack;
     pFunction appEntry;
     printf("Boot from Application firmware \r\n");
     printf("Jumping to app: SP=0x%08lx, PC=0x%08lx\r\n", *(uint32_t*)APP_START_ADDRESS, *(uint32_t*)(APP_START_ADDRESS+4));
     // 1. Disable WINC3400 IRQ line interrupt on STM32 MCU
     nm_bsp_interrupt_ctrl(0); // 0 = disable interrupts for WINC3400 ISR line
    
     // 2. Deinitialize WINC3400 bus and GPIO pins
     nm_bus_deinit(); // Deinit SPI bus or related bus layer
     nm_bsp_deinit(); // Deinit GPIO pins, power down WINC3400
     /* === 1️⃣ Stop all interrupts === */
     __disable_irq(); // Disable global IRQs
    
     /* === 2️⃣ Deinitialize peripherals used in bootloader === */
     HAL_SPI_DeInit(&hspi1); // Deinit SPI1
     // HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7); // example SPI1 pins
     // HAL_GPIO_DeInit(GPIOx_INT_PORT, GPIOx_INT_PIN); // your EXTI interrupt pin
    
     HAL_NVIC_DisableIRQ(EXTI1_IRQn); // Disable EXTI for GPIO interrupt
     HAL_NVIC_DisableIRQ(SPI1_IRQn); // Disable SPI1 IRQ
     HAL_NVIC_DisableIRQ(SysTick_IRQn); // Disable SysTick interrupt
    
     HAL_DeInit(); // Deinit HAL (SysTick, RCC, etc.)
     HAL_RCC_DeInit(); // Reset clock config
    
     /* === 3️⃣ Clear all pending IRQs in NVIC === */
     for (uint8_t i = 0; i < 8; i++) {
     NVIC->ICER[i] = 0xFFFFFFFF; // Disable all IRQs
     NVIC->ICPR[i] = 0xFFFFFFFF; // Clear all pending
     }
     /* === 4️⃣ Read application’s stack pointer and reset vector === */
     appStack = *((__IO uint32_t*)APP_START_ADDRESS);
     appEntry = (pFunction)*((__IO uint32_t*)(APP_START_ADDRESS + 4));
    
     /* === 5️⃣ Remap vector table to application area === */
     SCB->VTOR = APP_START_ADDRESS;
    	__enable_irq();
     /* === 6️⃣ Set new main stack pointer === */
     __set_MSP(appStack);
     /* === 7️⃣ Jump to application’s Reset_Handler === */
    
    
     appEntry();
    
     /* Should never return here */
     while (1);
    }
    
    
    This code use for the copy the firmware from sector to sector.
    
    
    void OTA_Process_Main(void)
    {
     if (!ota_done_flag) return;
     ota_done_flag = 0;
    
     printf("OTA_Process_Main: Erasing OTA scratch area (sector10..?)\r\n");
     // HAL_Delay(40);
     Erase scratch area (choose sufficient size, using OTA_MAX_SIZE)
     if (Erase_Flash_Area(FLASH_SECTOR_10_ADDR, OTA_SIZE) != 0) {
     printf("OTA_Process_Main: Erase OTA area failed\r\n");
     return;
     }
    
     HAL_Delay(30);
    
     printf("OTA_Process_Main: Writing %lu bytes to OTA scratch 0x%08lX\r\n", (unsigned long)ota_received_bytes, (unsigned long)FLASH_SECTOR_10_ADDR);
     if (ota_ram_buffer == NULL) {
     printf("OTA_Process_Main: No RAM buffer present!\r\n");
     return;
     }
    
     if (Flash_Write_Buffer(FLASH_SECTOR_10_ADDR, ota_ram_buffer, ota_received_bytes) != HAL_OK) {
     printf("OTA_Process_Main: Flash write to scratch failed\r\n");
     return;
     }
     uint32_t crc_scratch = Compute_CRC32_For_Flash(FLASH_SECTOR_10_ADDR, ota_received_bytes);
     printf("Scratchpad CRC = 0x%08lX\r\n", crc_scratch);
     HAL_Delay(50);
    
     //Optional: compute/verify CRC here against server-supplied CRC (not implemented)
    
     printf("OTA_Process_Main: Copying from scratch to application area 0x%08lX -> 0x%08lX size=%lu\r\n",
     (unsigned long)FLASH_SECTOR_10_ADDR, (unsigned long)FLASH_SECTOR_5_ADDR, (unsigned long)ota_received_bytes);
    
     if (Copy_Flash_Area(FLASH_SECTOR_10_ADDR, FLASH_SECTOR_5_ADDR, ota_received_bytes) == HAL_OK) {
     printf("OTA_Process_Main: Copy successful. Cleaning up and rebooting.\r\n");
     uint32_t crc_app = Compute_CRC32_For_Flash(FLASH_SECTOR_10_ADDR, ota_received_bytes);
     printf("Scratchpad CRC = 0x%08lX\r\n", crc_app);
     // Erase OTA scratch area after use to avoid leftover data
     if (Erase_Flash_Area(FLASH_SECTOR_10_ADDR, OTA_SIZE) != 0) {
     printf("OTA_Process_Main: Warning: erase scratch after copy failed\r\n");
     }
    
     if (ota_ram_buffer) {
     memset(ota_ram_buffer, 0xFF, OTA_MAX_SIZE);
     free(ota_ram_buffer);
     ota_ram_buffer = NULL;
     printf("OTA_Process_Main: freed OTA RAM buffer\r\n");
     }
     ota_received_bytes = 0;
    
     // Clean / invalidate caches before reset (Cortex-M7)
    //#if (__CORTEX_M >= 7)
    // SCB_CleanInvalidateDCache();
    // SCB_InvalidateICache();
    //#endif
    
     HAL_Delay(100);
     NVIC_SystemReset();
     } else {
     printf("OTA_Process_Main: Copy failed, not rebooting\r\n");
     // handle failure: maybe stay in bootloader, inform user, etc.
     }
    }
    */
    
    
    

     In the above, i also added how I use the code to save the firmware in the flash sector. 

    Graduate II
    October 29, 2025

    Primary start verify OTA after update with stlink method:

    1. run tcp OTA to end no jump only mark ended for example LED light

    2. connect stlink and verify HEX file over memory ...

    After image is valid youtest jump simpler method. Is in your code used IWDG or WWDG ?