Adding Pimoroni Keybow and Keybow 2040

This commit is contained in:
Ellie T 2022-02-21 12:34:44 +11:00 committed by Kyle Brown
parent ef7b29bd43
commit 2fce09986e
11 changed files with 254 additions and 4 deletions

28
boards/pimoroni/README.md Normal file
View File

@ -0,0 +1,28 @@
# Pimoroni Keybow family
A family of macro pads based on raspberry pi hardware:
![Keybow](image url for keybow)
(Original) Keybow - Raspberry Pi hat. 4x3 hotswap keys, with an APA102 LED per key.
![Keybow 2040](image url for keybow 2040)
Keybow 2040 - custom RP2040 board. 4x4 hotswap keys, with an RGB LED per key driven by a shared IS31FL3731 controller.
These boards share the 'feature' of using a single GPIO per key rather than a row and column matrix, so these both
use CircuitPython's `keypad.Keys` module instead of the regular KMK matrix scanner.
## Retailers
### UK
- Pimoroni
- [Keybow](https://shop.pimoroni.com/products/keybow)
- [Keybow 2040](https://shop.pimoroni.com/products/keybow-2040)
### AU
- Core Electronics
- [Keybow](https://core-electronics.com.au/pimoroni-keybow-mini-mechanical-keyboard-kit-clicky-keys.html)
- [Keybow 2040](https://core-electronics.com.au/pimoroni-keybow-2040-tactile-keys.html)
Extensions enabled by default
- [Layers](https://github.com/KMKfw/kmk_firmware/tree/master/docs/layers.md) Need more keys than switches? Use layers.
- [RGB](https://github.com/KMKfw/kmk_firmware/tree/master/docs/rgb.md) Light it up (Keybow only so far)
- [MediaKeys](https://github.com/KMKfw/kmk_firmware/tree/master/docs/media_keys.md) Control volume and other media functions

View File

View File

View File

@ -0,0 +1,24 @@
from keybow import Keybow
from kmk.extensions.media_keys import MediaKeys
from kmk.keys import KC
from kmk.modules.layers import Layers
keybow = Keybow()
# fmt: off
keybow.keymap = [
[
KC.A, KC.B, KC.C,
KC.E, KC.F, KC.G,
KC.I, KC.J, KC.K,
KC.M, KC.N, KC.O,
]
]
keybow.extensions.extend([MediaKeys()])
keybow.modules.extend([Layers()])
# fmt: on
if __name__ == '__main__':
keybow.go()

View File

@ -0,0 +1,94 @@
'''
KMK keyboard for Pimoroni Keybow.
WARNING: This doesn't currently function correctly on the Raspberry Pi Zero,
some of the keys are stuck in the 'pressed' position. There's either a bug in
the keypad implementation on the rpi0, or the pin numbers don't match the pins
in linux.
This is a 4x3 macro pad designed to fit the rpi's GPIO connector. Each key is
attached to a single GPIO and has an APA102 LED mounted underneath it.
The layout of the board is as follows (GPIO connector on the left):
R0 | D20 D6 D22
R1 | D17 D16 D12
R2 | D24 D27 D26
R0 | D13 D5 D23
------------------
C0 C1 C2
This board also functions with an adaptor (see
https://learn.adafruit.com/itsybitsy-keybow-mechanical-keypad/) to work with an
itsybitsy in place of the rpi, which uses an alternate pin mapping:
R0 | A2 A1 A0
R1 | A5 A4 A3
R2 | D10 D9 D7
R3 | D11 D12 D2
------------------
C0 C1 C2
This keyboard file should automatically select the correct mapping at runtime.
'''
import board
import adafruit_dotstar
import sys
from kmk.extensions.rgb import RGB, AnimationModes
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners.native_keypad_scanner import keys_scanner
# fmt: off
def raspi_pins():
return [
[board.D20, board.D16, board.D26],
[board.D6, board.D12, board.D13],
[board.D22, board.D24, board.D5],
[board.D17, board.D27, board.D23],
]
def itsybitsy_pins():
return [
[board.D11, board.D12, board.D2],
[board.D10, board.D9, board.D7],
[board.A5, board.A4, board.A3],
[board.A2, board.A1, board.A0],
]
# fmt: on
def isPi():
return sys.platform == 'BROADCOM'
if isPi():
_KEY_CFG = raspi_pins()
_LED_PINS = (board.SCK, board.MOSI)
else:
_KEY_CFG = itsybitsy_pins()
_LED_PINS = (board.SCK, board.MOSI)
led_strip = adafruit_dotstar.DotStar(_LED_PINS[0], _LED_PINS[1], 12)
rgb_ext = RGB(
pixel_pin=0,
pixels=led_strip,
num_pixels=12,
animation_mode=AnimationModes.BREATHING_RAINBOW,
)
class Keybow(KMKKeyboard):
'''
Default keyboard config for the Keybow.
'''
extensions = [rgb_ext]
def __init__(self):
self.matrix = keys_scanner(_KEY_CFG)

View File

View File

@ -0,0 +1,20 @@
from keybow_2040 import Keybow2040
from kmk.keys import KC
keybow = Keybow2040()
# fmt: off
keybow.keymap = [
[
KC.A, KC.B, KC.C, KC.D,
KC.E, KC.F, KC.G, KC.H,
KC.I, KC.J, KC.K, KC.L,
KC.M, KC.N, KC.O, KC.P,
KC.Q
]
]
# fmt: on
if __name__ == '__main__':
keybow.go()

View File

@ -0,0 +1,76 @@
'''
KMK keyboard for Pimoroni Keybow 2040.
This is a 4x4 macro pad based on the RP2040. Each key is attached to a single
GPIO, so the KMK matrix scanner needs to be overridden. Additionally, each
key has an RGB LED controlled by an IS31FL3731 controller which is incompatible
with the default RGB module.
The layout of the board is as follows:
[RESET] [USB-C] [BOOT]
R0 | SW3 SW7 SW11 SW15
R1 | SW2 SW6 SW10 SW14
R2 | SW1 SW5 SW9 SW13
R3 | SW0 SW4 SW8 SW12
-----------------------------
C0 C1 C2 C3
The binding defined in the _KEY_CFG array binds the switches to keys such that
the keymap can be written in a way that lines up with the natural order of the
key switches, then adds [BOOT] in (4,0). [RESET] can't be mapped as a key.
'''
import board
from adafruit_is31fl3731.keybow2040 import Keybow2040 as KeybowLeds
from adafruit_pixelbuf import PixelBuf
# from kmk.extensions.rgb import RGB
from kmk.kmk_keyboard import KMKKeyboard
from kmk.scanners.native_keypad_scanner import keys_scanner
# fmt: off
_KEY_CFG = [
[board.SW3, board.SW7, board.SW11, board.SW15],
[board.SW2, board.SW6, board.SW10, board.SW14],
[board.SW1, board.SW5, board.SW9, board.SW13],
[board.SW0, board.SW4, board.SW8, board.SW12],
[board.USER_SW],
]
# fmt: on
class Keybow2040Leds(PixelBuf):
'''
Minimal PixelBuf wrapper for the Keybow 2040's LED array.
NOTE: Currently broken.
'''
def __init__(self, size: int):
self.leds = KeybowLeds(board.I2C)
super().__init__(size, byteorder='RGB')
def _transmit(self, buffer):
for pixel in range(self._pixels):
r = buffer[pixel * 3 + 0]
g = buffer[pixel * 3 + 1]
b = buffer[pixel * 3 + 2]
self.leds.pixel(pixel // 4, pixel % 4, (r, g, b))
# rgb_ext = RGB(0, pixels=Keybow2040Leds(16), num_pixels=16)
class Keybow2040(KMKKeyboard):
'''
Default keyboard config for the Keybow2040.
TODO: Map the LEDs as well.
'''
# extensions = [rgb_ext]
def __init__(self):
self.matrix = keys_scanner(_KEY_CFG)

View File

@ -244,12 +244,15 @@ class KMKKeyboard:
Ensure the provided configuration is *probably* bootable
'''
assert self.keymap, 'must define a keymap with at least one row'
assert self.row_pins, 'no GPIO pins defined for matrix rows'
assert self.col_pins, 'no GPIO pins defined for matrix columns'
assert self.diode_orientation is not None, 'diode orientation must be defined'
assert (
self.hid_type in HIDModes.ALL_MODES
), 'hid_type must be a value from kmk.consts.HIDModes'
if not self.matrix:
assert self.row_pins, 'no GPIO pins defined for matrix rows'
assert self.col_pins, 'no GPIO pins defined for matrix columns'
assert (
self.diode_orientation is not None
), 'diode orientation must be defined'
return self
@ -262,6 +265,9 @@ class KMKKeyboard:
To save RAM on boards that don't use Split, we don't import Split
and do an isinstance check, but instead do string detection
'''
if self.matrix and self.matrix.coord_mapping:
self.coord_mapping = self.matrix.coord_mapping
if any(x.__class__.__module__ == 'kmk.modules.split' for x in self.modules):
return

View File

@ -3,6 +3,9 @@ class Scanner:
Base class for scanners.
'''
def __init__(self):
self.coord_mapping = None
def scan_for_changes(self):
'''
Scan for key events and return a key report if an event exists.

View File

@ -15,7 +15,6 @@ class NativeKeypadScanner(Scanner):
def __init__(self, pin_map, kp):
self.pin_map = pin_map
self.keypad = kp
# self.coord_mapping = [ic(row, col) for (row, col) in self.pin_map]
self.coord_mapping = list(range(len(pin_map)))
self.curr_event = keypad.Event()