diff --git a/boards/pimoroni/README.md b/boards/pimoroni/README.md new file mode 100644 index 0000000..97dfa7a --- /dev/null +++ b/boards/pimoroni/README.md @@ -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 diff --git a/boards/pimoroni/__init__.py b/boards/pimoroni/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boards/pimoroni/keybow/__init__.py b/boards/pimoroni/keybow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boards/pimoroni/keybow/code.py b/boards/pimoroni/keybow/code.py new file mode 100644 index 0000000..c416005 --- /dev/null +++ b/boards/pimoroni/keybow/code.py @@ -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() diff --git a/boards/pimoroni/keybow/keybow.py b/boards/pimoroni/keybow/keybow.py new file mode 100644 index 0000000..202e85b --- /dev/null +++ b/boards/pimoroni/keybow/keybow.py @@ -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) diff --git a/boards/pimoroni/keybow_2040/__init__.py b/boards/pimoroni/keybow_2040/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boards/pimoroni/keybow_2040/code.py b/boards/pimoroni/keybow_2040/code.py new file mode 100644 index 0000000..a5fe5f5 --- /dev/null +++ b/boards/pimoroni/keybow_2040/code.py @@ -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() diff --git a/boards/pimoroni/keybow_2040/keybow_2040.py b/boards/pimoroni/keybow_2040/keybow_2040.py new file mode 100644 index 0000000..fd8d0a9 --- /dev/null +++ b/boards/pimoroni/keybow_2040/keybow_2040.py @@ -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) diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index 4648710..4886bf8 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -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 diff --git a/kmk/scanners/__init__.py b/kmk/scanners/__init__.py index 348bda8..5d83f77 100644 --- a/kmk/scanners/__init__.py +++ b/kmk/scanners/__init__.py @@ -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. diff --git a/kmk/scanners/native_keypad_scanner.py b/kmk/scanners/native_keypad_scanner.py index 62ca664..45930f7 100644 --- a/kmk/scanners/native_keypad_scanner.py +++ b/kmk/scanners/native_keypad_scanner.py @@ -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()