cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H750B-DK: Integrate LwIP Ethernet + TouchGFX + External QSPI Flash

ksale.1
Senior II

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!

1 ACCEPTED SOLUTION

Accepted Solutions

Hello @ksale.1,

I've analyzed your project, fixed the issues, and attached the corrected version below. Here’s what I found:

1. The names of the memory sections dedicated to the descriptors in the linker file don’t match those declared inside the ethernetif.c file.
In ethernetif.c, the section names are RxDescripSection and TxDescripSection (with an "s"):

ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDescripSection"))); /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDescripSection")));   /* Ethernet Tx DMA Descriptors */

However, in your linker file, the sections are declared as RxDecripSection and TxDecripSection (without the "s"). In the project I attached, these names match the declarations in ethernetif.c. This mismatch was causing the issue, so please ensure the names are consistent.

Here is the relevant linker section:

    .lwip_sec (NOLOAD) : 
   {
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection) 
    
    . = ABSOLUTE(0x30040100);
    *(.TxDecripSection)
    
    . = ABSOLUTE(0x30040200);
    *(.Rx_PoolSection) 
  } >RAM_D2

2. Regarding the netmask issue, in CubeMX configuration, after typing the netmask, make sure to press Enter so the value change takes effect. If it still didn't work, change it manually in lwip.c.

I didn’t need to change anything else. Please let me know if this resolves the problem, as I tested it on my side and it worked.

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.

View solution in original post

24 REPLIES 24
Pavel A.
Super User

Unfortunately the internal flash is too small for a decent app, and it is only one sector, which complicated updating the app.

For best results, look at examples for the new STM32H7R/S family which is similar to H750.

 

STackPointer64
ST Employee

Hello @ksale.1,

Have you had the chance to review this article? Please let me know if you need any further assistance or if you are still facing issues.

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.

White screen after bootloader jumps to TouchGFX application in external QSPI flash. After a long journey of debugging, I have finally reached a state where my system seems to work – except the display remains white. I'm hoping you can help me identify the last missing piece.                                                                                                     

System overview

  • Board: STM32H750B-DK (internal flash 128KB, external QSPI flash MT25TL01G, SDRAM 8MB)

  • Bootloader (in internal flash at 0x08000000:(

    • Initialises clocks, QSPI in memory‑mapped mode, and an optional LED.

    • Calls HAL_QSPI_MemoryMapped with the correct command (0xEB) and config, then jumps to 0x90000000 without de‑initialising QSPI.

    • Verified that the bootloader runs (LED blinks) and that it can read the first word of external flash before jumping (non‑0xFFFFFFFF).

  • Application (linked for external flash at 0x90000000:(   

    • Based on a CubeMX project with ETH (MII), LwIP (static IP 192.168.1.10), and TouchGFX.

    • Linker script places code and read‑only data in EXT_FLASH at 0x90000000, vector table at the start.

    • system_stm32h7xx.c has USER_VECT_TAB_ADDRESS defined and VECT_TAB_BASE_ADDRESS = 0x90000000.

    • MPU is configured (but may be incorrect for SDRAM – see below).

    • Initialisation order in main()MX_FMC_Init() before MX_LTDC_Init().

    • Framebuffer address in LTDC layer set to 0xD0000000.

    • Backlight control pin is set high in MX_GPIO_Init().

  • The symptom

    When I reset the board, the bootloader runs (LED blinks), then it jumps. The display backlight turns on, but the screen remains completely white (no TouchGFX UI, no colours). I have tried adding a simple red‑fill test right after MX_FMC_Init():

uint16_t *fb = (uint16_t *)0xD0000000;
for (int i = 0; i < 480 * 272; i++) fb[i] = 0xF800;
  • but the screen stays white. This suggests that either SDRAM is not accessible, the LTDC is not configured correctly, or the MPU blocks access.

MPU configuration 

My current MPU_Config() includes:

MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.BaseAddress = 0xD0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8MB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
  • Bootloader works (stepped through during debug, All HAL_OK 

if (HAL_QSPI_MemoryMapped(&hqspi, &sCommand, &memMapCfg) != HAL_OK) {
      Error_Handler();
  }

 

  • Application compiles without errors.

  • Flashing the application with STM32CubeProgrammer using the external loader MT25TL01G_STM32H750B-DISCO.stldr to address 0x90000000 completes successfully.

  • Ethernet is not yet tested because the display doesn't work.

  • I have checked that the backlight pin is set high (it is).

  • I have checked the LTDC layer framebuffer address in the generated code – it is 0xD0000000.

  • I have checked the initialisation order: FMC before LTDC.

Hardware: STM32H750B-DK
Tools: STM32CubeMX 6.12.1, STM32CubeIDE 1.16.0, TouchGFX 4.26.0
External flash: MT25TL01G (128MB)

Hi, yes, I'll have another fresh look and come back to you for sure, thanks

Pavel A.
Super User

Linker script places code and read‑only data in EXT_FLASH at 0x90000000, vector table at the start.system_stm32h7xx.c has USER_VECT_TAB_ADDRESS defined and VECT_TAB_BASE_ADDRESS = 0x90000000.

For best results, copy the vectors table into internal SRAM. Short quick interrupt handlers such as timer tick update can be also relocated to internal RAM.

Hello @ksale.1

As mentioned in the comments in the article, you are getting a white screen because the MPU is misconfigured. You have set Region number 5’s AccessPermission to MPU_REGION_NO_ACCESS instead of the "All Access Permitted" option mentioned in the guide.

MPU_InitStruct.Number = MPU_REGION_NUMBER5;
MPU_InitStruct.BaseAddress = 0x30020000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS; /* <----- */
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.

Based on your advise, please note this diagram of various memory regions usage to be adoptedMemory_regions_usage.jpg

please advise

Please note my current script for MPU and LwIP:                                                                                                                

 
/* MPU Configuration */

void MPU_Config(void)
{
  MPU_Region_InitTypeDef MPU_InitStruct = {0};

  /* Disables the MPU */
  HAL_MPU_Disable();

  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER3;   // use a free region number
  MPU_InitStruct.BaseAddress = 0xD0000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_8MB;    // match your SDRAM size
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; // no code execution from SDRAM
  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  MPU_InitStruct.BaseAddress = 0x30020000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Number = MPU_REGION_NUMBER2;
  MPU_InitStruct.BaseAddress = 0x30040000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_512B;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  /* Enables the MPU */
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
/**
  * LwIP initialization function
  */
void MX_LWIP_Init(void)
{
  /* IP addresses initialization */
  IP_ADDRESS[0] = 192;
  IP_ADDRESS[1] = 168;
  IP_ADDRESS[2] = 1;
  IP_ADDRESS[3] = 10;
  NETMASK_ADDRESS[0] = 0;
  NETMASK_ADDRESS[1] = 0;
  NETMASK_ADDRESS[2] = 0;
  NETMASK_ADDRESS[3] = 0;
  GATEWAY_ADDRESS[0] = 0;
  GATEWAY_ADDRESS[1] = 0;
  GATEWAY_ADDRESS[2] = 0;
  GATEWAY_ADDRESS[3] = 0;

/* USER CODE BEGIN IP_ADDRESSES */
/* USER CODE END IP_ADDRESSES */

  /* Initialize the LwIP stack with RTOS */
  tcpip_init( NULL, NULL );

  /* IP addresses initialization without DHCP (IPv4) */
  IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
  IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
  IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);

  /* add the network interface (IPv4/IPv6) with RTOS */
  netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

  /* Registers the default network interface */
  netif_set_default(&gnetif);

  /* We must always bring the network interface up connection or not... */
  netif_set_up(&gnetif);

  /* Set the link callback function, this function is called on change of link status*/
  netif_set_link_callback(&gnetif, ethernet_link_status_updated);

  /* Create the Ethernet link handler thread */
/* USER CODE BEGIN H7_OS_THREAD_NEW_CMSIS_RTOS_V2 */
  memset(&attributes, 0x0, sizeof(osThreadAttr_t));
  attributes.name = "EthLink";
  attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;
  attributes.priority = osPriorityBelowNormal;
  osThreadNew(ethernet_link_thread, &gnetif, &attributes);
/* USER CODE END H7_OS_THREAD_NEW_CMSIS_RTOS_V2 */

/* USER CODE BEGIN 3 */

/* USER CODE END 3 */
}

 

Hello @ksale.1,

You are missing some key configurations. I suggest you carefully follow the article, especially the MPU section, for the application to work correctly. Also, please ensure that the netmask you set matches the one on your computer used to ping the board (e.g., 255.255.255.0); otherwise, the application will not respond to pings.

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.