Media Keyboard - Pico Controller
My keyboard doesn't include the media keys, so I built a macro keyboard to control the media on my desktop computer. It is plug'n'play and requires no configuration to work between different devices. The project can be easily tweaked to program macro's for work, steam games, and whatever else you can't be bothered to type out manually.
I won't be going through the first time setup of the pico here, so that is something you're going to have to look up yourself. Once you know how to get some python code on the device and get it to run, you're ready, so see you then!
Getting Started
When you know how to move data on and off the device and run
some python on it, you will want to install
CircuitPython
on it. We will be using the
Adafruit CircuitPython HID
library too. The easiest way to do this is to download the
newest mpy version from the releases page, unzip the
download, and copy the adafruit_hid/
directory into
the lib/
directory on the pico.
Button Set Up
First we need to import the libraries we need, including the adafruit consumer control modules:
import time
import digitalio
import board
import usb_hid
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
Next we can look at how to set up a single key:
cc = ConsumerControl(usb_hid.devices)
wait = 0.125
btn1 = digitalio.DigitalInOut(board.GP21)
btn1.direction = digitalio.Direction.INPUT
btn1.pull = digitalio.Pull.DOWN
If you have ever set up buttons using GPIO on a raspberry
pi, then this should all be looking pretty familiar.
We assign btn1
a GPIO address, state that
the GPIO is receiving input, and identify whether the
buttons awake state should be when UP or DOWN.
Of course this is a long process to go through for multiple buttons, and we will use some syntactic prowess to make this easier. But for now, let's finally look at the program loop that will be listening for key presses:
while True:
if btn1.value:
print('key pressed')
cc.send(Consumer.ControlCode.PLAY_PAUSE)
Optimising the Code
As mentioned, going through this process for 6 keys, and setting up the conditional logic to handle key presses would be a little tedious. To remedy this, we can use the power of python lists and dictionaries to create a list of each GPIO address, and what event that each specific event will correspond to:
media_keys = [
{'pin':board.GP21, 'code':ConsumerControlCode.MUTE},
{'pin':board.GP16, 'code':ConsumerControlCode.VOLUME_DECREMENT},
{'pin':board.GP20, 'code':ConsumerControlCode.VOLUME_INCREMENT},
{'pin':board.GP19, 'code':ConsumerControlCode.SCAN_PREVIOUS_TRACK},
{'pin':board.GP17, 'code':ConsumerControlCode.PLAY_PAUSE},
{'pin':board.GP18, 'code':ConsumerControlCode.SCAN_NEXT_TRACK}
]
Neat, right? What about assigning the active states to each key? Rather than add repeated information into our keys list, we can just use a for loop to add this info to each dictionary in the list:
for media_key in media_keys:
media_key['btn'] = digitalio.DigitalInOut(media_key['pin'])
media_key['btn'].direction = digitalio.Direction.INPUT
media_key['btn'].pull = digitalio.Pull.DOWN
Isn't python great? Lastly we can use a for loop again, this time for the key listener loop, making use of the power of python to save us typing out tedious and repetitive code:
while True:
for media_key in media_keys:
if media_key['btn'].value:
cc.send(media_key['code'])
time.sleep(wait)
time.sleep(wait)
The Finished Code
The following code should be saved on the pico's top-most
directory as code.py
. Adding print statements
to the while loop should aid in debugging if you find it
doesn't work correctly.
import time
import digitalio
import board
import usb_hid
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
cc = ConsumerControl(usb_hid.devices)
wait = 0.125
media_keys = [
{'pin':board.GP21,'code':ConsumerControlCode.MUTE},
{'pin':board.GP16,'code':ConsumerControlCode.VOLUME_DECREMENT},
{'pin':board.GP20,'code':ConsumerControlCode.VOLUME_INCREMENT},
{'pin':board.GP19,'code':ConsumerControlCode.SCAN_PREVIOUS_TRACK},
{'pin':board.GP17,'code':ConsumerControlCode.PLAY_PAUSE},
{'pin':board.GP18,'code':ConsumerControlCode.SCAN_NEXT_TRACK}
]
for media_key in media_keys:
media_key['btn'] = digitalio.DigitalInOut(media_key['pin'])
media_key['btn'].direction = digitalio.Direction.INPUT
media_key['btn'].pull = digitalio.Pull.DOWN
while True:
for media_key in media_keys:
if media_key['btn'].value:
cc.send(media_key['code'])
time.sleep(wait)
time.sleep(wait)