2026-03-10 2:32 PM - last edited on 2026-03-12 4:42 AM by Andrew Neil
Hi, I'm working on the STM32H750B-DK discovery board and need to combine two key functionalities:
1. Ethernet connectivity based on the LwIP stack (using the example from the `stm32-hotspot/STM32H7-LwIP-Examples` repository).
2. A TouchGFX GUI that stores its assets (images, fonts) in the external QSPI flash (`MT25TL01G`) due to the limited internal flash (128KB).
I have both components working **individually**: - The **standalone Ethernet example** (STM32H750_Disco_ETH) works perfectly – I can ping the board at `192.168.1.10`.
- The **standalone TouchGFX application** works when the external flash is properly initialized via a bootloader, and assets are loaded from `0x90200000`.
The challenge is to merge them into a single project where the application code runs from internal flash (the `APP_FLASH` region) and the Ethernet stack functions correctly alongside the TouchGFX GUI. ### **Current Memory Map (Linker Script)** I am using the following memory partition to accommodate both: `
MEMORY
{
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
SDRAM (xrw) : ORIGIN = 0xD0000000, LENGTH = 8M
ASSETS_FLASH (r) : ORIGIN = 0x90200000, LENGTH = 126M
/* Partition internal flash */
BOOTLOADER (rx) : ORIGIN = 0x08000000, LENGTH = 32K /* Reserve 32KB for bootloader */
APP_FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 96K /* Remaining 96KB for app */
}### **The Problem** When I attempt to merge the two, I encounter a "Load failed" error during debugging because the debugger cannot write to the external QSPI flash at `0x90200000` without an external loader. More importantly, the Ethernet stack often fails to initialize (ping timeout) or the system hard-faults, likely due to the QSPI not being initialized early enough for code that may be inadvertently placed in external flash.
My Approach & Questions
1. **Bootloader Role:** The bootloader at `0x08000000` initializes the QSPI in memory-mapped mode and then jumps to the application at `0x08008000`. Is this the correct sequence to ensure the external flash is ready before the main application (including LwIP) starts?
2. **Linker Script Placement:** How can I ensure that **all code and initialized data for LwIP and the core application** are placed strictly in internal flash (`APP_FLASH`) and **not** in external `ASSETS_FLASH`? The `ASSETS_FLASH` region should only contain read-only GUI assets, not executable code.
3. **MPU Configuration:** The Ethernet example uses specific MPU regions for the LwIP heap (`0x30020000`, non-cacheable) and descriptors (`0x30040000`, non-cacheable, shareable). Do I need an additional MPU region for the external QSPI flash address range (e.g., `0x90000000` or `0x90200000`) to make it cacheable for performance, or will that interfere with Ethernet DMA?
4. **Debugging with External Flash:** What is the recommended workflow for debugging such a project? I have used STM32CubeProgrammer with the `MT25TL01G_STM32H750B-DISCO.stldr` loader to flash the image manually, but it breaks the seamless debug-test cycle in CubeIDE. Is there a way to configure the IDE to use the external loader automatically?
Request
Has anyone successfully integrated the `STM32H750_Disco_ETH` LwIP example with a TouchGFX project that relies on external QSPI flash? Could you share a working linker script snippet, the necessary MPU settings, and your approach to debugging?
Any guidance or examples would be immensely helpful. Thank you!
Solved! Go to Solution.
2026-03-17 1:44 PM
Hello @STackPointer64 and @Pavel A.,
Thank you for your help, I am still couldn't get it to work. Please let me iterate the current state: The touchDFX pooldemo and the STM32H7 based Ethernet example both ran without problems on the board albite individually.
Board: STM32H750B‑DK (internal flash 128 KB, external QSPI flash 128 MB, SDRAM 8 MB)
Tools: STM32CubeMX 6.16.0, TouchGFX 4.26.1, STM32CubeIDE 1.16.0, STM32CubeProgrammer 2.21.0
Bootloader (internal flash 0x08000000, 32 KB):
Initialises clocks, QSPI in memory‑mapped mode (Quad I/O, read command 0xEB, 6 dummy cycles), and an optional LED.
Verifies that the first word at 0x90000000 is not 0xFFFFFFFF (indicating the flash is programmed) and then jumps to the application at 0x90000000.
Does not de‑initialise QSPI.
The bootloader runs (LED blinks) and passes the flash check (fast blink) – I added LED debug as suggested.
Application (linked for external flash at 0x90000000:(
Based on a CubeMX project with ETH (MII, PHY address 1, static IP 192.168.1.10), LwIP, and TouchGFX.
Linker script places .isr_vector at 0x90000000, code in EXT_FLASH, assets in ASSETS_FLASH at 0x90200000, framebuffers in SDRAM (0xD0000000), Ethernet buffers in RAM_D2 at 0x30040000 etc.
system_stm32h7xx.c has USER_VECT_TAB_ADDRESS defined and VECT_TAB_BASE_ADDRESS = 0x90000000U; also the RCC reset part is commented out to preserve bootloader clock settings.
MPU is configured with regions for background (whole memory, cacheable), LwIP heap (0x30020000, non‑cacheable), Ethernet descriptors (0x30040000, non‑cacheable, shareable), SDRAM (0xD0000000, cacheable), and external flash (0x90000000, cacheable, executable).
Backlight is set high in MX_GPIO_Init().
Framebuffer address in LTDC layer is 0xD0000000.
I have removed the Custom folder from the build to avoid duplicate symbols.
Bootloader works (LED blink before jump, fast blink after flash check, solid LED before jump).
External flash is programmed correctly – I can read the first few words via CubeProgrammer after programming; they are not 0xFFFFFFFF.
Application builds without errors (after excluding the Custom folder).
MPU – I temporarily disabled it in the application, but still no display (so probably not MPU).
Vector table – I added a very early LED toggle in Reset_Handler (assembly), and it does not blink. This tells me that the CPU never even reaches the application’s reset handler.
Despite the bootloader jumping to 0x90000000, the application does not start at all – the vector table is not being used, or the jump fails silently. The bootloader LED stays solid after the jump, indicating it performed the jump, but the application’s reset handler never runs.
2026-03-18 5:19 PM - edited 2026-03-18 5:22 PM
> Despite the bootloader jumping to 0x90000000, the application does not start at all
* Try to trace the app after the jump to 0x90000000 with debugger. Do you see the disassembly?
The SystemInit function of the app must NOT clobber the clock and QSPI settings prepared by the bootloader.
* Before jump to 0x90000000, check integrity of the app memory in memory-mapped space (CRC or checksum).
2026-03-19 2:35 PM
I’ve been working on a bootloader for the STM32H750 that loads an application from external QSPI flash (memory‑mapped at `0x90000000`). After fixing the linker script to add `KEEP` around the vector table and rebuilding cleanly, the bootloader still fails to jump to the application.
We’re now debugging with the attached register and variable dump. Here’s what we see:
- **QSPI registers**
- `qspi_cr = 50331649` → `0x03000001` (memory‑mapped mode enabled, other settings)
- `qspi_ccr = 253246955` → `0x0F1A5FEB` (instruction/address/mode configuration)
- `qspi_sr = 0` → no busy flag, transfer complete
- **Test read**
- `test_read = 1213` → a known value read from a fixed QSPI address (likely `0x9000xxxx`) – this suggests the QSPI read path is working.
- **Application vector table** (read from `0x90000000`)
- `app_stack = 134218429` → `0x0800003D`
- `app_reset = 0x0`The stack pointer value (`0x0800003D`) is suspicious – it points into internal flash (`0x08000000` region) and has the LSB set (Thumb indicator). A correct initial stack pointer for an H7 should reside in SRAM (e.g., `0x24000000` + size). The reset vector being zero means the second word is missing or invalid.
What I suspect
1. **Application binary placement** – The data at `0x90000000` might not be the actual vector table. Possibly the binary was linked for internal flash (`0x08000000`) but stored in external flash, so the first word is actually the reset handler address (hence `0x0800003D`), and the second word is zero because the vector table is misaligned or absent.
2. **QSPI memory mapping** – Although the test read works, the memory‑mapped access might have alignment or caching issues.
3. **Application linker script** – The application must be linked with `EXT_FLASH` (origin `0x90000000`) and its vector table must be placed at the very beginning of that region. If the application still uses internal flash as its load region, the vector table will contain wrong addresses.
Next steps I plan to take
- Directly read the QSPI content using non‑memory‑mapped commands (e.g., via HAL) to confirm what is actually stored at `0x90000000`.
- Verify the application’s `.map` file to see where its vector table is placed and what values it contains.
- Check that the application’s linker script defines the correct memory regions and that the vector table section (`isr_vector`) is placed first in `EXT_FLASH`.
- Ensure the bootloader correctly configures the QSPI controller for memory‑mapped mode before attempting to read the vector table.
I’d appreciate any insights or suggestions.
2026-03-20 5:46 AM
Yes this looks like right course of actions:
- Verify that the image code, data, stack are located at the correct addresses. Place is reserved in internal RAM to copy the vectors table.
- Verify that the flash contains precisely the app image - read either in memory mapped or indirect mode. If not, obviously it won't work.
2026-03-21 2:29 AM
I need to add Ethernet connectivity using LwIP (TCP/UDP echo server). I followed the article “LwIP integration in a TouchGFX project” from the ST community. After integrating LwIP (enabling it in CubeMX, adding the linker sections, creating the LwIP task), the Ethernet part works (ping replies) but the screen stays blank.
I suspect the issue is related to the MPU configuration, especially the region for QSPI memory (0x90000000). Below are the details.
In the working TouchGFX-only project, the MPU configuration looks like this (relevant parts):
// Region 0: SRAM1 (0x24000000) – cacheable
// Region 1: QSPI (0x90000000) – set to NO_ACCESS??
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.BaseAddress = 0x90000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256MB;
MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
Strangely, this configuration has QSPI as no access, but the screen still works. This leads me to believe that either the region is overridden later, or the working project actually runs from internal flash and accesses QSPI assets through a different mechanism. I'm confused.
I enabled LwIP in CubeMX, configured the Ethernet descriptors to be placed at 0x30040000 (non‑cacheable SRAM3), and added the necessary linker script section:
.lwip_sec (NOLOAD) :
{
. = ABSOLUTE(0x30040000);
KEEP(*(.RxDecripSection))
. = ABSOLUTE(0x30040100);
KEEP(*(.TxDecripSection))
. = ABSOLUTE(0x30040200);
KEEP(*(.Rx_PoolSection))
} >RAM_D2
2026-03-25 7:24 AM - edited 2026-03-25 7:24 AM
Hello @ksale.1,
To ensure your board works flawlessly, could you please try running this example and let me know if it successfully pings and if the TGFX app functions correctly?
Best regards,
2026-04-01 3:48 AM
2026-04-01 4:12 AM
Hello,
That example requires a DHCP server on your network to acquire an IP address and function properly. Please install software that provides DHCP functionality and try again, or regenerate the project after disabling the DHCP option in the LWIP general settings.
Best regards,
2026-04-03 7:09 AM
Hello,
still the
>ping 192.168.1.10 -t
Pinging 192.168.1.10 with 32 bytes of data:
Request timed out.
Request timed out.
Request timed out.
Request timed out.
Request timed out. Please note the CubeMX config. thank you for your patience
2026-04-03 8:35 AM
Hello @ksale.1,
After regenerating the project, have you re-added the following linker file configuration?
.lwip_sec (NOLOAD) :
{
. = ABSOLUTE(0x30040000);
*(.RxDecripSection)
. = ABSOLUTE(0x30040100);
*(.TxDecripSection)
. = ABSOLUTE(0x30040200);
*(.Rx_PoolSection)
} >RAM_D2
Best regards,