Aaron Watts Dev Media Keyboard
Back to home
RSS feed

Media Keyboard - Pico Controller

A Raspberry Pi Pico controller on a breadboard, wired
        up to six coloured buttons, in the pattern of white, blue,
        blue, white, blue, white.

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

Note: I will assume you know how to wire a button. I'm using the same ground pin for all 6. I have used GPIO pins 16 to 21, which keeps things nice and tidy as the are all in one corner of the pico.

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)
Back to Top

Comments Section