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.