The Metro M4 AirLift Lite provides another way to build this project. This is a Metro M4 Express with a built-in ESP32 coprocessor. One advantage is the we don't have to wire it or mount it. Another is that it's not using the board's GPIO for support connections (CS, BUSY, etc). It uses a different, internal set.

For this build we can use the ultimate GPS shield. It provides the GPS connected to the Metro's Tx/Rx, battery backup, an SD card interface (which we don't use here) and, importantly, a sizable prototyping area.

That prototyping area is used to mount the BME280 and air sensor breakout.  There is plenty of room for both. The air sensor breakout can go toward the end of the board to ease the connection of its cable.

Wiring is minimal:

  • 5v and Gnd to each breakout
  • SCL and SDA to the BME280 breakout
  • Tx/D5 and Rx/D7 to the air sensor breakout

You can solder the breakouts directly onto the shield or use strips of female header to match the make header on the breakouts.

Code Changes

There are a couple small changes to the code to accommodate running on a Metro M4 AirLift Lite as well as a Feather M4 Express:

  • The Metro has dedicated SPI control pins for the ESP32 instead of using general purpose GPIO pins as we did on the Feather.
  • The Metro's routing of physical pins on the SAMD51 to interface pins on the board are, unsurprisingly, done differently than on the Feather. The impact of that is that we need to use different pins for Rx and Tx. This build uses D5 and D7, respectively. How do we find pairs of pins that can be used for Tx/Rx? See the Where's my UART? section on this page in the CircuitPython Essentials guide

The constructor of the AIO class (in changes to accomodate the possibility of a built-in ESP. It saves whether it found one and makes that information available with a property.

    def __init__(self):
            esp32_cs = DigitalInOut(board.ESP_CS)
            esp32_busy = DigitalInOut(board.ESP_BUSY)
            esp32_reset = DigitalInOut(board.ESP_RESET)
            self._onboard_esp = True
        except AttributeError:
            esp32_cs = DigitalInOut(board.D10)
            esp32_busy = DigitalInOut(board.D9)
            esp32_reset = DigitalInOut(board.D6)
            self._onboard_esp = False

        spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
        self._esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_busy, esp32_reset)

        if self._esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
            logger.debug('ESP32 found and in idle mode')'Firmware vers. %s', self._esp.firmware_version)'MAC addr: %s', ':'.join([hex(i)[2:4] for i in self._esp.MAC_address]))


    def onboard_esp(self):
        return self._onboard_esp

The main hardware setup code changes a bit to select the appropriate Tx/Rx pins based on whether a built-in ESP was found (we're running on a Metro Air Lift) or not (we're running a Feather M4).

aio_interface = aio.AIO()

if aio_interface.onboard_esp:'Using onboard Airlift and air sensor on D5/D7')
    air_uart = busio.UART(board.D5, board.D7, baudrate=9600)
else:'Using external Airlift and air sensor on A2/A3')
    air_uart = busio.UART(board.A2, board.A3, baudrate=9600)
air = air_quality.AirQualitySensor(air_uart)

Other than those two changes, the code is identical.

Alternative Parts for this Version

Adafruit Metro M4 Express AirLift (WiFi) - Lite
Give your next project a lift with AirLift - our witty name for the ESP32 co-processor that graces this Metro M4. You already know about the Adafruit Metro...
In Stock
Adafruit Ultimate GPS Logger Shield - Includes GPS Module
Brand new and better than ever, we've replaced our Adafruit GPS shield kit with this assembled shield that comes with an Ultimate GPS module. This GPS shield works great with...
Out of Stock

This guide was first published on May 01, 2019. It was last updated on May 01, 2019.

This page (An Alternative Build) was last updated on May 05, 2021.

Text editor powered by tinymce.