STM32H743BIT6 Custom Ethernet Bootloader - Firmware Update and CRC Verification Successful, but Application Does Not Start touchGFX DisplayBlack
STM32H743BIT6 Custom Ethernet Bootloader – Firmware Update and CRC Verification Successful, but Application Does Not Start (TouchGFX Display Remains Blank)
Hello STM32 Community,
I am developing a custom Ethernet bootloader for the STM32H743BIT6 microcontroller. The bootloader successfully receives firmware over Ethernet, programs the internal Flash and external QSPI Flash, and verifies the received firmware using CRC32.
However, after completing the firmware update and attempting to launch the application, the TouchGFX display remains blank. The application does not appear to start correctly.
I would appreciate your guidance on the application launch procedure, memory configuration, and recommended firmware update architecture for a custom STM32H7 bootloader.
1. Hardware Configuration
-
Microcontroller: STM32H743BIT6
-
Ethernet PHY: LAN8742A-CZ-TR
-
Ethernet Interface: RMII
-
External QSPI Flash: Winbond W25Q01JV (128 MB)
-
External SDRAM: IS42S32400F (32 MB)
-
GUI Framework: TouchGFX
-
Development Environment: STM32CubeIDE
-
Host PC: Linux
-
Host Application: Python-based Ethernet firmware update application using Scapy
2. Memory Architecture
I have implemented the following memory arrangement.
Internal Flash:
-
Bootloader starting address:
0x08000000 -
Application starting address:
0x08040000
External QSPI Flash:
-
Used for storing TouchGFX assets.
-
Memory-mapped address:
0x90000000
External SDRAM:
-
Used for application runtime requirements, including the TouchGFX framebuffer.
The application linker script is configured to place the application at 0x08040000.
3. Ethernet Bootloader Architecture
I have developed a custom Ethernet bootloader using the STM32 HAL Ethernet driver and a custom Ethernet communication protocol.
On the Linux PC, I have developed a Python application that allows the user to select two separate binary files:
-
STM32 application firmware (
.bin). -
TouchGFX assets (
.bin).
The Python application transfers these files to the STM32 bootloader over Ethernet.
The bootloader receives the firmware and programs it into the appropriate memory locations:
-
Application firmware is programmed into internal Flash starting at
0x08040000. -
TouchGFX assets are programmed into external QSPI Flash.
4. Firmware Update Procedure
The current firmware update process follows these steps:
-
The Python application establishes communication with the STM32 bootloader over Ethernet.
-
The host sends firmware metadata, including firmware size and CRC32.
-
The bootloader validates the received metadata.
-
The bootloader erases the required internal Flash and external QSPI Flash regions.
-
The host transfers the application firmware in chunks.
-
The bootloader programs the application firmware into internal Flash.
-
The host transfers the TouchGFX asset binary.
-
The bootloader programs the TouchGFX assets into external QSPI Flash.
-
The bootloader reads back the programmed data and verifies individual packet CRCs.
-
The bootloader performs complete firmware CRC32 verification.
-
After successful verification, the bootloader initiates the application launch procedure.
The complete firmware transfer, Flash programming, readback, and CRC verification processes are successful.
5. Application Launch Procedure
After successful firmware verification, the Ethernet bootloader currently performs an MCU reset using:
NVIC_SystemReset();After the reset, the bootloader starts again, checks the application status, and attempts to launch the application from 0x08040000.
However, after this process, the TouchGFX display remains blank.
I would like to understand whether a complete MCU reset followed by application launch is sufficient, or whether additional peripheral and memory initialization or deinitialization is required before transferring control to the application.
6. Current Issue
The following operations are working correctly:
-
Ethernet communication between the PC and STM32.
-
Firmware metadata transfer.
-
Internal Flash erase and programming.
-
External QSPI Flash erase and programming.
-
Packet-level CRC verification.
-
Complete application firmware CRC32 verification.
-
TouchGFX asset transfer and CRC32 verification.
-
Firmware update completion and MCU reset.
However, the application does not appear to initialize correctly after the update, and the display remains blank.
I am investigating whether the issue could be related to:
-
Application jump procedure or vector table configuration.
-
Application linker script and application start address (
0x08040000). -
MPU configuration during application startup.
-
External QSPI Flash address mode (3-byte versus 4-byte addressing).
-
QSPI memory-mapped configuration.
-
Ethernet, DMA, or other peripheral states left by the bootloader.
-
SDRAM initialization and MPU configuration required by TouchGFX.
-
Application startup and clock configuration after bootloader handover.
-
Correct placement and access of TouchGFX assets in external QSPI Flash.
this is my jump to application function
the applcation which i am doing firmware update the flash adress i am chnaginf from 0x08000000 to 0x08040000 and also VECTO OFFEST i am changing to 0x00000000 to 0x00040000
typedef void (*pFunction)(void);
/**
* @brief Validate and cleanly jump to the application firmware.
* Tailored specifically for STM32H7 Ethernet bootloaders with TouchGFX assets.
* Integrates low-level W25Q01JV Winbond hardware state tearing down.
*/
void JumpToApplication(void)
{
uint32_t appStackPointer;
uint32_t appResetHandler;
/* 1. READ APP HEADER POINTERS Safely */
appStackPointer = *(volatile uint32_t *)APPLICATION_ADDRESS;
appResetHandler = *(volatile uint32_t *)(APPLICATION_ADDRESS + 4U);
/* 2. EDGE CASE VALIDATION: Main Stack Pointer (MSP)
Allows the pointer to sit exactly at the upper limit (0x24080000)
since the stack descends downwards, and ensures strict 8-byte alignment. */
if ((appStackPointer < APPLICATION_SRAM_START) ||
(appStackPointer > APPLICATION_SRAM_END) ||
((appStackPointer & 0x7U) != 0U))
{
return; /* Invalid Stack Pointer: Flash transmission likely failed/corrupted */
}
/* 3. EDGE CASE VALIDATION: Reset Vector
Ensures the execution entry address is an odd address (Thumb State requirement)
and sits perfectly within the allocated application space. */
if (((appResetHandler & 1U) == 0U) ||
((appResetHandler & ~1U) < APPLICATION_ADDRESS) ||
((appResetHandler & ~1U) >= APPLICATION_FLASH_END))
{
return; /* Invalid Reset Vector: Entry point missing */
}
/* ========================================================================= */
/* CRITICAL STEP: RESET EXTERNAL WINBOND FLASH CHIP TO POWER-ON DEFAULTS */
/* ========================================================================= */
(void)W25Q01JV_DisableMemoryMappedMode(); /* Release memory map bus control */
(void)W25Q01JV_Exit4ByteAddressing(); /* Hard command exit out of 4-byte mode */
(void)W25Q01JV_Reset(); /* Software reset fallback back to defaults */
/* 4. SILENCE ALL INTERRUPTS IMMEDIATELY
Prevents any incoming Ethernet packet or hardware event from preempting the jump sequence */
__disable_irq();
/* 5. STOP SYSTICK TIMER
Stops the OS/HAL tick source from firing interrupts during core configuration */
SysTick->CTRL = 0U;
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
/* 6. CLEAR ALL PENDING AND ACTIVE NVIC INTERRUPTS */
for (uint8_t i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFFU; /* Disable all peripheral interrupts */
NVIC->ICPR[i] = 0xFFFFFFFFU; /* Clear any lingering pending statuses */
}
/* ========================================================================= */
/* CRITICAL STEP: DISABLE MPU BEFORE JUMPING */
/* Clears bootloader memory protection rules blocking 0x90000000 (QSPI) */
/* and 0xC0000000 (SDRAM/LTDC Framebuffer regions used by TouchGFX). */
/* ========================================================================= */
HAL_MPU_Disable();
/* 7. FORCE RESET PERIPHERAL ENGINES (Tailored for STM32H7 Subsystems)
Instantly freezes running Ethernet DMA rings, MDMA channels, and QSPI flash transfers
so they don't corrupt the incoming application memory workspace. */
__HAL_RCC_ETH1MAC_FORCE_RESET();
__HAL_RCC_MDMA_FORCE_RESET();
__HAL_RCC_DMA1_FORCE_RESET();
__HAL_RCC_DMA2_FORCE_RESET();
#if defined(RCC_AHB3RSTR_QSPIRST)
__HAL_RCC_QSPI_FORCE_RESET();
#elif defined(RCC_AHB3RSTR_OSPIMRST) || defined(RCC_AHB3RSTR_OSPI1RST)
__HAL_RCC_OSPI1_FORCE_RESET();
#endif
/* Pipeline settling delay to guarantee hardware registers clear completely */
for(volatile uint32_t delay = 0; delay < 5000; delay++);
/* Release peripheral resets to leave a clean state for the destination app */
__HAL_RCC_ETH1MAC_RELEASE_RESET();
__HAL_RCC_MDMA_RELEASE_RESET();
__HAL_RCC_DMA1_RELEASE_RESET();
__HAL_RCC_DMA2_RELEASE_RESET();
#if defined(RCC_AHB3RSTR_QSPIRST)
__HAL_RCC_QSPI_RELEASE_RESET();
#elif defined(RCC_AHB3RSTR_OSPIMRST) || defined(RCC_AHB3RSTR_OSPI1RST)
__HAL_RCC_OSPI1_RELEASE_RESET();
#endif
/* 8. TEARDOWN CPU CACHES (Crucial for Cortex-M7 speculative fetching)
Flushes the pipeline cache so the application reads its new code instead
of stale instructions cached from the bootloader runtime. */
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
SCB_DisableDCache();
#endif
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
SCB_DisableICache();
#endif
/* ========================================================================= */
/* CRITICAL STEP: DE-INITIALIZE SYSTEM RCC CLOCKS MODIFIERS */
/* Drops the CPU clocks back to the internal 64MHz HSI safe baseline. */
/* Stops your main application's clock setup code from locking up on PLLs. */
/* ========================================================================= */
HAL_RCC_DeInit();
/* 9. TEARDOWN BASE HAL INFRASTRUCTURE */
HAL_DeInit();
/* Clear pending system core exceptions (PendSV, SysTick flags) */
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk;
/* Force thread execution context back to pure Main Stack Pointer defaults */
__set_CONTROL(0U);
/* 10. REFRESH CORES AND ARCHITECTURAL PIPELINES */
__DSB();
__ISB();
/* 11. INLINE ASSEMBLY JUMP
Loads the main stack pointer directly and jumps to the application entry.
Keeps interrupts disabled ("cpsie i" deliberately removed) so the application
re-enables them only when its own system clocks are stabilized. */
__asm volatile
(
"msr msp, %0 \n"
"bx %1 \n"
:
: "r" (appStackPointer), "r" (appResetHandler)
: "memory"
);
/* System Trap: Code should never fall through to this block */
while (1)
{
}
}
7. Questions About Custom Bootloader Design
I would appreciate clarification on the following points.
1. Application launch after firmware update
Is using NVIC_SystemReset() after a successful firmware update a recommended approach? After the reset, should the bootloader initialize normally and then jump to the application, or is there a preferred method for launching the updated application?
2. Application jump requirements
For STM32H743BIT6, what is the recommended procedure for jumping to an application located at 0x08040000?
Are there specific requirements concerning:
-
SCB->VTOR -
Main Stack Pointer (MSP)
-
NVIC interrupt disabling and clearing
-
MPU configuration
-
Peripheral deinitialization
-
Cache handling (I-Cache and D-Cache)
3. QSPI Flash configuration
If the bootloader initializes the external QSPI Flash in 4-byte addressing mode, but the application QSPI driver expects 3-byte addressing mode, can this cause problems when accessing TouchGFX assets?
Should the bootloader reset the QSPI device before launching an internal-Flash application, or should the application handle the QSPI configuration independently?
4. Peripheral and memory handover
Before launching the application, should the bootloader deinitialize Ethernet, DMA, QSPI, and other peripherals?
Are there any special considerations for SDRAM initialization and MPU configuration when using TouchGFX with external SDRAM and QSPI Flash?
5. Firmware file format for custom bootloader updates
For a custom STM32 bootloader, is it recommended to perform firmware updates using the complete ELF file, or is it better to generate and transfer separate binary files?
My current implementation uses two separate files:
-
Application firmware binary (
.bin) for internal Flash. -
TouchGFX assets binary (
.bin) for external QSPI Flash.
Is this a valid and recommended approach for a production firmware update system?
If separate binary files are acceptable, how should their addresses, sizes, versions, and CRC values be managed?
6. Recommended update sequence
If the application firmware and TouchGFX assets are maintained as separate binary files, which should be updated first?
-
Application firmware first, followed by TouchGFX assets?
-
TouchGFX assets first, followed by application firmware?
What is the recommended approach to avoid compatibility problems if power is lost or an update fails while only one of these two components has been updated?
7. Firmware integrity and compatibility
Apart from CRC32 verification, should a custom bootloader maintain separate metadata for the application firmware and TouchGFX assets, such as:
-
Firmware version
-
Firmware size
-
CRC32
-
Target Flash address
-
Compatibility/version information between application firmware and TouchGFX assets
Are there any recommended practices for managing these two firmware components together?
