Add TCP/IP stack to STM32
This guide shows how to add a TCP/IP stack to an existing STM32 firmware project. The example uses STM32 Cube and CubeMX, which is the most common STM32 workflow.
The target board is Nucleo-H723ZG with built-in Ethernet, but the same integration approach applies to other STM32 devices and networking hardware.
Mongoose provides a built-in TCP/IP stack. In this guide, we integrate it into an STM32 firmware project and run a minimal HTTP server.
Copy Mongoose files
Mongoose is distributed as two core source files. The easiest way to integrate it is to copy them directly into your project.
Create a mongoose/ directory in the project root:
project/
... your project files ...
mongoose/
mongoose.c
mongoose.h
mongoose_config.h
Download mongoose.c and
mongoose.h from the Mongoose repository and create mongoose_config.h:
#define MG_ARCH MG_ARCH_CUBE
#define MG_ENABLE_TCPIP 1
MG_ARCH_CUBE enables the STM32 Cube environment.MG_ENABLE_TCPIP enables the built-in TCP/IP stack.
Set Network Driver
Embedded networking typically follows a layered architecture:
driver → TCP/IP stack → TLS → protocols (HTTP, WebSocket, MQTT) → application
See TCP/IP stack for embedded devices for a detailed explanation.
In this section, we configure the lowest layer - the network driver, which connects your device to the network.
STM32 and other microcontrollers support multiple networking options:
- built-in Ethernet MAC
- external Ethernet controllers (e.g., W5500)
- WiFi modules (e.g., CYW43439)
- cellular modems
Mongoose provides built-in drivers for common embedded networking hardware, making it easy to bring up connectivity and build web-based device interfaces. See full list of built-in drivers.
In particular, Mongoose includes drivers for STM32 microcontrollers with a built-in Ethernet MAC. The Nucleo-H723ZG uses its internal Ethernet MAC, so enable the STM32 Ethernet driver in mongoose_config.h:
#define MG_ENABLE_DRIVER_STM32H 1
#define MG_ETH_RAM __attribute__((section(".eth_ram")))
For STM32F devices, use MG_ENABLE_DRIVER_STM32F.
Then configure the hardware in CubeMX:
- enable the Ethernet peripheral
- configure Ethernet pins
- enable a UART for debug output
Update the linker script
The MG_ETH_RAM definition places Ethernet DMA buffers into a memory region accessible by the MAC DMA engine. On STM32, the Ethernet DMA controller can access only specific RAM regions, so it is critical to map the .eth_ram section to a DMA-accessible area in the linker script. If buffers end up in the wrong memory region, Ethernet will not work.
This is how to choose the correct memory region for Ethernet DMA:
- Open the MCU reference manual. For STM32H723ZG, use RM0468.
- Locate the "Embedded SRAM" section (for STM32H723ZG, section 2.4).
- Identify which SRAM regions belong to which domains. For STM32H723ZG:
- 128 Kbytes of AXI-SRAM mapped onto the AXI bus on D1 domain
- 64 Kbytes of instruction TCM RAM
- 128 Kbytes of data TCM RAM
- 192 Kbyte SRAM on D1 domain
- 16 Kbyte SRAM1 mapped on D2 domain
- 16 Kbyte SRAM2 mapped on D2 domain
- 16 Kbyte SRAM4 mapped on D3 domain
- Check the system architecture section (2.1) to determine the Ethernet DMA domain. On STM32H723ZG, the Ethernet MAC is connected to the D2 domain.
- Use only SRAM regions in the same domain as Ethernet DMA. For STM32H723ZG, this means SRAM1, SRAM2, SRAM3 (D2 domain)
Update the linker script and define the .eth_ram section:
MEMORY {
RAM_D2(rwx): ORIGIN = 0x30000000, LENGTH = 32K
}
.eth_ram : { *(.eth_ram .eth_ram*) } > RAM_D2 AT > ROM
Create a minimal HTTP server
Open Core/Src/main.c. Include Mongoose header at the top:
#include "mongoose.h"
Before the main() function, add the following two functions:
// Redirect `printf()` to UART so debug output is visible:
int _write(int fd, unsigned char *buf, int len) {
HAL_UART_Transmit(&huart3, buf, len, HAL_MAX_DELAY);
return len;
}
// HTTP server event handler
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/hello"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%d}\n", MG_ESC("status"), 1);
} else {
mg_http_reply(c, 200, "", "hello world\n");
}
}
}
Modify your main() function main loop - before the loop, add
Mongoose instance initialisation, and inside the loop, add mg_mgr_poll() call:
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
while (1) {
mg_mgr_poll(&mgr, 0);
The mg_mgr_poll() processes packets, parses protocols, and calls your event handler when something happens.
Build and run
Add the mongoose/ directory to the project include paths and build the firmware.
Open a serial console. Mongoose prints the assigned IP address during startup. Open that address in a browser and the board should reply with:
hello world
At this point your STM32 firmware has a working TCP/IP stack and HTTP server powered by Mongoose.
Using Web UI builder
Here is a quick step-by-step guide to add Mongoose with a web dashboard to an existing CubeMX-based project:
- In CubeMX, configure debug UART, Ethernet, and RNG (for TLS)
- Go to Web UI builder, start a new project, select destination directory where your project lives
- Choose the board, dashboard template, click finish
- Click generate - do not cleanup the destination. That should create
mongoosedirectory in the project - Modify CMakeLists.txt:
- Add mongoose/*.c files to source
- Add mongoose/ to includes
- Add this snippet to generate .bin:
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary
${PROJECT_NAME}.elf
${PROJECT_NAME}.bin
)
- Edit mongoose/mongoose_config.h, make sure it has this:
#define MG_ARCH MG_ARCH_CUBE
#define MG_TLS MG_TLS_BUILTIN
#define MG_OTA MG_OTA_STM32F
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_PACKED_FS 1
#define MG_ENABLE_DRIVER_STM32F 1
- Edit Core/Src/main.c:
- Add
#include "mongoose_glue.h - Add
_write()override as described above - Add
mongoose_init()andmongoose_poll()calls tomain()