Mongoose Integration Guide: RP2040 / RP2350
This guide shows how to add Mongoose to an existing Raspberry Pi Pico SDK firmware project for RP2040 and RP2350 boards. 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 RP2040 or RP2350 board will:
- use Mongoose's built-in TCP/IP stack
- get an IP address using DHCP
- serve a minimal HTTP endpoint
For a quick start, use the following examples from the Mongoose repo. Note that the RP2040 example can be built for RP2350 and vise versa - just change the PICO_BOARD in the CMakeLists.txt.
- W5500-EVB-Pico for RP2040/RP2350 with W5500 Ethernet
- W55RP20-EVB-Pico for W55RP20 Wiznet chip
- Pico W for RP2040/RP2350 with CYW43 Wi-Fi
- Pico RMII for RP2040/RP2350 with LAN8720 RMII Ethernet
- Pico RM2 for RP2040/RP2350 with RM2 WiFi module (CYW43439)
- Pico RNDIS dashboard for RP2040/RP2350 Ethernet over USB
Add Mongoose
Create a mongoose/ directory in your Pico SDK project and copy two files there
from GitHub:
Create the mongoose/mongoose_config.h file. Copy one of the following:
- mongoose_config.h - for W5500 Ethernet
- mongoose_config.h - for W55RP20 Wiznet Ethernet
- mongoose_config.h - for Pico W / Pico 2 W Wi-Fi
- mongoose_config.h - for LAN8720 RMII Ethernet
- mongoose_config.h - for RM2 Wi-Fi
- mongoose_config.h - for Ethernet over USB
Add Mongoose sources to your firmware target, add the mongoose/
directory to the include paths, and link the Pico SDK libraries required by your
network driver.
file(GLOB MONGOOSE_SOURCES mongoose/*.c)
add_executable(firmware main.c ${MONGOOSE_SOURCES})
target_include_directories(firmware PRIVATE mongoose)
If your project enables OTA updates, make sure your mongoose_config.h has:
#define MG_OTA MG_OTA_PICOSDK
and prevent aggressive loop optimization in the firmware target:
target_compile_options(firmware PRIVATE -fno-tree-loop-distribute-patterns)
Choose a network driver
Mongoose's built-in TCP/IP stack needs one low-level network driver. On Pico SDK projects, the most common choices are:
MG_ENABLE_DRIVER_W5500for W5500-based Ethernet boards and modulesMG_ENABLE_DRIVER_PICO_Wfor Pico W / Pico 2 W Wi-Fi using the CYW43 chip- a project-local RMII driver for an external PHY such as LAN8720
- a project-local USB network driver that bridges Mongoose to TinyUSB RNDIS or CDC-ECM callbacks
Use the W5500 path for wired Ethernet, including the W5500-EVB-Pico2 example. Use the Pico W path for wireless networking on Pico W and Pico 2 W boards. Use the RMII path when you want Ethernet through an external PHY and are prepared to allocate PIO state machines and DMA channels. Use the USB path when the board should appear to the host computer as a USB network adapter.
Network hardware options
W5500 Ethernet
W5500 is the simplest wired option. It is available on boards such as
W5500-EVB-Pico and W5500-EVB-Pico2, or as a small add-on module connected over
SPI. The default example wiring uses spi0:
| W5500 | Raspberry Pi Pico / Pico 2 |
|---|---|
| MISO | GPIO16 / SPI0_RX |
| SCS / SCNn | GPIO17 / SPI0_CSn |
| SCLK | GPIO18 / SPI0_SCK |
| MOSI | GPIO19 / SPI0_TX |
| GND | GND |
| 3.3V | 3V3 |
If you use a board with the W5500 already fitted, keep the example pin mapping.
If you use another W5500 module, change the SPI pin constants in main.c to
match your wiring. The W5500 driver also needs a locally administered MAC
address; deriving one from the Pico unique board ID is a practical default for
development.
Pico W / Pico 2 W Wi-Fi
Pico W and Pico 2 W use the onboard CYW43 wireless chip. There is no external
network wiring, but the Pico SDK board type must be set to pico_w or
pico2-w so the SDK enables the wireless support libraries. In standalone
Mongoose TCP/IP mode, configure the SSID, password, optional access-point SSID,
and access-point IP settings through MG_SET_WIFI_CONFIG.
The Wi-Fi driver can join an existing 2.4 GHz network as a station, or create a small access point by enabling AP mode in the Wi-Fi configuration.
LAN8720 RMII Ethernet
The LAN8720 option uses an external RMII PHY connected directly to RP2040 GPIOs.
RP2040 does not have a hardware Ethernet MAC, so the example uses PIO state
machines and DMA channels to implement the MAC-side RMII logic. This is more
hardware-sensitive than W5500, but it avoids an SPI Ethernet controller.
The LAN8720 reference clock, often labelled RETCLK on add-on boards, must feed
the RP2040. The example connects it to GPIO20 and configures the RP2040 system
clock from that 50 MHz reference:
#define CLKREFPIN 20
clock_configure_gpin(clk_sys, CLKREFPIN, 50 * MHZ, 50 * MHZ);
The RMII and SMI signals are grouped on consecutive GPIOs because the driver uses PIO:
| LAN8720 signal | Raspberry Pi Pico |
|---|---|
| RX0, RX1, CRS_DV | GPIO6, GPIO7, GPIO8 |
| TX0, TX1, TX-EN | GPIO10, GPIO11, GPIO12 |
| MDIO, MDC | GPIO14, GPIO15 |
| RETCLK | GPIO20 |
| VCC | 3V3 |
| GND | GND |
Set the PHY address to match your LAN8720 module, commonly 0 or 1.
USB RNDIS / CDC-ECM
USB networking does not require Ethernet or Wi-Fi hardware. The firmware uses
TinyUSB to enumerate as a USB network adapter. Linux and Windows commonly use
RNDIS; macOS uses CDC-ECM, so the example can expose a dual configuration when
needed.
This mode is normally a point-to-point link between the Pico and the host
computer. The example assigns the board a static IP address, 192.168.3.1, and
enables Mongoose's DHCP server so the host receives 192.168.3.2. The USB
driver passes frames between TinyUSB callbacks and Mongoose's TCP/IP queue using
mg_tcpip_qwrite().
Because the project owns TinyUSB directly, USB stdio is disabled in the RNDIS example and UART stdio is used for logs.
Configure Pico SDK
Set the Pico SDK board type before including pico_sdk_import.cmake. For
example, for W5500-EVB-Pico2:
set(PICO_BOARD pico2)
include(pico_sdk_import.cmake)
For Pico 2 W:
set(PICO_BOARD pico2-w)
include(pico_sdk_import.cmake)
Use pico or pico_w instead when building for RP2040-based Pico boards.
Configure W5500 Ethernet
For W5500 Ethernet, initialize SPI and provide Mongoose with three callbacks: select, deselect, and transfer. The W5500-EVB-Pico2 example uses these pins:
enum { SPI_CS = 17, SPI_CLK = 18, SPI_TX = 19, SPI_RX = 16 };
Add the SPI support code to main.c:
#include "hardware/spi.h"
#include "pico/stdlib.h"
#include "pico/unique_id.h"
#include "mongoose.h"
enum { SPI_CS = 17, SPI_CLK = 18, SPI_TX = 19, SPI_RX = 16 };
void spi_begin(void *spi) {
gpio_put(SPI_CS, 0);
}
void spi_end(void *spi) {
gpio_put(SPI_CS, 1);
}
void spi_txn(void *spi, uint8_t *tx, uint8_t *rx, size_t len) {
if (tx != NULL && rx != NULL) {
spi_write_read_blocking(spi0, tx, rx, len);
} else if (tx != NULL) {
spi_write_blocking(spi0, tx, len);
} else {
spi_read_blocking(spi0, 0xff, rx, len);
}
}
static void ethernet_init(void) {
spi_init(spi0, 500 * 1000);
gpio_set_function(SPI_RX, GPIO_FUNC_SPI);
gpio_set_function(SPI_TX, GPIO_FUNC_SPI);
gpio_set_function(SPI_CLK, GPIO_FUNC_SPI);
gpio_init(SPI_CS);
gpio_set_dir(SPI_CS, GPIO_OUT);
gpio_put(SPI_CS, 1);
}
Set a stable locally administered MAC address. For example, derive it from the unique board ID:
void genmac(unsigned char *mac) {
pico_unique_board_id_t board_id;
pico_get_unique_board_id(&board_id);
mac[0] = 2;
memcpy(&mac[1], &board_id.id[3], 5);
}
Then extend mongoose_config.h:
extern void genmac(unsigned char *mac);
#define MG_SET_MAC_ADDRESS(mac) genmac(mac)
#include <stddef.h>
#include <stdint.h>
extern void spi_begin(void *);
extern void spi_end(void *);
extern void spi_txn(void *spi, uint8_t *tx, uint8_t *rx, size_t len);
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_spi spi_ = {NULL, spi_begin, spi_end, spi_txn}; \
static struct mg_tcpip_if mif_; \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.driver = &mg_tcpip_driver_w5500; \
mif_.driver_data = &spi_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: W5500, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
Configure Pico W / Pico 2 W Wi-Fi
For Pico W and Pico 2 W, set your Wi-Fi credentials in main.c:
#define WIFI_NAME "mywifi"
#define WIFI_PASS "mypassword"
#define WIFI_AP_NAME "mywifi2"
#define WIFI_AP_PASS "mypassword2"
#define ENABLE_AP 0
#include "mongoose.h"
void wifi_setconfig(void *data) {
struct mg_tcpip_driver_pico_w_data *d =
(struct mg_tcpip_driver_pico_w_data *) data;
struct mg_wifi_data *wifi = &d->wifi;
wifi->ssid = WIFI_NAME;
wifi->pass = WIFI_PASS;
wifi->apssid = WIFI_AP_NAME;
wifi->appass = WIFI_AP_PASS;
wifi->apip = MG_IPV4(192, 168, 111, 1);
wifi->apmask = MG_IPV4(255, 255, 255, 0);
wifi->security = 0;
wifi->apsecurity = 0;
wifi->apchannel = 10;
wifi->apmode = ENABLE_AP;
}
Then extend mongoose_config.h:
extern void wifi_setconfig(void *data);
#define MG_SET_WIFI_CONFIG(data) wifi_setconfig(data)
Set ENABLE_AP to 1 if you want the board to create an access point instead
of joining an existing Wi-Fi network.
Add an HTTP server
Initialize the Pico SDK, initialize the network hardware if required, then start the Mongoose event manager and poll it forever:
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;
uint64_t tick = to_ms_since_boot(get_absolute_time());
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), tick);
} else {
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", tick);
}
}
}
static void run_mongoose(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 1);
}
}
int main(void) {
stdio_init_all();
// W5500 Ethernet projects only:
ethernet_init();
MG_INFO(("Hardware initialised, starting firmware..."));
run_mongoose();
return 0;
}
For Pico W / Pico 2 W projects, do not call ethernet_init().
Build and run
If you are starting from one of the Mongoose examples, run:
cd ~/src/mongoose/tutorials/rp/w5500-evb-pico2
make build flash
or:
cd ~/src/mongoose/tutorials/rp/pico2-w
make build flash
The example Makefile downloads Pico SDK, copies mongoose.c and mongoose.h,
configures CMake, builds build/firmware.uf2, and flashes it with picotool.
For your own project, build it the same way you build any Pico SDK firmware. The
important output is the .uf2 file produced by pico_add_extra_outputs().
Start a serial console. In the serial logs, you should see messages like this:
mongoose.c:...:mg_mgr_init Mongoose version ...
mongoose.c:...:MG_TCPIP_DRIVER Driver: W5500, MAC: 02:...
mongoose.c:...:tx_dhcp_discover DHCP discover sent
mongoose.c:...:rx_dhcp_client Lease: 86400 sec
mongoose.c:...:onstatechange READY, IP: 192.168.1.42
For Pico W / Pico 2 W, the driver line reports the Pico W Wi-Fi driver instead of W5500.
Now you can open the printed IP address in a browser, call /api/tick, or
integrate a Web UI device dashboard.
Check the Troubleshooting section if it does not work as expected.
Troubleshooting
No IP address is printed
Check the serial console, USB stdio configuration, DHCP server, cable or Wi-Fi
credentials, and the selected Mongoose driver.
W5500 does not get a DHCP lease
Check the SPI pin mapping, chip select pin, W5500 power, Ethernet cable, and
switch or router. If you changed pins, update both the GPIO setup and the
SPI_CS, SPI_CLK, SPI_TX, and SPI_RX constants.
Pico W / Pico 2 W cannot join Wi-Fi
Check WIFI_NAME and WIFI_PASS in main.c, signal strength, 2.4 GHz network
availability, and whether the access point requires a security mode not
configured by your project.
No DHCP server
If DHCP discover messages repeat without a lease, either packets are not leaving the board or no DHCP server is present on that network.
For W5500 Ethernet, run Wireshark on the Ethernet network and filter for UDP ports 67 and 68. If you do not see messages from your device, check SPI and W5500 wiring. If packets are present but no response arrives, check the DHCP server.
When connecting a board directly to a workstation, the easiest option is often to use a USB-to-Ethernet dongle and enable Internet sharing on that Ethernet interface.
FAQ
Do I need lwIP for this?
No. This guide uses Mongoose's built-in TCP/IP stack instead of lwIP.
Does this work on RP2040 and RP2350?
Yes. Use the Pico SDK board definition that matches your board, for example
pico, pico_w, pico2, or pico2_w. The examples above target RP2350, but
the same integration pattern applies to RP2040 boards with a supported network
interface.
Can I use a static IP address?
Yes. Define MG_TCPIP_IP, MG_TCPIP_GW, and MG_TCPIP_MASK in
mongoose_config.h. If they are left unset and in this case Mongoose uses DHCP.
How do I know the network is working?
Check the serial log for READY, IP: ..., then open that IP address in a
browser.