Raspberry Pi Pico + W5500

Overview

This tutorial demonstrates how Mongoose Library can be used on an RP2040-based board using the Pico-SDK and MIP (a bare-metal embedded TCP/IP stack designed specifically for Mongoose)

We implement a minimal static web server that responds "ok" to any URL

Board options

You can use either a board like the W5500-EVB-Pico, that has an RP2040 and a W5500 with an Ethernet jack, or you can use for example a Raspberry Pi Pico wired to an add-on board with the W5500.

W5500-EVB-Pico

The default SPI pin selection is just for this board:

pic

mini W5500 add-on

pic

With a board like this one, you have to wire the SPI interface and power connections between the boards:

W5500 Raspberry Pi Pico
1, 2, 12 GND GND 38
3 MOSI GPIO19/SPI0_TX 25
4 SCLK GPIO18/SPI0_SCK 24
5 SCNn GPIO17/SPI0_CSn 22
7 MISO GPIO16/SPI0_RX 21
10, 11 3.3V 3V3 36

The Pico, powered from USB, will provide 3.3V to the add-on board through its built-in step-down power supply

This is the end result:

pic

W5500 add-on

pic

With a board like this one, you also have to wire the SPI interface and power connections between the boards:

W5500 Raspberry Pi Pico
1, 7 GND GND 38
2 SCLK GPIO18/SPI0_SCK 24
4 SCS GPIO17/SPI0_CSn 22
6 MOSI GPIO19/SPI0_TX 25
8 MISO GPIO16/SPI0_RX 21
10 3.3V 3V3 36

With this wiring, the Pico, powered from USB, will provide 3.3V to the add-on board. You can also power this board with a 5V source, which you can also take from the Pico. However, good practices dictate that you add series resistors on every signal connection, as the RP2040 and the W5500 will have different 3.3V power supplies that will not match. So, we recommend you follow the wiring suggested above.

Other RP2040-based boards

If you choose to use any other board, either mimic the wiring above or change this line in main.c accordingly:

Project structure

This example uses the Pico-SDK and compiles with its standard toolchain:

  • main.c - main application file, contains Mongoose logic, SPI low-level code, and pin initialization code
  • mongoose.c - Mongoose Library source code
  • mongoose.h - Mongoose Library header file
  • CMakeLists.txt - A cmake file that selects the source files and compilation options
  • Makefile - mainly pulls the SDK from its Github repo and calls cmake

Build the project

  • Start a terminal in the project directory; clone the Mongoose Library repo, and run the make command:
    $ git clone https://github.com/cesanta/mongoose
    $ cd mongoose/examples/rp2040/pico-w5500/
    $ make
    
  • Once the build succeeds, boot your Pico in USB Mass Storage Mode by holding down the BOOTSEL button while you plug it into the USB port. Once it is connected, then release the BOOTSEL button. It should automatically mount as a new USB disk in your machine. Now either manually copy or drag and drop the generated example.uf2 file into this new USB disk device.
  • The Pico will flash this code and reboot, unmounting this disk and running the flashed code.
  • Now open a second terminal, this will be our serial console to see the logs. Run a serial port software (we use picocom; make sure you configure it at 115200bps and to add a carriage return). Device name is usually /dev/ttyACM0. Wait a bit and plug your network cable:
    $ picocom /dev/ttyACM0 -i -b 115200 --imap=lfcrlf
    picocom v2.2
    ...
    4653 2 main.c:79:main                   Ethernet: down
    520b 2 main.c:79:main                   Ethernet: down
    5dc3 2 main.c:79:main                   Ethernet: down
    6593 1 mongoose.c:6757:onstatechange    Link up
    659a 3 mongoose.c:6840:tx_dhcp_discover DHCP discover sent
    667b 3 mongoose.c:6723:arp_cache_add    ARP cache: added 192.168.0.1 @ 90:5c:44:55:19:8b
    667d 2 mongoose.c:6749:onstatechange    READY, IP: 192.168.0.24
    667e 2 mongoose.c:6750:onstatechange           GW: 192.168.0.1
    6680 2 mongoose.c:6752:onstatechange           Lease: 86062 sec
    697b 2 main.c:79:main                   Ethernet: up
    7533 2 main.c:79:main                   Ethernet: up
    

Try it out

Start a browser on http://IP_ADDRESS where IP_ADDRESS is the board's IP address assigned by your DHCP server. You should see ok. You can also use curl:

$ curl 192.168.0.24
ok

How it works

Mongoose detects it is being compiled for an RP2040 architecture, so it will configure itself to call the proper SDK functions it needs, for example for its time base in milliseconds. Then the networking stack will call the W5500 driver that works over an SPI connection, we'll provide a set of basic functions that wrap the SDK functions so the driver can use them.

MCU and board initialization

We call SDK functions to initialize the MCU, stdio, and those peripherals we are going to use:

Mongoose initialization

Then we initialize Mongoose, this is no different from what we always do in any example.

There are also two timers we use to blink the LED and print the link state

In this tutorial we'll use a Mongoose low level function to check the timers, in the main loop

SPI low-level functions

We provide a basic set of functions to do transactions, that is, a couple of functions to begin and end the transaction (enable and disable chip select) and a third function to transfer a byte (send one, receive one, simultaneously). These are of course wrappers to SDK functions:

MIP initialization

MIP has to be enabled to be compiled in, and so Mongoose will work in association with it. This is done in the CMakeLists.txt file by defining MG_ENABLE_MIP=1.

Then this networking stack has to be configured and initialized. This is done by calling mip_init() and passing it a pointer to a struct mip_if. Inside this structure:

  • have pointers to a struct mip_driver and any extra data that it could need.
  • For DHCP: set ip as zero
  • For a static configuration, specify ip, mask, and gw in network byte order

In this example, we use DHCP:

Note that, we also need to specify a unique MAC address. For this example we chose a fancy unicast locally administered address.

Some drivers, as you have probably noticed, require extra data. In this case the W5500 driver needs to know which functions to call to perform SPI initialization and transfers, so we included pointers to those functions we described above

Run Mongoose

Then we run Mongoose. This is a bit different from what we always do in any example, as we'll check the timers, blink the LED and print a message. The logic is standard: initialize the event manager (as we already did), start a listener on port 80, and fall into an infinite event loop:

The listener will call this function when it receives an HTTP request:

There we just reply "ok"

Custom application

In order to create your own Mongoose-enabled application you have several ways:

  1. The obvious way, is to add the required functionality to this example.

  2. If, for some reason, you can't use this example as a base, you can do the following:

    • Add relevant project files to your project
    • Add mongoose.c and .h files to your project
    • Add Mongoose-specific configuration flags, see CMakeLists.txt
    • Add the required preprocessor symbols:
      • MG_ENABLE_MIP=1
    • Now write code similar to that in main.c; for that you can read Mongoose documentation and follow our examples and tutorials