2025-06-18 9:19 AM - last edited on 2025-06-20 4:44 AM by mƎALLEm
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
2025-06-18 4:06 PM - last edited on 2025-06-20 4:43 AM by mƎALLEm
/*
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.
2025-06-19 11:36 AM
Thanks for your reply.
I will be able to test the MPU configuration in a few days and I will come back to it.
2025-06-19 2:54 PM - edited 2025-06-19 2:56 PM
>Region 2: 0x30008000, 64 KB for the LwIP heap
Base address of MPU region must be aligned on its size. So for base 0x30008000 the size can be only 32 KB. To cover 64KB, add another 32 KB region.
2025-06-19 8:32 PM
2025-06-20 4:42 AM
Please check out my tutorial on setting up lwIP on STM32F7 Series using CubeIDE
2025-06-20 4:45 AM
hello @LWI_GUY and @Vangelis Fortounas ,
Please use </> button to share a code. See How to insert source code.
Thank you for your undestanding.
2025-06-23 2:45 AM
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.
2025-06-23 3:31 AM
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.
2025-06-23 5:03 AM
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.