# Commodore Keyboard to USB HID with CircuitPython

## Overview

https://youtu.be/8-WUAa3axDc

In this guide, you'll see how to adapt a Commodore 16 keyboard for use on modern computers using CircuitPython and an Adafruit KB2040 microcontroller board.

The techniques in this guide may also be helpful in converting other classic keyboards that use a key matrix. The `keypad.KeyMatrix` class in CircuitPython makes this task actually quite simple.

The code in this guide is made for the KB2040, which was designed for exactly this kind of project! With adaptation it would work on many other CircuitPython boards, provided they support the KeyMatrix class, USB HID, and (for the advanced version) asyncio.

## Homework

I received this bare keyboard with no documentation. Some internet image searches confirmed it was a Commodore 16 keyboard (the 4 arrow keys on the top row are a dead giveaway). This keyboard is much rarer than the more common Commodore 64 keyboard, which is similar but not 100% compatible.

The keyboard has 60-odd keys (including a physically locking shift key) and a 20-pin connector. Without more documentation or a plan, there are a lot of possible combinations to try.

Luckily, classic computers of this age tended to have good documentation, including schematics. A search for a Commodore 16 schematic [finds the following](https://archive.org/details/SAMS_Computerfacts_Commodore_C16_1984-12_Howard_W_Sams_Co_CC8/page/n9/mode/2up) (screenshot from archive.org):

![](https://cdn-learn.adafruit.com/assets/assets/000/114/877/medium800/circuitpython_Screenshot_2022-09-08_13-37-08.png?1662662733)

From this, you can intuit the following pin numbers on the keyboard connector

- Rows: 6, 16, 1, 13, 11, 12, 8, 19
- Columns: 5, 3, 10, 9, 7, 17, 14, 15, 18
- Unused: 5, 20
- Optional GND: 4
- Orientation key: 2

From the [source code of the Commodore 16's kernal](https://github.com/mist64/cbmsrc/blob/master/KERNAL_TED_04/ed7.src) (sic), I found the following table giving the key that corresponds to each row and column--noting that both "shift" keys as well as "shift lock" activate the same row & column:

```terminal
del   return  £       help    f1      f2      f3      @
3     w       a       4       z       s       e       shift
5     r       d       6       c       f       t       x
7     y       g       8       b       h       u       v
9     i       j       0       m       k       o       n
down  p       l       up      .       :       -       ,
left  *       ;       right   escape  =       +       /
1     home    control 2       space   c=key   q       stop
```

Armed with this information, you can connect your keyboard to a microcontroller. You can use individual jumper wire connections, or build an adapter cable as detailed on the next page of the guide.

## Parts
### Adafruit KB2040 - RP2040 Kee Boar Driver

[Adafruit KB2040 - RP2040 Kee Boar Driver](https://www.adafruit.com/product/5302)
A wild Kee Boar appears! It’s a shiny **KB2040**! An Arduino Pro Micro-shaped board for Keebs with RP2040. (#keeblife 4 evah) A lot of folks like using Adafruit parts for their Keeb builds – but with the ItsyBitsy not being pin-compatible with the Pro Micro pinout, it...

Out of Stock
[Buy Now](https://www.adafruit.com/product/5302)
[Related Guides to the Product](https://learn.adafruit.com/products/5302/guides)
![Angled shot of short black microcontroller.](https://cdn-shop.adafruit.com/640x480/5302-07.jpg)

### Large Single Row Housing Pack for DIY Jumper Cables

[Large Single Row Housing Pack for DIY Jumper Cables](https://www.adafruit.com/product/3146)
Are you frustrated by the lack of customization options for your jumper wires? Look no further!

Compatible with both male and female wires, these fully customizable wire housings bring DIY to a new level, giving you near limitless options when using jumper wires in your...

In Stock
[Buy Now](https://www.adafruit.com/product/3146)
[Related Guides to the Product](https://learn.adafruit.com/products/3146/guides)
![Collection of many Large Single Row Wire Housing Packs](https://cdn-shop.adafruit.com/640x480/3146-00.jpg)

### Premium Male/Female Raw Jumper Wires - 40 x 6"

[Premium Male/Female Raw Jumper Wires - 40 x 6"](https://www.adafruit.com/product/3633)
Our **Raw Male/Female Jumper Wires** are perfect for making a custom jumper wire sets for wire harnesses or jumpering between headers on PCBs. These premium jumpers are a little over 6", almost 8" (about 200mm) long and come in a 'strip' of 40 (5 pieces of the ten rainbow...

In Stock
[Buy Now](https://www.adafruit.com/product/3633)
[Related Guides to the Product](https://learn.adafruit.com/products/3633/guides)
![Angled shot of Premium Male/Female Raw Jumper Wires - 40 x 6](https://cdn-shop.adafruit.com/640x480/3633-02.jpg)

### USB Type A to Type C Cable - approx 1 meter / 3 ft long

[USB Type A to Type C Cable - approx 1 meter / 3 ft long](https://www.adafruit.com/product/4474)
As technology changes and adapts, so does Adafruit. This&nbsp;&nbsp; **USB Type A to Type C** cable will help you with the transition to USB C, even if you're still totin' around a USB Type A hub, computer or laptop.

USB C is the latest industry-standard connector for...

In Stock
[Buy Now](https://www.adafruit.com/product/4474)
[Related Guides to the Product](https://learn.adafruit.com/products/4474/guides)
![Angled shot of a coiled black, USB-C to USB-A cable.](https://cdn-shop.adafruit.com/640x480/4474-02.jpg)

### USB C to USB C Cable - USB 3.1 Gen 4 with E-Mark - 1 meter long

[USB C to USB C Cable - USB 3.1 Gen 4 with E-Mark - 1 meter long](https://www.adafruit.com/product/4199)
As technology changes and adapts, so does Adafruit! Rather than the regular USB A, this cable has&nbsp; **USB C to USB C** &nbsp;plugs!

USB C is the latest industry-standard connector for transmitting data&nbsp;_and_&nbsp;power. Like Lightning and MagSafe cables, USB C...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4199)
[Related Guides to the Product](https://learn.adafruit.com/products/4199/guides)
![USB C to USB C cable. USB 3.1 gen 4 with E-Mark. 1 meter long](https://cdn-shop.adafruit.com/640x480/4199-01.jpg)

# Commodore Keyboard to USB HID with CircuitPython

## Wiring the Commodore 16 Keyboard

The Commodore 16's keyboard contains 64 distinct keys. Logically, they are organized as an 8x8 matrix. This is perfect to use with the `keypad.KeyMatrix` object in CircuitPython.

Warning: 

The keyboard's connector is a .100", 20-pin female header with a key at position 2 and several unused positions.

On the KB2040 side, the connection will be split into 8 pins on one side and 8 pins on the other side.

The following diagram shows the connections to make. Pin 1 of the keyboard connector is at the left, and the 2nd position is the "key", a position that is blocked with plastic and cannot accept a matching pin.

You can either make connections with 16 separate jumper wires or build a cable as discussed below. Since the connectors are pre-crimped this is a really quick way to build a custom wiring adapter!

&nbsp;

![](https://cdn-learn.adafruit.com/assets/assets/000/114/879/medium800/circuitpython_commodorekbwiring_bb.png?1662666840)

Choose the correct jumper wires: At the keyboard end of the cable, you must have **Male** pins. What you have at the other end depends how you're connecting to the KB2040.

- If you want to push the connector onto a male pin header that you've soldered to the KB2040, then the other end needs to be female ( **Male/Female** jumper wires).
- If you want to use a solderless breadboard, then both ends need to be male ( **Male/Male** jumper wires).
- If you want to solder the wires directly to the KB2040, use either Male/Female or Male/Male, just snip the connector off and strip a bit of the insulation from the wire.

Here, I'll describe using pin headers on the KB2040. From the package of housings, select two with eight positions and two with ten positions.

Separate out two sets of 8 wires from bare **Male/Female** jumper wires.

Take one of the 8-position housings and insert the **Female** ends into the housing, one after the the other.

Repeat this with a second 8-position housing.

Now, take a 10-pin housing and one of the finished 8-pin housings. Note the position of the "1" arrow on the 8-pin and 10-pin housings (▲). Starting at Pin 1 on each side, make sequential connections inserting the **male** ends, skipping positions 2, 4, and 5 on the 10-pin housing. Finish the first housing with wire #7.

Insert the last wire (#8) in position #1 of the 2nd housing. Then, again noting the pisition 1 marking (▲) of the 2nd 8-pin housing, insert all 8 pins without any gaps.

The final position on the 2nd housing will be left empty.

Your finished cable should look like this: (In these photos, the two 10-pin connectors have been carefully dabbed with super glue to make a single, solid 20-pin connector.)

![](https://cdn-learn.adafruit.com/assets/assets/000/114/881/medium800/circuitpython_PXL_20220908_182948178.jpg?1662668044)

Now, plug the first connector into the KB2040 with its "pin 1" to the KB2040 "2" and continuing to the end of that side; plug the other connector with its "pin 1" to the opposite corner (labeled "10" on the bottom) continuing to the pin labeled "A3".

Last, plug the two 10-pin connectors into the Commodore 16 keyboard's connector.

![](https://cdn-learn.adafruit.com/assets/assets/000/114/882/medium800/circuitpython_PXL_20220908_182144532.MP.jpg?1662668409)

![](https://cdn-learn.adafruit.com/assets/assets/000/114/883/medium800/circuitpython_PXL_20220908_201914733.jpg?1662668422)

![]( )

# Commodore Keyboard to USB HID with CircuitPython

## Coding the Keyboard

## Download the Project Bundle

Your project will use a specific set of CircuitPython libraries and the&nbsp; **code.py** &nbsp;file. To get everything you need, click on the&nbsp; **Download Project Bundle** &nbsp;link below, and uncompress the .zip file.

Drag the contents of the uncompressed bundle directory onto your board's **CIRCUITPY** &nbsp;drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.

Continue below the full code listing for a walkthrough of the code.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_Commodore_16_KB2040/basic/code.py

The code begins with required imports, then defines the pins that make up the rows and columns of the keyboard matrix:

```python
import board
import keypad
from adafruit_hid.keycode import Keycode as K
from adafruit_hid.keyboard import Keyboard
import usb_hid

rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4]
cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3]
```

Define a keymap. This is a "positional" keymap, in which keys are assigned according to their position, rather than their legend, so the key to the right of "0" acts as "-" rather than as an arrow key.

Because the rows and columns of the matrix have only a loose relationship to the rows of keys on the keyboard, the order of these items may seem very arbitrary.

Here, `K` refers to the `adafruit_hid.keycode.Keycode` object, making it easier to refer to keycodes in short-hand.

```python
keycodes = [
    K.BACKSPACE, K.ENTER, K.LEFT_ARROW, K.F8, K.F1, K.F2, K.F3, K.LEFT_BRACKET,
    K.THREE, K.W, K.A, K.FOUR, K.Z, K.S, K.E, K.LEFT_SHIFT,
    K.FIVE, K.R, K.D, K.SIX, K.C, K.F, K.T, K.X,
    K.SEVEN, K.Y, K.G, K.EIGHT, K.B, K.H, K.U, K.V,
    K.NINE, K.I, K.J, K.ZERO, K.M, K.K, K.O, K.N,
    K.DOWN_ARROW, K.P, K.L, K.UP_ARROW, K.PERIOD, K.SEMICOLON, K.BACKSLASH, K.COMMA,
    K.MINUS, K.WINDOWS, K.QUOTE, K.EQUALS, K.ESCAPE, K.RIGHT_ARROW, K.RIGHT_BRACKET,
    K.FORWARD_SLASH, K.ONE, K.HOME, K.LEFT_CONTROL, K.TWO, K.SPACE, K.ALT, K.Q, K.GRAVE_ACCENT,
]
```

All that's left to do is loop forever, translating key up/down events into their USB HID equivalents:

```python
kbd = Keyboard(usb_hid.devices)

with keypad.KeyMatrix(rows, cols) as keys:
    while True:
        if ev := keys.events.get():
            keycode = keycodes[ev.key_number]
            if ev.pressed:
                kbd.press(keycode)
            else:
                kbd.release(keycode)
```

That's all there is to it! At this point, typing keys on the keyboard will cause corresponding keypresses on your computer.

If some keys aren't working at all, make sure all the wire connections are good, and make sure all the right pin positions are used. If some keys are sending the wrong codes to the computer, trace out the wiring and see if you made an error. If you got two wires flipped, you can correct it in software by swapping them in the `rows` and/or `cols` pin lists.

Continue to the next page for a more complex implementation of the translation to USB HID.

# Commodore Keyboard to USB HID with CircuitPython

## Advanced Keyboard Features

Info: 

## Download the Project Bundle

Your project will use a specific set of CircuitPython libraries and the&nbsp; **code.py** &nbsp;file. To get everything you need, click on the&nbsp; **Download Project Bundle** &nbsp;link below, and uncompress the .zip file.

Drag the contents of the uncompressed bundle directory onto your board's **CIRCUITPY** &nbsp;drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.

Continue below the full code listing for a walkthrough of the code.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_Commodore_16_KB2040/advanced/code.py

The previous example was focused on creating a simple and easy to explain keyboard program. If you want the utmost in configurability, you probably want to use [kmk\_firmware](https://github.com/KMKfw/kmk_firmware), a sophisticated keyboard firmware using CircuitPython (The Adafruit Learning System has [some guides about kmk](https://learn.adafruit.com/search?q=kmk), naturally). As a middle ground, this second code example shows how to implement some advanced keyboard features from scratch. Because the example is a bit long, only key parts will be explained.

## Asyncio keyboard

A special adapter class, AsyncEventQueue, makes it possible to `await` the press of a key:

```auto
class AsyncEventQueue:
    def __init__(self, events):
        self._events = events

    async def __await__(self):
        await asyncio.core._io_queue.queue_read(self._events)
        return self._events.get()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass
```

To use it, the "keyboard main loop" becomes a function defined with `async def`:

```auto
async def key_task():
    # Initialize Keyboard
    kbd = Keyboard(usb_hid.devices)

    with keypad.KeyMatrix(rows, cols) as keys, AsyncEventQueue(keys.events) as q:
        while True:
            ev = await q
```

Alongside this key task you could run any other "forever task" you want. In this example the other task does nothing, but for instance it could run a LED animation, or whatever your heart desires (as long as it also calls await asyncio.sleep or otherwise yields control back to the key task at regular intervals). There is [a guide about asyncio if you want to know more](https://learn.adafruit.com/cooperative-multitasking-in-circuitpython-with-asyncio).

## Fn modifier key

The Commodore keyboard lacks some keys that are now standard on computer keyboards, like F4 through F12. A common workaround is to add an "Fn" key; in more advanced systems this can become an entire system of "layers". In this case, the key labeled "CLEAR HOME" is assigned to be the Fn modifier key. There is also a mapping that controls how key presses are affected when the Fn modifier is in effect:

```python
# Implement an FN-key for some keys not present on the default keyboard
class FnState:
    def __init__(self):
        self.state = False

    def fn_event(self, event):
        self.state = event.pressed

    def fn_modify(self, keycode):
        if self.state:
            return self.mods.get(keycode, keycode)
        return keycode

    mods = {
            K.ONE: K.F1,
            K.TWO: K.F2,
            ...
    }
    
 
fn_state = FnState()

K_FN = fn_state.fn_event
```

Further down, in the `forever` loop, code is added to allow `fn_state` to do its work:

```auto
...
                if callable(keycode): # note: K_FN is callable
                    keycode = keycode(ev)
                keycode = fn_state.fn_modify(keycode)
                if keycode is None:
                    continue
```

## Avoiding ghost keys

The Comomdore keyboard does not have diodes, so when more than two keys are pressed it's possible for additional "ghost" keys to appear. For example, pressing "w" + "a" + "r" causes a ghost "d" to appear.

The `XKROFilter` class can track how many keys are pressed, and prevent any ghosts from appearing. However, it also prevents some "safe" combinations of more than two keys from being pressed together. An example safe combination that is prevented is "z" + "f" + "u", since these keys don't share rows or columns.

```auto
class XKROFilter:
    """Perform an X-key rollover algorithm, blocking ghosts if more than X keys are pressed at once
A key matrix without diodes can support 2-key rollover.
    """
    def __init__(self, rollover=2):
        self._count = 0
        self._rollover = rollover
        self._real = [0] * 64
        self._ghost = [0] * 64

    def __call__(self, event):
        self._ghost[event.key_number] = event.pressed
        if event.pressed:
            if self._count &lt; self._rollover:
                self._real[event.key_number] = True
                yield event
            self._count += 1
        else:
            self._real[event.key_number] = False
            yield event
            self._count -= 1

twokey_filter = XKROFilter(2)
```

Later, in the `key` loop, the `twokey_filter` object is used:

```auto
...
            for ev in twokey_filter(ev):
```

## Logical Keyboard Mapping

The Commodore keyboard has some unusual assignments, like placing "@" on an unshifted key, the double-quote character on the shifted "2" key and the single-quote character on the shifted "7" key. To support this, two things are required:

- The action taken while "shift+key" is pressed must (sometimes) be different than key along
- The action taken has to be able to do things like temporarily hold or release the shift modifier as seen by the host computer

One shortcoming of the implementation in this code is that a special shifted key does NOT repeat like normal keys.

For this program, when a key map entry is a tuple, it will be treated specially as described in the code comment:

```auto
# A tuple is special, it:
# * Clears shift modifiers &amp; pressed keys
# * Presses the given sequence
# * Releases all pressed keys
# * Restores the original modifiers
# It's mostly used to send a key that requires a shift keypress on a standard
# keyboard (or which is mapped to a shifted key but requires that shift NOT
# be pressed)
#
# A consequence of this is that the key will not repeat, even if it is held
# down.  So for example in the positional mapping, shift-1 will repeat "!"
# but shift-7 will not repeat "'" and shift-0 will not repeat "^".
K_AT = (K.SHIFT, K.TWO)
...
```

As usual, this requires a change in how raw key presses are handled, down in the `forever` loop:

```auto
...
                if isinstance(keycode, tuple):
                    if ev.pressed:
                        kbd.report_modifier[0] = old_report_modifier &amp; ~MASK_ANY_SHIFT
                        kbd.press(*keycode)
                        kbd.release_all()
                        kbd.report_modifier[0] = old_report_modifier
```

This handles the case of the "@" key and a few others. An additional map of "keys that are different with shift pressed" does the rest:

```auto
...
    shifted = {
            K.TWO: (K.SHIFT, K.QUOTE),  # double quote
            K.SIX: (K.SHIFT, K.SEVEN),  # ampersand
            ...
    }
```

The main loop code to use the shifted key map:

```auto
...
                if shift_pressed:
                    keycode = shifted.get(keycode, keycode)
```

The positional keymap is enabled by default. If you prefer a more logical/PC-style layout, set the variable to False:

```auto
# True to use a more POSITIONAL mapping, False to use a more PC-style mapping
POSITIONAL = True
```

# Commodore Keyboard to USB HID with CircuitPython

## Key Matrix Whisperer

If you have an unknown key matrix, the Key Matrix Whisperer can help. Hook up all the possible connections, load the key whisperer, open the REPL, and press keys one by one until you get a plausible list of row and column pins.

## Download the Project Bundle

Your project will use a specific set of CircuitPython libraries and the&nbsp; **code.py** &nbsp;file. To get everything you need, click on the&nbsp; **Download Project Bundle** &nbsp;link below, and uncompress the .zip file.

Drag the contents of the uncompressed bundle directory onto your board's **CIRCUITPY** &nbsp;drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.

Continue below the full code listing for more information on using the Key Matrix Whisperer.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_Commodore_16_KB2040/matrixwhisperer/code.py

Typical output in the REPL:

```terminal
code.py output:
Press keys now
Key registered. Release to continue
Rows 1 D5
Cols 1 A0
Last pressed D5 A0

Key registered. Release to continue
Rows 1 D5
Cols 1 A0
Last pressed A2 D4
...
Key registered. Release to continue
Rows 8 A1 A2 CLK D3 D5 D7 D8 MISO
Cols 8 A0 A3 D10 D2 D4 D6 D9 MOSI
Last pressed D5 D10
```

Note how the rows and cols lists are a rearrangement (permutation) of the rows and cols from the real keyboard programs (SCK and CLK are two names for the same pin):

```auto
rows = [board.A3, board.D6, board.D10, board.D9, board.MOSI, board.D2, board.A0, board.D4]
cols = [board.A2, board.SCK, board.MISO, board.A1, board.D5, board.D7, board.D8, board.D3]
```


## Featured Products

### Adafruit KB2040 - RP2040 Kee Boar Driver

[Adafruit KB2040 - RP2040 Kee Boar Driver](https://www.adafruit.com/product/5302)
A wild Kee Boar appears! It’s a shiny **KB2040**! An Arduino Pro Micro-shaped board for Keebs with RP2040. (#keeblife 4 evah) A lot of folks like using Adafruit parts for their Keeb builds – but with the ItsyBitsy not being pin-compatible with the Pro Micro pinout, it...

Out of Stock
[Buy Now](https://www.adafruit.com/product/5302)
[Related Guides to the Product](https://learn.adafruit.com/products/5302/guides)
### Large Single Row Housing Pack for DIY Jumper Cables

[Large Single Row Housing Pack for DIY Jumper Cables](https://www.adafruit.com/product/3146)
Are you frustrated by the lack of customization options for your jumper wires? Look no further!

Compatible with both male and female wires, these fully customizable wire housings bring DIY to a new level, giving you near limitless options when using jumper wires in your...

In Stock
[Buy Now](https://www.adafruit.com/product/3146)
[Related Guides to the Product](https://learn.adafruit.com/products/3146/guides)
### Premium Male/Female Raw Jumper Wires - 40 x 6"

[Premium Male/Female Raw Jumper Wires - 40 x 6"](https://www.adafruit.com/product/3633)
Our **Raw Male/Female Jumper Wires** are perfect for making a custom jumper wire sets for wire harnesses or jumpering between headers on PCBs. These premium jumpers are a little over 6", almost 8" (about 200mm) long and come in a 'strip' of 40 (5 pieces of the ten rainbow...

In Stock
[Buy Now](https://www.adafruit.com/product/3633)
[Related Guides to the Product](https://learn.adafruit.com/products/3633/guides)
### USB Type A to Type C Cable - approx 1 meter / 3 ft long

[USB Type A to Type C Cable - approx 1 meter / 3 ft long](https://www.adafruit.com/product/4474)
As technology changes and adapts, so does Adafruit. This&nbsp;&nbsp; **USB Type A to Type C** cable will help you with the transition to USB C, even if you're still totin' around a USB Type A hub, computer or laptop.

USB C is the latest industry-standard connector for...

In Stock
[Buy Now](https://www.adafruit.com/product/4474)
[Related Guides to the Product](https://learn.adafruit.com/products/4474/guides)
### USB C to USB C Cable - USB 3.1 Gen 4 with E-Mark - 1 meter long

[USB C to USB C Cable - USB 3.1 Gen 4 with E-Mark - 1 meter long](https://www.adafruit.com/product/4199)
As technology changes and adapts, so does Adafruit! Rather than the regular USB A, this cable has&nbsp; **USB C to USB C** &nbsp;plugs!

USB C is the latest industry-standard connector for transmitting data&nbsp;_and_&nbsp;power. Like Lightning and MagSafe cables, USB C...

Out of Stock
[Buy Now](https://www.adafruit.com/product/4199)
[Related Guides to the Product](https://learn.adafruit.com/products/4199/guides)

## Related Guides

- [Adafruit KB2040](https://learn.adafruit.com/adafruit-kb2040.md)
- [Planetary Gear Dreidels](https://learn.adafruit.com/planetary-gear-dreidels.md)
- [16-Step Drum Sequencer](https://learn.adafruit.com/16-step-drum-sequencer.md)
- [Stepper Motor Turntable](https://learn.adafruit.com/stepper-motor-turntable.md)
- [Big Key Switches Macro Pad](https://learn.adafruit.com/big-key-switch.md)
- [Rotary Phone Dial Keypad](https://learn.adafruit.com/rotary-phone-dial-keypad.md)
- [Using QMK on RP2040 Microcontrollers](https://learn.adafruit.com/using-qmk-on-rp2040-microcontrollers.md)
- [PB Gherkin 30% keyboard with KMK, CircuitPython, & KB2040](https://learn.adafruit.com/pb-gherkhin-30-keyboard-with-kmk-circuitpython-kb2040.md)
- [USB MIDI Keyset Controller](https://learn.adafruit.com/midi-keyset.md)
- [Navi10 MacroPad with KB2040 and KMK CircuitPython keyboard firmware](https://learn.adafruit.com/navi10-macropad-with-kb2040-and-kmk-circuitpython-keyboard-firmware.md)
- [Arcade Fightstick](https://learn.adafruit.com/arcade-fightstick.md)
- [Fisher-Price USB Foot Pedal](https://learn.adafruit.com/fisher-price-usb-foot-pedal.md)
- [4x12 Ortho Mechanical Keyboard](https://learn.adafruit.com/4x12-ortho-mechanical-keyboard.md)
- [Fisher-Price USB Controller](https://learn.adafruit.com/fisher-price-usb-controller.md)
- [May Pad Macropad with the KB2040, KMK, and CircuitPython](https://learn.adafruit.com/maypad-macropad-with-the-kb2040-kmk-and-circuitpython.md)
