NeoTrellis M4 CircuitPy Tutorial (AdaBox 010)




I subscribe to Adafruit's AdaBox and I get a fun project every 3 months mailed to me. A little while back I received AdaBox 010 which came with a NeoTrellis M4 board as the centerpiece. The board comes with a microcontroller that can be programmed with Python using CircuitPython.

The NeoTrellis M4 comes with a grid of 32 NeoPixels, which double as buttons. On top of that, it has an audio processor with an 1/8" audio output jack so it can play sound files and generate noise. Furthermore, it even has an accelerometer that can detect motion in three direction so you have even more input available to control the audio and lights. It also comes with a microphone. To top it off, it can act as a MIDI device! It can use the USB port as the MIDI interface or it can use the UART connector for MIDI.

The micro-USB connector is used for power but also acts as a USB removable drive for easy access to the source code files and easily upload sound files. It can also serve as a MIDI USB interface to interact with MIDI software. The USB can also serve as a generic USB HID (Human interface device) like a mouse or keyboard to send events to a computer that it understands like a keypress or a mouse event.

In this tutorial, I try to provide an overview of all the capabilities and give you an idea of all the possibilities with this board. You will find several code examples that focus on one specific aspect of the board.

NeoTrellis M4 Board components

The NeoTrellis M4 board is made up of several chips. The name NeoTrellis M4 is a combination of the various products it is built with. When you understand how the components fit together, you can really appreciate the form-factor and engineering that went in to it.

Let's break it down a little further. These are the main chips that we will look at:

  • SAMD51
  • Flash storage
  • ADXL343 Accelerometer
  • NeoPixels
  • Elastomer keypad
  • MAX4466 Microphone amplifier
  • TRSS Audio connector
  • USB connector
  • 4-JST-PH connector

SAMD51 processor

The SAMD51 is the main microcontroller is a 32-bit ARM processor. It is a Cortex-M4 high performance embedded processor. It runs with 3.3V power. It has 512KB flash storage (for the program code) and for memory it has 192KB SRAM (to store variables during run-time). The NeoTrellis M4 board comes with another chip dedicated to storage, but we'll look at that later.

The SAMD51 runs at 120MHz, or 0.120GHz. While it is not much to be impressed with when you compare it to a 64-bit 32-core 4.2GHz AMD Ryzen Threadripper 2990WX, but when you compare it to an Arduino Uno, which clocks in at 16MHz with an 8-bit ATmega328P, the SAMD51 is about 10-times faster and has much more floating-point precision with 32-bits, making it much more suitable for real-time audio processing.

For further reference, a current Raspberry Pi 3B has a Cortex-A53 ARM that clocks in around 1.5GHz with 4-core and 64-bits.

One other key factor in the SAMD51 is tha it contains two digital-to-analog (DAC) converters (12-bit, 500KSPS) that are used to generate the two analog output channels for stereo sound, as well as hardware accelerated audio processing.

QSPI FLASH storage

The board comes with an 8MB QSPI flash storage, which is good enough to store many low quality WAV files and your Python source code along with supporting configuration files. This 8MB storage is the removable drive you see named CIRCUITPY when connecting it via USB to a computer.


The RGB LEDs are Adafruit NeoPixels. The nice thing about NeoPixels is that they come with libraries that are easyt to use and come in a variety of form factors from strips, to sew-on buttons, to a grid like is on the NeoTrellis M4 board. There is a copper ring that surrounds each NeoPixel that is used for button press detection. These are connected on pin 10 of the main microcontroller if using Arduino software to control them.

Elastomer keypad

The elastomer keypad is an important component because there is a conductive ring around the bottom of each button that connects with the copper ring that surrounds the NeoPixel LEDs. Connecting the conductive ring from the bottom of the button to the circuit on the board is how button presses are detected. The silicone allows the light from the NeoPixel below it to shine through.

ADXL343 accelerometer

The ADXL343 is a three-axis accelerometer that lets you detect acceleration/tilt in three directions. You can use it to turn the board in to a joystick, or just use the tilt information for general input.

TRRS audio jack

The board comes with a TRRS 1/8" audio headset jack so you can use any standard headphones or connect standard speakers to it. It can also be used as an input line if programmed that way.

It has more than the normal three pins: ground, left, and right. It has an extra pin on the jack that goes to the MAX4466 mic amplifier and through the analog-to-digital converter before going to the SAMD51 processor. This means you can use iPhone earbuds that have the built in microphone with it and it will support stereo output and be able to use your microphone.

The audio pins are analog 1 (left) and analog 2 (right) if using Arduino. The equivalents in CircuitPY are board.A0 (right) and board.A1 (left).

It can potentially also detect the up/down and pause button presses on a headset. See for more details.

Microphone amplifier

The board does not have a microphone itself, but it has an MAX4466 electret mic amplifier that goes in to an analog-to-digital converter (ADC) that allows the processor to use the microphone input. The audio jack connects to this allowing the use of headphone+microphone combos. Input is available on board.MICOUT in CircuitPy. The Arduino equivalents is PIN_MIC.

Micro-USB connector

It comes with a micro-USB connector that is used for power. It also allows you to connect it to a computer and have it detected as a removable disk drive. You can also configure it to act as a generic USB-HID device like a keyboard, mouse, or joystick, or have it act like a MIDI device.

JST connector & GPIO

Between the USB connector and the audio jack, there is white connector with four pins. This is the JST connector. More specifically, 4-pin JST-PH connector.

It has four pins: power (3.3V), ground, and two general purpose pins for doing whatever you want. The two general pins can be used for just about anything you want, including MIDI, digital in/out, ADC, UART, and I2C.

This feature makes it incredibly easy to connect and disconnect various devices.

Around the edge of the board there are areas with a series of 5 copper stripes together. These are the equivalent to the four pins in the JST connector, with one extra (INT). You can use these to connect additional devices.

Start from scratch

By 'start from scratch' I mean updating the bootloader and then installing CircuitPy and then restoring the default sample files. This way you can restore your board to a clean state no matter how bad you mess it up and make sure you have the latest versions.

If you got the board from Adafruit it should already have the bootloader and sample code on it so you don't need to do all these steps. In my case the bootloader was old and I wanted to fully understand how it worked so I wanted to start from scratch.

Install bootloader

  1. Plug in the USB to a computer. In its default state from packaging it will connect as a removable drive named CIRCUITPY.
  2. Double-tap the reset button on the bottom side of the board to restart it in setup mode. It will create a new removable drive named TRELM4BOOT (Trellis M4 Bootloader).
  3. Download the .uf2 file that contains the upadted bootloader from Bootloader updater uf2. It will have a name like update-bootloader-trellis_m4-v2.0.0-adafruit.7.uf2. Do not change the name.
  4. Copy-and-paste the .uf2 file to the TRELM4BOOT drive. It will disconnect itself and process the file, removing it in the process. It will boot back in to a drive named TRELM4BOOT. Check the info file to see what version it is.

Install CircuitPy

After the bootloader is installed, and your board is connecting over USB as the TREL4BOOT drive still, you can then install CircuitPy.

  1. Install CircuitPy by downloading it from Circuit Python uf2. It will have a name like adafruit-circuitpython-trellis_m4_express-en_US-4.0.2.uf2.
  2. Copy-and-paste the .uf2 file over to the TRELM4BOOT drive. It will disconnect itself again and run the file. This time it will boot in to a CIRCUITPY drive. This is where you can place an updated file.
  3. You can also download the default files that go in the CIRCUITPY drive from

The most important files to keep are the lib folder which contains the importable Python modules, and which is the entry point on boot.

Connect via serial console

To see the STDOUT text output, you can monitor it over the serial connection. This step is not required but is helpful for debugging. If you connect after the board has already been running for a while, it may not actively be printing anything to STDOUT and you may see a blank screen. In this case, you can press CTRL-C and it will exit the currently running Python script. It will ask you if you want to enter the REPL or restart the Python program with CTRL-D. This makes it easy for you to run raw Python commands and debug.

Once in the REPL, you can execute Python code. You can run some commands to get some more information about the environment:

>>> import sys
>>> sys.version
>>> sys.path 
['', '/', '.frozen', '/lib']
>>> sys.implementation
(name='circuitpython', version=(4, 0, 2))
>>> sys.platform
'MicroChip SAMD51'

The sys.path is a list of directories where it will look for modules when calling import. Learn more about Python syspath in my Python import, sys.path, and PYTHONPATH Tutorial

You can load and execute a Python source file from the REPL like this:

# Execute a Python file from the REPL
>>> exec(open('').read())


Note that you can also use various programming languages to connect and communicate over serial. For example, you can also use Python from your host environment to connect to the board over serial with PySerial. This isn't recommended unless you have a particular reason for connecting programmatically. The easier way is to simply use putty. Read on for more options.


  1. Check which serial port is being used for the device. Go to Device Manager, and expand Ports (COM & LPT). You will see a device listed like USB Serial Device (COM4). If you aren't sure which one it is, you can unplug and replug your device to see which one shows up as new.
  2. Connect using putty or MobaXTerm with the serial option. Set the speed to 115200 and the port to COM4 or whatever name your device manager listed.


  1. Identify the serial port/device name by checking in /dev/. It will have a name like ttys004 or ttyACM4. If you aren't sure which device it is, you can check the list of devices in /dev/ before and after plugging it in to see which one is new. Try ls /dev/tty* to see the list of devices. Alternatively, you can use dmesg to look for information about recently connected devices.
  2. Connect using any serial client. You can use putty on Linux just like Windows. You can also use screen like screen /dev/ttyACM0 115200. You may need to run it with sudo or add yourself to the device group (e.g. dialout in Fedora).

Code examples

The most important file on the CIRCUITPY drive is the file. Whenever the board is powered up, this is the file it will run. You can browse the default for an example of how to use the different things like:

  • Interacting with serial console
  • Controlling NeoPixel LEDs
  • Detecting button presses
  • Reading files
  • Reading data from the accelerometer
  • Playing a .wav file out
  • Generate raw audio
  • Get microphone input
  • UART communication
  • MIDI input and output over USB and UART

We will look at simple code examples that isolate each of these in order to better understand each aspect.

To upload and run the code, you simply need to copy your file over to the CIRCUITPY removable drive. Copy over any other support files that you need if you have any configuration files our sound files to go with your program. Any time you modify a file on the disk, it will restart and re-run the file.

The rotation value provided to the TrellisM4Express constructor indicates the orientation that the board is intending to use. For example, if you plan to stand it upright vs use it horizontally. (0, 0) is supposed to be the top-left. 0 degree rotation is horizontal with the USB connector facing away from you. 90 degree rotation is vertical with USB connector to the right.

Interact with serial console

Since the serial console is just using STDIN and STDOUT communication, you can use print() and input() to interact. For example:

name = input('Enter your name: ')
print("Your name is: %s" % name)

Control NeoPixel LEDs

To control the NeoPixels, first instantiate the TrellisM4Express class, and then access the pixels via the pixels property. Here is an example of how to check the size of the grid, change the brightness, fill the whole board with a color, and control specific pixels. Remember to call show() in order to display the changes. The lights will all turn off once the program exits, so there is an infinite loop at the end to keep the program from exiting. NeoPixels are on pin 10.

import time
import adafruit_trellis_express

trellis = adafruit_trellis_express.TrellisM4Express(rotation=90)

print('Width: %s' % trellis.pixels._width)
print('Height: %s' % trellis.pixels._height)
print('Total buttons: %s' % (trellis.pixels._width * trellis.pixels._height))

# Change the overall brightness 0-1
trellis.pixels._neopixel.brightness = .25

# Fill every pixel with a certain color (RED, GREEN, BLUE)
# (0, 0, 0) is black/empty/off
# (255, 255, 255) is white
trellis.pixels._neopixel.fill((0, 0, 0))

# Set specific neopixels. Rotation value affects grid coordinates
trellis.pixels._neopixel[17] = (255, 0, 0)  # Pixel 17 to red
trellis.pixels[(2, 1)] = (0, 255, 0)  # Pixel (2, 1) to green

# Changes to neopixels won't be displayed until `show()` is called

# Do nothing else except stay on
while True:

Detect button presses

Here is a simple example that shows how to detect button presses. It will print out any button presses detected to the serial console.

import adafruit_trellis_express

trellis = adafruit_trellis_express.TrellisM4Express(rotation=90)

while True:
    # Keypress is stored as a separate variable because `trellis.pressed_keys`
    # might detect a key press on one line, but when read later on a different
    # line, no keypress is detected. Storing it ensures the pressed key
    # is not lost between the time it is checked and the time it is used.
    pressed_buttons = trellis.pressed_keys
    if pressed_buttons:
        # Keypresses are stored as a list of tuples indicating the
        #  (x, y) coordinates of the button press
        print('Button press detected: %s' % pressed_buttons)

Read files

You might want to read configuration information in a text file, You can use the standard Python file operations to do this. The Python official documentation for reading and writing files has many examples. Note that the json module is available, and can be a good way to structure text data if necessary.

This is not specific to the Trellis M4 board, but here is a simple example:

# Read contents of a file (make sure a test.txt exists first)
with open('') as in_file:
    data =

print('======= Text file data  ==========')

Read from accelerometer

The accelerometer on board, an ADXL343 - Triple-Axis Accelerometer, will tell you what orientation the board is in and how fast it is moving. This lets you detect whether it is face-down, face-up, tilted to the side and anything in between. You can also detect if it moving up or down.

The accelerometer is a totally separate chip and can be used without having to even import the Trellis library (which controls the buttons and lights). Since they are separate chips, i2c is used. This is how the ADXL343 communicates with the main SAMD51 Cortex M4 32-bit ARM processor. Learn more about i2c with CircuitPython at

import time
import board
import busio

import adafruit_adxl34x

accelerometer = adafruit_adxl34x.ADXL345(i2c)

while True:
    # From the default 0 degree rotation, with the USB port facing away
    # from you and the buttons on top

    # The ranges go roughly from -10 to +10 to represent the full
    # 360 degree rotation when it is not accelerating.
    # The numbers can jump to over 20 when there is rapid movement.

    # Left (-10) & right (+10) like steering wheel
    tilt_x = accelerometer.acceleration[1]
    # Forward/nose dive (-10) & backward/pull up (+10)
    tilt_y = accelerometer.acceleration[0]
    # Like tilt_x but reversed when stable, otherwise detects up and down motion
    tilt_z = accelerometer.acceleration[2]
    print('Text x, y, z: %s, %s, %s' % (tilt_x, tilt_y, tilt_z))

Play a .wav file

Here is an example of how to play a wave file. You need to first setup the audio device and tell it which pins to use for the left and right channels. Then the wavefile is loaded from storage and turned in to a WaveFile object. The thread is slept until the audio is done playing causing the rest of the program to block until the sound file is done.

import adafruit_trellis_express
import audioio
import board
import time

trellis = adafruit_trellis_express.TrellisM4Express()

def play_sound_file(file_path):
        with open(file_path, "rb") as f:
            wave = audioio.WaveFile(f)
            while audio.playing:
    except OSError as e:
        print('Error opening file: %s' % e)

with audioio.AudioOut(left_channel=board.A1, right_channel=board.A0) as audio:

Generate raw audio

You can generate raw audio and send it out through the audio jack. To do this with CircuitPython, you need to do a few things:

  1. Initialize and obtain the audio device with audioio.AudioOut()
  2. Generate the sound wave data
  3. Convert the sound wave data to a sample
  4. Tell the audio device to play the sample

This example is adapted from the official AudioOut CircuitPy Documentation.

# Adapted from examples at:
import time
import array
import math
import audioio
import board

SAMPLERATE = 8000  # 8000 samples per second

def generate_sine_wave(frequency=440):
    length = SAMPLERATE // frequency
    sine_wave_data = array.array("H", [0] * length)
    for i in range(length):
        sine_wave_data[i] = int(math.sin(math.pi * 2 * i / 18) * (2 ** 15) + 2 ** 15)
    sound_sample = audioio.RawSample(sine_wave_data)
    return sound_sample

sample = generate_sine_wave()
audio = audioio.AudioOut(right_channel=board.A0, left_channel=board.A1), loop=True)

while True:  # Stay running

Input from microphone

This example shows how to use the microphone input (e.g. an iPhone headset) to get values. This won't create wave forms or samples from the input received, but it will tell you generally how loud the input is.

import time
import board
from analogio import AnalogIn

mic_pin = AnalogIn(board.MICOUT)

while True:
    raw_value = mic_pin.value
    # Sits around 32,700 when quiet (half of 65,535)
    print("Raw value: %s" % raw_value)

    # Normalize it to sit around 500 and go up to 999
    # strictly for convenience and to lower range of fluctuation
    friendly_value = int((mic_pin.value / 65536) * 1000)
    print("Friendly value: %s" % friendly_value)


This example was created using a reference from a different project: Sound Reactive NeoPixel Peace Pendant.

Use as generic USB HID device

Adafruit_CircuitPython_HID Python library on GitHub Download all Adafruit CircuitPy libraries

You can use the NeoTrellis M4 board as a generic keyboard, mouse, joystick, or other USB HID device. CircuitPy HID documentation


This example shows how to behave like a keyboard and send key press and release events.

from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

# Tell the device to act like a keyboard.
keyboard = Keyboard()

# See all available keycodes in:

# Send a keypress of ESCAPE

# Send CTRL-A (select all in most text editors)
keyboard.send(Keycode.CONTROL, Keycode.A)

# You can also control key press and release manually:, Keycode.A)


This example shows you how to behave like a mouse, sending button clicks, mouse wheel events, and have it move the mouse cursor.

import time
import board
import digitalio
from adafruit_hid.mouse import Mouse

mouse = Mouse()

while True:
    # Scroll the mouse wheel up one click/unit

    # Scroll mouse wheel down

    # Send a click
    # To double click, just send two click events back-to-back

    # Like the keyboard, you can manually control press and release events
    mouse.move(x=20, y=-10)  # Drag up-and-right while clicked
    mouse.release(mouse.LEFT_BUTTON)  # or .release_all()

    time.sleep(15)  # Enough time to unplug it after you test it


To have the NeoTrellis M4 act like a gamepad or joystick, it is very similar to the keyboard and mouse usage. A couple important things to know about how the gamepad works:

  • There are sixteen available buttons numbered 1-16
  • There are two joysticks, one controls x & y, the other controls z & r_z (Z-axis rotation)
  • Joystick values range from -127 to 127
# Example adapted from:
import board
import digitalio
import analogio

from adafruit_hid.gamepad import Gamepad

pad = Gamepad()

# Equivalent of Arduino's map() function.
# Useful for turning analog input ranging from 0-65,535 to
# values expected -127-127
def range_map(x, in_min, in_max, out_min, out_max):
    return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min
# Example usage:
# value = range_map(analog_value, 0, 65535, -127, 127)

# Send press+release in one command

# Control press and release manually

# Move joysticks to position
pad.move_joysticks(x=-127, y=127)
pad.move_joysticks(z=-127, r_z=127)

# Reset everything (like releasing all joysticks and buttons)

UART communication

The 4-pin JST-PH connector on the board, along with the 5-pin copper stripes that you find around the edge of the board, are all connectors that can be used for UART communication with other devices. The same connector could be used for I2C or general purpose input/output (GPIO) if desired.

Read more about UART with some of these tutorials, and check out this very simple example.

import board
import busio
import time

uart = busio.UART(tx=board.SDA, rx=board.SCL, baudrate=31250)

# Write some bytes to the connection
uart.write(bytes([0xDE, 0xAD, 0xBE, 0xEF]))
# Read bytes from the connection
some_bytes =  # Read up to 64 bytes

# Also available: # Reads to end of connection when no limit set


You can use the NeoTrellis M4 board to do MIDI input and output. You can send and receive MIDI signals over USB or over UART.

Using the NeoTrellis M4 as a MIDI output device, you can use it as a MIDI controller or as a unique instrument. Using it to process incoming MIDI signals allows you to let external MIDI controllers or software to send signals to the NeoTrellis M4 allowing you to either play sounds, trigger lights, or do whatever else you want.

The USB method is much easier whenever possible. You might need to use the UART method if you are connecting to or from a MIDI device that does not have a USB connection and understand MIDI over USB. Most audio workstations and computer/tablet software will support USB MIDI.

MIDI output over USB

To do MIDI output over USB and have your NeoTrellis M4 act like a MIDI instrument, you can use the Adafruit MIDI library for CircuitPy. You can download it from the GitHub releases page. For example, I downloaded one named After downloading it, copy the adafruit_midi directory to the lib directory inside your CIRCUITPY drive.

In addition to sending out signals like adafruit.midi.note_on.NoteOn shown in this example, you can send adafruit_midi.control_change.ControlChange or adafruit_midi.pitch_bend.PitchBend signals as well.

To test that this MIDI output is working, you can use MIDI software like Anvil Studio. For example, open Anvil Studio, go to View -> Synthesizer, MIDI + Audio devices -> Test MIDI connections. On the left side, where it says "Listening for MIDI Input from" you can set the Port to "CircuitPython Audio" you should start seeing note on and off messages appearing.

# Get adafruit_midi from
# and copy the `adafruit_midi` folder in to the `lib` folder on CIRCUITPY drive
# Source
import usb_midi
from adafruit_midi import MIDI
import time

middle_c = 60

midi = MIDI(midi_out=usb_midi.ports[1], out_channel=0)  # MIDI channel 1

while True:
    # Set note to full volume
    midi.note_on(middle_c, 100)

    # Turn note off
    midi.note_off(middle_c, 100)

    # Can also send other signals like this:
    # midi.control_change(control, value)
    # midi.pitch_bend(bend_value)
    # message = adafruit_midi.note_on.NoteOn(middle_c, 100)
    # midi.send(message)

MIDI input over USB

This simple example will listen on MIDI channel 1 and print out received messages. This would allow you to plug a MIDI instrument or controller in to the USB port and send messages to the NeoTrellis M4. To provide a simple example, you could have the NeoTrellis M4 board play a specific .wav file and light up specific NeoPixels if a NoteOn MIDI message is received specific note.

To test that this MIDI output is working, you can use MIDI software like Anvil Studio. For example, open Anvil Studio, go to View -> Synthesizer, MIDI + Audio devices -> Test MIDI connections. On the right side, where it says "Sending MIDI Output to" you can set the Port to "CircuitPython Audio" and click "Start sending notes". You should then start seeing the note information being printed out in the CircuitPy serial console.

import usb_midi
import adafruit_midi

# Just by importing these, it allows `midi.receive()` to translate `msg`
from adafruit_midi.timing_clock import TimingClock
from adafruit_midi.channel_pressure import ChannelPressure
from adafruit_midi.control_change import ControlChange
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn
from adafruit_midi.pitch_bend import PitchBend

midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0)  # MIDI channel 1

while True:
    msg = midi.receive()
    if msg is not None:
        # Print MIDI message details

MIDI out over UART

This simple example shows how to use MIDI over UART. To connect a MIDI device over UART, you might have to improvise and modify some connectors, or use adapters.

For an example of how to take a standard round MIDI connector and connect it to the 4-in JST-PH connector on the NeoTrellis M4, check out this tutorial from Adafruit

import board
import busio
import time

uart = busio.UART(board.SDA, board.SCL, baudrate=31250)

middle_c = 60
full_volume = 100
no_volume = 0

while True:
    # Set note to full volume (0x90 = NoteOn, 0x80 = NoteOff)
    uart.write(bytes([0x90, middle_c, 100]))
    uart.write(bytes([0x90, middle_c, 10]))

Use with Arduino IDE

If you don't want to use Circuit Python and you want to use the Arduino libraries like these:

In the Arduino IDE use the Board Manager to install support for:

  • Arduino SAMD Boards
  • Adafruit SAMD Boards

After installing the support and restarting, you should see several new boards including the Trellis M4 Express which is what you want.

You can use the Library Manager to install libraries like NeoPixel, DMA NeoPixel, Unified Sensor, QSPI, Zero DMA, SPIFlash, Keypad, MIDIUSB, ADXL343, NeoTrellis M4.

You may also need to install Adafruit Windows drivers Check out the Adafruit guide on using Arduino IDE with NeoTrellis M4.

There are more Arduino examples for the NeoTrellis M4 here:


After following this guide you should have a solid understanding of your NeoTrellis M4 board, the parts it is composed of, how to reset it to a pristine state, connect to it via serial console, and control all of the hardware like the NeoPixels, buttons, serial connection, UART, and MIDI. With the examples above, you should have near mastery over your board.