Skip to main content
Visitor II
June 18, 2025
Question

STM32H7 + LwIP: Problems with MPU and Custom Heap at 0x30008000 – No Packets Received

  • June 18, 2025
  • 10 replies
  • 1419 views

Hi everyone,

I'm working on a project using an STM32H743 and LwIP (v2.1.2) without an RTOS (NO_SYS=1), and I've been debugging persistent issues related to LwIP's heap allocation and MPU configuration. After days of digging, I’d really appreciate the community's input.

What I'm trying to do:

I want to place the LwIP heap in RAM_D2 at address 0x30008000, enable MPU regions for protection, and successfully use Ethernet and ping using LwIP.

My configuration:

.ld file (.lwip_heap section):

.lwip_heap (NOLOAD) :
{
. = ABSOLUTE(0x30008000);
KEEP(*(.lwip_heap))
. = ALIGN(4);
} >RAM_D2

In mem.c:

attribute((section(".lwip_heap"))) attribute((used))
u8_t lwip_heap[MEM_SIZE];

#define LWIP_RAM_HEAP_POINTER ((void*)lwip_heap)

In lwipopts.h:

#define NO_SYS 1
#define MEM_ALIGNMENT 4
#define MEM_SIZE (64*1024)
#define MEM_LIBC_MALLOC 0
#define LWIP_SUPPORT_CUSTOM_PBUF 1

The symptoms:

  • With MPU disabled: I can receive ARP requests and the STM seems to process them correctly by trying to send a broadcasting ARP Response. But the only thing I can see in Wireshark is the following entry:
    02:00:00:00:00:01 SchneiderEle_a5:92:d0 0x4d35 60 Ethernet II
    The first MAC Address makes sense as I gave it to the STM via config, but the rest doesn't make sense at all.

  • With MPU enabled: the heap at 0x30008000 is not written properly, mem_init() fails silently, and LwIP crashes or behaves unexpectedly.

I got it to work once and i could receive and respond to arp packages and ping requests, but not any more, which seems quite weird.

MPU Configuration:

I configured two regions:
Region 1: 0x30000000, 32 KB for DMA descriptors & pbuf pool
Region 2: 0x30008000, 64 KB for the LwIP heap

My MPU Config with Region 2 commented out:

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

/* Disables the MPU */
HAL_MPU_Disable();

/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x0;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.SubRegionDisable = 0x87;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_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 = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);


/*
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.BaseAddress = 0x30008000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
*/


/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

}

My MwIP Stackup looks as follows:
RxDescripSection 0x30000000
TxDescripSection 0x30000080
RxBufferSection 0x30000100 (RxBufferLength 1524 and RxBufferCNT 12)
LwIP HEAP 0x30008000 (MEM_SIZE 0x10000)

 

What I’ve tried so far:

  • Disabling/enabling D-Cache and I-Cache

  • Using attribute((used)) and KEEP() in the linker script

  • Verifying .map file shows lwip_heap placed at 0x30008000

  • Debugging mem pointer after mem_init()

  • Checking Ethernet TX via ethernet_output() and linkoutput()

  • Confirming headers are correct before linkoutput(), but incorrect on wire

The issue:

As soon as I enable the MPU region for 0x30008000, writing to lwip_heap no longer works. mem becomes invalid, allocations fail, and Ethernet frames on the wire have corrupted headers. No ICMP replies, no ARP responses. With the MPU region disabled, I can receive packages but cannot respond to them correctly.

My question:

What is the correct MPU configuration for 0x30008000 to safely use it as non-cacheable memory for lwip_heap with LwIP?

Also:

  • Do I need to align anything else?

  • Is there a required TEX/C/B/S config for LwIP memory?

  • Am I missing something obvious regarding MPU or memory attributes?

Bonus Info:

STM32CubeIDE 1.18.0, default HAL drivers, no RTOS.

Thanks in advance for any help – this has been a very tricky issue to debug!
Or is there any other solution besides LwIP to establish communication over ethernet.
P.S.: I am using a custom PCB but I used the layout of the STM32H743VIH6 Nucleo Board for the basic functionality including a LAN8742A PHY IC.

Best regards,
Tobias

    This topic has been closed for replies.

    10 replies

    Visitor II
    June 18, 2025
    /*
    MPU_InitStruct.Number = MPU_REGION_NUMBER2;
    MPU_InitStruct.BaseAddress = 0x30008000;
    MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.SubRegionDisable = 0x0;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    */

    Try this configuration..  Works fine.

     

    LWI_GUYAuthor
    Visitor II
    June 19, 2025

    Thanks for your reply.
    I will be able to test the MPU configuration in a few days and I will come back to it.

    Graduate
    June 20, 2025
    Graduate
    June 20, 2025

    Please check out my tutorial on setting up lwIP on STM32F7 Series using CubeIDE

    https://www.youtube.com/watch?v=A8ii-t40r-w

    Technical Moderator
    June 20, 2025

    hello @LWI_GUY and @Vangelis Fortounas ,

    Please use </> button to share a code. See How to insert source code.

    Thank you for your undestanding.

    LWI_GUYAuthor
    Visitor II
    June 23, 2025

    Hi everyone,

    I just wanted to give you a quick update on this issue.

    I tried your suggestions regarding the MPU configuration and the faulty memory region. However, the same issue still persists.

    The code fails in the following section of mem.c within the LWIP stack:

    memset(ram, 0, MEM_SIZE);
    /* initialize the start of the heap */
    mem = (struct mem *)(void *)ram;
    mem->next = MEM_SIZE_ALIGNED;
    mem->prev = 0;
    mem->used = 0;

    There’s still a failure when assigning the variable mem, as the specified memory region does not get updated. It does get updated when I disable MPU region 2.

    That said, I managed to get ARP working by modifying the code in ethernetif.c, specifically in the low_level_output() function. Here is my current version of the function:

    static err_t low_level_output(struct netif *netif, struct pbuf *p)
    {
     uint32_t i = 0U;
     struct pbuf *q = NULL;
     err_t errval = ERR_OK;
     ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};
    
     memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef));
    
     for(q = p; q != NULL; q = q->next)
     {
     if(i >= ETH_TX_DESC_CNT)
     return ERR_IF;
    
     Txbuffer[i].buffer = q->payload;
     Txbuffer[i].len = q->len;
    
     // New code beginning
     uint32_t addr = (uint32_t)q->payload;
     uint32_t aligned_addr = addr & ~0x1F;
     uint32_t end_addr = addr + q->len;
     uint32_t aligned_len = ((end_addr - aligned_addr + 31) & ~0x1F);
    
     SCB_CleanDCache_by_Addr((uint32_t *)aligned_addr, aligned_len);
     // New code ending
    
     if(i>0)
     {
     Txbuffer[i-1].next = &Txbuffer[i];
     }
    
     if(q->next == NULL)
     {
     Txbuffer[i].next = NULL;
     }
    
     i++;
     }
    
     TxConfig.Length = p->tot_len;
     TxConfig.TxBuffer = Txbuffer;
     TxConfig.pData = p;
    
     HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);
    
     return errval;
    }

    I’ve marked the newly added code using comments.

    What I’m still wondering is why this cache-cleaning code is necessary here and why the MPU setup doesn’t seem to work as expected.

    Super User
    June 23, 2025

    As stated earlier, your MPU regions 1 and 2 are invalid and can cause UB. Base address of a region must be aligned on its size.

     

    LWI_GUYAuthor
    Visitor II
    June 23, 2025

    Hi Pavel,
    below is the updated MPU configuration, which I was also using for the post above:

    void MPU_Config(void)
    {
     MPU_Region_InitTypeDef MPU_InitStruct = {0};
    
     /* Disables the MPU */
     HAL_MPU_Disable();
    
     /** Initializes and configures the Region and the memory to be protected
     */
     MPU_InitStruct.Enable = MPU_REGION_ENABLE;
     MPU_InitStruct.Number = MPU_REGION_NUMBER0;
     MPU_InitStruct.BaseAddress = 0x0;
     MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
     MPU_InitStruct.SubRegionDisable = 0x87;
     MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
     MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS;
     MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
     MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
     MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
     MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_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 = 0x30000000;
     MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
     MPU_InitStruct.SubRegionDisable = 0x0;
     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
     MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
     MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
     MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
     MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
     MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
    
     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
     /*
     MPU_InitStruct.Number = MPU_REGION_NUMBER2;
     MPU_InitStruct.BaseAddress = 0x30008000;
     MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
     MPU_InitStruct.SubRegionDisable = 0x0;
     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
     MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
     MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
     MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
     MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
     MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
    
     HAL_MPU_ConfigRegion(&MPU_InitStruct);
     */
    
    
     /* Enables the MPU */
     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    
    }

    When I enable Region 2, I get the behavior as described above. Region 1 seems to be working fine.
    I can't see where I might have made a mistake in the MPU configuration.

    Technical Moderator
    June 23, 2025

    Hello,

    Question: why are you setting the MPU background region size to 256k?

     MPU_InitStruct.Number = MPU_REGION_NUMBER0;
     MPU_InitStruct.BaseAddress = 0x0;
     MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
     MPU_InitStruct.SubRegionDisable = 0x87;

    That region needs to be 4G:

     MPU_InitStruct.Number = MPU_REGION_NUMBER0;
     MPU_InitStruct.BaseAddress = 0x0;
     MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;
     MPU_InitStruct.SubRegionDisable = 0x87;

     

     

     

    Super User
    June 23, 2025

    why are you setting the MPU background region size to 256k?

    In that code, region 0 is not the background. It probably catches null pointer references.

     

    Technical Moderator
    June 23, 2025

    This setting means that the MPU config was used as a background region:

    MPU_InitStruct.SubRegionDisable = 0x87;

     

    LWI_GUYAuthor
    Visitor II
    June 24, 2025

    Hi everyone,

    I've noticed that I don't need the extra code in ethernetif.c, and I also don't need to adjust the background region of the MPU.

    However, for everything to work properly, I currently need to disable the Data Cache entirely.
    That said, I would like to enable the Data Cache in my upcoming project, where I plan to use a REST API to control the STM32 and retrieve data.

    Unfortunately, I can't get it working properly when I define MPU regions to manage caching behavior. Does anyone know why the STM behaves like this?

    I do understand that enabling the Data Cache can lead to inconsistencies between the CPU cache and RAM, especially since the Ethernet DMA won't see the updated data. But why doesn't it help to define MPU regions correctly to disable caching just for the buffers used by the DMA?

    Any ideas or experiences with this would be greatly appreciated.

    Technical Moderator
    June 24, 2025

    @LWI_GUY wrote:

    and I also don't need to adjust the background region of the MPU.


    You need to enable the MPU background region to prevent any CM7 read speculation issue .

    Also please refer to the AN4839 "Level 1 cache on STM32F7 Series and STM32H7 Series"

    Super User
    June 24, 2025

    With a dual core STM32H7 you can handle ETH with the CM4 core as it has no D cache anyway. Do other work with the CM7 core, cache enabled.