Update on 17th November 2021: having now played with an ESP32-S2 based board, I think it is much better than the Pico/ESP32 combination.

Introduction

Although the Raspberry Pi Pico1 is a fine board, it lacks a WiFi connection. To solve this problem one can add a “WiFi-coprocessor”. Using an ESP322 is sufficiently popular for this role that open firmware is available. You could use almost any ESP32 board, but Pimoroni make the Pico Wireless Pack3 which fits neatly onto the Pico. The Wireless Pack also boasts a microSD card slot, but I’ve not played with that.

[Pico Wireless Pack]

Such a coprocessor is not a new idea. The ESP82664 (the forerunner to the ESP32) first appeared as the ESP-01 module designed for this very role. It allowed any device with a serial port to connect to WiFi via an extension of the Hayes Modem Commands5. By contrast the ESP32 often sits on the main processor’s SPI bus for extra speed. The Pimoroni board is very similar to the Adafruit AirLift6 and includes the same RGB LED connected to the ESP32. In turn, the AirLift drew inspiration from the Arduino UNO Wifi Rev.27 which uses a ublox NINA-W102 for WiFi. Peer inside the ublox module though and you’ll find an ESP32.

The ESP32 is a powerful microcontroller in its own right though, and its arguably overkill to use it purely to handle the WiFi connection: I suspect many applications using a Pico and ESP32 could be ported to run wholly on the ESP32. Personally though, I somewhat prefer developing code for the Pico. For large volumes or when space or unit costs matter, dropping the Pico probably sensible; othewise I think the Pico adds value.

Update: Having now explored using a pure ESP32-S2 solution, it would be remiss not to point out a couple of drawpacks of the Pico/ESP32 combo. Firstly the Wireless Pack draws about 17mA when idle which is rather thirsty; secondly the CircuitPython API isn’t compatible with the normal wifi library used on boards with integrated WiFi.

Software

Given that the Pico/ESP32 combo makes little sense in purely hardware terms, it is disappointing that the Pimoroni software supporting the board is poorly documented. There are a few examples which illustrate how to do HTTP things from MicroPython and C++, but doing more seems hard. I couldn’t find good documentation for the API or a schematic for the hardware. Perhaps I am being unfair, so do look for yourself.

Happily the software that Adafruit provide for their AirLift board works perfectly well with the Wireless Pack too. The main incompatibility is that Adafruit use CircuitPython rather than MicroPython, so you have to upload new firmware to the Pico. Happily that’s easy: just follow the instructions8. Once installed the differences between Circuit- and Micro-Python didn’t bother me. Although Adafruit are keen on the Mu IDE, you can use Thonny too (but you have to tell Thonny it’s talking to CircuitPython, not MicroPython).

The ESP32 firmware provided by Pimoroni works with the Adafruit CircuitPython libraries, so there’s nothing to change there.

GPIO mapping

Unsurprisingly, the Adafruit documentation doesn’t cover using the Wireless Pack, so we have to sort out which GPIO pins to use by ourselves. Happily, though, this is the extent of the work we have to do. I wasn’t able to find a proper schematic, but there’s a helpful diagram on the Pimoroni page9. The key connections are:

Adafruit Code Pimoroni Diagram GPIO
esp32_cs ESP_CS GP7
esp32_ready BUSY GP10
esp32_reset RESET GP11
SCK SCLK GP18
MOSI MOSI GP19
MISO MISO GP16

Other Pico pins are used: some for the SD card, others for a serial connection to the ESP32 which (I think) are used to flash new ESP32 firmware. I’ve not played with these at all.

A working example

Adafruit provide a simple example on their website10 which scans for wireless networks and reports which ones it finds. If this works, then the basic system is sound.

Code

import board
import busio
from digitalio import DigitalInOut

from adafruit_esp32spi import adafruit_esp32spi
import adafruit_requests as requests

print("ESP32 SPI hardware test")

esp32_cs = DigitalInOut(board.GP7)
esp32_ready = DigitalInOut(board.GP10)
esp32_reset = DigitalInOut(board.GP11)

spi = busio.SPI(board.GP18, board.GP19, board.GP16)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])

for ap in esp.scan_networks():
    print("\t%s\t\tRSSI: %d" % (str(ap['ssid'], 'utf-8'), ap['rssi']))

print("Done!")

In CircuitPython this file must be saved as code.py.

Dependencies

Besides the code above, you also need a couple of CircuitPython libraries:

Both can be extracted manually from the Adafruit CircuitPython Bundle11, and copied to the Pico.

Walkthrough

If you run the code e.g. with Thonny or Mu, you should see this on the Serial port:

ESP32 SPI hardware test
ESP32 found and in idle mode
Firmware vers. bytearray(b'1.7.3\x00')
MAC addr: ['0x44', '0x97', '0x8e', '0x57', '0xdd', '0xc4']
    XXXXXXX     RSSI: -49
    XXXXXXX     RSSI: -51
    XXXXXXX     RSSI: -55
    XXXXXXX     RSSI: -73
    XXXXXXX     RSSI: -76
    XXXXXXX     RSSI: -77
    XXXXXXX     RSSI: -77
Done!

Other examples

As you might guess from the code above, the adafruit_esp32spi library contains the code which talks to the ESP32. Their repo14 on GitHub contains many other examples15.

Most of these examples actually connect to a WiFi network, and so need credentials for that network. Conventionally this is done by supplying a secrets.py file which looks like this:

secrets = {
    'ssid' : 'My Network SSID',
    'password' : 'My WiFi Password',
}

then calling:

esp.connect_AP(secrets["ssid"], secrets["password"])

Conclusion

Although the Pico lacks native WiFi support, adding an ESP32 solves that problem easily if inelegantly.

Update: Although this works, I think you’ll probably be happier using an ESP32 as the main processor if you want WiFi.