Setup Adafruit Trinket M0 for CircuitPython
We'll need to get our board setup so we can run CircuitPython code. First thing we'll need to do is connect the board to your computer with a microUSB cable. Then double-click on the reset button to put it in "UF2" boot-loader mode. The NeoPixel will turn green. The board will then show up as a USB storage device on your computer named "TRINKETBOOT".
Follow the guide below to setup the firmware, once complete, come back here and proceed.
Download Adafruit CircuitPython Library Bundle
In order to run the code, we'll need to download some libraries. The download linked below will contain all the libraries available for Circuit Python. To run the code for this project, we only need a few. Unzip the downloaded file and look for the following libraries.
Required Libraries
- Adafruit Neopixel – neopixel.mpy
- Adafruit HID – adafruit_hid
Install Circuit Python Libraries
Now that we have all of the libraries and know which ones this project needs, we'll need to copy them onto the Trinket M0 USB drive (which will be named CIRCUITPY after flashing the firmware). In the CIRCUITPY drive, create a new folder and name it "lib". Then, copy the libraries to that "lib" folder. The lib folder should contain neopixel.mpy and adafruit_hid .
Upload Code
OK, now it's time to upload the code for this project onto the CIRCUITPY drive. Create a new text document using a text app. Then, copy the code below and paste it into that newly created text document. Save that text document to the CIRCUITPY drive and name it "main.py". Once saved, the code will automatically run and will start working.
Modify Key Codes
You can customize the key codes to form custom commands which can be multiple keys or have it execute just single keyboard characters.
The rotary encode can execute up to 3 different commands. Pressing the knob and turning the knob left or right. These are commented in the code and can be changed by adjusting the key code value.
List of USB HID Keycodes
The long list of available keyboard characters are listed in the webpage linked below. Most of the characters are for USA keyboard only. Function keys and modifiers can be used but only some special characters are not supported.
Starting with the first command, turning the knob to the right, will execute the ctrl+up arrow keys. These are two different keyboard characters that are separated with commas. This will essentially press the two keys simultaneously. The values inside the parentheses kbd.press(keycode.THISKEY) are the ones you want to change. For example, the block of code below is executed when turning the knob to the right.
# Check if rotary encoder went up
if encoder_direction == 1:
kbd.press(Keycode.CONTROL, Keycode.UP_ARROW)
kbd.release_all()
For more information and trouble shooting, please check out the Circuit Python library guide, linked below.
"""
A CircuitPython 'multimedia' dial demo
Uses a Trinket M0 + Rotary Encoder -> HID keyboard out with neopixel ring
"""
import time
from digitalio import *
from board import *
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import neopixel
import usb_hid
DOT_COLOR = 0xFF0000 # set to your favorite webhex color
PRESSED_DOT_COLOR = 0x008080 # set to your second-favorite color
LIT_TIMEOUT = 15 # after n seconds, turn off ring
# NeoPixel LED ring on pin D1
# Ring code will auto-adjust if not 16 so change to any value!
ring = neopixel.NeoPixel(D1, 16, brightness=0.2)
dot_location = 0 # what dot is currently lit
# Encoder button is a digital input with pullup on D2
button = DigitalInOut(D2)
button.direction = Direction.INPUT
button.pull = Pull.UP
# Rotary encoder inputs with pullup on D3 & D4
rot_a = DigitalInOut(D3)
rot_a.direction = Direction.INPUT
rot_a.pull = Pull.UP
rot_b = DigitalInOut(D4)
rot_b.direction = Direction.INPUT
rot_b.pull = Pull.UP
# Used to do HID output, see below
kbd = Keyboard(usb_hid.devices)
# time keeper, so we know when to turn off the LED
timestamp = time.monotonic()
######################### MAIN LOOP ##############################
# the counter counts up and down, it can roll over! 16-bit value
encoder_counter = 0
# direction tells you the last tick which way it went
encoder_direction = 0
# constants to help us track what edge is what
A_POSITION = 0
B_POSITION = 1
UNKNOWN_POSITION = -1 # initial state so we know if something went wrong
rising_edge = falling_edge = UNKNOWN_POSITION
# get initial/prev state and store at beginning
last_button = button.value
rotary_prev_state = [rot_a.value, rot_b.value]
while True:
# reset encoder and wait for the next turn
encoder_direction = 0
# take a 'snapshot' of the rotary encoder state at this time
rotary_curr_state = [rot_a.value, rot_b.value]
if rotary_curr_state != rotary_prev_state:
#print("Changed")
if rotary_prev_state == [True, True]:
# we caught the first falling edge!
if not rotary_curr_state[A_POSITION]:
#print("Falling A")
falling_edge = A_POSITION
elif not rotary_curr_state[B_POSITION]:
#print("Falling B")
falling_edge = B_POSITION
else:
# uhh something went deeply wrong, lets start over
continue
if rotary_curr_state == [True, True]:
# Ok we hit the final rising edge
if not rotary_prev_state[B_POSITION]:
rising_edge = B_POSITION
# print("Rising B")
elif not rotary_prev_state[A_POSITION]:
rising_edge = A_POSITION
# print("Rising A")
else:
# uhh something went deeply wrong, lets start over
continue
# check first and last edge
if (rising_edge == A_POSITION) and (falling_edge == B_POSITION):
encoder_counter -= 1
encoder_direction = -1
print("%d dec" % encoder_counter)
elif (rising_edge == B_POSITION) and (falling_edge == A_POSITION):
encoder_counter += 1
encoder_direction = 1
print("%d inc" % encoder_counter)
else:
# (shrug) something didn't work out, oh well!
encoder_direction = 0
# reset our edge tracking
rising_edge = falling_edge = UNKNOWN_POSITION
rotary_prev_state = rotary_curr_state
# Check if rotary encoder went up
if encoder_direction == 1:
kbd.press(Keycode.CONTROL, Keycode.UP_ARROW)
kbd.release_all()
# Check if rotary encoder went down
if encoder_direction == -1:
kbd.press(Keycode.CONTROL, Keycode.DOWN_ARROW)
kbd.release_all()
# Button was 'just pressed'
if (not button.value) and last_button:
print("Button pressed!")
kbd.press(44) #Keycode.SPACE
kbd.release_all()
ring[dot_location] = PRESSED_DOT_COLOR # show it was pressed on ring
timestamp = time.monotonic() # something happened!
elif button.value and (not last_button):
print("Button Released!")
# kbd.press(Keycode.SHIFT, Keycode.SIX)
# kbd.release_all()
ring[dot_location] = DOT_COLOR # show it was released on ring
timestamp = time.monotonic() # something happened!
last_button = button.value
if encoder_direction != 0:
timestamp = time.monotonic() # something happened!
# spin neopixel LED around!
previous_location = dot_location
dot_location += encoder_direction # move dot in the direction
dot_location += len(ring) # in case we moved negative, wrap around
dot_location %= len(ring)
if button.value:
ring[dot_location] = DOT_COLOR # turn on new dot
else:
ring[dot_location] = PRESSED_DOT_COLOR # turn on new dot
ring[previous_location] = 0 # turn off previous dot
if time.monotonic() > timestamp + LIT_TIMEOUT:
ring[dot_location] = 0 # turn off ring light temporarily
Page last edited March 08, 2024
Text editor powered by tinymce.