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 the "Project Manager" tab, set heap size at least to 64Kb (0x10000), and stack size at least to 8Kb (0x2000)
- 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 the following files there from GitHub:
- mongoose.c - source file with implementation
- mongoose.h - header file with API
- sign.js - for firmware digital signing. only required if you use firmware OTA
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.
Secure Firmware Updates
If you use Mongoose OTA Manager, enable digital signing for your firmware. In the terminal, generate public and private keys for your digital signature. In the project root, run:
node mongoose/sign.js keygen
Wrote private.pem (keep secret, never ship in firmware)
Add to your mongoose_config.h:
#define MG_OTA_PUBLIC_KEY { \
....
}
The "#define MG_OTA_PUBLIC_KEY ..." is your public key. Copy/paste it into your mongoose_config.h.
Then, rebuild your firmware. From now on, your firmware will accept
only firmwares which are signed by the private.pem. Keep it private!
To sign your firmware, run:
node mongoose/sign.js build/Debug/PROJECT_NAME.bin
This will create build/Debug/PROJECT_NAME.signed.bin. The sign.js script
adds a 64-byte P-256 signature block followed by 4-bytes MGSG marker, so we
can detect if the given .bin file was signed or not. Mongoose OTA code uses
MG_OTA_PUBLIC_KEY from mongoose_config.h to verify the signature.
To sign your builds automatically, add this snippet to your CMakeLists.txt:
If you use Mongoose OTA Manager and wish to use secure updates, add this snippet for automatic firmware digital signing:
if (EXISTS ${CMAKE_SOURCE_DIR}/private.pem)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND node ${CMAKE_SOURCE_DIR}/mongoose/sign.js sign ${PROJECT_NAME}.bin ${CMAKE_SOURCE_DIR}/private.pem
VERBATIM
)
endif()
Login to Mongoose OTA Manager. Click on the
"info" toolbar button to trigger the instructions. Copy-paste configuration
snippet to your mongoose_config.h that define MG_OTA_URL and MG_OTA_FIRMWARE_VERSION.
Rebuild and reflash your firmware. Now it should appear in the device list. Make changes to your firmware, upload the signed binary, assign it to your devices - and they will pull periodically and uppdate automatically.
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.