Maker.io main logo

Adafruit MatrixPortal M4

2024-10-02 | By Adafruit Industries

License: See Original Project LED Matrix Microcontrollers

Courtesy of Adafruit

Guide by M. LeBlanc-Williams

Overview

led_matrices_4745-05

Folks love Adafruit's wide selection of RGB matrices and accessories for ‎making custom colorful LED displays... and Adafruit RGB Matrix ‎Shields and FeatherWings can be quickly soldered together to make ‎the wiring much easier.‎

But what if we made it even easier than that? Like, no solder, no ‎wiring, just instant plug-and-play? Dream no more - with ‎the Adafruit Matrix Portal add-on for RGB Matrices, there has never ‎been an easier way to create powerful internet-connected LED ‎displays.‎

matrix_1

Plug The Matrix Portal directly into the back of any HUB-75 compatible ‎display (all the ones we stock will work) from 16x32 up to 64x64! Use the ‎included screws to attach the power cable to the power plugs with a ‎common screwdriver, then power it with any USB C power supply. ‎‎(For larger projects, power the matrices with a separate 5V power ‎adapter.)‎

display_2

Then code up your project in CircuitPython or Arduino, the Adafruit ‎Protomatter matrix library works great on the SAMD51 chipset, ‎knowing that you've got the wiring and level shifting all handled. ‎Here's what you get:‎

  • ATSAMD51J19 Cortex M4 processor, 512KB flash, 192K of SRAM, ‎with full Arduino or CircuitPython support

  • ESP32 WiFi co-processor with TLS support and SPI interface to ‎the M4, with full Arduino or CircuitPython support

  • USB Type C connector for data and power connectivity

  • I2C STEMMA QT connector for plug-n-play use of any of our STEMMA ‎QT devices or sensors can also be used with any Grove I2C devices using ‎this adapter cable

  • JST 3-pin connector that also has analog input/output, say for ‎adding audio playback to projects

  • LIS3DH accelerometer for digital sand projects or detecting ‎taps/orientation

  • GPIO breakouts including 4 analog outputs with PWM and SPI ‎support for adding other hardware

  • Address E line jumper for use with 64x64 matrices (check your ‎matrix to see which pin is used for address E!‎

  • Two user interface buttons + one reset button‎

  • Indicator NeoPixel and red LED

  • Green power indicator LEDs for both 3V and 5V power

  • ‎2x10 socket connector fits snugly into 2x8 HUB75 ports ‎without worrying about 'off by one' errors

portal_3

The Matrix Portal uses an ATMEL (Microchip) ATSAMD51J19, and an ‎Espressif ESP32 Wi-Fi coprocessor with TLS/SSL support built in. The ‎M4 and ESP32 are a great couple - and each bring their own ‎strengths to this board. The SAMD51 M4 has native USB, so it can ‎show up like a disk drive, act as a MIDI or HID keyboard/mouse, and ‎of course bootload and debug over a serial port. It also has DACs, ‎ADC, PWM, and tons of GPIO, so it can handle the high-speed ‎updating of the RGB matrix.‎

rgb_4

Meanwhile, the ESP32 has secure WiFi capabilities, and plenty of ‎Flash and RAM to buffer sockets. By letting the ESP32 focus on the ‎complex TLS/SSL computation and socket buffering, it frees up the ‎SAMD51 to act as the user interface. You get a great programming ‎experience thanks to the native USB with files available for drag-n-‎drop, and you don't have to spend a ton of processor time and ‎memory to do SSL encryption/decryption and certificate ‎management. It's the best of both worlds!‎

quarter_5

Text editor powered by tinymce.‎

board_6

pinout_7

Click here to view a PDF version of the pinout diagram.

There are so many great features on the Adafruit MatrixPortal M4. ‎Let's take a look at what's available!‎

Microcontroller and Flash

The main processor chip is the ATSAMD51J19 Cortex M4 running at ‎‎120MHz with 3.3v logic/power. It has 512KB of Flash and 192KB of ‎RAM.‎

We also include 2 MB of QSPI Flash for storing images, sounds, ‎animations, whatever!‎

m4_8

WiFi

The WiFi capability uses an Espressif ESP32 Wi-Fi coprocessor with ‎TLS/SSL support built in.‎

The ESP32 uses the SPI port for data, and also uses a CS pin ‎‎(board.ESP_CS or Arduino 33), Ready/Busy pin (board.ESP_BUSY or Arduino 31), ‎and reset pin (board.ESP_RESET or Arduino 30).‎

wifi_9

HUB75 Connector

There is a 2x8 pin HUB75 connector on the reverse side that plugs ‎directly into the HUB75 port on your RGB Matrix.

‎The socket itself is 2x10 so that it fits snug and lined up in a 2x8 IDC ‎socket. Otherwise, it’s easy to get it 'off by one'.‎

connector_10

connector_11

RGB Matrix Power

There are +5V and Ground M3-threaded screw terminals on either ‎side of the HUB75 connector. These provide power to the RGB Matrix.‎

If you would like to power the RGB Matrix with external power, we ‎recommend disconnecting it from here and providing power directly ‎to the matrix.‎

These terminals were designed as outputs ONLY - power from the ‎USB port connects directly to these pads, so you should power from ‎USB and then connect the matrix power inputs to these terminals.‎

While it's technically possible to power the MatrixPortal through ‎here, we strongly discourage that because plugging anything into ‎the USB port at the same time could result in damage.‎

power_12

Sensors

The MatrixPortal M4 includes a LIS3DH Triple-Axis Accelerometer. ‎The accelerometer is connected via the I2C bus.‎

Please note the address of the accelerometer is 0x19 not 0x18 ‎which is the default in our libraries.‎

sensors_13

Stemma QT Connector

There is a 4-pin Stemma QT connector on the left. The I2C has ‎pullups to 3.3V power and is connected to the LIS3DH already.‎

In CircuitPython, you can use the STEMMA connector ‎with board.SCL and board.SDA, or board.STEMMA_I2C().‎

stemma_14

Reset Pin

RST is the Reset pin. Tie to ground to manually reset the ATSAMD51, ‎as well as launch the bootloader manually.‎

rst_15

Debugging Interface

If you'd like to do more advanced development, trace-debugging, or ‎not use the bootloader, we have the SWD interface exposed.‎

debugging_16

Serial UART Pins

The TX pin and RX pin are for serial communication with the SAMD51 ‎microcontroller and can be used to connect various peripherals such ‎as a GPS.‎

The RX pin is attached to board.RX and Arduino 0 and the TX pin is ‎attached to board.TX and Arduino 1.‎

serial_17

Analog Connector/Pins

On the bottom side towards the right, there is a connector ‎labeled A0. This is a 3-pin JST analog connector for sensors or ‎NeoPixels, analog output or input.

Along the bottom there are also pins labeled A1 through A4.‎

All of these pins can be used for analog inputs or digital I/O.‎

pins_18

Power Pins

3V is the output from the 3.3V regulator, it can supply 500mA peak.‎

GND is the common ground for all power and logic.‎

powerpins_19

Status LED and NeoPixel

There are two LEDs on the board.‎

There is the RGB status NeoPixel labeled "STATUS". It is connected ‎to board.NEOPIXEL or Arduino 4‎.

As well, there is the D13 LED. This is attached to board.LED and ‎Arduino 13‎.

status_20

USB-C Connector

There is one USB port on the board.‎

On the left side, towards the bottom, is a USB Type C port, which is ‎used for powering and programming both the board and RGB Matrix.‎

usb_21

Buttons

There are three buttons along the left side of the MatrixPortal M4.‎

The reset button is located in the top position. Click it once to re-‎start your firmware. Click twice to enter bootloader mode.‎

The up button is located in the middle and is attached ‎to board.BUTTON_UP and Arduino 2.‎

The down button is located on the bottom and is attached ‎to board.BUTTON_DOWN and Arduino 3.‎

The up and down buttons do not have any pull-up resistors ‎connected to them and pressing either of them pulls the input low.‎

buttons_22

Address E Line Jumper

This jumper is used for use with 64x64 matrices and is either ‎connected to pin 8 or pin 16 of the HUB75 connector. Check your ‎matrix to see which pin is used for address E.‎

You can close the jumper by using your soldering iron to melt a blob ‎of solder on the bottom solder jumper, so the middle pad is 'shorted' ‎to 8. (This is compatible with 64x64 matrices in the Adafruit store. ‎For 64x64 matrices from other sources, you might need to use 16 ‎instead, check the datasheet of your display.)‎

address_23

‎Text editor powered by tinymce.‎ 

Power Prep

The MatrixPortal supplies power to the matrix display panel via two ‎standoffs. These come with protective tape applied (part of our ‎manufacturing process) which MUST BE REMOVED!‎

Use some tweezers or a fingernail to remove the two amber circles.‎

prep_24

prep_25

Power Terminals

Next, screw in the spade connectors to the corresponding standoff.‎

  • red wire goes to +5V

  • black wire goes to GND

terminals_26

terminals_27

Panel Power

Plug either one of the four-conductor power plugs into the power ‎connector pins on the panel. The plug can only go in one way, and ‎that way is marked on the board's silkscreen.‎

panel_28

panel_29

Dual Matrix Setup

If you're planning to use a 64x64 matrix, follow these instructions on ‎soldering the Address E Line jumper.‎

Board Connection

Now, plug the board into the left side shrouded 8x2 connector as ‎shown. The orientation matters, so take a moment to confirm that ‎the white indicator arrow on the matrix panel is oriented pointing ‎up and right as seen here and the MatrixPortal overhangs the edge ‎of the panel when connected. This allows you to use the edge ‎buttons from the front side.‎‎ ‎

Check nothing is impeding the board from plugging in firmly. If ‎there's a plastic nub on the matrix that's keeping the Portal from ‎sitting flat, cut it off with diagonal cutters.‎

board_30

board_31

portal_32

black_33

For info on adding LED diffusion acrylic, see the page LED Matrix ‎Diffuser.‎

Text editor powered by tinymce.‎

LED Diffusion Acrylic

You can add an LED diffusion acrylic faceplate to the your LED matrix ‎display. (Pictured here with the ON AIR project)‎

This can help protect the LEDs as well as enhance the look of the ‎sign both indoors and out by reducing glare and specular highlights ‎of the plastic matrix grid.‎

offair_34

Measure and Cut the Plastic

You can use the sign to measure and mark cut lines on the paper ‎backing of the acrylic sheet.‎

Then, use a tablesaw or bandsaw with a fine-toothed blade and a ‎guide or sled to make the cuts.‎

Note: it is possible to score and snap acrylic, but it can be very tricky ‎to get an even snap without proper clamping.‎

measure_35

measure_36

measure_37

measure_38

measure_39

Peel away the paper backing from both sides and set the acrylic ‎onto your matrix display with the matte finished side facing out.‎

peel_40

Uglu Dashes

The best method we've found for adhering acrylic to the matrix ‎display is to use Uglu Dashes clear adhesive rectangles from Pro Tapes. ‎They are incredibly strong (although can be removed if necessary), ‎easy to apply, and are invisible once attached.‎

Use one at each corner and one each at the halfway point of the long ‎edges, then press the acrylic and matrix panel together for about 20 ‎seconds.‎

dashes_41

dashes_42

dashes_43

Here you can see the impact of using the diffusion acrylic. (Pictured ‎here with the ON AIR sign project.)

led_matrices_diffusionOnAir

Stand

A very simple and attractive way to display your matrix is with the ‎adjustable bent wire stand.‎

stand_44

stand_45

stand_46

stand_47

sign_48

Alternately, you can use a frame, 3D printed brackets, tape, glue, or ‎even large binder clips to secure the acrylic to the sign and then ‎mount it on a wall, shelf, or display cabinet.‎

These mini-magnet feet can be used to stick the sign to a ferrous ‎surface.‎

Text editor powered by tinymce.‎

CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It ‎makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY drive to iterate.‎

Set up CircuitPython Quick Start!‎

Follow this quick step-by-step for super-fast Python power :)

‎Download the latest version of CircuitPython for this board via circuitpython.org

Further Information

For more detailed info on installing CircuitPython, check out Installing ‎CircuitPython.‎

Click the link above and download the latest UF2 file.‎

Download and save it to your desktop (or wherever is handy).‎

Save_to_Desktop_49

Plug your MatrixPortal M4 into your computer using a known-good ‎USB cable.‎

A lot of people end up using charge-only USB cables and it is very ‎frustrating! So, make sure you have a USB cable you know is good ‎for data sync.‎

Double-click the Reset button (indicated by the green arrow) on ‎your board, and you will see the NeoPixel RGB LED (indicated by the ‎magenta arrow) turn green. If it turns red, check the USB cable, try ‎another USB port, etc.‎

If double-clicking doesn't work the first time, try again. Sometimes it ‎can take a few tries to get the rhythm right!‎

You will see a new disk drive appear called MATRIXBOOT.‎‎ ‎

Drag the adafruit_circuitpython_etc.uf2 file to MATRIXBOOT.‎

drive_50

drive_51

The LED will flash. Then, the MATRIXBOOT drive will disappear, and ‎a new disk drive called CIRCUITPY will appear.‎

That's it, you're done! :)‎

disk_52

Text editor powered by tinymce.‎

CircuitPython is a programming language designed to simplify ‎experimenting and learning to program on low-cost microcontroller ‎boards. It makes getting started easier than ever with no upfront ‎desktop downloads needed. Once you get your board set up, open ‎any text editor, and get started editing code. It's that simple.‎

python_53

CircuitPython is based on Python

Python is the fastest growing programming language. It's taught in ‎schools and universities. It's a high-level programming language ‎which means it's designed to be easier to read, write and maintain. It ‎supports modules and packages which means it's easy to reuse your ‎code for other projects. It has a built-in interpreter which means ‎there are no extra steps, like compiling, to get your code to work. ‎And of course, Python is Open-Source Software which means it's free ‎for anyone to use, modify or improve upon.‎

CircuitPython adds hardware support to all of these amazing ‎features. If you already have Python knowledge, you can easily apply ‎that to using CircuitPython. If you have no previous experience, it's ‎really simple to get started!‎

blinka_54

Why would I use CircuitPython?‎

CircuitPython is designed to run on microcontroller boards. A ‎microcontroller board is a board with a microcontroller chip that's ‎essentially an itty-bitty all-in-one computer. The board you're holding ‎is a microcontroller board! CircuitPython is easy to use because all ‎you need is that little board, a USB cable, and a computer with a ‎USB connection. But that's only the beginning.‎

Other reasons to use CircuitPython include:‎

  • You want to get up and running quickly. Create a file, edit ‎your code, save the file, and it runs immediately. There is no ‎compiling, no downloading and no uploading needed

  • You're new to programming. CircuitPython is designed with ‎education in mind. It's easy to start learning how to program ‎and you get immediate feedback from the board

  • Easily update your code. Since your code lives on the disk ‎drive, you can edit it whenever you like, you can also keep ‎multiple files around for easy experimentation

  • The serial console and REPL. These allow for live feedback ‎from your code and interactive programming

  • File storage. The internal storage for CircuitPython makes it ‎great for data-logging, playing audio clips, and otherwise ‎interacting with files

  • Strong hardware support. CircuitPython has builtin support ‎for microcontroller hardware features like digital I/O pins, ‎hardware buses (UART, I2C, SPI), audio I/O, and other ‎capabilities. There are also many libraries and drivers for ‎sensors, breakout boards and other external components

  • It's Python! Python is the fastest-growing programming ‎language. It's taught in schools and universities. CircuitPython ‎is almost-completely compatible with Python. It simply adds ‎hardware support

This is just the beginning. CircuitPython continues to evolve and is ‎constantly being updated. Adafruit welcomes and encourages ‎feedback from the community and incorporate it into the ‎development of CircuitPython. That's the core of the open-source ‎concept. This makes CircuitPython better for you and everyone who ‎uses it!‎

Text editor powered by tinymce.‎

To use all the amazing features of your MatrixPortal M4 with ‎CircuitPython, you must first install a number of libraries. This page ‎covers that process.‎

Adafruit CircuitPython Bundle

Download the Adafruit CircuitPython Library Bundle. You can find ‎the latest release here:‎

Download latest Library Bundle

Download the adafruit-circuitpython-bundle-version-mpy-‎‎*.zip bundle zip file and unzip a folder of the same name. Inside ‎you'll find a lib folder. The entire collection of libraries is too large to ‎fit on the CIRCUITPY drive. Instead, add each library as you need it, ‎this will reduce the space usage, but you'll need to put in a little ‎more effort.‎

At a minimum we recommend the following libraries, in fact we ‎more than recommend. They're basically required. So, grab them ‎and install them into CIRCUITPY/lib now!‎

  • adafruit_matrixportal - this library is the main library used with ‎the MatrixPortal

  • adafruit_debouncer.mpy - this library is used for debouncing a ‎digital input pin

  • adafruit_portalbase - This is the base library that ‎adafruit_matrixportal is built on top of

  • adafruit_esp32spi - this is the library that gives you internet ‎access via the ESP32 using (you guessed it!) SPI transport. You ‎need this for anything Internet

  • neopixel.mpy - for controlling the onboard NeoPixel

  • adafruit_bus_device - low level support for I2C/SPI

  • adafruit_requests.mpy - this library allows us to perform HTTP ‎requests and get responses back from servers. ‎GET/POST/PUT/PATCH - they're all in here!

  • adafruit_fakerequests.mpy - This library allows you to create ‎fake HTTP requests by using local files

  • adafruit_io - this library helps connect the PyPortal to our free ‎data logging and viewing service

  • adafruit_bitmap_font - we have fancy font support, and it's ‎easy to make new fonts. This library reads and parses font files

  • adafruit_display_text - not surprisingly, it displays text on the ‎screen

  • adafruit_lis3dh.mpy - this library is used for the onboard ‎accelerometer to detect the orientation of the MatrixPortal

  • adafruit_minimqtt - this is used for communicating with MQTT ‎servers

Text editor powered by tinymce.‎

CircuitPython works with WiFi-capable boards to enable you to ‎make projects that have network connectivity. This means working ‎with various passwords and API keys. As of CircuitPython 8, there is ‎support for a settings.toml file. This is a file that is stored on ‎your CIRCUITPY drive, which contains all of your secret network ‎information, such as your SSID, SSID password and any API keys for ‎IoT services. It is designed to separate your sensitive information ‎from your code.py file so you are able to share your code without ‎sharing your credentials.‎

CircuitPython previously used a secrets.py file for this purpose. ‎The settings.toml file is quite similar.‎

Your settings.toml file should be stored in the main directory of your ‎CIRCUITPY drive. It should not be in a folder.‎

CircuitPython settings.toml File

This section will provide a couple of examples of what ‎your settings.toml file should look like, specifically for CircuitPython ‎WiFi projects in general.‎

The most minimal settings.toml file must contain your WiFi SSID ‎and password, as that is the minimum required to connect to WiFi. ‎Copy this example, paste it into your settings.toml, and update:‎

  • your_wifi_ssid

  • your_wifi_password

‎Download File

Copy Code
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"

Many CircuitPython network-connected projects on the Adafruit ‎Learn System involve using Adafruit IO. For these projects, you ‎must also include your Adafruit IO username and key. Copy the ‎following example, paste it into your settings.toml file, and update:‎

  • your_wifi_ssid

  • your_wifi_password

  • your_aio_username

  • your_aio_key‎

Download File‎

Copy Code
CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
ADAFRUIT_AIO_USERNAME = "your_aio_username"
ADAFRUIT_AIO_KEY = "your_aio_key"

Some projects use different variable names for the entries in ‎the settings.toml file. For example, a project might ‎use ADAFRUIT_AIO_ID in the place of ADAFRUIT_AIO_USERNAME. If you run into ‎connectivity issues, one of the first things to check is that the ‎names in the settings.toml file match the names in the code.‎

Not every project uses the same variable name for each entry in the ‎settings.toml file! Always verify it matches the code.‎

settings.toml File Tips

Here is an example settings.toml file.‎

Download File

Copy Code
# Comments are supported
CIRCUITPY_WIFI_SSID = "guest wifi"
CIRCUITPY_WIFI_PASSWORD = "guessable"
CIRCUITPY_WEB_API_PORT = 80
CIRCUITPY_WEB_API_PASSWORD = "passw0rd"
test_variable = "this is a test"
thumbs_up = "\U0001f44d"

In a settings.toml file, it's important to keep these factors in mind:‎

  • Strings are wrapped in double quotes; ex: "your-string-here"

  • ‎Integers are not quoted and may be written in decimal with ‎optional sign (+1, -1, 1000) or hexadecimal (0xabcd).‎

    • Floats, octal (0o567) and binary (0b11011) are not supported

  • Use \u escapes for weird characters, \x and \ooo escapes are not ‎available in .toml files

    • Example: \U0001f44d for 👍 (thumbs up emoji) ‎and \u20ac for € (EUR sign)‎

  • Unicode emoji, and non-ASCII characters, stand for themselves ‎as long as you're careful to save in "UTF-8 without BOM" format‎ ‎ ‎‎ ‎

When your settings.toml file is ready, you can save it in your text ‎editor with the .toml extension.‎

settings_55

Accessing Your settings.toml Information ‎in code.py

In your code.py file, you'll need to import the os library to access ‎the settings.toml file. Your settings are accessed with ‎the os.getenv() function. You'll pass your settings entry to the function ‎to import it into the code.py file.‎

‎Download File

Copy Code
import os

print(os.getenv("test_variable"))

repl_57

In the upcoming CircuitPython WiFi examples, you'll see how ‎the settings.toml file is used for connecting to your SSID and ‎accessing your API keys.‎

Text editor powered by tinymce.‎

Connect to WiFi

OK, now that you have your settings.toml file set up - you can ‎connect to the Internet.‎

To do this, you need to first install a few libraries, into the lib folder on ‎your CIRCUITPY drive. Then you need to update code.py with the ‎example script.‎

Thankfully, we can do this in one go. In the example below, click ‎the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file, open the directory examples/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎

drive_58

If you are using CircuitPython 9.0.x on a board with frozen libraries, ‎such the Matrix Portal M4, use this version of the "Internet Connect" ‎program. If you are using CircuitPython 9.1.0 or later, use the second ‎version below.‎‎

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

from os import getenv
import board
import busio
from digitalio import DigitalInOut
import adafruit_connection_manager
import adafruit_requests
from adafruit_esp32spi import adafruit_esp32spi

# Get wifi details and more from a settings.toml file
# tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD
secrets = {
    "ssid": getenv("CIRCUITPY_WIFI_SSID"),
    "password": getenv("CIRCUITPY_WIFI_PASSWORD"),
}
if secrets == {"ssid": None, "password": None}:
    try:
        # Fallback on secrets.py until depreciation is over and option is removed
        from secrets import secrets
    except ImportError:
        print("WiFi secrets are kept in settings.toml, please add them there!")
        raise

print("ESP32 SPI webclient test")

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"


# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an AirLift Shield:
# esp32_cs = DigitalInOut(board.D10)
# esp32_ready = DigitalInOut(board.D7)
# esp32_reset = DigitalInOut(board.D5)

# If you have an AirLift Featherwing or ItsyBitsy Airlift:
# esp32_cs = DigitalInOut(board.D13)
# esp32_ready = DigitalInOut(board.D11)
# esp32_reset = DigitalInOut(board.D12)

# If you have an externally connected ESP32:
# NOTE: You may need to change the pins to reflect your wiring
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
    spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
    spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version.decode("utf-8"))
print("MAC addr:", ":".join("%02X" % byte for byte in esp.MAC_address))

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

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except OSError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print(
    "IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))
)
print("Ping google.com: %d ms" % esp.ping("google.com"))

# esp._debug = True
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print("-" * 40)
print(r.text)
print("-" * 40)
r.close()

print()
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print("-" * 40)
print(r.json())
print("-" * 40)
r.close()

print("Done!")

View on GitHub

If you are using CircuitPython 9.1.0, or using the latest version of the ‎ESP32SPI library, using the version below. If you are using ‎CircuitPython 9.0.x on a board with frozen libraries, such as the ‎Matrix Portal M4, use the first version above.‎‎

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

from os import getenv
import board
import busio
from digitalio import DigitalInOut
import adafruit_connection_manager
import adafruit_requests
from adafruit_esp32spi import adafruit_esp32spi

# Get wifi details and more from a settings.toml file
# tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD
secrets = {
    "ssid": getenv("CIRCUITPY_WIFI_SSID"),
    "password": getenv("CIRCUITPY_WIFI_PASSWORD"),
}
if secrets == {"ssid": None, "password": None}:
    try:
        # Fallback on secrets.py until depreciation is over and option is removed
        from secrets import secrets
    except ImportError:
        print("WiFi secrets are kept in settings.toml, please add them there!")
        raise

print("ESP32 SPI webclient test")

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"


# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an AirLift Shield:
# esp32_cs = DigitalInOut(board.D10)
# esp32_ready = DigitalInOut(board.D7)
# esp32_reset = DigitalInOut(board.D5)

# If you have an AirLift Featherwing or ItsyBitsy Airlift:
# esp32_cs = DigitalInOut(board.D13)
# esp32_ready = DigitalInOut(board.D11)
# esp32_reset = DigitalInOut(board.D12)

# If you have an externally connected ESP32:
# NOTE: You may need to change the pins to reflect your wiring
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
    spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
    spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)

if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", ":".join("%02X" % byte for byte in esp.MAC_address))

for ap in esp.scan_networks():
    print("\t%-23s RSSI: %d" % (ap.ssid, ap.rssi))

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except OSError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi)
print("My IP address is", esp.ipv4_address)
print(
    "IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))
)
print("Ping google.com: %d ms" % esp.ping("google.com"))

# esp._debug = True
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print("-" * 40)
print(r.text)
print("-" * 40)
r.close()

print()
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print("-" * 40)
print(r.json())
print("-" * 40)
r.close()

print("Done!")

View on GitHub

And save it to your board, with the name code.py.‎

Don't forget you'll also need to create the settings.toml file as seen ‎above, with your WiFi ssid and password.‎

In a serial console, you should see something like the following. For ‎more information about connecting with a serial console, view the ‎guide Connecting to the Serial Console.‎

serial_58

In order, the example code...‎

Initializes the ESP32 over SPI using the SPI port and 3 control pins:‎

Download File

Copy Code
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

#...

else:
    spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

Gets the socket pool and the SSL context, and then tells ‎the adafruit_requests library about them.‎

‎Download File

Copy Code
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)

Verifies an ESP32 is found, checks the firmware and MAC address.‎

Download File

Copy Code
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])

Performs a scan of all access points it can see and prints out the ‎name and signal strength:‎

‎Download File

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

Connects to the AP we've defined here, then prints out the local IP ‎address, attempts to do a domain name lookup and ping ‎google.com to check network connectivity (note sometimes the ping ‎fails or takes a while, this isn't a big deal).‎

Download File

Copy Code
print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(secrets["ssid"], secrets["password"])
    except RuntimeError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
print(
    "IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))

OK now we're getting to the really interesting part. With a SAMD51 or ‎other large-RAM (well, over 32 KB) device, we can do a lot of neat ‎tricks. Like for example we can implement an interface a lot ‎like requests - which makes getting data really really easy.

To read in all the text from a web URL call requests.get - you can pass ‎in https URLs for SSL connectivity.‎

‎Download File

Copy Code
TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print('-'*40)
print(r.text)
print('-'*40)
r.close()

Or, if the data is in structured JSON, you can get the json pre-parsed ‎into a Python dictionary that can be easily queried or traversed. ‎‎(Again, only for nRF52840, M4 and other high-RAM boards.)‎

‎Download File

Copy Code
JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print('-'*40)
print(r.json())
print('-'*40)
r.close()

Requests

We've written a requests-like library for web interfacing ‎named Adafruit_CircuitPython_Requests. This library allows you to send ‎HTTP/1.1 requests without "crafting" them and provides helpful ‎methods for parsing the response from the server.‎

To use with CircuitPython, you need to first install a few libraries, into ‎the lib folder on your CIRCUITPY drive. Then you need to ‎update code.py with the example script.‎

Thankfully, we can do this in one go. In the example below, click ‎the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file, open the directory examples/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎

requests_98

‎Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import socket as pool
import ssl

import adafruit_requests

# Initialize a requests session
requests = adafruit_requests.Session(pool, ssl.create_default_context())

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_GET_URL = "https://httpbin.org/get"
JSON_POST_URL = "https://httpbin.org/post"

print("Fetching text from %s" % TEXT_URL)
with requests.get(TEXT_URL) as response:
    print("-" * 40)
    print("Text Response: ", response.text)
    print("-" * 40)

print("Fetching JSON data from %s" % JSON_GET_URL)
with requests.get(JSON_GET_URL) as response:
    print("-" * 40)
    print("JSON Response: ", response.json())
    print("-" * 40)

data = "31F"
print(f"POSTing data to {JSON_POST_URL}: {data}")
with requests.post(JSON_POST_URL, data=data) as response:
    print("-" * 40)
    json_resp = response.json()
    # Parse out the 'data' key from json_resp dict.
    print("Data received from server:", json_resp["data"])
    print("-" * 40)

json_data = {"Date": "July 25, 2019"}
print(f"POSTing data to {JSON_POST_URL}: {json_data}")
with requests.post(JSON_POST_URL, json=json_data) as response:
    print("-" * 40)
    json_resp = response.json()
    # Parse out the 'json' key from json_resp dict.
    print("JSON Data received from server:", json_resp["json"])
    print("-" * 40)

View on GitHub

The code first sets up the ESP32SPI interface. Then, it initializes ‎a request object using an ESP32 socket and the esp object.‎

Download File‎

Copy Code
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi
import adafruit_connection_manager
import adafruit_requests as requests

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(b'MY_SSID_NAME', b'MY_SSID_PASSWORD')
    except RuntimeError as e:
        print("could not connect to AP, retrying: ",e)
        continue
print("Connected to", str(esp.ssid, 'utf-8'), "\tRSSI:", esp.rssi)

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)

HTTP GET with Requests

The code makes a HTTP GET request to Adafruit's WiFi testing ‎website - http://wifitest.adafruit.com/testwifi/index.html.‎

To do this, we'll pass the URL into requests.get(). We're also going to ‎save the response from the server into a variable named response.‎

Having requested data from the server, we'd now like to see what ‎the server responded with. Since we already saved the ‎server's response, we can read it back. Luckily for ‎us, requests automatically decodes the server's response into ‎human-readable text, you can read it back by calling response.text.‎

Lastly, we'll perform a bit of cleanup by calling response.close(). This ‎closes, deletes, and collect's the response's data. ‎

Download File‎

Copy Code
print("Fetching text from %s"%TEXT_URL)
response = requests.get(TEXT_URL)
print('-'*40)

print("Text Response: ", response.text)
print('-'*40)
response.close()

While some servers respond with text, some respond with json-‎formatted data consisting of attribute–value pairs.‎

CircuitPython_Requests can convert a JSON-formatted response ‎from a server into a CPython dict. object.‎

We can also fetch and parse json data. We'll send a HTTP get to a url ‎we know returns a json-formatted response (instead of text data). ‎

Then, the code calls response.json() to convert the response to a ‎CPython dict. ‎

‎Download File

Copy Code
print("Fetching JSON data from %s"%JSON_GET_URL)
response = requests.get(JSON_GET_URL)
print('-'*40)

print("JSON Response: ", response.json())
print('-'*40)
response.close()

HTTP POST with Requests

Requests can also POST data to a server by calling the requests.post method, passing it a data value.

Download File

Copy Code
data = '31F'
print("POSTing data to {0}: {1}".format(JSON_POST_URL, data))
response = requests.post(JSON_POST_URL, data=data)
print('-'*40)

json_resp = response.json()
# Parse out the 'data' key from json_resp dict.
print("Data received from server:", json_resp['data'])
print('-'*40)
response.close()

You can also post json-formatted data to a server by ‎passing json_data into the requests.post method.‎

Download File‎ 

Copy Code
    json_data = {"Date" : "July 25, 2019"}
print("POSTing data to {0}: {1}".format(JSON_POST_URL, json_data))
response = requests.post(JSON_POST_URL, json=json_data)
print('-'*40)

json_resp = response.json()
# Parse out the 'json' key from json_resp dict.
print("JSON Data received from server:", json_resp['json'])
print('-'*40)
response.close()
  

Advanced Requests Usage

Want to send custom HTTP headers, parse the response as raw ‎bytes, or handle a response's http status code in your CircuitPython ‎code?‎

We've written an example to show advanced usage of the requests ‎module below.‎

To use with CircuitPython, you need to first install a few libraries, into ‎the lib folder on your CIRCUITPY drive. Then you need to ‎update code.py with the example script.‎

Thankfully, we can do this in one go. In the example below, click ‎the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file, open the directory examples/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎

advanced_99

‎Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import socket as pool
import ssl

import adafruit_requests

# Initialize a requests session
requests = adafruit_requests.Session(pool, ssl.create_default_context())

JSON_GET_URL = "https://httpbin.org/get"

# Define a custom header as a dict.
headers = {"user-agent": "blinka/1.0.0"}

print("Fetching JSON data from %s..." % JSON_GET_URL)
with requests.get(JSON_GET_URL, headers=headers) as response:
    print("-" * 60)
    json_data = response.json()
    headers = json_data["headers"]
    print("Response's Custom User-Agent Header: {0}".format(headers["User-Agent"]))
    print("-" * 60)

    # Read Response's HTTP status code
    print("Response HTTP Status Code: ", response.status_code)
    print("-" * 60)

View on GitHub

WiFi Manager

That simpletest example works but it's a little finicky - you need to ‎constantly check WiFi status and have many loops to manage ‎connections and disconnections. For more advanced uses, we ‎recommend using the WiFiManager object. It will wrap the ‎connection/status/requests loop for you - reconnecting if WiFi drops, ‎resetting the ESP32 if it gets into a bad state, etc.‎

Here's a more advanced example that shows the WiFi manager and ‎also how to POST data with some extra headers:‎

To use with CircuitPython, you need to first install a few libraries, into ‎the lib folder on your CIRCUITPY drive. Then you need to ‎update code.py with the example script.‎

Thankfully, we can do this in one go. In the example below, click ‎the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file, open the directory examples/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎ ‎

drive_59

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
from os import getenv
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager

print("ESP32 SPI webclient test")

# Get wifi details and more from a settings.toml file
# tokens used by this Demo: CIRCUITPY_WIFI_SSID, CIRCUITPY_WIFI_PASSWORD
#                           CIRCUITPY_AIO_USERNAME, CIRCUITPY_AIO_KEY
secrets = {}
for token in ["ssid", "password"]:
    if getenv("CIRCUITPY_WIFI_" + token.upper()):
        secrets[token] = getenv("CIRCUITPY_WIFI_" + token.upper())
for token in ["aio_username", "aio_key"]:
    if getenv("CIRCUITPY_" + token.upper()):
        secrets[token] = getenv("CIRCUITPY_" + token.upper())

if not secrets:
    try:
        # Fallback on secrets.py until depreciation is over and option is removed
        from secrets import secrets
    except ImportError:
        print("WiFi secrets are kept in settings.toml, please add them there!")
        raise

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

# Secondary (SCK1) SPI used to connect to WiFi board on Arduino Nano Connect RP2040
if "SCK1" in dir(board):
    spi = busio.SPI(board.SCK1, board.MOSI1, board.MISO1)
else:
    spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
"""Uncomment below for an externally defined RGB LED (including Arduino Nano Connect)"""
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)

wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

counter = 0

while True:
    try:
        print("Posting data...", end="")
        data = counter
        feed = "test"
        payload = {"value": data}
        response = wifi.post(
            "https://io.adafruit.com/api/v2/"
            + secrets["aio_username"]
            + "/feeds/"
            + feed
            + "/data",
            json=payload,
            headers={"X-AIO-KEY": secrets["aio_key"]},
        )
        print(response.json())
        response.close()
        counter = counter + 1
        print("OK")
    except OSError as e:
        print("Failed to get data, retrying\n", e)
        wifi.reset()
        continue
    response = None
    time.sleep(15)

View on GitHub

You'll note here we use a secrets.py file to manage our SSID info. The ‎wifimanager is given the ESP32 object, secrets and a NeoPixel for ‎status indication.‎

Note, you'll need to add some additional information to your secrets ‎file so that the code can query the Adafruit IO API:‎

  • aio_username

  • aio_key

You can go to your adafruit.io View AIO Key link to get those two ‎values and add them to the secrets file, which will now look ‎something like this:‎

Download File

Copy Code
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : '_your_ssid_',
    'password' : '_your_wifi_password_',
    'timezone' : "America/Los_Angeles", # http://worldtimeapi.org/timezones
    'aio_username' : '_your_aio_username_',
    'aio_key' : '_your_aio_key_',
    }

Next, set up an Adafruit IO feed named test

We can then have a simple loop for posting data to Adafruit IO ‎without having to deal with connecting or initializing the hardware!‎

Take a look at your test feed on Adafruit.io and you'll see the value ‎increase each time the CircuitPython board posts data to it!‎

feed_60

For more information on the basics of doing networking in ‎CircuitPython, see this guide:‎

Networking in CircuitPython

By Anne Barela

View Guide

network

Text editor powered by tinymce.‎

The MatrixPortal library was inspired by the PyPortal library, but a ‎slightly different approach was taken. Rather than having everything ‎in a single module, it was divided into layers. The reason for having ‎different layers is you can use lower layers if you want more control ‎and better memory usage.‎

The main library now piggyback's on top of the base library. The base ‎library was named PortalBase which is split up into 3 components. ‎The main base, the GraphicsBase, and the NetworkBase. In the ‎diagram, you can see these components represented in blue.‎

We also have a library for lower-level control of just the RGB Matrix, but it ‎doesn't have integrated WiFi access so we recommend using the ‎MatrixPortal library.‎

Here is the way it is logically laid out with dependencies. The ‎MatrixPortal library is comprised of the top layer, the Network and ‎Graphics layers, and the WiFi and Matrix layers in the diagram.‎

diagram_61

There are two main branches of dependencies related to Network ‎Functionality and Graphics functionality. The MatrixPortal library ties ‎them both together and allows easier coding, but at the cost of more ‎memory usage and less control. We'll go through each of the classes ‎starting from the bottom and working our way up the diagram ‎starting with the Network branch.‎

Network Branch

The network branch contains all of the functionality related to ‎connecting to the internet and retrieving data. You will want to use ‎this branch if your project needs to retrieve any data that is not ‎stored on the device itself.‎

WiFi Module

The WiFi module is responsible for initializing the hardware libraries, ‎controlling the status NeoPixel colors, and initializing the WiFi ‎manager. You would want to use this library if you only wanted to ‎handle the automatic initialization of hardware and connection to ‎WiFi and didn't need any other functionality.‎

Network Module

The network module has many convenience functions for making ‎network calls. It handles a lot of things from automatically ‎establishing the connection to getting the time from the internet, to ‎getting data at certain URLs. This is one of the largest of the modules ‎as there is a lot of functionality packed into this.‎

Graphics Branch

This branch is a lot lighter than the Network Branch because so ‎much of the functionality is built into CircuitPython and displayio.‎

Matrix Module

The matrix module is responsible for detecting and initializing the ‎matrix through the CircuitPython rgbmatrix and framebufferio ‎modules. It currently supports the MatrixPortal M4 and Metro M4 ‎with RGB Matrix Shield. If you just wanted to initialize the matrix, you ‎could use this module. If you would like to go lower level than this ‎and use the rgbmatrix and framebufferio libraries directly, be sure to ‎check out the guide RGB LED Matrices with CircuitPython.‎

Graphics Module

This module will initialize the Matrix through the matrix module. The ‎main purpose of this module was to add any graphics convenience ‎functions in such as displaying a background easily.‎

MatrixPortal Module

The MatrixPortal module is top level module and will handle ‎initializing everything below it. Using this module is very similar to ‎using the PyPortal library. The main differences are:‎

  • Text labels are added after the module is initialized

  • Text labels can either be scrolling or static

  • There are more Adafruit IO functions

Library Demos

The MatrixPortal library has been used in a number of projects. Here ‎are a few of them with guides available.‎

Text editor powered by tinymce.‎

CircuitPython is designed to run on microcontrollers and allows you ‎to interface with all kinds of sensors, inputs, and other hardware ‎peripherals. There are tons of guides showing how to wire up a ‎circuit, and use CircuitPython to, for example, read data from a ‎sensor, or detect a button press. Most CircuitPython code includes ‎hardware setup which requires various modules, such ‎as board or digitalio. You import these modules and then use them in ‎your code. How does CircuitPython know to look for hardware in the ‎specific place you connected it, and where do these modules come ‎from?‎

This page explains both. You'll learn how CircuitPython finds the pins ‎on your microcontroller board, including how to find the available ‎pins for your board and what each pin is named. You'll also learn ‎about the modules built into CircuitPython, including how to find all ‎the modules available for your board.‎

CircuitPython Pins

When using hardware peripherals with a CircuitPython compatible ‎microcontroller, you'll almost certainly be utilising pins. This section ‎will cover how to access your board's pins using CircuitPython, how ‎to discover what pins and board-specific objects are available in ‎CircuitPython for your board, how to use the board-specific objects, ‎and how to determine all available pin names for a given pin on your ‎board.‎

import board

When you're using any kind of hardware peripherals wired up to ‎your microcontroller board, the import list in your code will ‎include import board. The board module is built into CircuitPython and is ‎used to provide access to a series of board-specific objects, including ‎pins. Take a look at your microcontroller board. You'll notice that next ‎to the pins are pin labels. You can always access a pin by its pin label. ‎However, there are almost always multiple names for a given pin.‎

To see all the available board-specific objects and pins for your board, ‎enter the REPL (>>>) and run the following commands:‎

‎Download File

Copy Code
import board
dir(board)

Here is the output for the QT Py SAMD21. You may have a different ‎board, and this list will vary, based on the board.‎

commands_62

The following pins have labels on the physical QT Py SAMD21 board: ‎A0, A1, A2, A3, SDA, SCL, TX, RX, SCK, MISO, and MOSI. You see that ‎there are many more entries available in board than the labels on the ‎QT Py.‎

You can use the pin names on the physical board, regardless of ‎whether they seem to be specific to a certain protocol.‎

For example, you do not have to use the SDA pin for I2C - you can ‎use it for a button or LED.‎

On the flip side, there may be multiple names for one pin. For ‎example, on the QT Py SAMD21, pin A0 is labeled on the physical ‎board silkscreen, but it is available in CircuitPython as both A0 and D0. ‎For more information on finding all the names for a given pin, see ‎the What Are All the Available Pin Names? section below.‎

The results of dir(board) for CircuitPython compatible boards will look ‎similar to the results for the QT Py SAMD21 in terms of the pin names, ‎e.g. A0, D0, etc. However, some boards, for example, the Metro ‎ESP32-S2, have different styled pin names. Here is the output for the ‎Metro ESP32-S2.‎

import_63

Note that most of the pins are named in an IO# style, such ‎as IO1 and IO2. Those pins on the physical board are labeled only ‎with a number, so an easy way to know how to access them in ‎CircuitPython, is to run those commands in the REPL and find the ‎pin naming scheme.‎

If your code is failing to run because it can't find a pin name you ‎provided, verify that you have the proper pin name by running these ‎commands in the REPL.‎

I2C, SPI, and UART

You'll also see there are often (but not always!) three special board-‎specific objects included: I2C, SPI, and UART - each one is for the default ‎pin-set used for each of the three common protocol busses they are ‎named for. These are called singletons.‎What's a singleton?

When you create an object in CircuitPython, you ‎are instantiating ('creating') it. Instantiating an object means you are ‎creating an instance of the object with the unique values that are ‎provided, or "passed", to it.‎

For example, when you instantiate an I2C object using ‎the busio module, it expects two pins: clock and data, typically SCL ‎and SDA. It often looks like this:‎

Download File

Copy Code
i2c = busio.I2C(board.SCL, board.SDA)

Then, you pass the I2C object to a driver for the hardware you're ‎using. For example, if you were using the TSL2591 light sensor and its ‎CircuitPython library, the next line of code would be:‎

Download File

Copy Code
tsl2591 = adafruit_tsl2591.TSL2591(i2c)

However, CircuitPython makes this simpler by including ‎the I2C singleton in the board module. Instead of the two lines of code ‎above, you simply provide the singleton as the I2C object. So if you ‎were using the TSL2591 and its CircuitPython library, the two above ‎lines of code would be replaced with:‎

‎Download File

Copy Code
tsl2591 = adafruit_tsl2591.TSL2591(board.I2C())

The board.I2C(), board.SPI(), and board.UART() singletons do not exist ‎on all boards. They exist if there are board markings for the default ‎pins for those devices.‎

This eliminates the need for the busio module and simplifies the code. ‎Behind the scenes, the board.I2C() object is instantiated when you call ‎it, but not before, and on subsequent calls, it returns the same object. ‎Basically, it does not create an object until you need it and provides ‎the same object every time you need it. You can call board.I2C() as ‎many times as you like, and it will always return the same object.‎

The UART/SPI/I2C singletons will use the 'default' bus pins for each ‎board - often labeled as RX/TX (UART), MOSI/MISO/SCK (SPI), or ‎SDA/SCL (I2C). Check your board documentation/pinout for the ‎default busses.‎

What Are All the Available Names?‎

Many pins on CircuitPython compatible microcontroller boards have ‎multiple names, however, typically, there's only one name labeled on ‎the physical board. So how do you find out what the other available ‎pin names are? Simple, with the following script! Each line printed ‎out to the serial console contains the set of names for a particular pin.‎

On a microcontroller board running CircuitPython, first, connect to ‎the serial console.‎

In the example below, click the Download Project Bundle button ‎below to download the necessary libraries and the code.py file in a ‎zip file. Extract the contents of the zip file, open the ‎directory CircuitPython_Essentials/Pin_Map_Script/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎

drive_64

‎Download Project Bundle‎

Copy Code
# SPDX-FileCopyrightText: 2020 anecdata for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Neradoc for Adafruit Industries
# SPDX-FileCopyrightText: 2021-2023 Kattni Rembor for Adafruit Industries
# SPDX-FileCopyrightText: 2023 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""CircuitPython Essentials Pin Map Script"""
import microcontroller
import board
try:
    import cyw43  # raspberrypi
except ImportError:
    cyw43 = None

board_pins = []
for pin in dir(microcontroller.pin):
    if (isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin) or
        (cyw43 and isinstance(getattr(microcontroller.pin, pin), cyw43.CywPin))):
        pins = []
        for alias in dir(board):
            if getattr(board, alias) is getattr(microcontroller.pin, pin):
                pins.append(f"board.{alias}")
        # Add the original GPIO name, in parentheses.
        if pins:
            # Only include pins that are in board.
            pins.append(f"({str(pin)})")
            board_pins.append(" ".join(pins))

for pins in sorted(board_pins):
    print(pins)

‎View on GitHub

Here is the result when this script is run on QT Py SAMD21:‎

results_65

Each line represents a single pin. Find the line containing the pin ‎name that's labeled on the physical board, and you'll find the other ‎names available for that pin. For example, the first pin on the board ‎is labeled A0. The first line in the output is board.A0 board.D0 (PA02). This ‎means that you can access pin A0 in CircuitPython using ‎both board.A0 and board.D0.‎

The pins in parentheses are the microcontroller pin names. See the ‎next section for more info on those.‎

You'll notice there are two "pins" that aren't labeled on the board but ‎appear in the list: board.NEOPIXEL and board.NEOPIXEL_POWER. Many boards have ‎several of these special pins that give you access to built-in board ‎hardware, such as an LED or an on-board sensor. The QT Py SAMD21 ‎only has one on-board extra piece of hardware, a NeoPixel LED, so ‎there's only the one available in the list. But you can also control ‎whether or not power is applied to the NeoPixel, so there's a ‎separate pin for that.‎

That's all there is to figuring out the available names for a pin on a ‎compatible microcontroller board in CircuitPython!‎

Microcontroller Pin Names

The pin names available to you in the CircuitPython board module are ‎not the same as the names of the pins on the microcontroller itself. ‎The board pin names are aliases to the microcontroller pin names. If ‎you look at the datasheet for your microcontroller, you'll likely find a ‎pinout with a series of pin names, such as "PA18" or "GPIO5". If you ‎want to get to the actual microcontroller pin name in CircuitPython, ‎you'll need the microcontroller.pin module. As with board, you can ‎run dir(microcontroller.pin) in the REPL to receive a list of the ‎microcontroller pin names.‎

names_66

Microcontroller pin names for QT Py SAMD21.‎

CircuitPython Built-In Modules

There is a set of modules used in most CircuitPython programs. One ‎or more of these modules is always used in projects involving ‎hardware. Often hardware requires installing a separate library from ‎the Adafruit CircuitPython Bundle. But, if you try to ‎find board or digitalio in the same bundle, you'll come up lacking. So, ‎where do these modules come from? They're built into CircuitPython! ‎You can find an comprehensive list of built-in CircuitPython modules ‎and the technical details of their functionality from ‎CircuitPython here and the Python-like modules included here. ‎However, not every module is available for every board due to size ‎constraints or hardware limitations. How do you find out what ‎modules are available for your board?‎

There are two options for this. You can check the support matrix, and ‎search for your board by name. Or you can use the REPL.‎

Plug in your board, connect to the serial console, and enter the REPL. ‎Type the following command.‎

‎Download File

Copy Code
help("modules")

 

command_67

help("modules") results for QT Py

That's it! You now know two ways to find all of the modules built into ‎CircuitPython for your compatible microcontroller board.‎

Text editor powered by tinymce.‎

CircuitPython BLE UART Example

It's easy to use Adafruit AirLift ESP32 co-processor boards for ‎Bluetooth Low Energy (BLE) with CircuitPython. When you reset the ‎ESP32, you can put it in WiFi mode (the default), or in BLE mode; you ‎cannot use both modes simultaneously.‎

Here's a simple example of using BLE to connect CircuitPython with ‎the Bluefruit Connect app. Use CircuitPython 6.0.0 or later.‎

Note: Don't confuse the ESP32 with the ESP32-S2, which is a ‎different module with a similar name. The ESP32-S2 does not ‎support BLE.‎

Currently, AirLift BLE support is not currently available on boards ‎with Espressif chips. If the Espressif board provides _bleio, it is for ‎native BLE support (e.g. ESP32-S3), not AirLift.‎

Currently the AirLift support for CircuitPython only provides BLE ‎peripheral support. BLE central is under development. So you cannot ‎connect to BLE devices like Heart Rate monitors, etc., but you can ‎act as a BLE peripheral yourself.‎

On-Board Airlift Co-Processor - No Wiring ‎Needed

If you have an Adafruit Metro M4 AirLift Lite, an Adafruit ‎PyPortal (regular, Pynt or Titano), an Adafruit MatrixPortal, or other ‎Adafruit board with an onboard ESP32 co-processor, then everything ‎is prewired for you, and the pins you need to use are predefined in ‎CircuitPython.‎

Update the AirLift Firmware

You will need to update the AirLift's firmware to at least version ‎‎1.7.1. Previous versions of the AirLift firmware do not support BLE. ‎

Follow the instructions in the guide below, and come back to this ‎page when you've upgraded the AirLift's firmware:‎

Upgrade ESP32 AirLift Firmware

Ensure the AirLift firmware is version 1.7.1 or higher for BLE to work.‎

Install CircuitPython Libraries

First make sure you are running the latest version of Adafruit ‎CircuitPython for your board.‎

Next, you'll need to install the necessary libraries to use the ‎hardware. Thankfully, we can do this in one go. In the example below, ‎click the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file and copy the entire lib folder and ‎the code.py file to your CIRCUITPY drive.‎

Your CIRCUITPY/lib folder should contain the following folders and ‎files:‎

  • /adafruit_airlift‎

  • ‎/adafruit_ble

  • ‎/adafruit_bus_device‎

  • ‎/adafruit_esp32spi‎

  • adafruit_requests.mpy

circuitpy_68

Install the Adafruit Bluefruit LE Connect ‎App

The Adafruit Bluefruit LE Connect iOS and Android apps allow you to ‎connect to BLE peripherals that provide a over-the-air "UART" service. ‎Follow the instructions in the Bluefruit LE Connect Guide to download ‎and install the app on your phone or tablet.‎

BLE Example

TAKE NOTE: Adjust the program as needed to suit the AirLift board ‎you have. Comment and uncomment lines 19-55 below as necessary.‎‎

Download Project Bundle‎

Copy Code
# SPDX-FileCopyrightText: 2020 Dan Halbert, written for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

# pylint: disable=unused-import
import board
import busio
from digitalio import DigitalInOut
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_airlift.esp32 import ESP32

# If you are using a Metro M4 Airlift Lite, PyPortal,
# or MatrixPortal, you can use the default pin settings.
# Leave this DEFAULT line uncommented.
# If you are using a board with pre-defined ESP32 Pins:
esp32 = ESP32()

# If you are using a Metro M7 **OR**
# if you are using CircuitPython 6.0.0 or earlier,
# on PyPortal and PyPortal Titano only, use the pin settings
# below. Comment out the DEFAULT line above and uncomment
# the line below. For CircuitPython 6.1.0, the pin names
# have changed for these boards, and the DEFAULT line
# above is correct.
# esp32 = ESP32(tx=board.TX, rx=board.RX)

# If you are using an AirLift FeatherWing or AirLift Bitsy Add-On,
# use the pin settings below. Comment out the DEFAULT line above
# and uncomment the lines below.
# If you are using an AirLift Breakout, check that these
# choices match the wiring to your microcontroller board,
# or change them as appropriate.
# esp32 = ESP32(
#     reset=board.D12,
#     gpio0=board.D10,
#     busy=board.D11,
#     chip_select=board.D13,
#     tx=board.TX,
#     rx=board.RX,
# )

# If you are using an AirLift Shield,
# use the pin settings below. Comment out the DEFAULT line above
# and uncomment the lines below.
# esp32 = ESP32(
#     reset=board.D5,
#     gpio0=board.D6,
#     busy=board.D7,
#     chip_select=board.D10,
#     tx=board.TX,
#     rx=board.RX,
# )

adapter = esp32.start_bluetooth()

ble = BLERadio(adapter)
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)

while True:
    ble.start_advertising(advertisement)
    print("waiting to connect")
    while not ble.connected:
        pass
    print("connected: trying to read input")
    while ble.connected:
        # Returns b'' if nothing was read.
        one_byte = uart.read(1)
        if one_byte:
            print(one_byte)
            uart.write(one_byte)

View on GitHub

Talk to the AirLift via the Bluefruit LE ‎Connect App

Start the Bluefruit LE Connect App on your phone or tablet. You ‎should see a CIRCUITPY device available to connect to. Tap the ‎Connect button (1):‎

app_69

You'll then see a list of Bluefruit Connect functions ("modules"). ‎Choose the UART module (2):‎

app_70

On the UART module page, you can type a string and press Send (3). ‎You'll see that string entered, and then see it echoed back (echoing ‎is in gray).‎

page_71

Text editor powered by tinymce.‎

The first thing you will need to do is to download the latest release of ‎the Arduino IDE. You will need to be using version 1.8 or higher for ‎this guide.

Arduino IDE Download

After you have downloaded and installed the latest version of ‎Arduino IDE, you will need to start the IDE and navigate ‎to the Preferences menu. You can access it from the File menu ‎in Windows or Linux, or the Arduino menu on OS X.‎

menu_72

A dialog will pop up just like the one shown below.‎

preferences_73

We will be adding a URL to the new Additional Boards Manager ‎URLs option. The list of URLs is comma separated, and you will only ‎have to add each URL once. New Adafruit boards and updates to ‎existing boards will automatically be picked up by the Board ‎Manager each time it is opened. The URLs point to index files that ‎the Board Manager uses to build the list of available & installed ‎boards.‎

To find the most up to date list of URLs you can add, you can visit the ‎list of third party board URLs on the Arduino IDE wiki. We will only need to ‎add one URL to the IDE in this example, but you can add multiple ‎URLS by separating them with commas. Copy and paste the link ‎below into the Additional Boards Manager URLs option in the ‎Arduino IDE preferences.‎

https://adafruit.github.io/arduino-board-‎index/package_adafruit_index.json

preferences_74

Here's a short description of each of the Adafruit supplied packages ‎that will be available in the Board Manager when you add the URL:‎

  • Adafruit AVR Boards - Includes support for Flora, Gemma, ‎Feather 32u4, ItsyBitsy 32u4, Trinket, & Trinket Pro.‎

  • Adafruit SAMD Boards - Includes support for Feather M0 and ‎M4, Metro M0 and M4, ItsyBitsy M0 and M4, Circuit Playground ‎Express, Gemma M0 and Trinket M0‎

  • Arduino Leonardo & Micro MIDI-USB - This adds MIDI over ‎USB support for the Flora, Feather 32u4, Micro and Leonardo ‎using the arcore project.‎

If you have multiple boards you want to support, say ESP8266 and ‎Adafruit, have both URLs in the text box separated by a comma (,)

‎Once done click OK to save the new preference settings. Next, we ‎will look at installing boards with the Board Manager.‎

Now continue to the next step to actually install the board support ‎package!‎

Text editor powered by tinymce.‎

Adafruit boards that use ATSAMD21 ("M0") or ATSAMD51 ("M4") chips ‎are easy to get working with the Arduino IDE. Most libraries ‎‎(including the popular ones like NeoPixels and display) will work ‎with those boards, especially devices & sensors that use I2C or SPI.‎

Now that you have added the appropriate URLs to the Arduino IDE ‎preferences in the previous page, you can open the Boards ‎Manager by navigating to the Tools->Board menu.‎

menu_75

Once the Board Manager opens, click on the category drop down ‎menu on the top left-hand side of the window and select All. You will ‎then be able to select and install the boards supplied by the URLs ‎added to the preferences.‎

Remember you need SETUP the Arduino IDE to support our board ‎packages - see the previous page on how to add Adafruit’s URL to ‎the preferences.

Install SAMD Support

First up, install the latest Arduino SAMD Boards (version 1.6.11 or ‎later)‎

You can type Arduino SAMD in the top search bar, then when you ‎see the entry, click Install.‎

install_76 

Install Adafruit SAMD

Next you can install the Adafruit SAMD package to add the board file ‎definitions.

Make sure you have Type All selected to the left of the Filter your ‎search... box.

You can type Adafruit SAMD in the top search bar, then when you ‎see the entry, click Install.‎

install_77

Quit and reopen the Arduino IDE to ensure that all of the boards are ‎properly installed. You should now be able to select and upload to ‎the new boards listed in the Tools->Board menu.‎

Select the matching board, the current options are:‎

  • Feather M0 (for use with any Feather M0 other than the ‎Express)

  • Feather M0 Express

  • Metro M0 Express

  • Circuit Playground Express

  • Gemma M0‎

  • Trinket M0

  • QT Py M0‎

  • ItsyBitsy M0

  • Hallowing M0

  • Crickit M0 (this is for direct programming of the Crickit, which ‎is probably not what you want! For advanced hacking only)‎

  • Metro M4 Express

  • Grand Central M4 Express

  • ItsyBitsy M4 Express

  • Feather M4 Express

  • Trellis M4 Express

  • PyPortal M4

  • PyPortal M4 Titano

  • PyBadge M4 Express

  • Metro M4 Airlift Lite

  • PyGamer M4 Express

  • MONSTER M4SK

  • Hallowing M4

  • MatrixPortal M4

  • BLM Badge

tools_78

Windows 7 and 8.1

Windows 7 and Windows 8.1 have reached end-of-life and are no ‎longer supported. They required driver installation. A limited set of ‎drivers is available for older boards, but drivers for most newer boards are ‎not available.‎

Blink

Now you can upload your first blink sketch!‎

Plug in the SAMD21 M0 or SAMD51 M4 board and wait for it to be ‎recognized by the OS (just takes a few seconds). It will create a ‎serial/COM port, you can now select it from the drop-down, it'll even ‎be 'indicated' as Trinket/Gemma/Metro/Feather/ItsyBitsy/QT ‎Py/Trellis or whatever the board is named!‎

A few boards, such as the QT Py SAMD21, Trellis M4 Express, and ‎certain Trinkey boards, do not have an onboard pin 13 LED. You can ‎follow this section to practice uploading but you won't see an LED ‎blink!‎

tools_79

Now load up the Blink example

‎Download File

Copy Code
// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

And click upload! That's it, you will be able to see the LED blink rate ‎change as you adapt the delay() calls.‎

If you are having issues, make sure you selected the matching Board ‎in the menu that matches the hardware you have in your hand.‎

Successful Upload

If you have a successful upload, you'll get a bunch of red text that ‎tells you that the device was found and it was programmed, verified ‎& reset.‎

upload_80

After uploading, you may see a message saying "Disk ‎Not Ejected Properly" about the ...BOOT drive. You can ignore that ‎message: it's an artifact of how the bootloader and uploading work.‎

Compilation Issues

If you get an alert that looks like

Cannot run program "{runtime.tools.arm-none-eabi-‎gcc.path}\bin\arm-non-eabi-g++"‎

Make sure you have installed the Arduino SAMD boards package, ‎you need both Arduino & Adafruit SAMD board packages.‎

issues_81

Manually bootloading

If you ever get in a 'weird' spot with the bootloader, or you have ‎uploaded code that crashes and doesn't auto-reboot into the ‎bootloader, click the RST button twice (like a double-click) to get ‎back into the bootloader.‎

The red LED will pulse and/or RGB LED will be green, so you know ‎that it’s in bootloader mode.‎

Once it is in bootloader mode, you can select the newly created ‎COM/Serial port and re-try uploading.‎

boot_82

You may need to go back and reselect the 'normal' USB serial port ‎next time you want to use the normal upload.‎

Ubuntu & Linux Issue Fix

Follow the steps for installing Adafruit's udev rules on this page.‎

Text editor powered by tinymce.‎

OK now that you have Arduino IDE set up, drivers installed if ‎necessary and you've practiced uploading code, you can start ‎installing all the libraries we'll be using to program it.‎

There's a lot of libraries!‎

Install Libraries

Open the library manager...‎

lib_83

And install the following libraries:‎

Adafruit NeoPixel

This will let you light up the status LED on the back.‎

neo_84

Adafruit SPIFlash

This is also needed to use the filesystem on QSPI.‎

spi_85

Adafruit Protomatter

This library is used for writing to the RGB Matrix.‎

proto_86

Adafruit LIS3DH

This will let you use the onboard accelerometer.‎

lis_87

Adafruit GFX

This is the graphics library used to draw to the screen.‎

gfx_88

If using an older (pre-1.8.10) Arduino IDE, locate and ‎install Adafruit_BusIO (newer versions do this automatically when ‎installing Adafruit_GFX).‎

WiFiNINA

Will talk to the ESP32 WiFi co-processor to connect to the internet! ‎We're using a variant of the Arduino WiFiNINA library, which is ‎amazing and written by the Arduino team! The official WiFi101 ‎library won't work because it doesn't support the ability to change ‎the pins.‎

So! We made a fork that you can install. For more installation ‎information see Arduino IO Library.‎

Download Adafruit's version of WiFiNINA

Adafruit ImageReader

For reading bitmaps from SD and displaying.‎

reader_89

Adafruit PixelDust

To compile and run the PixelDust demo, you will need this library. ‎This library calculates where the particles should be.‎

pixel_90

Text editor powered by tinymce.‎

Let’s look at a minimal Arduino example for the ‎Adafruit_Protomatter library to illustrate how this works (this is pared ‎down from the “simple” example sketch):‎

‎Download File

Copy Code
#include <Adafruit_Protomatter.h>

uint8_t rgbPins[]  = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20};
uint8_t clockPin   = 14;
uint8_t latchPin   = 15;
uint8_t oePin      = 16;

Adafruit_Protomatter matrix(
  64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false);

void setup(void) {
  Serial.begin(9600);

  // Initialize matrix...
  ProtomatterStatus status = matrix.begin();
  Serial.print("Protomatter begin() status: ");
  Serial.println((int)status);
  if(status != PROTOMATTER_OK) {
    for(;;);
  }

  // Make four color bars (red, green, blue, white) with brightness ramp:
  for(int x=0; x<matrix.width(); x++) {
    uint8_t level = x * 256 / matrix.width(); // 0-255 brightness
    matrix.drawPixel(x, matrix.height() - 4, matrix.color565(level, 0, 0));
    matrix.drawPixel(x, matrix.height() - 3, matrix.color565(0, level, 0));
    matrix.drawPixel(x, matrix.height() - 2, matrix.color565(0, 0, level));
    matrix.drawPixel(x, matrix.height() - 1, matrix.color565(level, level, level));
  }

  // Simple shapes and text, showing GFX library calls:
  matrix.drawCircle(12, 10, 9, matrix.color565(255, 0, 0));               // Red
  matrix.drawRect(14, 6, 17, 17, matrix.color565(0, 255, 0));             // Green
  matrix.drawTriangle(32, 9, 41, 27, 23, 27, matrix.color565(0, 0, 255)); // Blue
  matrix.println("ADAFRUIT"); // Default text color is white

  // AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!

  matrix.show(); // Copy data to matrix buffers
}

void loop(void) {
  Serial.print("Refresh FPS = ~");
  Serial.println(matrix.getFrameCount());
  delay(1000);
}

Breaking it down into steps…‎

Include Protomatter Library

First is to #include the library’s header file. This in ‎turn #includes Adafruit_GFX.h, so you don’t have to.‎

‎Download File‎

Copy Code
#include <Adafruit_Protomatter.h>

Setting Up Matrix Pin Usage

The next few lines spell out the pin numbers being used. Using ‎variables for this isn’t entirely necessary…one could just pass the ‎same numeric values directly to functions…but it makes the code a ‎little more self-documenting (and easier to adapt the same sketch ‎for multiple boards — the full example code has #ifdefs for each ‎board with different pin assignments). These also could ‎be #defines or const if one wants to be all Proper™ about it.‎

Technical stuff for developers, skip this if you just want to use the ‎library:‎

This is the one part of the Arduino code where some knowledge of ‎the underlying hardware is required. rgbPins[] and clockPin must all be ‎on the same GPIO PORT peripheral (e.g., all PORTA, all PORTB, etc.). ‎The other pins have no such restrictions. Additionally, if the PORT ‎has an atomic bit-toggle register, RAM requirements are minimized ‎if rgbPins[] and clockPin are all within the same byte of that PORT*. ‎They do not need to be contiguous nor in any particular ‎sequence within that byte. If not within the same byte, next most ‎efficient has them in the same upper or lower 16-bit word of the ‎PORT. Scattered around a full 32-bit PORT still works but is the least ‎RAM-efficient option.‎

‎* For devices lacking an atomic bit-toggle register…clockPin does ‎not need to be in the same byte, but still must be in the same PORT. ‎Should still aim for rgbPins[] in a single byte or word though!‎

With those constraints in mind, here’s what the code looks like for an ‎Adafruit MatrixPortal M4 with a 64x32 pixel matrix:‎

‎Download File

Copy Code
uint8_t rgbPins[]  = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20};
uint8_t clockPin   = 14;
uint8_t latchPin   = 15;
uint8_t oePin      = 16;

The full “simple” example sketch has setups for a number of different ‎boards and adapters.‎

Create the Protomatter Object

Next, still in the global area above setup(), we call the constructor. The ‎Arduino library can only drive one matrix at a time (or one chain of ‎matrices, where “out” from one is linked to “in” of the next), so we ‎just have one instance of an Adafruit_Protomatter object here, which we’ll ‎call matrix:‎

‎Download File‎

Copy Code
Adafruit_Protomatter matrix(
  64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false);

The Adafruit_Protomatter constructor expects between 9 and 11 ‎arguments depending on the situation. The vital ones here, in order, ‎are:‎

  • 64 — the total matrix chain width, in pixels. This will usually ‎be 64 or 32, the width of most common RGB LED matrices…but, ‎if you have some other size or multiple matrices chained ‎together, add up the total width here. For example, three ‎chained 32-pixel-wide matrices would be 96.‎

  • 4 — the bit depth, in planes, from 1 to 6 (see below). More ‎bitplanes provides greater color fidelity at the expense of more ‎RAM. A value of 4 here (4 bits) provides 16 brightness levels ‎each for red, green, and blue — yielding 4,096 distinct colors ‎possible.‎

  • 1 — the number of matrix chains in parallel. This will almost ‎always be 1, but the library could conceivably support up to ‎‎5, if the hardware driving it is set up precisely just so.‎

  • rgbPins — a uint8_t array of pin numbers, which issue the red, ‎green and blue data for the upper and lower half of the matrix ‎‎(sometimes labeled R1, G1, B1, R2, G2, B2 on the matrix input). ‎The array should contain six times the prior argument…so, ‎usually, six. If driving two chains in parallel, then 12 pin numbers ‎and so forth. Obviously 12 pins won’t fit in a single PORT byte, ‎and you should aim for the upper or lower 16-bit word in that ‎case, for best RAM utilization. Three or more chains, doesn’t ‎matter, but the pins all do still need to be in the same PORT.‎

  • 4 — the number of row-select “address lines” used by the LED ‎matrix (sometimes labeled A, B, C, etc. on the matrix input). 16-‎pixel-tall matrices will be three row-select lines, 32-pixel will ‎have four, and 64-pixel will have five. Matrix height is ‎always inferred from this value, not passed explicitly like width.‎

  • addrPins — a uint8_t array of pin numbers, one for each row-‎select address line, starting from least-significant bit. These do ‎not need to be on the same PORT as rgbPins or each other…they ‎can be mixed about anywhere.‎

  • clockPin — pin number which drives the RGB clock (CLK on ‎matrix input). This must be on the same PORT register ‎as rgbPins, and in most cases should also try to be in the ‎same byte.‎

  • latchPin — pin number for “latch” signal (LAT on matrix input), ‎indicating end-of-data. Can be any output-capable pin, no ‎special constraints.‎

  • oePin — pin number for “!OE” signal (output-enable low, OE on ‎matrix input). Can be any output-capable pin, no special ‎constraints.‎

  • false — this flag indicates if the display should be double-‎buffered, better for animation at the expense of double the ‎RAM usage. Since the protomatter example isn’t using ‎animation, it passes false here…but if you look at ‎the doublebuffer_scrolltext example, it uses true. A double-‎buffered display only modifies the matrix between refreshes, ‎avoiding “tearing” artifacts. Optional. Default, if left unspecified, ‎is false.‎

  • Not used here, an optional 11th argument supports “tiling” of ‎matrices vertically. Horizontal tiling is already implicit in the ‎first argument — if you had two 64x32 matrices side-by-side, ‎you’d pass 128 there. But if you had four such matrices arranged ‎‎2x2, you’d still pass 128 for the first argument, but then add ‎either 2 here (if cabling is in a “progressive” order) or -2 (if a ‎‎“serpentine” order, where the second row of panels is rotated ‎‎180° relative to the first…the cabling is a little easier). The ‎‎“tiled.ino” example demonstrates this. The concept is explained ‎further in the CircuitPython LED Matrix guide…the same principles ‎apply to the Arduino library, the arguments are just a little ‎different here. Default if unspecified is 1 (no vertical tiling).‎

  • Also not used here, an optional 12th argument is a pointer to a ‎hardware-specific timer structure…this is super exceedingly ‎esoteric and not really used for now, but in principle would ‎allow the library to work with other timer peripherals than the ‎default.‎

Begin Protomatter Driver

Now, with the matrix object created, inside setup() we call ‎its begin() function. It’s pretty important to look at the value returned, ‎which is a ProtomatterStatus type:‎

‎Download File

Copy Code
ProtomatterStatus status = matrix.begin();

Possible return status values include:

  • PROTOMATTER_OK — everything is good, and the program can ‎proceed (otherwise it should stop…the example code is not a ‎good neighbor in this regard).

  • PROTOMATTER_ERR_PINS — the RGB data and clock pins are not all ‎on the same PORT. Can’t continue, the library requires these ‎pins in this layout.‎

  • PROTOMATTER_ERR_MALLOC — couldn’t allocate enough memory for ‎display. Can’t continue. This is usually an error that happens in ‎the begin() function, but in extreme cases even the constructor ‎could hit an allocation problem, but you won’t get this response ‎until calling begin().‎

  • PROTOMATTER_ERR_ARG — some other bad input to function, distinct ‎from PROTOMATTER_ERR_PINS. Exceedingly rare, might only ‎happen if constructor failed.‎

Draw Shapes & Text Using ‎Adafruit GFX

Then we draw some stuff on the display. Any graphics primitive ‎supported by the Adafruit_GFX library is available here.‎

Adafruit_GFX is the same library that drives ‎many of our LCD and OLED displays…if you’ve ‎done other graphics projects, you might ‎already be familiar! And if not, we have a ‎separate guide explaining all of the ‎available drawing functions. Most folks can ‎get a quick start by looking at the “simple” ‎and “doublebuffer_scrolltext” examples and ‎tweaking these for their needs.‎

Any color argument passed to a drawing function here is a 16-bit ‎value, with the highest 5 bits representing red brightness (0 to 31), ‎middle 6 bits for green (0 to 63), and least 5 bits for blue (0 to 31). It’s ‎just how Adafruit_GFX works and is a carryover from early PC ‎graphics and most small LCD/OLED displays.

The effect of bit depth on image quality. Color values are always ‎specified as full 16-bit “565” values but will quantize to coarser ‎representations at lower bit depths.‎

Sometimes you might want to avoid 6-bit depth even if RAM permits ‎it. Only green handles the full 6 bits, while red and blue are ‎quantized to 5 bits. This can result in some colors or gradients having ‎slight green or magenta tints to them. 5-bit depth is slightly blockier, ‎but colors are more predictable.

bit_91

‎Download File

Copy Code
matrix.drawCircle(12, 10, 9, matrix.color565(255, 0, 0));               // Red
matrix.drawRect(14, 6, 17, 17, matrix.color565(0, 255, 0));             // Green
...etc...
matrix.show(); // Copy data to matrix buffers

Notice though the call to matrix.show() at the end. Drawing operations ‎have no immediate effect on the LED matrix, and instead are ‎working on a buffer in RAM behind the scenes. ‎Calling show() is required — it “pushes” the display data from that ‎buffer to the matrix. You can call it after each drawing function, or ‎group up a bunch of drawing commands with a ‎single show() afterward to all appear at once. If you’ve worked with ‎NeoPixel programming, it’s a similar phenomenon.‎

Since this program isn’t animating anything, it’s finished at that ‎point and loop() could be empty.‎

Check Refresh Rate

For the sake of curious information though, the example shows the ‎matrix refresh rate using getFrameCount(). This returns the number of ‎frames since the last call to the same function, not the refresh ‎rate…but if spaced about one second apart (delay(1000)), you get a fair ‎approximation of refresh rate:‎

‎Download File

Copy Code
Serial.println(matrix.getFrameCount());
delay(1000);

The matrix refresh rate is influenced by so many factors…processor ‎speed, matrix chain length, bit depth…that it’s difficult to accurately ‎predict ahead of time, so this is a way to see what you get when ‎changing different values in the constructor.‎

This is a subjective thing, but in broad terms 200 Hz or better should ‎provide a solid image…any less and it starts to become flickery, so ‎you might want a lower bit depth in that case. Conversely, refreshing ‎too fast would waste CPU cycles that you probably want for other ‎tasks like animation. The library does its best to throttle back and not ‎refresh faster than practically needed.‎

Text editor powered by tinymce.‎

We have a Sand/Pixel Dust demo available for the MatrixPortal that ‎runs in Arduino. Below is the code to run it. The demo is also ‎available as an example in the Arduino Protomatter library.‎

‎Download File

Copy Code
/* ----------------------------------------------------------------------
"Pixel dust" Protomatter library example. As written, this is
SPECIFICALLY FOR THE ADAFRUIT MATRIXPORTAL with 64x32 pixel matrix.
Change "HEIGHT" below for 64x64 matrix. Could also be adapted to other
Protomatter-capable boards with an attached LIS3DH accelerometer.

PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH,
or "doublebuffer" for animation basics.
------------------------------------------------------------------------- */

#include <Wire.h>                 // For I2C communication
#include <Adafruit_LIS3DH.h>      // For accelerometer
#include <Adafruit_PixelDust.h>   // For sand simulation
#include <Adafruit_Protomatter.h> // For RGB matrix

#define HEIGHT  32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!
#define WIDTH   64 // Matrix width (pixels)
#define MAX_FPS 45 // Maximum redraw rate, frames/second

#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
uint8_t rgbPins[]  = {7, 8, 9, 10, 11, 12};
uint8_t addrPins[] = {17, 18, 19, 20, 21};
uint8_t clockPin   = 14;
uint8_t latchPin   = 15;
uint8_t oePin      = 16;
#else // MatrixPortal ESP32-S3
uint8_t rgbPins[]  = {42, 41, 40, 38, 39, 37};
uint8_t addrPins[] = {45, 36, 48, 35, 21};
uint8_t clockPin   = 2;
uint8_t latchPin   = 47;
uint8_t oePin      = 14;
#endif

#if HEIGHT == 16
#define NUM_ADDR_PINS 3
#elif HEIGHT == 32
#define NUM_ADDR_PINS 4
#elif HEIGHT == 64
#define NUM_ADDR_PINS 5
#endif

Adafruit_Protomatter matrix(
  WIDTH, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins,
  clockPin, latchPin, oePin, true);

Adafruit_LIS3DH accel = Adafruit_LIS3DH();

#define N_COLORS   8
#define BOX_HEIGHT 8
#define N_GRAINS (BOX_HEIGHT*N_COLORS*8)
uint16_t colors[N_COLORS];

Adafruit_PixelDust sand(WIDTH, HEIGHT, N_GRAINS, 1, 128, false);

uint32_t prevTime = 0; // Used for frames-per-second throttle

// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------

void err(int x) {
  uint8_t i;
  pinMode(LED_BUILTIN, OUTPUT);       // Using onboard LED
  for(i=1;;i++) {                     // Loop forever...
    digitalWrite(LED_BUILTIN, i & 1); // LED on/off blink to alert user
    delay(x);
  }
}

void setup(void) {
  Serial.begin(115200);
  //while (!Serial) delay(10);

  ProtomatterStatus status = matrix.begin();
  Serial.printf("Protomatter begin() status: %d\n", status);

  if (!sand.begin()) {
    Serial.println("Couldn't start sand");
    err(1000); // Slow blink = malloc error
  }

  if (!accel.begin(0x19)) {
    Serial.println("Couldn't find accelerometer");
    err(250);  // Fast bink = I2C error
  }
  accel.setRange(LIS3DH_RANGE_4_G);   // 2, 4, 8 or 16 G!

  //sand.randomize(); // Initialize random sand positions

  // Set up initial sand coordinates, in 8x8 blocks
  int n = 0;
  for(int i=0; i<N_COLORS; i++) {
    int xx = i * WIDTH / N_COLORS;
    int yy =  HEIGHT - BOX_HEIGHT;
    for(int y=0; y<BOX_HEIGHT; y++) {
      for(int x=0; x < WIDTH / N_COLORS; x++) {
        //Serial.printf("#%d -> (%d, %d)\n", n,  xx + x, yy + y);
        sand.setPosition(n++, xx + x, yy + y);
      }
    }
  }
  Serial.printf("%d total pixels\n", n);

  colors[0] = matrix.color565(64, 64, 64);  // Dark Gray
  colors[1] = matrix.color565(120, 79, 23); // Brown
  colors[2] = matrix.color565(228,  3,  3); // Red
  colors[3] = matrix.color565(255,140,  0); // Orange
  colors[4] = matrix.color565(255,237,  0); // Yellow
  colors[5] = matrix.color565(  0,128, 38); // Green
  colors[6] = matrix.color565(  0, 77,255); // Blue
  colors[7] = matrix.color565(117,  7,135); // Purple
}

// MAIN LOOP - RUNS ONCE PER FRAME OF ANIMATION ----------------------------

void loop() {
  // Limit the animation frame rate to MAX_FPS.  Because the subsequent sand
  // calculations are non-deterministic (don't always take the same amount
  // of time, depending on their current states), this helps ensure that
  // things like gravity appear constant in the simulation.
  uint32_t t;
  while(((t = micros()) - prevTime) < (1000000L / MAX_FPS));
  prevTime = t;

  // Read accelerometer...
  sensors_event_t event;
  accel.getEvent(&event);
  //Serial.printf("(%0.1f, %0.1f, %0.1f)\n", event.acceleration.x, event.acceleration.y, event.acceleration.z);

  double xx, yy, zz;
  xx = event.acceleration.x * 1000;
  yy = event.acceleration.y * 1000;
  zz = event.acceleration.z * 1000;

  // Run one frame of the simulation
  sand.iterate(xx, yy, zz);

  //sand.iterate(-accel.y, accel.x, accel.z);

  // Update pixel data in LED driver
  dimension_t x, y;
  matrix.fillScreen(0x0);
  for(int i=0; i<N_GRAINS ; i++) {
    sand.getPosition(i, &x, &y);
    int n = i / ((WIDTH / N_COLORS) * BOX_HEIGHT); // Color index
    uint16_t flakeColor = colors[n];
    matrix.drawPixel(x, y, flakeColor);
    //Serial.printf("(%d, %d)\n", x, y);
  }
  matrix.show(); // Copy data to matrix buffers
}

View on GitHub

This sketch was written for a 64x32 pixel matrix but is easily ‎modified for a 64x64 matrix!‎

Look for this line in the code:‎

‎Download File‎

Copy Code
#define HEIGHT  32 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!

and change it to:‎

‎Download File‎

Copy Code
#define HEIGHT  64 // Matrix height (pixels) - SET TO 64 FOR 64x64 MATRIX!

Now upload the sketch to your Qualia S3and make sure a round ‎display is connected. You may need to press the Reset button to ‎reset the microcontroller. You should see a series of colored ‎rectangles along the bottom. Go ahead and start moving the matrix ‎around!‎

led_matrices_pixel_dust_demo

If you have a 3D Printer, be sure to check out the Matrix Portal Sand ‎Handles guide.‎

Text editor powered by tinymce.‎

matrix_92

On the back of the MatrixPortal is a triple-axis accelerometer that ‎you can use to detect motion. (That's how our cool digital sand demo ‎works!)‎

You can use this sensor in both CircuitPython and Arduino. After the ‎library support is installed, you can then use the example code ‎provided to get X, Y and Z acceleration values.‎

For more details on the LIS3DH sensor, we have a full guide you should read ‎once you get the basics working below.‎

Arduino Usage

In Arduino, make sure to install our LIS3DH library, our guide for the ‎individual sensor covers all that here.

You can then load up this example

However, before you upload it - change this line:‎

if (! lis.begin(0x18)) { // change this to 0x19 for alternative i2c ‎address

to:‎

if (! lis.begin(0x19)) { // change this to 0x19 for alternative i2c ‎address

This will change the library to use the alternate address 0x19 of the ‎accelerometer instead of the default 0x18!‎

‎Download File‎

Copy Code
// Basic demo for accelerometer readings from Adafruit LIS3DH

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>

// Used for software SPI
#define LIS3DH_CLK 13
#define LIS3DH_MISO 12
#define LIS3DH_MOSI 11
// Used for hardware & software SPI
#define LIS3DH_CS 10

// software SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);
// hardware SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Low Power 5Khz data rate needs faster SPI, and calling setPerformanceMode & setDataRate
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, 2000000);
// I2C
Adafruit_LIS3DH lis = Adafruit_LIS3DH();

void setup(void) {
  Serial.begin(115200);
  while (!Serial) delay(10);     // will pause Zero, Leonardo, etc until serial console opens

  Serial.println("LIS3DH test!");

  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
    Serial.println("Couldnt start");
    while (1) yield();
  }
  Serial.println("LIS3DH found!");

  // lis.setRange(LIS3DH_RANGE_4_G);   // 2, 4, 8 or 16 G!

  Serial.print("Range = "); Serial.print(2 << lis.getRange());
  Serial.println("G");

  // lis.setPerformanceMode(LIS3DH_MODE_LOW_POWER);
  Serial.print("Performance mode set to: ");
  switch (lis.getPerformanceMode()) {
    case LIS3DH_MODE_NORMAL: Serial.println("Normal 10bit"); break;
    case LIS3DH_MODE_LOW_POWER: Serial.println("Low Power 8bit"); break;
    case LIS3DH_MODE_HIGH_RESOLUTION: Serial.println("High Resolution 12bit"); break;
  }

  // lis.setDataRate(LIS3DH_DATARATE_50_HZ);
  Serial.print("Data rate set to: ");
  switch (lis.getDataRate()) {
    case LIS3DH_DATARATE_1_HZ: Serial.println("1 Hz"); break;
    case LIS3DH_DATARATE_10_HZ: Serial.println("10 Hz"); break;
    case LIS3DH_DATARATE_25_HZ: Serial.println("25 Hz"); break;
    case LIS3DH_DATARATE_50_HZ: Serial.println("50 Hz"); break;
    case LIS3DH_DATARATE_100_HZ: Serial.println("100 Hz"); break;
    case LIS3DH_DATARATE_200_HZ: Serial.println("200 Hz"); break;
    case LIS3DH_DATARATE_400_HZ: Serial.println("400 Hz"); break;

    case LIS3DH_DATARATE_POWERDOWN: Serial.println("Powered Down"); break;
    case LIS3DH_DATARATE_LOWPOWER_5KHZ: Serial.println("5 Khz Low Power"); break;
    case LIS3DH_DATARATE_LOWPOWER_1K6HZ: Serial.println("1.6 Khz Low Power"); break;
  }
}

void loop() {
  lis.read();      // get X Y and Z data at once
  // Then print out the raw data
  Serial.print("X:  "); Serial.print(lis.x);
  Serial.print("  \tY:  "); Serial.print(lis.y);
  Serial.print("  \tZ:  "); Serial.print(lis.z);

  /* Or....get a new sensor event, normalized */
  sensors_event_t event;
  lis.getEvent(&event);

  /* Display the results (acceleration is measured in m/s^2) */
  Serial.print("\t\tX: "); Serial.print(event.acceleration.x);
  Serial.print(" \tY: "); Serial.print(event.acceleration.y);
  Serial.print(" \tZ: "); Serial.print(event.acceleration.z);
  Serial.println(" m/s^2 ");

  Serial.println();

  delay(200);
}

View on GitHub

CircuitPython Usage

To use with CircuitPython, you need to first install a few libraries, into ‎the lib folder on your CIRCUITPY drive. Then you need to ‎update code.py with the example script.‎

Thankfully, we can do this in one go. In the example below, click ‎the Download Project Bundle button below to download the ‎necessary libraries and the code.py file in a zip file. Extract the ‎contents of the zip file, open the directory examples/ and then click ‎on the directory that matches the version of CircuitPython you're ‎using and copy the contents of that directory to ‎your CIRCUITPY drive.‎

Your CIRCUITPY drive should now look similar to the following ‎image:‎

drive_93

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import busio
import adafruit_lis3dh

# Hardware I2C setup. Use the CircuitPlayground built-in accelerometer if available;
# otherwise check I2C pins.
if hasattr(board, "ACCELEROMETER_SCL"):
    i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
    lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
else:
    i2c = board.I2C()  # uses board.SCL and board.SDA
    # i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
    lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# Hardware SPI setup:
# spi = board.SPI()
# cs = digitalio.DigitalInOut(board.D5)  # Set to correct CS pin!
# lis3dh = adafruit_lis3dh.LIS3DH_SPI(spi, cs)

# PyGamer or MatrixPortal I2C Setup:
# i2c = board.I2C()  # uses board.SCL and board.SDA
# lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)


# Set range of accelerometer (can be RANGE_2_G, RANGE_4_G, RANGE_8_G or RANGE_16_G).
lis3dh.range = adafruit_lis3dh.RANGE_2_G

# Loop forever printing accelerometer values
while True:
    # Read accelerometer values (in m / s ^ 2).  Returns a 3-tuple of x, y,
    # z axis values.  Divide them by 9.806 to convert to Gs.
    x, y, z = [
        value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration
    ]
    print("x = %0.3f G, y = %0.3f G, z = %0.3f G" % (x, y, z))
    # Small delay to keep things responsive but give time for interrupt processing.
    time.sleep(0.1)

‎View on GitHub

Before you save, however, you must tell the example where to find ‎the sensor!‎

Remove these lines:‎

‎Download File‎

Copy Code
# Hardware I2C setup. Use the CircuitPlayground built-in accelerometer if available;
# otherwise check I2C pins.
if hasattr(board, "ACCELEROMETER_SCL"):
    i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
    lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)
else:
    i2c = board.I2C()  # uses board.SCL and board.SDA
    # i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
    lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

And 'uncomment' these lines:‎

‎Download File‎

Copy Code
# PyGamer or MatrixPortal I2C Setup:
# i2c = board.I2C()  # uses board.SCL and board.SDA
# lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)

So, they look like this:‎

‎Download File‎

Copy Code
# PyGamer or MatrixPortal I2C Setup:
i2c = board.I2C()  # uses board.SCL and board.SDA
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19)

Now you can save and check the REPL for acceleration data!‎

Text editor powered by tinymce.‎

There may come a time when you want to update the firmware on ‎the ESP32 itself. This isn't something we expect you'll do often if at all, ‎but it’s good to know how if you need to.‎

We have a guide here which details the process of updating the ESP32 ‎firmware on Airlift All-in-One boards (including the PyPortal, MatrixPortal, and ‎Metro M4 AirLift) here...‎

Text editor powered by tinymce.‎

If powering a MatrixPortal and associated LED matrix solely through ‎the USB port, you might encounter a situation where the image ‎appears stable when only a few pixels are lit, but brighter images ‎with more pixels lit may exhibit “ghosting” or “sparkling” artifacts.‎

This is not a software bug. Please do not report it as a software bug.‎

This is a USB power issue. It does not affect everyone because there ‎are so many different USB power sources and permutations. What ‎follows is an explanation and some workarounds.‎

The Source of Gremlins

Not all USB ports are created equal. The ports on many computers ‎are designed to provide 5 Volts at a maximum of 1 Ampere (1A)…or ‎sometimes as little as 500 mA (0.5A). Likewise with USB wall ‎chargers: small ones might deliver 1A or less, and even beefier ‎models seldom exceed 2–2.5A.‎

LED matrices can be power hungry. And the more pixels in use, the ‎hungrier they are. When a matrix demands more power than is ‎available through the USB port, this can result in a ‎momentary brown-out condition, and the sort of visual glitches ‎described above. This can be exacerbated by cheap, thin USB cables ‎that have a lot of resistance.‎

Complicating issues, the voltage and current from a USB port are ‎not rock steady. This is especially true of USB battery banks. The ‎buck/boost converter circuit in these devices creates a sort of “buzz” ‎in the output; on average it might be 5V 2A, but ‎the instantaneous voltage and current will oscillate around this. This ‎is no problem for a typical steady use like charging a phone, but LED ‎matrices are weird…it’s not just that they’re hungry, but like the ‎supply voltage, the LED current draw also oscillates they’re actually ‎flashed on and off hundreds of times a second to produce an image.‎

Some Solutions to Try

If you encounter the aforementioned phenomenon, here are some ‎things to try, starting from the very simple and working our way up…‎

  • Try swapping the USB cable if the one you have seemed thin. ‎That free cable included for charging a mouse or Bluetooth ‎speaker might be unsuitable for the amount of current we ‎need. Look for something beefy, like a tablet or laptop charging ‎cable. Those luxury braided cables are also sometimes a sign of ‎‎“substance.”

  • If powering from a computer’s USB port: try using a sizable (2A ‎or better) USB wall charger instead.‎

  • If powering from a USB wall charger or USB battery bank: ‎Try swapping out for any others, you might have on hand. More ‎current (higher amperage) is always nice, but that’s not the ‎whole story. As explained above, the output filtering (or lack of) ‎can affect the steadiness of the output voltage and current.‎

Those are the easy “switch something out” fixes. If none of those ‎address the issue, now things get progressively more complex…‎

  • Try powering the MatrixPortal and LED matrix separately: ‎USB for the MatrixPortal, and a 5V DC “wall wart” supply (2A or ‎preferably more) for the matrix, using one of these screw ‎terminal power adapters (i.e., don’t use the screw terminals on ‎the MatrixPortal, use this instead):‎

Female DC Power adapter - 2.1mm jack to screw ‎terminal block

  • If the project must be powered from a USB battery bank and ‎you’ve already tested some others: try powering the LED matrix ‎and the MatrixPortal separately. This might involve two power ‎banks, or one with multiple ports might suffice (most enforce ‎per-port current limits), you’ll need to experiment. Cables exist ‎to convert USB type A or C to a DC plug that’s compatible with ‎the screw terminal adapter above — one would power the LED ‎matrix through this, and the MatrixPortal through a normal ‎USB cable. Just make sure the cable provides 5V output; some ‎are “booster” cables to higher voltages that could destroy the ‎matrix.‎

  • If nothing else seems to work, the last line of defense is better ‎filtering on the power supply output. This involves adding one ‎or more capacitors across the + and – terminals until the image ‎stabilizes. Unfortunately, there is no one-size-fits-all solution ‎here…it requires experimentation with an assortment of caps ‎on hand. One might start with a 1000 µF cap and work up or ‎down from there. If something seems promising but imperfect, ‎two or more with different values might be needed in parallel ‎‎(e.g., 100 µF + 1000 µF).‎

This photo shows NeoPixels, but the principle is exactly the same: ‎capacitance helps smooth out fluctuations in the power supply. Look ‎for something at least 10V rated (higher is OK) and values from 50 µF ‎to 2000 µF. Folks who’ve been doing electronics for a while usually ‎end up with a little parts drawer of random caps, so maybe you (or ‎someone you know, or a local maker space) already have something ‎suitable.‎

neo_94

Text editor powered by tinymce.‎

Files

SVG for MatrixPortal M4 Board Diagram

Simpletest UF2 for MatrixPortal M4 + 64x32 Matrix

Simpletest UF2 for MatrixPortal M4 + 64x64 Matrix

Schematic

schematic_95 

Fab Print

fab_96

Text editor powered by tinymce.‎

制造商零件编号 4745
ADAFRUIT MATRIX PORTAL - CIRCUIT
Adafruit Industries LLC
¥203.09
Details
制造商零件编号 2276
64X32 RGB LED MATRIX - 6MM PITCH
Adafruit Industries LLC
¥547.25
Details
制造商零件编号 2277
64X32 RGB LED MATRIX - 5MM PITCH
Adafruit Industries LLC
¥420.87
Details
制造商零件编号 3826
ADDRESS LED MATRIX PWM R/G/B
Adafruit Industries LLC
¥378.74
Details
制造商零件编号 2279
64X32 RGB LED MATRIX - 3MM PITCH
Adafruit Industries LLC
¥378.74
Details
制造商零件编号 2278
64X32 RGB LED MATRIX - 4MM PITCH
Adafruit Industries LLC
¥336.61
Details
制造商零件编号 4594
BLACK LED DIFFUSION ACRYLIC PANE
Adafruit Industries LLC
¥80.99
Details
制造商零件编号 4199
CABLE C PLUG TO C PLUG 3.28'
Adafruit Industries LLC
¥80.99
Details
制造商零件编号 4298
AC/DC WALL MNT ADAPTER 5.1V 15W
Adafruit Industries LLC
¥64.71
Details
制造商零件编号 4474
CABLE A PLUG TO C PLUG 3'
Adafruit Industries LLC
¥40.29
Details
制造商零件编号 4631
MINI-MAGNET FEET FOR RGB LED MAT
Adafruit Industries LLC
¥21.06
Details
制造商零件编号 2104
IDC BREAKOUT HELPER - 2X8 (16 PI
Adafruit Industries LLC
¥10.18
Details
制造商零件编号 4528
4-PIN STEMMA/GROVE - QT/QWIIC 4"
Adafruit Industries LLC
¥15.87
Details
制造商零件编号 3885
STEMMA SPEAKER & AUDIO AMPLIFIER
Adafruit Industries LLC
¥48.43
Details
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum