Using Raspberry Pi Pico as a Keyboard

My arcade machine’s graphics card doesn’t want to play nicely with its motherboard. Everytime the machine boots, a warning is displayed saying that the graphics card is running in PCI x1 mode. I’ve looked through all the BIOS settings, and there’s no way of disabling this warning dialog. The worst part is that you need to pull out a keyboard and press the F1 key to continue. This makes it really annoying to turn the machine on!

To fix this, I’m going to emulate a keyboard pressing the ‘F1’ key shortly after the machine powers on. Some arduinos can emulate an HID keyboard, but I chose to use a Raspberry Pi Pico instead. I was mostly interested in seeing what the Pi Pico had to offer, plus it was cheaper and more compact than an Arduino Uno.

Installing CircuitPython

First, I had to install CircuitPython on my Pi Pico. I installed CircuitPython version 7.3.3 because it was the latest at the time of writing this. Here’s the official installation guide that I followed to install CircuitPython. Once CircuitPython is installed, your pi should mount itself to your PC as a drive called CIRCUITPY. The drive should contain the following files/directories

CIRCUITPY/
    lib/
    boot_out.txt
    code.py

Installing Adafruit HID Library

Download the Adafruit CicruitPython HID library. I used version 5.3.2, but whatever is the latest version will probably do. To install it, you simply extract the zip and copy the adafruit_hid directory to your pi’s CIRCUITPY/lib/ directory.

code.py (the real code)

code.py is the python script that the pico will run at power-on. We’ll edit the default code.py to do the keyboard emulation logic. Here’s the code that I used to emulate pressing the F1 key after a short delay. I wanted an indication that the pico was alive, so I made it blink the onboard LED while it’s waiting for the PC to boot before it presses the F1 key.

import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode

# setup status LED
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT

# blink status LED while we wait for PC to startup
BOOT_DELAY_S = 8
for d in range(BOOT_DELAY_S):
    led.value = True
    time.sleep(0.5)
    led.value = False
    time.sleep(0.5)

# send the F1 key
keyboard = Keyboard(usb_hid.devices)
keyboard_layout = KeyboardLayoutUS(keyboard)
keyboard.press(Keycode.F1)
keyboard.release_all()

# do nothing else... forever
while True:
    time.sleep(1.0)

boot.py

The above program worked fine when debugging, but when I plugged it into the arcade machine, the BIOS wouldn’t pick up on the F1 key. It turns out that keyboards provide a different response protocol for BIOSs. I didn’t really dig into the details, but this github issue contained the necessary code I needed to put the pico’s keyboard into “boot_device” mode.

Paste the below code into a file called boot.py within your pico’s CIRCUITPY drive. boot.py is a special script that gets ran prior to CircuitPython calling code.py. boot.py also enables you to change special parameters on your pico that you otherwise wouldn’t be able to do in code.py.

WARNING!!! For the boot_mode keyboard to work, boot.py has to disable the pico’s USB mass storage functionality. This means your CIRCUITPY drive won’t show up after a fresh power cycle. To get USB mass storage functionality back, simply short GP2 to ground then power on the pico. I learned this lesson the hard way… :)

import board
import digitalio
import storage
import usb_cdc
import usb_midi
import usb_hid

# Modified boot descriptor to enable RP2040 macropad to work with KVM switch
# https://github.com/adafruit/circuitpython/issues/1136#issuecomment-1002833056
BOOT_KEYBOARD_DESCRIPTOR=bytes((
0x05, 0x01,        # Usage Page (Generic Desktop Ctrls)
0x09, 0x06,        # Usage (Keyboard)
0xA1, 0x01,        # Collection (Application)
0x05, 0x07,        #   Usage Page (Kbrd/Keypad)
0x19, 0xE0,        #   Usage Minimum (0xE0)
0x29, 0xE7,        #   Usage Maximum (0xE7)
0x15, 0x00,        #   Logical Minimum (0)
0x25, 0x01,        #   Logical Maximum (1)
0x75, 0x01,        #   Report Size (1)
0x95, 0x08,        #   Report Count (8)
0x81, 0x02,        #   Input (Data,Var,Abs,No Wrap,Linear,Pr
0x95, 0x01,        #   Report Count (1)
0x75, 0x08,        #   Report Size (8)
0x81, 0x01,        #   Input (Const,Array,Abs,No Wrap,Linear
0x95, 0x03,        #   Report Count (3)
0x75, 0x01,        #   Report Size (1)
0x05, 0x08,        #   Usage Page (LEDs)
0x19, 0x01,        #   Usage Minimum (Num Lock)
0x29, 0x05,        #   Usage Maximum (Kana)
0x91, 0x02,        #   Output (Data,Var,Abs,No Wrap,Linear,P
0x95, 0x01,        #   Report Count (1)
0x75, 0x05,        #   Report Size (5)
0x91, 0x01,        #   Output (Const,Array,Abs,No Wrap,Linea
0x95, 0x06,        #   Report Count (6)
0x75, 0x08,        #   Report Size (8)
0x15, 0x00,        #   Logical Minimum (0)
0x26, 0xFF, 0x00,  #   Logical Maximum (255)
0x05, 0x07,        #   Usage Page (Kbrd/Keypad)
0x19, 0x00,        #   Usage Minimum (0x00)
0x2A, 0xFF, 0x00,  #   Usage Maximum (0xFF)
0x81, 0x00,        #   Input (Data,Array,Abs,No Wrap,Linear,
0xC0,              # End Collection
))

maintenance_pin = digitalio.DigitalInOut(board.GP2)
maintenance_pin.direction = digitalio.Direction.INPUT
maintenance_pin.pull = digitalio.Pull.UP
maintenance_mode = not maintenance_pin.value # active low

# Keyboard object using modified boot descriptor
kbd = usb_hid.Device(
    report_descriptor=BOOT_KEYBOARD_DESCRIPTOR,
    usage=0x06,
    usage_page=0x01,
    report_ids=(0,),
    in_report_lengths=(8,),
    out_report_lengths=(1,),
)

if maintenance_mode:
    print("Booting in maintenance mode")
    print("All USB devices enabled")
else:
    storage.disable_usb_drive()
    usb_cdc.disable()
    usb_midi.disable()
    usb_hid.enable((kbd,), boot_device=1)

Conclusion

With everything above, the pico has been working flawlessly on my arcade machine! I can power on the machine and let the pico automatically skip over the BIOS warning.


Load Comments