Adding Pimoroni Keybow and Keybow 2040
This commit is contained in:
		
							
								
								
									
										28
									
								
								boards/pimoroni/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								boards/pimoroni/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
# Pimoroni Keybow family
 | 
			
		||||
 | 
			
		||||
A family of macro pads based on raspberry pi hardware:
 | 
			
		||||

 | 
			
		||||
(Original) Keybow - Raspberry Pi hat. 4x3 hotswap keys, with an APA102 LED per key.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										0
									
								
								boards/pimoroni/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								boards/pimoroni/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								boards/pimoroni/keybow/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								boards/pimoroni/keybow/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										24
									
								
								boards/pimoroni/keybow/code.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								boards/pimoroni/keybow/code.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
							
								
								
									
										94
									
								
								boards/pimoroni/keybow/keybow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								boards/pimoroni/keybow/keybow.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										0
									
								
								boards/pimoroni/keybow_2040/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								boards/pimoroni/keybow_2040/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										20
									
								
								boards/pimoroni/keybow_2040/code.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								boards/pimoroni/keybow_2040/code.py
									
									
									
									
									
										Normal 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()
 | 
			
		||||
							
								
								
									
										76
									
								
								boards/pimoroni/keybow_2040/keybow_2040.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								boards/pimoroni/keybow_2040/keybow_2040.py
									
									
									
									
									
										Normal 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)
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user