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 aio.py) 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): try: 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') logger.info('Firmware vers. %s', self._esp.firmware_version) logger.info('MAC addr: %s', ':'.join([hex(i)[2:4] for i in self._esp.MAC_address])) requests.set_interface(self._esp) @property 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: logger.info('Using onboard Airlift and air sensor on D5/D7') air_uart = busio.UART(board.D5, board.D7, baudrate=9600) else: logger.info('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.