Here’s a little desktop media controller I just finished. It has 4 keys and a rotary encoder. I designed the case for it in Fusion360 and printed it myself. Cheap, easy, fun project with all the parts coming from Adafruit (see the details below). The knob changes the volume, and toggles mute when clicked in. The 4 keys control media playback or launch apps.
Details:
Hardware:
- Adafruit QT Py RP2040
- Adafruit NeoKey 1×4 QT I2C Board
- Adafruit I2C QT Rotary Encoder Board
- 2x STEMMA QT Cables [4-pin]
- Key Switches (I used the white ones)
- Keycaps (I had some clear ones around that I used)
Software:
The QT Pi runs CircuitPython, here’s the code for it (this is my code.py):
import time
import board
import busio
from digitalio import DigitalInOut, Direction, Pull
import rotaryio
from adafruit_neokey.neokey1x4 import NeoKey1x4
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
from adafruit_seesaw import seesaw, rotaryio, digitalio, neopixel
try:
import _pixelbuf
except ImportError:
import adafruit_pypixelbuf as _pixelbuf
# Initialize I2C Bus and board addresses
i2c = busio.I2C(board.SCL1, board.SDA1, frequency=786_000)
neokey = NeoKey1x4(i2c, addr=0x31)
seesaw = seesaw.Seesaw(i2c, addr=0x36)
encoder = rotaryio.IncrementalEncoder(seesaw)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
button = digitalio.DigitalIO(seesaw, 24)
# Turn off Rotary Encoder Neopixel
pixel = neopixel.NeoPixel(seesaw, 6, 1)
pixel.brightness = 0.0
color = 0
pixel.fill(_pixelbuf.colorwheel(color))
last_encoder_val = encoder.position
kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)
print("Media Controller")
# Neokey Stuff
# states for key presses
key_0_state = False
key_1_state = False
key_2_state = False
key_3_state = False
while True:
# switch debouncing
# also turns off NeoPixel on release
if not neokey[0] and key_0_state:
key_0_state = False
neokey.pixels[0] = 0x0
if not neokey[1] and key_1_state:
key_1_state = False
neokey.pixels[1] = 0x0
if not neokey[2] and key_2_state:
key_2_state = False
neokey.pixels[2] = 0x0
if not neokey[3] and key_3_state:
key_3_state = False
neokey.pixels[3] = 0x0
# if 1st neokey is pressed...
if neokey[0] and not key_0_state:
print("Mute")
neokey.pixels[0] = 0xFF0000
cc.send(ConsumerControlCode.MUTE)
# update key state
key_0_state = True
# if 2nd neokey is pressed...
if neokey[1] and not key_1_state:
print("Play/Pause")
neokey.pixels[1] = 0xFFFF00
cc.send(ConsumerControlCode.PLAY_PAUSE)
# update key state
key_1_state = True
# if 3rd neokey is pressed...
if neokey[2] and not key_2_state:
print("Stop")
neokey.pixels[2] = 0x00FF00
cc.send(ConsumerControlCode.STOP)
# update key state
key_2_state = True
# if 4th neokey is pressed...
if neokey[3] and not key_3_state:
print("Notepad")
# turn on NeoPixel
neokey.pixels[3] = 0x00FFFF
kbd.send(Keycode.WINDOWS, Keycode.THREE)
time.sleep(0.5)
# update key state
key_3_state = True
# Rotary Encoder stuff
diff = last_encoder_val - encoder.position # encoder clicks since last read
last_encoder_val = encoder.position
if button.value == False: # button pressed
cc.send(ConsumerControlCode.MUTE) # toggle mute
while not button.value: # wait for release
pass
else: # not pressed
if diff > 0:
cc.send(ConsumerControlCode.VOLUME_INCREMENT)
elif diff < 0:
cc.send(ConsumerControlCode.VOLUME_DECREMENT)
time.sleep(0.01)