Mongoose Integration Guide: STM32

This guide shows how to add Mongoose to an existing STM32 firmware project. The example uses CubeMX, the most common STM32 workflow. Mongoose can run on top of existing TCP/IP stacks such as lwIP or Zephyr; however, this guide explains how to integrate Mongoose in standalone mode using Mongoose's built-in TCP/IP stack.

By the end of this guide, your STM32 board will:

  • use Mongoose's built-in TCP/IP stack
  • get an IP address over Ethernet using DHCP

For a quick start, you can use the following examples from the Mongoose repo:

Configure peripherals

Open CubeMX and configure the following peripherals:

  • In Pinout & Configuration → Security, enable RNG
  • In Pinout & Configuration → Connectivity, enable USART in Asynchronous mode and configure the TX and RX pins. See board pinouts for the correct pins
  • In Pinout & Configuration → Connectivity, enable ETH in RMII mode and configure the Ethernet pins. Again, see board pinouts for the correct pins
  • Click "Generate Code"

NOTE: Do not enable the Ethernet IRQ handler in CubeMX, because it enables Cube's Ethernet driver. This guide uses Mongoose's Ethernet driver.

Add Mongoose

Create a Mongoose/ directory and copy two files there from GitHub:

Create the Mongoose/mongoose_config.h file:

// Mongoose configuration file. For a full list of build options, see
// https://mongoose.ws/docs/getting-started/build-options/

#pragma once
#define MG_ARCH MG_ARCH_CUBE

Add the Mongoose/ directory to the include paths, and add Mongoose/mongoose.c to the build. The exact steps depend on your chosen IDE. For VS Code CMake, edit the CMakeLists.txt file in the project root:

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
    ${CMAKE_SOURCE_DIR}/Mongoose/mongoose.c
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user-defined include paths
    ${CMAKE_SOURCE_DIR}/Mongoose/
)

As an alternative to the steps above, instead of copying files from GitHub, you can enable the I-CUBE-Mongoose software pack in CubeMX. That will copy those three files to Middlewares/Third_Party/Cesanta_Mongoose/.

Configure .bin file generation

If you're using CubeIDE, select Project / Properties / C/C++ build / Settings / MCU/MPU Post build outputs, enable "Convert to binary file (-O binary)"

If you're using VSCode, add this section to the end of the top-level CMakeLists.txt:

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
  COMMAND ${CMAKE_OBJCOPY} -O binary
          ${PROJECT_NAME}.elf
          ${PROJECT_NAME}.bin
)

Update linker script

Open the .ld linker script in the project root, and verify the configured memory region for the .data section. If that memory region is not accessible by the Ethernet DMA controller, add the following line after the .data rules. You may need to change RAM_D2 to the DMA-accessible region:

/* The Mongoose driver puts DMA buffers into the .eth_ram ELF section. It should be accessible by the ETH DMA */
.eth_ram : { *(.eth_ram .eth_ram*) } > RAM_D2 AT > FLASH

Modify main.c

In Core/Src/main.c, add the Mongoose include. Make sure to add the following snippets inside the BEGIN/END markers to prevent CubeMX from deleting them during code regeneration:

/* USER CODE BEGIN Includes */
#include "mongoose.h"
/* USER CODE END Includes */

In Core/Src/main.c, add a log_fn callback to route Mongoose log output to UART:

/* USER CODE BEGIN 0 */
static void log_fn(char ch, void *param) {
  HAL_UART_Transmit(param, (unsigned char *) &ch, 1, HAL_MAX_DELAY);
}
/* USER CODE END 0 */

In Core/Src/main.c, initialize the event manager, set the log function, and run the event loop:

  /* USER CODE BEGIN WHILE */
  struct mg_mgr mgr;
  mg_mgr_init(&mgr);
  mg_log_set_fn(log_fn, &huart3);

  while (1)
  {
    mg_mgr_poll(&mgr, 1);
    /* USER CODE END WHILE */

Build and run

Start a serial console, rebuild, and flash the device. In the serial logs, you should see something like this:

0      2 mongoose.c:26153:mg_phy_init   PHY ID: 0x07 0xc131 (LAN87x)
6      2 mongoose.c:5834:mg_mgr_init    Driver: stm32h, MAC: 2a:37:94:03:d5:74
d      3 mongoose.c:5841:mg_mgr_init    MG_IO_SIZE: 512, TLS: builtin
13     3 mongoose.c:5758:mg_listen      1 0 http://0.0.0.0:80
19     1 mongoose.c:6260:onstatechange  Link down
7d6    3 mongoose.c:28424:mg_tcpip_driv Link is 100M full-duplex
7db    3 mongoose.c:6404:tx_dhcp_discov DHCP discover sent. Our MAC: 2a:37:94:03:d5:74
831    3 mongoose.c:6382:tx_dhcp_reques DHCP req sent
836    2 mongoose.c:6559:rx_dhcp_client Lease: 3600 sec (3602)
83c    2 mongoose.c:6249:onstatechange  READY, IP: 192.168.2.31
842    2 mongoose.c:6250:onstatechange         GW: 192.168.2.1
847    2 mongoose.c:6253:onstatechange        MAC: 2a:37:94:03:d5:74

Now you can ping the device using the IP address printed in the logs, and integrate a Web UI device dashboard.

Check the Troubleshooting section if it does not work as expected.

Troubleshooting

No IP address is printed
Check Ethernet cable, PHY pins, RMII configuration, DHCP server, and board pinout.

No free descriptors

If you see the "No free descriptors:" message, the likely cause is the missing/incorrect .eth_ram snippet in the linker script, or the wrong Ethernet pins; double-check the list against your device documentation.

No DHCP server

If you see log messages like this:

130b0  3 mongoose.c:4776:tx_dhcp_discov DHCP discover sent. Our MAC: 02:03:04:05:06:07
13498  3 mongoose.c:4776:tx_dhcp_discov DHCP discover sent. Our MAC: 02:03:04:05:06:07

One possible reason is that the transmission side of the driver is not working. Run Wireshark on the Ethernet network, filter for UDP ports 67 and 68. If you do not see messages from your device, then transmission is not working; check the pins and the linker script.

Another possible reason is that no DHCP server is running on the network your device is connected to. That usually happens when a device is connected back-to-back with the workstation.

The easiest option is to use a USB-to-Ethernet dongle and enable Internet sharing on the Ethernet interface.

FAQ

Do I need LwIP for this?
No. This guide uses Mongoose's built-in TCP/IP stack instead of LwIP.

Should I enable the Ethernet IRQ handler in CubeMX?

No. Do not enable CubeMX's Ethernet IRQ handler, because that enables Cube's Ethernet driver. This guide uses Mongoose's Ethernet driver.

Why is .eth_ram needed on STM32H7?

STM32H7 Ethernet DMA needs buffers in DMA-accessible memory. The .eth_ram section ensures Ethernet buffers are placed correctly.

How do I know the network is working?

Check the serial log for READY, IP: ..., then open that IP address in a browser.