How to get started with Mongoose Ethernet Library
We have covered the STM32 Ethernet tutorial series in the past using the LwIP. Even though the LwIP was part of the ST’s cube package, a lot of users had issues to make the ethernet work on STM32 devices. Today I have a better alternative for the LwIP, the Mongoose library. Mongoose is a network library for C/C++. It provides event-driven non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT, and other protocols.
Over the next few tutorials, we will see how to use the Mongoose library with STM32. We will also cover some interesting applications involving Mongoose, ethernet and STM32.
Today in this tutorial we will see how to integrate the Mongoose library with minimal configuration. We will simply copy the library header and source files, and that’s it, we will have a working ethernet on our STM32.
We will follow the guide in the Mongoose docs, https://mongoose.ws/documentation/tutorials/device-dashboard/#action-plan. As per this guide, below are the steps to implement the library on your dev board:
- Build a minimal firmware on a hardware board
- Add Mongoose Library and make TCP/IP stack work
- Add web server or whatever you want
Now we will discuss these steps in detail. Let’s start with the first one, Building a skeleton firmware.
Step1 -> Skeleton Firmware
A skeleton firmware is a minimal firmware on a hardware board, which perform the minimum setup required for the reference projects:
- Set CPU clock to the maximum frequency,
- Set up LED pins to GPIO output mode, and blink an LED,
- Set up network hardware, e.g. initialise Ethernet pins,
- Set up UART and prints debug log on a console
I am using a STM32H745 Discover board for this project and now I will create a skeleton firmware for this board.
CubeMX Configuration
Below is the image showing the clock configuration.
The board has 25MHz crystal and I am running the system at 400MHz.
Next we will configure the ethernet peripheral.
The pins configured by the cube are sometimes not correct as per the schematic of the board. Therefore you must cross check these pins with the schematic. In this case, the pin PH6 was configured as the ETH_RXD2, whereas in the schematic it is the pin PB2. Therefore I configured PB2 as ETH_RXD2.
The parameter configuration in the Ethernet should be kept to default.

We also need to enable the interrupt for the Ethernet. But make sure to uncheck the IRQ handler Generation in the NVIC configuration.
Next we will configure the UART. Below is the image showing the USART3 configuration.
The UART is configured in the Asynchronous mode. The Baud Rate is set to 115200 with 8 Data bits, 1 Stop bit and no parity.
We will also configure a LED for testing purpose and later we will use it with the web server. Below are the images showing the LED configuration.
Make sure to assign the Pin to the correct core in case of a dual core architecture.
Next we will configure the Random Number Generator. If your hardware does not support RNG, you can skip this. This RNG will be used by the mongoose to generate a random Mac address for the device, but mongoose do have inbuilt functions to do the job using software itself.
The code
Now we will write some code to verify if everything we configured is working correctly. This is going to be a basic test before we start implementing the mongoose library.
Above the main function, define the following.
/* USER CODE BEGIN 0 */
int _write(int fd, unsigned char *buf, int len) {
if (fd == 1 || fd == 2) { // stdout or stderr ?
HAL_UART_Transmit(&huart3, buf, len, 999); // Print to the UART
}
return len;
}
uint64_t mg_millis(void) {
return HAL_GetTick();
}
/* USER CODE END 0 */
Now inside the main function print the uptime inside the while loop.
int main ()
{
....
while (1){
printf("Tick: %lu\r\n", HAL_GetTick());
HAL_Delay(500);
}
}
The Output
Below is the image showing the output of the above code on the serial console.
You can see the uptime is printing every 500ms. This means that our skeleton firmware is ready to be used.
Step2 -> Integrate Mongoose
Here we will first create a new src file, mongoose.c and 2 new header files, mongoose.h and mongoose_config.h. This is shown in the image below.
Now copy the content of the mongoose.c and mongoose.h files from github page and paste it in the mongoose.c and mongoose.h files we created in our project. There is no modification needed in these 2 files. All the configuration will be managed in the mongoose_config.h file.
Below is the content of the mongoose_config.h file.
#pragma once
#define MG_ARCH MG_ARCH_NEWLIB // For all ARM GCC based environments
#define MG_ENABLE_TCPIP 1 // Enables built-in TCP/IP stack
#define MG_ENABLE_CUSTOM_MILLIS 1 // We must implement mg_millis()
#define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 1 // Enable debug stats log
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_TCPIP_PHY_ADDR 1
// For static IP configuration, define MG_TCPIP_{IP,MASK,GW}
// By default, those are set to zero, meaning that DHCP is used
#define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10) // IP
#define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1) // Gateway
#define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0) // Netmask
// Uncomment the driver for your device
#define MG_ENABLE_DRIVER_STM32H 1
// #define MG_ENABLE_DRIVER_STM32F 1
Here we are enabling the Mongoose TCP/IP stack by defining the #define MG_ENABLE_TCPIP 1
. As I mentioned in the cube configuration, we will use the hardware Random Number Generator (RNG), therefore I am enabling the custom random by defining #define MG_ENABLE_CUSTOM_RANDOM 1
.
The PHY address is already defined for the STM32F and H series dev board. But in case if the project does not work, you should define a custom PHY address by #define MG_TCPIP_PHY_ADDR 1
. The PHY address can vary from 0 to 4 for different devices. The default for the STM32 boards is set to 0.
You also need to uncomment the driver for your dev board. Since I am using STM32H745, I have uncommented the #define MG_ENABLE_DRIVER_STM32H 1
.
By default, the Mongoose is set to use the DHCP. If you have connected the Ethernet cable to a router, you can use the DHCP to assign an IP address for the device. In such case, you can get the address from the UART logs. Although I am connecting the Ethernet cable directly to the computer, hence I need to use a static IP. The static IP, Gateway and Netmask are all defined in the mongoose_config.h file itself.
The main file
First of all define a mg_random function, so that the mongoose can use it to generate the random Mac address for the device.
/* USER CODE BEGIN 0 */
bool mg_random(void *buf, size_t len) { // Use on-board RNG
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r;
HAL_RNG_GenerateRandomNumber(&hrng, &r);
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true; // TODO(): ensure successful RNG init, then return on false above
}
/* USER CODE END 0 */
Inside this function, we will make a call to the hardware RNG to generate the random numbers.
Now create a run_mongoose function, which will handle all the network related events.
// In RTOS environment, run this function in a separate task. Give it 8k stack
static void run_mongoose(void) {
struct mg_mgr mgr; // Mongoose event manager
mg_mgr_init(&mgr); // Initialise event manager
mg_log_set(MG_LL_DEBUG); // Set log level to debug
for (;;) { // Infinite event loop
mg_mgr_poll(&mgr, 0); // Process network events
}
}
This function has an infinite loop of its own. Therefore it should be called after all the initialisations are over, basically before the while loop.
Inside the main function we will simply call the run_mongoose function before the while loop. It will manage the rest.
int main()
{
....
/* USER CODE BEGIN 2 */
run_mongoose();
/* USER CODE END 2 */
while (1)
{}
}
Other Configuration
If you are connecting the STM32 board to the Router, there is nothing you need to do at the computer end. But if you are connecting the ethernet cable directly to the computer, you need to configure your computer’s ethernet as per the images shown below.
Below is the configuration for a Windows computer.
Below is the configuration for Mac.
The test
When the project runs on the board, you should see the logs on the serial console as shown in the image below.
The Red Line shows that a random mac is generated at the beginning. If the logs are stuck at this line itself, it means either the Ethernet pins are not configured correctly, or the PHY address is wrong.
The Blue Line shows that the PHY is initialised.
The Green box shows that the link is now UP and running with the IP address 192.168.0.10. If you are using DHCP, you will see something like as shown below.
We can now ping the board to confirm the Ethernet working.
You can see in the image above, the dev board is responding to the ping. Therefore we have a working driver now.
Create a Simple Web server
Along with the ping test, we will also test a simple web server on our dev board. This is part of the tutorial available at https://mongoose.ws/documentation/tutorials/device-dashboard/#action-plan. Below are the steps to create a simple web server for controlling the LED on board.
Step 1. Before the infinite loop inside the run_mongoose function, add this line that creates HTTP listener with event_handler
event handler function:
mg_http_listen(&mgr, "http://0.0.0.0:80", event_handler, NULL);
Step 2. Add the event_handler()
function before run_mongoose()
:
static void event_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/led/get"), NULL)) {
mg_http_reply(c, 200, "", "%d\n", HAL_GPIO_ReadPin(GPIOJ, GPIO_PIN_2)); // change the LED pin
} else if (mg_match(hm->uri, mg_str("/api/led/toggle"), NULL)) {
HAL_GPIO_TogglePin(GPIOJ, GPIO_PIN_2); // change the LED pin
mg_http_reply(c, 200, "", "true\n");
} else {
struct mg_http_serve_opts opts = {.root_dir = "/web_root", .fs = &mg_fs_packed};
mg_http_serve_dir(c, hm, &opts);
}
}
}
The above function handles the HTTP events. Here we are simply controlling the LED based on the input from the client. You need to change the LED pin according to the board configuration.
Step 3. Add the following lines to mongoose_config.h
:
#define MG_ENABLE_PACKED_FS 1 // Enable "embedded", or packed, filesystem
#define MG_ENABLE_POSIX_FS 0 // Disable POSIX filesystem
Step 4. Visit https://mongoose.ws/ui-pack/, and copy generated packed_fs.c
to the project.
Step 5. Rebuild and re-flash, load the UI and see how LED control works.
The test
Open the browser on your computer and go to 192.168.0.10. You will see a webpage for controlling the LED on board.
As you can see in the gif above, the LED on board is responding to the button on the webpage.