A robust PS/2 to USB HID keyboard converter for the Raspberry Pi Pico (RP2040), written in MicroPython.
- PIO-based PS/2 Driver: Uses the RP2040's PIO state machines for precise, non-blocking signal reading.
- Asyncio Core: Fully asynchronous event loop handles USB reports and PS/2 events concurrently.
- Robust Error Handling: Includes a watchdog to restart the PS/2 reader if it crashes and file-based logging (
log.txt). - Status Feedback: Uses the RP2040-Zero's onboard NeoPixel for visual status indication.
- Full Mapping: Supports standard keys, modifiers, navigation clusters, and numpad.
- Easy macro definitions: Change macro definitions in
keymap.pyusing Thonny.
- Board: Raspberry Pi Pico or RP2040-Zero (Code configured for RP2040-Zero NeoPixel on GPIO 16).
- PS/2 Connector: Female PS/2 socket or breakout.
- Level Shifting: PS/2 is 5V, Pico is 3.3V. While my testing keyboard works with 3.3V, a level shifter or voltage divider could be necessary.
| PS/2 Pin | Pico Pin | Function |
|---|---|---|
| 5V | VBUS | Power |
| GND | GND | Ground |
| CLK | GPIO 0 | Clock |
| DATA | GPIO 1 | Data |
Note: Pins can be changed in main.py.
-
Flash MicroPython: Install the latest MicroPython firmware on your RP2040.
-
Install Dependencies: You need the
usb-device-keyboardlibrary.import mip mip.install("usb-device-keyboard")
In case it fails:
- Close Thonny
- run
pipx run mpremote mip install usb-device-keyboard
(Or copy the library manually if offline)
-
Copy Files: Upload the following files to the root of the Pico:
main.pyps2_pio.pykeymap.pyps2_constants.pyusb_constants.pysimple_test.py(needed only if testing the PS/2 wiring)
-
Change key definitions (optional): User friendly key/macro system defined in
keymap.py. Edit in Thonny for example (hitStop/Restart Backenduntil you see the terminal in which you could type). -
Run: Reset the board. It will wait 1 second (flashing yellow) before starting.
| Color | Pattern | Meaning |
|---|---|---|
| 🟡 Yellow | Flashing | Initialization / Waiting |
| 🟢 Green | Solid (Dim) | Ready / Idle |
| 🟢 Green | Flash (Bright) | Key Press Detected |
| 🔴 Red | Solid | USB Error (Check connection) |
| 🔴 Red | Flashing | PS/2 Error (Unknown key/Protocol error) |
- Log File: Errors are written to
log.txton the device. Not cleared on startup unlessDEBUG = Trueinmain.py. - Debug Mode: Set
DEBUG = Trueinmain.pyto log all events, not just errors. Clears on startup.
All keys are read correctly from the PS/2 keyboard. Even Pause/Break (it is special though: it doesn't have a release code so a press is a press-release and a release is unnoticed).
Not working with HID right now (out of the box): F13-F24 extended function keys. These are great for macro use in apps that support them, as they cannot be accessed (and be pressed) from most keyboards.
This is a MicroPython HID limitation. In micropython-lib/micropython/usb/usb-device-keyboard/usb/device/keyboard.py the _KEYBOARD_REPORT_DESC is using logical and usage maximum of 101. A value of 115 (x73) is needed for extended function keys to work.
How to fix:
- Close Thonny
- Install
mpy-crossandmpremotewithpiporpipx:pipx install mpy-cross mpremote - Optional: Download latest
keyboard.pyfromhttps://github.com/micropython/micropython-lib/blob/master/micropython/usb/usb-device-keyboard/usb/device/keyboard.pyand copy this into the repolibfolder; patch the file: modifyb'\x25\x65'and'\x29\x65'tob'\x25\x73'andb'\x29\x73'respectively, and add F13-F24 keycodes (104-115) - Run this in the repo
libfolder:mpy-cross keyboard.py - Optional: Save original
keyboard.mpy:mpremote cp :lib/usb/device/keyboard.mpy original_keyboard.mpy(you can revert by re-installing the library, see above) - Copy the patched
keyboard.mpy:mpremote cp keyboard.mpy :lib/usb/device/keyboard.mpy - Reboot the Pico.
- Test the extended function keys (remember to change some key definitions first then reboot the Pico):
pipx install hid-toolsthen runhid-recorderand selectMicroPython Board in FS mode
Note: precompiled patched keyboard.mpy is available in the repo lib folder.
Note: on my KDE Ubuntu 25.10 system, extended function keys are interpreted differently (F14 as XF86Launch5, you can check this with xev -event keyboard or evtest). You can try to fix this with assigning the keycode (the number xev showed you) to the key:
e.g. xmodmap -e "keycode 184 = F14" (not tested) or use xkb/xmodmap.
- Add SSD1306 support
- Add onboard menu for changing keymap, macros and providing feedback
- Add EC11 rotary encoder support (for onboard menu navigation)
- Add Rubber ducky functionality, accessible from onboard menu and as macro definitions
- Define keymap in JSON so it is more readable and can be changed from onboard menu
- Add a button/switch to trigger HID only mode (no MicroPython) to increase compatibility
- Add hardware (MOSFET/BJT) and software (is it possible with current PIO? Maybe 2 other control pins for transistors) to control the PS/2 keyboard onboard LEDs (CapsLock, NumLock, ScrollLock)
I accept any help and suggestions.
Disclaimer: this project was created using AI (Claude Sonnet 4.5 and Google Gemini 3 Pro (Preview) in VS Code). This is hobby code, usage of AI speeds up development drastically.