Maker.io main logo

USB Rotary Media Dial

2024-07-16 | By Adafruit Industries

License: See Original Project 3D Printing LED Strips RP2040 STEMMA

Courtesy of Adafruit

Guide by Ruiz Brothers

Overview

 

dial_1

Build a custom USB media dial using Adafruit’s STEMMA QT Rotary ‎Encoder, QT Py RP2040, and CircuitPython.‎

Set it up as a media controller to adjust your computer's volume or ‎customize it to be a macropad with keyboard shortcuts. ‎

led_strips_guide-demo

Embedded underneath the 3D printed knob is a NeoPixel strip that ‎changes color whenever it’s turned up or down.‎

With a rotary encoder you can make a single-click to play and pause ‎media. Double-click to skip to the next track and long press to mute ‎the volume.‎

encoder_2

Parts

Circuit Diagram

The diagram below provides a general visual reference for wiring of ‎the components once you get to the Assembly page. This diagram ‎was created using the software package Fritzing.‎

Adafruit Library for Fritzing

Adafruit uses the Adafruit's Fritzing parts library to create circuit ‎diagrams for projects. You can download the library or just grab ‎individual parts. Get the library and parts from GitHub - Adafruit ‎Fritzing Parts.‎

fritzing_3

Wired Connections

  • Rotary STEMMA QT connects to STEMMA QT port on QT Py ‎RP2040
  • GND from NeoPixel strip connects to GND pin on QT Py ‎RP2040
  • DIN from NeoPixel strip connects to A1 on QT Py RP2040
  • 5V from NeoPixel strip connects to 5V on QT Py RP2040

‎5V USB Power‎

The QT Py RP2040 is powered by a computer's 5V USB hub.‎

CAD Files

‎3D Printed Parts‎

STL files for 3D printing are oriented to print "as-is" on FDM style ‎machines. Parts are designed to 3D print without any support ‎material using PLA filament. Original design source may be ‎downloaded using the links below.‎

cad_4

Build Volume

The parts require a 3D printer with a minimum build volume.‎

  • ‎66mm (X) x 66mm (Y) x 35mm (Z)

build_5

Download CAD Source

Download STLs

CAD Assembly

The QT Py RP2040 press fits into the built-in holder on the bottom ‎cover. The case snap fits over the bottom cover. The NeoPixel LED ‎strip fits into the top channel on the case. The STEMMA QT rotary ‎encoder is secured to the built-in standoffs on the bottom cover. The ‎knob press fits onto the shaft of the rotary encoder.‎

led_strips_cad-explode

Multi-Color Knob

The knob is printed in multiple colors using the change ‎filament technique. In CURA slicer, open the post ‎processing plugin window by going to the top file menu:‎

Extensions > Post Processing > Modify GCode

Click, Add a script and choose Filament Change.‎

Enter 5 in the layer input box for the first change. Then, add another ‎filament change and enter 15 in the layer input box.‎

box_6

Filament Swap

Use a dark colored filament for the first six layers. When layer 6 ‎finishes, the printer will pause to allow the filament to be swapped.‎

Use a white or clear colored filament for layers 6 through 15. When ‎the printer pauses the second time, change the filament back to the ‎dark colored filament.‎‎

filament_7

Design Source Files

The project assembly was designed in Fusion 360. This can be ‎downloaded in different formats like STEP, STL and more.‎

Electronic components like Adafruit's boards, displays, connectors ‎and more can be downloaded from the Adafruit CAD parts GitHub ‎Repo.‎

led_strips_4991_QT_Rotary_Encoder

CircuitPython

CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It ‎makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY drive to iterate.‎

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your ‎board.‎

Download the latest version of CircuitPython for this board via ‎circuitpython.org

Click the link above to download the latest CircuitPython UF2 file.‎

Save it wherever is convenient for you.‎

download_8

board_9

To enter the bootloader, hold down the BOOT/BOOTSELbutton (highlighted in red above), and while continuing to hold it ‎‎(don't let go!), press and release the reset button (highlighted in ‎blue above). Continue to hold the BOOT/BOOTSEL button until the ‎RPI-RP2 drive appears!‎

If the drive does not appear, release all the buttons, and then repeat ‎the process above.‎

You can also start with your board unplugged from USB, press and ‎hold the BOOTSEL button (highlighted in red above), continue to ‎hold it while plugging it into USB, and wait for the drive to appear ‎before releasing the button.‎

A lot of people end up using charge-only USB cables and it is very ‎frustrating! Make sure you have a USB cable you know is good for ‎data sync.‎ 

You will see a new disk drive appear called RPI-RP2.‎‎ ‎

Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.

drive_10

drive_11

The RPI-RP2 drive will disappear, and a new disk drive ‎called CIRCUITPY will appear.‎

That's it, you're done! :)‎

drive_12

Safe Mode

You want to edit your code.py or modify the files on ‎your CIRCUITPY drive but find that you can't. Perhaps your board ‎has gotten into a state where CIRCUITPY is read-only. You may have ‎turned off the CIRCUITPY drive altogether. Whatever the reason, safe ‎mode can help.‎

Safe mode in CircuitPython does not run any user code on startup ‎and disables auto-reload. This means a few things. First, safe ‎mode bypasses any code in boot.py (where you can ‎set CIRCUITPY read-only or turn it off completely). Second, it does ‎not run the code in code.py. And finally, it does not automatically ‎soft-reload when data is written to the CIRCUITPY drive.‎

Therefore, whatever you may have done to put your board in a non-‎interactive state, safe mode gives you the opportunity to correct it ‎without losing all of the data on the CIRCUITPY drive.‎

Entering Safe Mode

To enter safe mode when using CircuitPython, plug in your board or ‎hit reset (highlighted in red above). Immediately after the board ‎starts up or resets, it waits 1000ms. On some boards, the onboard ‎status LED (highlighted in green above) will blink yellow during that ‎time. If you press reset during that 1000ms, the board will start up in ‎safe mode. It can be difficult to react to the yellow LED, so you may ‎want to think of it simply as a slow double click of the reset button. ‎‎(Remember, a fast double click of reset enters the bootloader.)‎

In Safe Mode

If you successfully enter safe mode on CircuitPython, the LED will ‎intermittently blink yellow three times.‎

If you connect to the serial console, you'll find the following message.‎

Copy Code
Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

You can now edit the contents of the CIRCUITPY drive. ‎Remember, your code will not run until you press the reset button, or ‎unplug and plug in your board, to get out of safe mode.‎

Flash Resetting UF2‎

If your board ever gets into a really weird state and CIRCUITPY ‎doesn't show up as a disk drive after installing CircuitPython, try ‎loading this 'nuke' UF2 to RPI-RP2. which will do a 'deep clean' on ‎your Flash Memory. You will lose all the files on the board, but at ‎least you'll be able to revive it! After loading this UF2, follow the steps ‎above to re-install CircuitPython.‎

Download flash erasing "nuke" UF2‎

Code the Media Dial

media_13

Once you've finished setting up your RP2040 QT Py 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.‎

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import board
import usb_hid
import neopixel
from rainbowio import colorwheel
from adafruit_debouncer import Button
from adafruit_seesaw import seesaw, rotaryio, digitalio
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode

enc_inc = ConsumerControlCode.VOLUME_INCREMENT
enc_dec = ConsumerControlCode.VOLUME_DECREMENT
one_press = ConsumerControlCode.PLAY_PAUSE
two_press = ConsumerControlCode.SCAN_NEXT_TRACK
three_press = [Keycode.LEFT_CONTROL, Keycode.UP_ARROW]
long_press = ConsumerControlCode.MUTE

cc = ConsumerControl(usb_hid.devices)
kbd = Keyboard(usb_hid.devices)

pixel_pin = board.A1
num_pixels = 18
pixels = neopixel.NeoPixel(pixel_pin, num_pixels,
brightness=.5, auto_write=True)
hue = 0
pixels.fill(colorwheel(hue))

i2c = board.STEMMA_I2C()
seesaw = seesaw.Seesaw(i2c, 0x36)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
ss_pin = digitalio.DigitalIO(seesaw, 24)
button = Button(ss_pin, long_duration_ms=600)

encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0

while True:
position = -encoder.position
button.update()
if position != last_position:
if position > last_position:
cc.send(enc_dec)
hue = hue - 7
if hue <= 0:
hue = hue + 256
else:
cc.send(enc_inc)
hue = hue + 7
if hue >= 256:
hue = hue - 256
pixels.fill(colorwheel(hue))
last_position = position
if button.short_count == 1:
cc.send(one_press)
if button.short_count == 2:
cc.send(two_press)
if button.short_count == 3:
kbd.press(*three_press)
kbd.release_all()
if button.long_press:
cc.send(long_press)

View on GitHub

Upload the Code and Libraries to the ‎RP2040 QT Py

After downloading the Project Bundle, plug your RP2040 QT Py 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 RP2040 QT Py CIRCUITPY drive should look like this after ‎copying the lib folder and the code.py file.‎

copying_14

How the CircuitPython Code Works

At the top of the code are variables for the keycodes that are sent by ‎the media dial. You can update these keycodes if you want the ‎media dial to do different things. By default, turn the encoder ‎increases or decreases volume, one short press sends play/pause, ‎two short presses send next track, three short presses send the ‎mission control keyboard short for MacOS and a long press mutes ‎your speaker volume.‎

Download File

Copy Code
enc_inc = ConsumerControlCode.VOLUME_INCREMENT
enc_dec = ConsumerControlCode.VOLUME_DECREMENT
one_press = ConsumerControlCode.PLAY_PAUSE
two_press = ConsumerControlCode.SCAN_NEXT_TRACK
three_press = [Keycode.LEFT_CONTROL, Keycode.UP_ARROW]
long_press = ConsumerControlCode.MUTE

HID, NeoPixels and seesaw

The Keyboard USB HID device is instantiated, followed by the ‎NeoPixels and seesaw rotary encoder.‎

Download File

Copy Code
cc = ConsumerControl(usb_hid.devices)
kbd = Keyboard(usb_hid.devices)

pixel_pin = board.A1
num_pixels = 18
pixels = neopixel.NeoPixel(pixel_pin, num_pixels,
brightness=.5, auto_write=True)
hue = 0
pixels.fill(colorwheel(hue))

i2c = board.STEMMA_I2C()
seesaw = seesaw.Seesaw(i2c, 0x36)
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
ss_pin = digitalio.DigitalIO(seesaw, 24)
button = Button(ss_pin, long_duration_ms=600)

encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0

The Loop

In the loop, the rotary encoder and its switch are monitored for ‎inputs. If the encoder is turned, then the volume is increased or ‎decreased. The NeoPixels will cycle through the colorwheel colors.‎

Download File

Copy Code
if position != last_position:
if position > last_position:
cc.send(enc_dec)
hue = hue - 7
if hue <= 0:
hue = hue + 256
else:
cc.send(enc_inc)
hue = hue + 7
if hue >= 256:
hue = hue - 256
pixels.fill(colorwheel(hue))
last_position = position

The switch on the encoder is defined as ‎an adafruit_debouncer Button object, allowing you to define a long ‎press duration and count short presses. When the defined presses ‎are detected in the loop, the associated keycodes are sent.‎

‎Download File

Copy Code
if button.short_count == 1:
cc.send(one_press)
if button.short_count == 2:
cc.send(two_press)
if button.short_count == 3:
kbd.press(*three_press)
kbd.release_all()
if button.long_press:
cc.send(long_press)

List of USB HID Keycodes

A list of available keyboard characters and consumer controls are ‎listed in the documentation linked below. Function keys and ‎modifiers can be used to create custom keyboard shortcuts.‎

USB HID KeyCodes

Wiring Assembly

Parts

Get the parts ready to wire and assemble.‎

parts_14

NeoPixel Strip

Start by removing the NeoPixel strip from the reel.‎

Remove the stock cable from the beginning of the strip using wire ‎cutters. ‎

Measure and cut a piece from the strip so it's about 6 inches (152mm) ‎in length. This should have 18 NeoPixel LEDs in the cut piece.‎

Remove the silicone sheathing from the cut piece.‎‎

strip_15

NeoPixel Wire

Measure and cut a piece from the 4-wire silicone ribbon cable so it's ‎‎4 inches (102mm) in length.‎

Peel off a single wire from the cable so it's only 3 wires.‎

Using wire strippers, remove a bit of insulation from each wire.‎

Tin the exposed wire using solder to prevent the strands of wire from ‎fraying.‎

tin_16

Solder Wire to NeoPixel

Locate the end of the NeoPixel strip with the DI (Data In) label.‎

Solder the three wires from the cable to the pads on the back of the ‎NeoPixel strip.‎

Double check the wires are properly soldered ‎to 5V, DI and GND pads on the NeoPixel LED strip.‎

solder_17

solder_18

Wire QT Py

Make the following connections to the QT Py RP2040.‎

  • GND from NeoPixel to GND on QT Py
  • DI from NeoPixel strip to A1 on QT Py
  • 5V from NeoPixel strip to 5V on QT Py

connections_19

QT Py Bottom Cover

Get the QT Py and bottom cover ready to assemble.‎

Orient the flat edge of the bottom cover with the USB port on the QT ‎Py.‎

cover_20

Install QT Py to Bottom

Fit the QT Py PCB in between the four standoffs.‎

Insert the QT Py at an angle to fit one side of the PCB is fitted ‎underneath the built-in clips.‎

Carefully flex the bottom cover to allow the other side of the QT Py ‎PCB to fit other the built-in clips.‎

fit_21

fit_22

Connect STEMMA QT

Plug in the short STEMMA QT cable to the STEMMA QT port on the ‎QT Py RP2040.‎

plug_23

Install Rotary STEMMA QT

Place the Rotary STEMMA QT breakout over the bottom covers built-‎in standoffs with the mounting holes lined up.‎

Orient the breakout so that STEMMA QT ports are is lined up.‎

install_24

Secure Rotary STEMMA QT

Use four M2.5 x 6mm steel machine screws to secure the Rotary ‎STEMMA QT breakout to the bottom cover.‎

secure_25

Connect QT Py to Rotary STEMMA QT

Plug in the STEMMA QT cable from the Rotary Breakout to the QT Py ‎RP2040.‎

plug_26

Install Case

Pull the NeoPixel strip through the 3D printed case.‎

Orient the USB cutout with the QT Py's USB-C port.‎

Firmly press the bottom cover and case to snap fit them closed.‎

case_27

case_28

USB Port

The QT Py's USB-C port is accessible on the side of the case.‎

port_29

Install NeoPixel Strip

Orient the NeoPixel strip so the LEDs are facing up.‎

Carefully fit the NeoPixel strip into the inner channel on the top of ‎the case. ‎

strip_30

Install Knob

Orient the 3D printed knob so the flat edge is lined up with the shaft ‎of the rotary encoder.‎

Press the knob into the shaft of the rotary encoder until the rotary ‎encoder clicks.‎

knob_31

knob_32

Final Build

Congratulations on your build! Rotate the knob to check if ‎everything is lined properly.‎

final_33

Usage

led_strips_guide-usage

USB HID Controls

  • Turn the encoder increases or decreases volume.‎
  • Single click to play/pause media.‎
  • Double click to skip to next track.
  • Triple click to envoke the mission control keyboard shortcut for ‎MacOS.
  • Long press to mute your speaker volume.‎

Customize USB HID Keycodes

You can update these keycodes if you want the media dial to do ‎different things.‎

A list of keycodes and consumer controls are listed in the ‎documentation linked below. Function keys and modifiers can be ‎used to create custom keyboard shortcuts. Just change the ‎keycodes in the variables located in the top of the code.‎

Download File

Copy Code
enc_inc = ConsumerControlCode.VOLUME_INCREMENT
enc_dec = ConsumerControlCode.VOLUME_DECREMENT
one_press = ConsumerControlCode.PLAY_PAUSE
two_press = ConsumerControlCode.SCAN_NEXT_TRACK
three_press = [Keycode.LEFT_CONTROL, Keycode.UP_ARROW]
long_press = ConsumerControlCode.MUTE

USB Keycodes

制造商零件编号 4900
QT PY RP2040
Adafruit Industries LLC
¥80.99
Details
制造商零件编号 5880
ADAFRUIT I2C STEMMA QT ROTARY EN
Adafruit Industries LLC
¥64.71
Details
制造商零件编号 3634
ADDRESS LED STRIP 1M SIDE LIT
Adafruit Industries LLC
¥294.48
Details
制造商零件编号 4399
STEMMA QWIIC JST SH CABLE 50MM
Adafruit Industries LLC
¥7.73
Details
制造商零件编号 5153
CABLE A PLUG TO C PLUG 3.28'
Adafruit Industries LLC
¥24.01
Details
制造商零件编号 3891
CBL RIBN 4COND FLAT BLACK 3.28'
Adafruit Industries LLC
¥16.36
Details
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum