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.