If you are new to CircuitPython, we recommend you start with the Welcome to CircuitPython Guide then come back here.
Adafruit recommends installing and using the Mu editor on your computer to edit CircuitPython files. Mu is available for Windows, macOS, and Linux. You can learn about Mu in this guide.
CircuitPython Preparation
To prepare the NeoTrellis M4 to run the code, follow these steps:
- Update the bootloader for NeoTrellis from the Trellis M4 guide
- Install the latest version of CircuitPython (at least 4.0.0 Alpha 3) for NeoTrellis M4.
Click the link above to download the latest UF2 file.
Download and save it to your desktop (or wherever is handy).
Plug your NeoTrellis M4 Express 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 next to the USB connector on your board, and you will see the status DotStar RGB LED 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 TRELM4BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to TRELM4BOOT.
The LED will flash. Then, the TRELM4BOOT drive will disappear and a new disk drive called CIRCUITPY will appear.
- Get the latest CircuitPython library pack matching your version of CircuitPython and save it onto your hard drive. You will need three .mpy files and one folder within the library pack for this guide. Drag the files listed below over into the /lib folder on CIRCUITPY:
- adafruit_trellism4.mpy
- adafruit_hid folder
- neopixel.mpy
- adafruit_matrixkeypad.mpy
HID Keyboard Basics
This guide page has a great intro to CircuitPython HID Keyboard.
For even more details, check out the documentation at https://circuitpython.readthedocs.io/projects/hid/en/latest/ which includes all of the keycodes and media codes you can use.
First, we'll import the adafruit_hid library into our program. This will allow us to make calls to send keyboard keys and media keys.
from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode kbd = Keyboard(usb_hid.devices) cc = ConsumerControl(usb_hid.devices)
Keyboard Press/Release
Now we can send this command to "type" the letter 'a':
kbd.press(Keycode.A)
kbd.release(Keycode.A)
This would send a lowercase 'a' to the computer just as if you had typed it yourself. To send a capital 'A', we'd add the shift key to the command like this:
kbd.press(Keycode.SHIFT, Keycode.A)
kbd.release(Keycode.SHIFT, Keycode.A)
This is pretty cool, since it means we can layer on lots of keys all at the same time, just like you do on your physical keyboard when using keyboard shortcuts!
So, if there's some keyboard shortcut you want to use (or create for yourself in something like Quicksilver or AutoKeys) that is command+option+ctrl+a the CircuitPython code would look like this:
kbd.press(Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.A)
kbd.release(Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.A)
Media Control
There is a second command we'll use when we want to adjust volume, play/pause, skip tracks, and so on with media such as songs and videos. These are often represented on a physical keyboard as icons silkscreened onto the rightmost function keys.
In USB HID speak, these are known as "Consumer Control codes". To play or pause a track we'll use this command:
cc.send(ConsumerControlCode.PLAY_PAUSE)
Launch Deck Code
Here's the full code for the Launch Deck. Copy it and then paste it into Mu and save it to your Trellis M4 as code.py
Now, you'll need to edit the keycodes sent in the code to the ones you want to use! Just remember to match them up with your app launcher, such as Quicksilver, AutoKeys, or whatever you choose!
# SPDX-FileCopyrightText: 2018 John Edgar Park for Adafruit Industries # # SPDX-License-Identifier: MIT # Launch Deck Trellis M4 # USB HID button box for launching applications, media control, camera switching and more # Use it with your favorite keyboard controlled launcher, such as Quicksilver and AutoHotkey import time import random import adafruit_trellism4 import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode # Rotation of the trellis. 0 is when the USB is upself. # The grid coordinates used below require portrait mode of 90 or 270 ROTATION = 270 # the two command types -- MEDIA for ConsumerControlCodes, KEY for Keycodes # this allows button press to send the correct HID command for the type specified MEDIA = 1 KEY = 2 # button mappings # customize these for your desired postitions, colors, and keyboard combos # specify (button coordinate): (color hex value, command type, command/keycodes) keymap = { (0,0): (0x001100, MEDIA, ConsumerControlCode.PLAY_PAUSE), (1,0): (0x110011, MEDIA, ConsumerControlCode.SCAN_PREVIOUS_TRACK), (2,0): (0x110011, MEDIA, ConsumerControlCode.SCAN_NEXT_TRACK), (3,0): (0x000033, MEDIA, ConsumerControlCode.VOLUME_INCREMENT), (0,1): (0x110000, MEDIA, ConsumerControlCode.MUTE), # intentional blank button # intentional blank button (3,1): ((0,0,10), MEDIA, ConsumerControlCode.VOLUME_DECREMENT), (0,2): (0x551100, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.ONE)), (1,2): (0x221100, KEY, (Keycode.CONTROL, Keycode.SHIFT, Keycode.TAB)), # back cycle tabs (2,2): (0x221100, KEY, (Keycode.CONTROL, Keycode.TAB)), # cycle tabs (3,2): (0x333300, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.TWO)), (0,3): (0x001155, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.THREE)), # intentional blank button # intentional blank button (3,3): (0x330000, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.FOUR)), (0,4): (0x005511, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.FIVE)), (1,4): (0x440000, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.SIX)), # intentional blank button (3,4): (0x003300, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.EIGHT)), (0,5): (0x222222, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.W)), (1,5): (0x000044, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.E)), # intentional blank button (3,5): (0x332211, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.T)), (0,6): (0x001133, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.C)), (1,6): (0x331100, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.V)), (2,6): (0x111111, KEY, (Keycode.GUI, Keycode.SHIFT, Keycode.FOUR)), # screen shot (3,6): (0x110000, KEY, (Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.N)), (0,7): (0x060606, KEY, (Keycode.GUI, Keycode.H)), # hide front app, all windows (1,7): (0x222200, KEY, (Keycode.GUI, Keycode.GRAVE_ACCENT)), # cycle windows of app (2,7): (0x010001, KEY, (Keycode.GUI, Keycode.SHIFT, Keycode.TAB)), # cycle apps backards (3,7): (0x010001, KEY, (Keycode.GUI, Keycode.TAB))} # cycle apps forwards # Time in seconds to stay lit before sleeping. TIMEOUT = 90 # Time to take fading out all of the keys. FADE_TIME = 1 # Once asleep, how much time to wait between "snores" which fade up and down one button. SNORE_PAUSE = 0.5 # Time in seconds to take fading up the snoring LED. SNORE_UP = 2 # Time in seconds to take fading down the snoring LED. SNORE_DOWN = 1 TOTAL_SNORE = SNORE_PAUSE + SNORE_UP + SNORE_DOWN kbd = Keyboard(usb_hid.devices) cc = ConsumerControl(usb_hid.devices) trellis = adafruit_trellism4.TrellisM4Express(rotation=ROTATION) for button in keymap: trellis.pixels[button] = keymap[button][0] current_press = set() last_press = time.monotonic() snore_count = -1 while True: pressed = set(trellis.pressed_keys) now = time.monotonic() sleep_time = now - last_press sleeping = sleep_time > TIMEOUT for down in pressed - current_press: if down in keymap and not sleeping: print("down", down) # Lower the brightness so that we don't draw too much current when we turn all of # the LEDs on. trellis.pixels.brightness = 0.2 trellis.pixels.fill(keymap[down][0]) if keymap[down][1] == KEY: kbd.press(*keymap[down][2]) else: cc.send(keymap[down][2]) # else if the entry starts with 'l' for layout.write last_press = now for up in current_press - pressed: if up in keymap: print("up", up) if keymap[up][1] == KEY: kbd.release(*keymap[up][2]) # Reset the LEDs when there was something previously pressed (current_press) but nothing now # (pressed). if not pressed and current_press: trellis.pixels.brightness = 1 trellis.pixels.fill((0, 0, 0)) for button in keymap: trellis.pixels[button] = keymap[button][0] if not sleeping: snore_count = -1 else: sleep_time -= TIMEOUT # Fade all out if sleep_time < FADE_TIME: brightness = (1 - sleep_time / FADE_TIME) # Snore by pausing and then fading a random button up and back down. else: sleep_time -= FADE_TIME current_snore = int(sleep_time / TOTAL_SNORE) # Detect a new snore and pick a new button if current_snore > snore_count: button = random.choice(list(keymap.keys())) trellis.pixels.fill((0, 0, 0)) trellis.pixels[button] = keymap[button][0] snore_count = current_snore sleep_time = sleep_time % TOTAL_SNORE if sleep_time < SNORE_PAUSE: brightness = 0 else: sleep_time -= SNORE_PAUSE if sleep_time < SNORE_UP: brightness = sleep_time / SNORE_UP else: sleep_time -= SNORE_UP brightness = 1 - sleep_time / SNORE_DOWN trellis.pixels.brightness = brightness current_press = pressed
Customizing
In order to customize the Launch Deck, you'll want to know a few things about how the Trellis M4 code in CircuitPython works. This guide is a great place to read up on all the details, but we'll go over a few specifics here.
Single, Unmodified Keystrokes
If you want to use the Launch Deck to send a single, unmodified keystroke, we need to enclose it in brackets. This is because of the code's expectation of a tuple.
So, to send a letter g with the bottom, left button in portrait orientation, this would be the command (replacing line 61 in the code above):
(0, 7): (0x052405, KEY, ([Keycode.G])),
Button Coordinates
The Trellis M4 can be oriented in landscape (wide) or portrait (tall) modes. We've set the rotation to portrait mode, so the button grid coordinate system consists of eight rows of four buttons. Here's an example with the rotation set at 90.
Add a Button
Let's say you wanted to add a button to the second row. Currently, there are two unused ones in that row in our example code.
This is the code currently:
(0,1): (0x110000, MEDIA, ConsumerControlCode.MUTE),
# intentional blank button
# intentional blank button
(3,1): ((0,0,10), MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
Here you can see we've made it easy to drop in two more buttons by commenting out the second and third lines.
So, let's add a shortcut to the second button that is green and can be used to force a reload on a web page in a web browser inside of Windows (you can also add this on a macOS machine by following these instructions). That's the ctrl+F5 key combo.
First, we need to specify the button coordinate: (1,0):
Then, we'll specify the green color. You can use an RGB color value such as (0,50,0)
or a hexidecimal color value. (0x004400)
You don't want them too bright, so these are dimmer green values.
Next, we specify that this is a keycode, rather than a media key with the KEY
flag.
Lastly, we list out any keys that will be held down simultaneously, in this case (Keycode.CONTROL, Keycode.F5)
This is the full line of code: (1,1): (0x004400, KEY, (Keycode.CONTROL, Keycode.F5))
And, here it is inserted among the rest of that row:
(0,1): (0x110000, MEDIA, ConsumerControlCode.MUTE),
(1,1): (0x004400, KEY, (Keycode.CONTROL, Keycode.F5)),
# intentional blank button
(3,1): ((0,0,10), MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
Edit a Button
If you want to edit the functionality or color of an existing button, simply edit that section. For example, to set the mute button to blue:
(0,1): (0x000011, MEDIA, ConsumerControlCode.MUTE),
Button Removal
To remove a button you can simply delete the line of code, or just comment it out with a '#' symbol like this:
# (0,1): (0x110000, MEDIA, ConsumerControlCode.MUTE),
(1,1): (0x004400, KEY, (Keycode.CONTROL, Keycode.F5)),
# intentional blank button
(3,1): ((0,0,10), MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
That will effectively remove the first button of the second row.
Layout Tips
Here's how I have mine laid out. Here are a few tips for an easy-to-use Launch Deck:
- Color code buttons to their icons
- Group like things together in rows or columns. The entire upper two row set is used for media control here
- Notice the brighter colored Vol + above the dimmer Vol - which mimics a remote control layout
- Leave some space. Blank buttons help serve as visual landmarks for the important functional buttons nearby
Page last edited January 22, 2025
Text editor powered by tinymce.