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