Once you've finished setting up your Feather RP2040 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import board
import keypad
import rotaryio
import neopixel
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.mouse import Mouse
mouse = Mouse(usb_hid.devices)
# neopixel colors
RED = (255, 0, 0)
ORANGE = (255, 127, 0)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 0)
AQUA = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (127, 0, 255)
PINK = (255, 0, 255)
OFF = (50, 50, 50)
# axis states selected with keys 9-11
axis_states = [0, "x", "y", "z"]
state = axis_states[0]
# keymap for key matrix
keymap = {
(0): (axis_states[0], (Keycode.COMMAND, Keycode.COMMA), BLUE), # SETTINGS
(1): (axis_states[0], (Keycode.COMMAND, Keycode.P), ORANGE), # SLICE MODEL
(2): (axis_states[0], (Keycode.COMMAND, Keycode.D), RED), # CLEAR BED
(3): (axis_states[0], [Keycode.T], GREEN), # MOVE
(4): (axis_states[0], [Keycode.S], AQUA), # SCALE
(5): (axis_states[0], [Keycode.R], BLUE), # ROTATE
(6): (axis_states[0], [Keycode.M], AQUA), # MIRROR
(7): (axis_states[0], [Keycode.E], PURPLE), # SUPPORT BLOCKERS
(8): (axis_states[0], [Keycode.I], PINK), # TABS
(9): (axis_states[1], None, RED), # SET X-AXIS STATE
(10): (axis_states[2], None, GREEN), # SET Y-AXIS STATE
(11): (axis_states[3], None, BLUE), # SET Z-AXIS STATE
}
# keymap for encoder based on state; pos = [0], neg = [1]
encoder_map = {
("x"): ([Keycode.RIGHT_ARROW], [Keycode.LEFT_ARROW]),
("y"): ([Keycode.UP_ARROW], [Keycode.DOWN_ARROW]),
# ("z"): ([Keycode.W], [Keycode.S]),
}
# make a keyboard
kbd = Keyboard(usb_hid.devices)
# key matrix
COLUMNS = 3
ROWS = 4
keys = keypad.KeyMatrix(
row_pins=(board.D12, board.D11, board.D10, board.D9),
column_pins=(board.A0, board.A1, board.A2),
columns_to_anodes=False,
)
# neopixels and key num to pixel function
pixels = neopixel.NeoPixel(board.D5, 12, brightness=0.3)
def key_to_pixel_map(key_number):
row = key_number // COLUMNS
column = key_number % COLUMNS
if row % 2 == 1:
column = COLUMNS - column - 1
return row * COLUMNS + column
pixels.fill(OFF) # Begin with pixels off.
# make an encoder
encoder = rotaryio.IncrementalEncoder(board.D24, board.D25)
last_position = 0
while True:
# poll for key event
key_event = keys.events.get()
# get position of encoder
position = encoder.position
# if position changes..
if position != last_position:
# ..and it increases..
if position > last_position:
# ..and state is x:
if state is axis_states[1]:
kbd.press(*encoder_map[state][0])
# ..and state is y:
if state is axis_states[2]:
kbd.press(*encoder_map[state][0])
# ..and state is z:
if state is axis_states[3]:
mouse.move(wheel=-1)
# ..and it decreases..
if position < last_position:
# ..and state is x:
if state is axis_states[1]:
kbd.press(*encoder_map[state][1])
# ..and state is y:
if state is axis_states[2]:
kbd.press(*encoder_map[state][1])
# ..and state is z:
if state is axis_states[3]:
mouse.move(wheel=1)
# print(position)
# release all keys
kbd.release_all()
# update last_position
last_position = position
# if a key event..
if key_event:
# print(key_event)
# ..and it's pressed..
if key_event.pressed:
# ..and it's keys 0-8, send key presses from keymap:
if keymap[key_event.key_number][0] is axis_states[0]:
state = axis_states[0]
kbd.press(*keymap[key_event.key_number][1])
# ..and it's key 9, set state to x
if keymap[key_event.key_number][0] is axis_states[1]:
state = axis_states[1]
pixels[key_to_pixel_map(10)] = OFF
pixels[key_to_pixel_map(11)] = OFF
# ..and it's key 10, set state to y
if keymap[key_event.key_number][0] is axis_states[2]:
state = axis_states[2]
pixels[key_to_pixel_map(9)] = OFF
pixels[key_to_pixel_map(11)] = OFF
# ..and it's key 11, set state to z
if keymap[key_event.key_number][0] is axis_states[3]:
state = axis_states[3]
pixels[key_to_pixel_map(9)] = OFF
pixels[key_to_pixel_map(10)] = OFF
# turn on neopixel for key with color from keymap
pixels[key_to_pixel_map(key_event.key_number)] = keymap[key_event.key_number][2]
# ..and it's released..
if key_event.released:
# if it's key 0-8, release the key press and turn off neopixel
if keymap[key_event.key_number][0] is axis_states[0]:
kbd.release(*keymap[key_event.key_number][1])
pixels.fill(OFF)
Upload the Code and Libraries to the Feather RP2040
After downloading the Project Bundle, plug your Feather RP2040 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the Feather's CIRCUITPY drive:
- lib folder
- code.py
Your Feather RP2040 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.
How the CircuitPython Code Works
The top of the code has a dictionary called keymap. This dictionary has the attributes for each key on the macropad. These include the keyboard shortcut and NeoPixel color. The axis_states array affects the keyboard shortcuts for the rotary encoder. These shortcuts are defined in the encoder_map dictionary. You can edit these dictionaries to customize the code for your setup.
# axis states selected with keys 9-11
axis_states = [0, "x", "y", "z"]
state = axis_states[0]
# keymap for key matrix
keymap = {
(0): (axis_states[0], (Keycode.COMMAND, Keycode.COMMA), BLUE), # SETTINGS
(1): (axis_states[0], (Keycode.COMMAND, Keycode.P), ORANGE), # SLICE MODEL
(2): (axis_states[0], (Keycode.COMMAND, Keycode.D), RED), # CLEAR BED
(3): (axis_states[0], [Keycode.T], GREEN), # MOVE
(4): (axis_states[0], [Keycode.S], AQUA), # SCALE
(5): (axis_states[0], [Keycode.R], BLUE), # ROTATE
(6): (axis_states[0], [Keycode.M], AQUA), # MIRROR
(7): (axis_states[0], [Keycode.E], PURPLE), # SUPPORT BLOCKERS
(8): (axis_states[0], [Keycode.I], PINK), # TABS
(9): (axis_states[1], None, RED), # SET X-AXIS STATE
(10): (axis_states[2], None, GREEN), # SET Y-AXIS STATE
(11): (axis_states[3], None, BLUE), # SET Z-AXIS STATE
}
# keymap for encoder based on state; pos = [0], neg = [1]
encoder_map = {
("x"): ([Keycode.RIGHT_ARROW], [Keycode.LEFT_ARROW]),
("y"): ([Keycode.UP_ARROW], [Keycode.DOWN_ARROW]),
# ("z"): ([Keycode.W], [Keycode.S]),
}
Next the key matrix is setup using a KeyMatrix. The key_to_pixel_map() function maps the NeoPixels to their key in the matrix. The rotary encoder is instantiated using rotaryio.
# make a keyboard
kbd = Keyboard(usb_hid.devices)
# key matrix
COLUMNS = 3
ROWS = 4
keys = keypad.KeyMatrix(
row_pins=(board.D12, board.D11, board.D10, board.D9),
column_pins=(board.A0, board.A1, board.A2),
columns_to_anodes=False,
)
# neopixels and key num to pixel function
pixels = neopixel.NeoPixel(board.D5, 12, brightness=0.3)
def key_to_pixel_map(key_number):
row = key_number // COLUMNS
column = key_number % COLUMNS
if row % 2 == 1:
column = COLUMNS - column - 1
return row * COLUMNS + column
pixels.fill(OFF) # Begin with pixels off.
# make an encoder
encoder = rotaryio.IncrementalEncoder(board.D24, board.D25)
last_position = 0
The Loop
The loop consists of two parts: monitoring the rotary encoder and monitoring the key matrix. The rotary encoder is controlled by keys 9, 10 and 11 in the matrix. These keys select the axis_states index to control either the x, y or z axis. Depending on which axis is selected, the rotary encoder will send its defined keycode from the encoder_map dictionary; or in the case of the z axis a mouse scroll.
while True:
# poll for key event
key_event = keys.events.get()
# get position of encoder
position = encoder.position
# if position changes..
if position != last_position:
# ..and it increases..
if position > last_position:
# ..and state is x:
if state is axis_states[1]:
kbd.press(*encoder_map[state][0])
# ..and state is y:
if state is axis_states[2]:
kbd.press(*encoder_map[state][0])
# ..and state is z:
if state is axis_states[3]:
mouse.move(wheel=-1)
# ..and it decreases..
if position < last_position:
# ..and state is x:
if state is axis_states[1]:
kbd.press(*encoder_map[state][1])
# ..and state is y:
if state is axis_states[2]:
kbd.press(*encoder_map[state][1])
# ..and state is z:
if state is axis_states[3]:
mouse.move(wheel=1)
# print(position)
# release all keys
kbd.release_all()
# update last_position
last_position = position
The keypad module monitors the key matrix for events. If keys 0-8 are pressed, then their keyboard shortcut is sent over USB HID. If keys 9-11 are pressed, then their NeoPixel remains lit and an axis is selected for the rotary encoder to control.
# if a key event..
if key_event:
# print(key_event)
# ..and it's pressed..
if key_event.pressed:
# ..and it's keys 0-8, send key presses from keymap:
if keymap[key_event.key_number][0] is axis_states[0]:
state = axis_states[0]
kbd.press(*keymap[key_event.key_number][1])
# ..and it's key 9, set state to x
if keymap[key_event.key_number][0] is axis_states[1]:
state = axis_states[1]
pixels[key_to_pixel_map(10)] = OFF
pixels[key_to_pixel_map(11)] = OFF
# ..and it's key 10, set state to y
if keymap[key_event.key_number][0] is axis_states[2]:
state = axis_states[2]
pixels[key_to_pixel_map(9)] = OFF
pixels[key_to_pixel_map(11)] = OFF
# ..and it's key 11, set state to z
if keymap[key_event.key_number][0] is axis_states[3]:
state = axis_states[3]
pixels[key_to_pixel_map(9)] = OFF
pixels[key_to_pixel_map(10)] = OFF
# turn on neopixel for key with color from keymap
pixels[key_to_pixel_map(key_event.key_number)] = keymap[key_event.key_number][2]
# ..and it's released..
if key_event.released:
# if it's key 0-8, release the key press and turn off neopixel
if keymap[key_event.key_number][0] is axis_states[0]:
kbd.release(*keymap[key_event.key_number][1])
pixels.fill(OFF)
Page last edited January 22, 2025
Text editor powered by tinymce.