├── terminal.sh ├── src ├── input │ ├── InputDevice.cpp │ ├── InputDevice.h │ ├── MatrixInput.h │ └── MatrixInput.cpp ├── wire │ ├── WireHandler.cpp │ ├── USBWireHandler.h │ ├── SerialWireHandler.h │ ├── SerialWireHandler.cpp │ ├── WireHandler.h │ ├── USBMIDIHandler.h │ ├── TinyUSBWireHandler.h │ ├── I2CWireHandler.h │ ├── USBWireHandler.cpp │ ├── TinyUSBWireHandler.cpp │ ├── USBMIDIHandler.cpp │ └── I2CWireHandler.cpp ├── boards │ ├── teensy-65 │ │ ├── board.h │ │ └── board.cpp │ └── split-pico │ │ ├── board.h │ │ └── board.cpp ├── plugin │ ├── FnPlugin.h │ ├── PicoRebootPlugin.h │ ├── KeyPlugin.h │ ├── PicoRebootPlugin.cpp │ ├── FnPlugin.cpp │ ├── SticKeyPlugin.h │ ├── TapHoldPlugin.h │ ├── KeyPlugin.cpp │ ├── MacroPlugin.h │ ├── SticKeyPlugin.cpp │ ├── MacroPlugin.cpp │ └── TapHoldPlugin.cpp ├── InputEventPool.h ├── InputEvent.h ├── InputEvent.cpp ├── InputEventPool.cpp ├── usb_descriptors.h ├── KeyboardState.h ├── tusb_config.h ├── main.cpp ├── KeyDefines.h ├── KeyboardState.cpp └── usb_descriptors.c ├── deploy.sh ├── left.sh ├── platformio.ini ├── CMakeLists.txt ├── pico_sdk_import.cmake ├── README.md └── LICENSE /terminal.sh: -------------------------------------------------------------------------------- 1 | minicom -b 115200 -o -D /dev/ttyACM0 2 | -------------------------------------------------------------------------------- /src/input/InputDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "InputDevice.h" 2 | 3 | InputDevice::InputDevice() { 4 | } 5 | 6 | bool InputDevice::scan(KeyboardState *keyboardState) { 7 | return true; 8 | } 9 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | stty -F /dev/ttyACM0 1200 3 | echo waiting 4 | while [ ! -d /media/daire/RPI-RP2 ]; do sleep 0.1; done 5 | sleep 0.5 6 | echo copying 7 | cp $1 /media/daire/RPI-RP2 8 | echo done 9 | -------------------------------------------------------------------------------- /left.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo waiting 3 | while [ ! -d /media/daire/RPI-RP2 ]; do sleep 0.1; done 4 | sleep 0.5 5 | echo copying 6 | cp compile/pico-firmware-left.uf2 /media/daire/RPI-RP2 7 | echo done 8 | -------------------------------------------------------------------------------- /src/wire/WireHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "WireHandler.h" 2 | 3 | WireHandler::WireHandler() { 4 | } 5 | 6 | bool WireHandler::inputEvent(InputEvent* event, KeyboardState* kbState){ 7 | return true; 8 | } 9 | -------------------------------------------------------------------------------- /src/input/InputDevice.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_DEVICE 2 | #define INPUT_DEVICE 3 | 4 | #include "../KeyboardState.h" 5 | 6 | class InputDevice { 7 | public: 8 | InputDevice(); 9 | virtual bool scan(KeyboardState* keyboardState); 10 | 11 | }; 12 | 13 | #endif -------------------------------------------------------------------------------- /src/wire/USBWireHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef USB_WIRE_HANDLER 2 | #define USB_WIRE_HANDLER 3 | #include "WireHandler.h" 4 | 5 | class USBWireHandler : public WireHandler { 6 | public: 7 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 8 | }; 9 | 10 | #endif -------------------------------------------------------------------------------- /src/wire/SerialWireHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIAL_WIRE_HANDLER 2 | #define SERIAL_WIRE_HANDLER 3 | #include "WireHandler.h" 4 | 5 | class SerialWireHandler : public WireHandler { 6 | public: 7 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 8 | }; 9 | 10 | #endif -------------------------------------------------------------------------------- /src/boards/teensy-65/board.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_CONFIG 2 | #define BOARD_CONFIG 3 | 4 | #include "../../KeyDefines.h" 5 | 6 | #define NUM_KEYMAPS 2 7 | #define NUM_ROWS 5 8 | #define NUM_COLS 16 9 | 10 | #define NUM_INPUT_DEVICES 1 11 | #define NUM_KEYPLUGINS 2 12 | #define NUM_WIREHANDLERS 2 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/plugin/FnPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FN_PLUGIN 2 | #define FN_PLUGIN 3 | #include "KeyPlugin.h" 4 | 5 | class FnPlugin:public KeyPlugin { 6 | public : 7 | FnPlugin(); 8 | FnPlugin(uint8_t *scanCodes, int nCodes); 9 | virtual bool inputEvent(InputEvent* event, KeyboardState* kbState); 10 | }; 11 | 12 | #endif -------------------------------------------------------------------------------- /src/wire/SerialWireHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "../KeyDefines.h" 2 | #include "WireHandler.h" 3 | #include "SerialWireHandler.h" 4 | 5 | bool SerialWireHandler::inputEvent(InputEvent* event, KeyboardState* kbState) { 6 | Serial.print(event->state == KEY_PRESSED ? "PRESSED ":"RELEASED "); 7 | Serial.println(event->scancode); 8 | return true; 9 | } -------------------------------------------------------------------------------- /src/wire/WireHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef WIRE_HANDLER 2 | #define WIRE_HANDLER 3 | #include "../InputEvent.h" 4 | #include "../KeyboardState.h" 5 | class KeyboardState; 6 | 7 | class WireHandler { 8 | public : 9 | WireHandler(); 10 | virtual bool inputEvent(InputEvent * event, KeyboardState* kbState); 11 | }; 12 | 13 | 14 | #endif -------------------------------------------------------------------------------- /src/input/MatrixInput.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX_INPUT 2 | #define MATRIX_INPUT 3 | #include "../KeyboardState.h" 4 | #include "../InputEvent.h" 5 | #include "InputDevice.h" 6 | #include "board.h" 7 | 8 | class MatrixInput: public InputDevice { 9 | public: 10 | MatrixInput(); 11 | virtual bool scan(KeyboardState* keyboardState); 12 | }; 13 | 14 | #endif -------------------------------------------------------------------------------- /src/plugin/PicoRebootPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef PICO_REBOOT_PLUGIN 2 | #define PICO_REBOOT_PLUGIN 3 | 4 | #if defined(PICO) 5 | 6 | #include "KeyPlugin.h" 7 | 8 | class PicoRebootPlugin:public KeyPlugin { 9 | public : 10 | PicoRebootPlugin(); 11 | PicoRebootPlugin(uint8_t *scanCodes, int nCodes); 12 | virtual bool inputEvent(InputEvent* event, KeyboardState* kbState); 13 | }; 14 | #endif 15 | #endif -------------------------------------------------------------------------------- /src/InputEventPool.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_EVENT_POOL 2 | #define INPUT_EVENT_POOL 3 | 4 | #include "InputEvent.h" 5 | 6 | #define INPUT_EVENT_POOL_SIZE 20 7 | 8 | class InputEventPool { 9 | public: 10 | InputEventPool(); 11 | InputEvent* getInputEvent(InputEventType type); 12 | InputEvent* getInputEvent(InputEvent event); 13 | 14 | private: 15 | InputEvent inputEvents[INPUT_EVENT_POOL_SIZE]; 16 | }; 17 | #endif -------------------------------------------------------------------------------- /src/plugin/KeyPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef KEY_PLUGIN 2 | #define KEY_PLUGIN 3 | #include "../KeyboardState.h" 4 | 5 | class KeyboardState; 6 | 7 | class KeyPlugin { 8 | public : 9 | KeyPlugin(); 10 | KeyPlugin(uint8_t *scanCodes, int nCodes); 11 | bool isKey(uint8_t scanCode); 12 | virtual bool inputEvent(InputEvent* event, KeyboardState* kbState); 13 | uint8_t *scanCodes; 14 | int nCodes; 15 | }; 16 | 17 | 18 | #endif -------------------------------------------------------------------------------- /src/plugin/PicoRebootPlugin.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(PICO) 3 | #include "PicoRebootPlugin.h" 4 | #include "KeyDefines.h" 5 | #include "pico/bootrom.h" 6 | 7 | PicoRebootPlugin::PicoRebootPlugin(uint8_t *scanCodes, int nCodes):KeyPlugin(scanCodes, nCodes){ 8 | } 9 | 10 | bool PicoRebootPlugin::inputEvent(InputEvent* event, KeyboardState* kbState){ 11 | if(isKey(event->scancode)) { 12 | reset_usb_boot(0, 0); 13 | } 14 | return true; 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /src/plugin/FnPlugin.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "FnPlugin.h" 3 | #include "KeyDefines.h" 4 | 5 | 6 | FnPlugin::FnPlugin(uint8_t *scanCodes, int nCodes):KeyPlugin(scanCodes, nCodes){ 7 | } 8 | 9 | bool FnPlugin::inputEvent(InputEvent* event, KeyboardState* kbState){ 10 | if(isKey(event->scancode)) { 11 | if(event->state == KEY_PRESSED) { 12 | kbState->raise(); 13 | } else { 14 | kbState->lower(); 15 | } 16 | } 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /src/plugin/SticKeyPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef STICKEY_PLUGIN 2 | #define STICKEY_PLUGIN 3 | #include "KeyPlugin.h" 4 | 5 | class SticKeyPlugin : public KeyPlugin { 6 | public : 7 | SticKeyPlugin(uint8_t *scanCodes, int nCodes); 8 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 9 | 10 | uint8_t lastKeyPressed = KC_NONE; 11 | uint8_t lastKeyClicked = KC_NONE; 12 | uint32_t lastKeyPressedTs = 0; 13 | uint32_t lastKeyClickedTs = 0; 14 | 15 | }; 16 | 17 | #endif -------------------------------------------------------------------------------- /src/boards/split-pico/board.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_CONFIG 2 | #define BOARD_CONFIG 3 | 4 | #include "../../KeyDefines.h" 5 | 6 | #if defined(LEFT_HAND_SIDE) 7 | #define NUM_ROWS 5 8 | #define NUM_COLS 9 9 | #elif defined(RIGHT_HAND_SIDE) 10 | #define NUM_ROWS 5 11 | #define NUM_COLS 9 12 | #endif 13 | 14 | #define NUM_KEYMAPS 2 15 | #define NUM_INPUT_DEVICES 2 16 | #define NUM_KEYPLUGINS 2 17 | 18 | #if defined(PRIMARY) 19 | #define NUM_WIREHANDLERS 2 20 | #else // secondary 21 | #define NUM_WIREHANDLERS 1 22 | #endif 23 | 24 | #endif -------------------------------------------------------------------------------- /src/wire/USBMIDIHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef USB_MIDI_HANDLER 2 | #define USB_MIDI_HANDLER 3 | #include "WireHandler.h" 4 | 5 | enum MIDIMode {DISABLED,JANKO,SONOME,CHROMATIC}; 6 | 7 | class USBMIDIHandler : public WireHandler, public KeyPlugin { 8 | public: 9 | USBMIDIHandler (uint8_t *scanCodes, int nCodes); 10 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 11 | private: 12 | uint8_t getNote(MIDIMode mode, int scancode); 13 | MIDIMode mode = DISABLED; 14 | }; 15 | 16 | #endif -------------------------------------------------------------------------------- /src/plugin/TapHoldPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef TAPHOLD_PLUGIN 2 | #define TAPHOLD_PLUGIN 3 | #include "KeyPlugin.h" 4 | 5 | enum TapHoldState {WAITING, PRESSED, HOLDING}; 6 | 7 | class TapHoldPlugin : public KeyPlugin { 8 | public : 9 | TapHoldPlugin(uint8_t* scanCodes, uint8_t* holdCodes, int nCodes); 10 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 11 | 12 | uint8_t holdScanCode = KC_T; 13 | uint32_t pressedTS; 14 | uint8_t pressedScanCode = KC_NONE; 15 | 16 | TapHoldState state = WAITING; 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /src/plugin/KeyPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "KeyPlugin.h" 2 | 3 | KeyPlugin::KeyPlugin() { 4 | nCodes = 0; 5 | } 6 | 7 | KeyPlugin::KeyPlugin(uint8_t *scanCodes, int nCodes) { 8 | this->scanCodes = scanCodes; 9 | this->nCodes = nCodes; 10 | } 11 | 12 | bool KeyPlugin::isKey(uint8_t scanCode) { 13 | for (int i = 0; i < nCodes; i++) { 14 | if(scanCodes[i] == scanCode) { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | bool KeyPlugin::inputEvent(InputEvent* event, KeyboardState* kbState){ 21 | return true; 22 | } -------------------------------------------------------------------------------- /src/wire/TinyUSBWireHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef TINY_USB_WIRE_HANDLER 2 | #define TINY_USB_WIRE_HANDLER 3 | #include "WireHandler.h" 4 | #include "tusb.h" 5 | 6 | class TinyUSBWireHandler : public WireHandler { 7 | public: 8 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 9 | private: 10 | void addKey(unsigned char scanCode); 11 | void removeKey(unsigned char scanCode); 12 | void setModifier(unsigned char modifier, unsigned char state); 13 | unsigned char keys[6] = {0}; 14 | unsigned char modifiers = 0; 15 | bool kbStateDirty = false; 16 | }; 17 | 18 | #endif -------------------------------------------------------------------------------- /src/InputEvent.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_EVENT 2 | #define INPUT_EVENT 3 | 4 | #if defined(PICO) 5 | #include "pico/stdlib.h" 6 | #elif defined(TEENSY) 7 | #include 8 | #endif 9 | 10 | #include "KeyDefines.h" 11 | 12 | enum InputEventType {FREE,SCANCODE,TIMER}; 13 | 14 | class InputEvent { 15 | public: 16 | InputEvent(); 17 | InputEvent(const InputEvent& event); 18 | InputEvent(uint8_t state, uint8_t scancode); 19 | InputEvent(uint8_t state, uint8_t scancode,uint8_t source); 20 | void clear(); 21 | 22 | uint8_t scancode = KC_NONE; 23 | uint8_t state = 0; 24 | uint32_t timestamp; 25 | uint8_t source = 0; 26 | 27 | InputEventType type = SCANCODE; 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /src/wire/I2CWireHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef I2C_WIRE_HANDLER 2 | #define I2C_WIRE_HANDLER 3 | #include "WireHandler.h" 4 | #include "../input/InputDevice.h" 5 | #include "../plugin/KeyPlugin.h" 6 | 7 | #define LOCAL_I2C_ADDRESS 0x00 8 | #define PRIMARY_I2C_ADDRESS 0x75 9 | #define SECONDARY_I2C_ADDRESS 0x76 10 | #define I2C_TIMEOUT_US 100000 11 | #define I2C_BUS_SPEED 100000 12 | 13 | class I2CWireHandler : public InputDevice, public WireHandler { 14 | public: 15 | I2CWireHandler(bool primary); 16 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 17 | bool scan(KeyboardState *keyboardState); 18 | private: 19 | unsigned int busSpeed = 0; 20 | bool primary = true; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /src/InputEvent.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "InputEvent.h" 3 | 4 | InputEvent::InputEvent() { 5 | type = FREE; 6 | } 7 | 8 | InputEvent::InputEvent(const InputEvent& event) { 9 | source = event.source; 10 | state = event.state; 11 | scancode = event.scancode; 12 | type = event.type; 13 | timestamp = event.timestamp; 14 | } 15 | 16 | InputEvent::InputEvent(uint8_t state, uint8_t scancode, uint8_t source) { 17 | this->source = source; 18 | InputEvent(state, scancode); 19 | } 20 | 21 | InputEvent::InputEvent(uint8_t state, uint8_t scancode) { 22 | type = SCANCODE; 23 | this->state = state; 24 | this->scancode = scancode; 25 | } 26 | 27 | void InputEvent::clear() { 28 | this->type = FREE; 29 | this->scancode = KC_NONE; 30 | this->source = 0; 31 | } -------------------------------------------------------------------------------- /src/plugin/MacroPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef MACRO_PLUGIN 2 | #define MACRO_PLUGIN 3 | #include "KeyPlugin.h" 4 | #include "../InputEvent.h" 5 | 6 | #define MACRO_TIMEOUT 200 7 | #define NUKE_TIMEOUT 2000 8 | #define MACRO_SIZE 200 9 | enum MacroPluginState {IDLE, ACTIVATED, RECORDING}; 10 | 11 | class MacroPlugin:public KeyPlugin { 12 | public: 13 | MacroPlugin(); 14 | MacroPlugin(uint8_t *scanCodes, int nCodes); 15 | bool inputEvent(InputEvent* event, KeyboardState* kbState); 16 | void replayMacro(KeyboardState* kbState); 17 | void storeCode(uint8_t state, uint8_t code); 18 | 19 | //int *stored; 20 | uint8_t *stored; 21 | int storedIndex = 8; 22 | int macroSize = 0; 23 | uint8_t lastKeyPressed = KC_NONE; 24 | uint32_t lastKeyPressedTs; 25 | MacroPluginState pluginState = IDLE; 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:teensylc] 12 | platform = teensy 13 | board = teensylc 14 | framework = arduino 15 | ;build_flags = -D USB_MIDI -I src/boards/teensy-65 -D TEENSY 16 | ;build_flags = -D USB_KEYBOARDONLY -I src/boards/teensy-65 17 | ;USB_SERIAL_HID 18 | build_flags = -D USB_EVERYTHING 19 | -D TEENSY ;this is our flag, triggeres conditional defines in code for Arduino/Teensy 20 | -I src/boards/teensy-65 21 | ;ok some stuff that just _won't_ compile for arduino code 22 | src_filter = +<*> - - - - + 23 | -------------------------------------------------------------------------------- /src/InputEventPool.cpp: -------------------------------------------------------------------------------- 1 | #if defined(PICO) 2 | #include 3 | #include "pico/stdlib.h" 4 | #elif defined(TEENSY) 5 | #include 6 | #endif 7 | #include "InputEventPool.h" 8 | 9 | InputEventPool::InputEventPool(){ 10 | 11 | } 12 | 13 | InputEvent* InputEventPool::getInputEvent(InputEvent event){ 14 | InputEvent* poolEvent = getInputEvent(event.type); 15 | poolEvent->source = event.source; 16 | poolEvent->state = event.state; 17 | poolEvent->scancode = event.scancode; 18 | poolEvent->timestamp = event.timestamp; 19 | return poolEvent; 20 | } 21 | 22 | InputEvent* InputEventPool::getInputEvent(InputEventType type) { 23 | for(int c1 = 0;c1 < INPUT_EVENT_POOL_SIZE;c1 ++) { 24 | if(inputEvents[c1].type == FREE) { 25 | inputEvents[c1].type = type; 26 | #if defined(PICO) 27 | inputEvents[c1].timestamp = time_us_32(); 28 | #elif defined(TEENSY) 29 | inputEvents[c1].timestamp = micros(); 30 | #endif 31 | 32 | return &inputEvents[c1]; 33 | } 34 | } 35 | //printf("UNABLE TO FIND INPUT EVENT"); 36 | return nullptr; 37 | } -------------------------------------------------------------------------------- /src/usb_descriptors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #ifndef USB_DESCRIPTORS_H_ 26 | #define USB_DESCRIPTORS_H_ 27 | 28 | enum { 29 | REPORT_ID_KEYBOARD = 1, 30 | REPORT_ID_MOUSE 31 | }; 32 | 33 | #endif /* USB_DESCRIPTORS_H_ */ 34 | -------------------------------------------------------------------------------- /src/plugin/SticKeyPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "../KeyDefines.h" 2 | #include "SticKeyPlugin.h" 3 | 4 | 5 | //click & double click keys inside this time period to lock the key until pressed again. 6 | #define STICKEY_TIMEOUT 200 7 | 8 | SticKeyPlugin::SticKeyPlugin(uint8_t *scanCodes, int nCodes):KeyPlugin(scanCodes, nCodes) { 9 | lastKeyClicked = KC_NONE; 10 | lastKeyPressed = KC_NONE; 11 | } 12 | 13 | bool SticKeyPlugin::inputEvent(InputEvent* event, KeyboardState* kbState) { 14 | if(event->state == KEY_PRESSED) { 15 | lastKeyPressed = event->scancode; 16 | lastKeyPressedTs = millis(); 17 | } else { // key released, this is where all the magic happens 18 | if(lastKeyPressed == event->scancode && (millis() - lastKeyPressedTs < STICKEY_TIMEOUT)) { // A CLICK ! A VERITABLE CLICK ! TODO check timing here 19 | //this is a CLICK so check see if the LAST clicked key WAS THIS ONE AS WELL 20 | if(lastKeyClicked == event->scancode && (millis()-lastKeyClickedTs < STICKEY_TIMEOUT)) { // ok so last click WAS ALSO this scan code i.e. double click. 21 | if(isKey(event->scancode)){ 22 | //so we have a double click on one of our keys. 23 | //we return false to swallow this KEY_RELEASED 24 | return false; 25 | } 26 | lastKeyClicked = KC_NONE; 27 | } else { 28 | lastKeyClicked = event->scancode; 29 | lastKeyClickedTs = millis(); 30 | } 31 | } 32 | } 33 | return true; 34 | } -------------------------------------------------------------------------------- /src/KeyboardState.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYBOARD_STATE 2 | #define KEYBOARD_STATE 3 | 4 | #if defined(PICO) 5 | 6 | #elif defined(TEENSY) 7 | #include 8 | #endif 9 | 10 | #include "KeyDefines.h" 11 | #include "InputEvent.h" 12 | #include "InputEventPool.h" 13 | #include "plugin/KeyPlugin.h" 14 | #include "wire/WireHandler.h" 15 | 16 | #include "board.h" 17 | 18 | class KeyPlugin; 19 | class WireHandler; 20 | 21 | class KeyboardState { 22 | public: 23 | KeyboardState( uint8_t keyMaps[2][NUM_ROWS][NUM_COLS], int nKeyMaps, 24 | KeyPlugin* keyPlugins[], int nKeyPlugins, 25 | WireHandler* wireHandlers[], int nWireHandlers); 26 | void clearKeyStates(); 27 | void resetKeyStates(uint8_t fromKeyMap[NUM_ROWS][NUM_COLS], uint8_t toKeyMap[NUM_ROWS][NUM_COLS]); 28 | void raise(); 29 | void lower(); 30 | uint8_t getScanCode(int row, int col); 31 | uint8_t getScanCode(uint8_t keyMap[NUM_ROWS][NUM_COLS], int row, int col); 32 | void inputEvent(InputEvent *event); 33 | void runWireHandlers(InputEvent *event); 34 | void wirePrint(char *str); 35 | 36 | unsigned char keyIterCount[NUM_ROWS][NUM_COLS]; 37 | uint8_t keyState[NUM_ROWS][NUM_COLS]; 38 | uint8_t (*keyMaps)[NUM_ROWS][NUM_COLS]; 39 | int nKeyMaps; 40 | KeyPlugin** keyPlugins; 41 | int nKeyPlugins; 42 | WireHandler** wireHandlers; 43 | int nWireHandlers; 44 | 45 | int activeKeyMapIndex = 0; 46 | 47 | InputEventPool inputEventPool; 48 | }; 49 | 50 | #endif -------------------------------------------------------------------------------- /src/wire/USBWireHandler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../KeyDefines.h" 3 | #include "WireHandler.h" 4 | #include "USBWireHandler.h" 5 | 6 | bool USBWireHandler::inputEvent(InputEvent* event, KeyboardState* kbState) { 7 | if(event->type == SCANCODE && event->scancode < KC_SPECIAL){ 8 | uint16_t scancode = event->scancode; 9 | if (scancode < KC_CONTROL_LEFT) { 10 | scancode = scancode | 0xF000; 11 | } else { 12 | switch(scancode) { 13 | case KC_CONTROL_LEFT: scancode = 0x01;//0xE0 14 | break; 15 | case KC_SHIFT_LEFT: scancode = 0x02;//0xE1 16 | break; 17 | case KC_ALT_LEFT: scancode = 0x04; //0xE2 18 | break; 19 | case KC_GUI_LEFT: scancode = 0x08; //0xE3 20 | break; 21 | case KC_CONTROL_RIGHT: scancode = 0x10; //0xE4 22 | break; 23 | case KC_SHIFT_RIGHT: scancode = 0x20; //0xE5 24 | break; 25 | case KC_ALT_RIGHT: scancode = 0x40; //0xE6 26 | break; 27 | case KC_GUI_RIGHT: scancode = 0x80; //0xE7 28 | break; 29 | default: scancode = KC_NONE; 30 | } 31 | scancode = scancode | 0xE000; 32 | } 33 | 34 | if(event->state == KEY_PRESSED) { 35 | Keyboard.press(scancode); 36 | } else { 37 | Keyboard.release(scancode); 38 | } 39 | } 40 | return false; 41 | } 42 | -------------------------------------------------------------------------------- /src/plugin/MacroPlugin.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "MacroPlugin.h" 3 | #include "../KeyDefines.h" 4 | 5 | 6 | MacroPlugin::MacroPlugin(uint8_t *scanCodes, int nCodes):KeyPlugin(scanCodes, nCodes){ 7 | lastKeyPressed = KC_NONE; 8 | stored = new uint8_t[MACRO_SIZE]; 9 | storedIndex = 0; 10 | } 11 | 12 | void MacroPlugin::replayMacro(KeyboardState* kbState) { 13 | for (int key = 0; key < storedIndex; key += 2) { 14 | InputEvent* ie = kbState->inputEventPool.getInputEvent(SCANCODE); 15 | ie->state = stored[key]; 16 | ie->scancode = stored[key+1]; 17 | kbState->runWireHandlers(ie); 18 | } 19 | } 20 | 21 | void MacroPlugin::storeCode(uint8_t state, uint8_t scanCode) { 22 | stored[storedIndex++] = state; 23 | stored[storedIndex++] = scanCode; 24 | } 25 | 26 | bool MacroPlugin::inputEvent(InputEvent* event, KeyboardState* kbState){ 27 | 28 | if(pluginState == RECORDING) { 29 | if(isKey(event->scancode)) { 30 | pluginState = IDLE; 31 | } else { 32 | storeCode(event->state, event->scancode); 33 | return false; 34 | } 35 | } else if (pluginState == ACTIVATED) { 36 | //click 37 | if(isKey(event->scancode)){ 38 | if (lastKeyPressed == event->scancode) 39 | { //so last key pressed was the MACRO key, and then released. 40 | //if the timeout is < MACRO_TIMEOUT it's a click we'll replay the macro. 41 | //if it's bigger than the NUKE_TIMEOUT we'll nuke the macro. 42 | if (millis() - lastKeyPressedTs < MACRO_TIMEOUT) { 43 | replayMacro(kbState); 44 | } else if (millis() - lastKeyPressedTs > NUKE_TIMEOUT) { 45 | storedIndex = 0; 46 | } 47 | } 48 | pluginState = IDLE; 49 | } else { 50 | pluginState = RECORDING; 51 | //only reason for handing all three states is to know when to zero out the storedIndex 52 | storedIndex = 0; 53 | storeCode(event->state,event->scancode); 54 | return false; 55 | } 56 | } else { // IDLE 57 | if(isKey(event->scancode)) { 58 | pluginState = ACTIVATED; 59 | } 60 | lastKeyPressed = event->scancode; 61 | lastKeyPressedTs = millis(); 62 | } 63 | 64 | return true; 65 | } 66 | -------------------------------------------------------------------------------- /src/wire/TinyUSBWireHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "../KeyDefines.h" 2 | #include "WireHandler.h" 3 | #include "TinyUSBWireHandler.h" 4 | 5 | 6 | bool TinyUSBWireHandler::inputEvent(InputEvent* event, KeyboardState* kbState) { 7 | 8 | if(event->type == SCANCODE && event->scancode < KC_SPECIAL){ 9 | 10 | if(event->state == KEY_PRESSED) { 11 | addKey(event->scancode); 12 | if(event->scancode >= HID_KEY_CONTROL_LEFT && event->scancode <= HID_KEY_GUI_RIGHT) { 13 | setModifier(event->scancode - HID_KEY_CONTROL_LEFT,1); 14 | } 15 | } else { 16 | removeKey(event->scancode); 17 | if(event->scancode >= HID_KEY_CONTROL_LEFT && event->scancode <= HID_KEY_GUI_RIGHT) { 18 | setModifier(event->scancode - HID_KEY_CONTROL_LEFT,0); 19 | } 20 | } 21 | //send the new keyboard report here IFF we're able. Otherwise set the 22 | //dirty flag and it will be sent on the next timer event tick 23 | if (tud_hid_ready()) { 24 | tud_hid_keyboard_report(HID_PROTOCOL_KEYBOARD,modifiers,keys); 25 | //reset the dirty flag if we've managed to sent the report, could happen 26 | //on next input event without having to wait for timer event. 27 | kbStateDirty = false; 28 | } else { 29 | kbStateDirty = true; 30 | } 31 | } else if (event->type == TIMER) { //timer event 32 | if (tud_hid_ready() && kbStateDirty) { 33 | tud_hid_keyboard_report(HID_PROTOCOL_KEYBOARD,modifiers,keys); 34 | kbStateDirty = false; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | void TinyUSBWireHandler::addKey(uint8_t scancode) { 41 | //let's check if it's already IN the keys 42 | for(int i = 0; i < 6; i++) { 43 | if(keys[i] == scancode){ 44 | return; 45 | } 46 | } 47 | //now lets find an empty slot 48 | //no empty slot => no keypress 49 | for(int i = 0; i < 6; i++) { 50 | if(keys[i] == 0) { 51 | keys[i] = scancode; 52 | break; 53 | } 54 | } 55 | } 56 | 57 | void TinyUSBWireHandler::removeKey(uint8_t scancode) { 58 | for(int i = 0; i < 6; i++) { 59 | if(keys[i] == scancode) { 60 | keys[i] = 0; 61 | break; 62 | } 63 | } 64 | } 65 | 66 | void TinyUSBWireHandler::setModifier(uint8_t modifier, uint8_t state) { 67 | state ? modifiers |= 1UL << modifier : modifiers &= ~(1UL << modifier); 68 | } 69 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | # Pull in SDK (must be before project) 4 | include(pico_sdk_import.cmake) 5 | 6 | project(pico-firmware C CXX) 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | # Initialize the SDK 11 | pico_sdk_init() 12 | 13 | #common for both single keyboards and left/right splits 14 | add_library(pico-firmware-common INTERFACE) 15 | target_sources(pico-firmware-common INTERFACE 16 | ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/src/KeyboardState.cpp 18 | ${CMAKE_CURRENT_SOURCE_DIR}/src/InputEvent.cpp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/src/InputEventPool.cpp 20 | ${CMAKE_CURRENT_SOURCE_DIR}/src/wire/WireHandler.cpp 21 | ${CMAKE_CURRENT_SOURCE_DIR}/src/wire/TinyUSBWireHandler.cpp 22 | ${CMAKE_CURRENT_SOURCE_DIR}/src/plugin/KeyPlugin.cpp 23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/plugin/PicoRebootPlugin.cpp 24 | ${CMAKE_CURRENT_SOURCE_DIR}/src/plugin/FnPlugin.cpp 25 | ${CMAKE_CURRENT_SOURCE_DIR}/src/input/InputDevice.cpp 26 | ${CMAKE_CURRENT_SOURCE_DIR}/src/input/MatrixInput.cpp 27 | ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c 28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/wire/I2CWireHandler.cpp 29 | ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/split-pico/board.cpp 30 | ) 31 | target_link_libraries(pico-firmware-common INTERFACE 32 | pico_stdlib tinyusb_device tinyusb_board hardware_i2c 33 | ) 34 | target_include_directories(pico-firmware-common INTERFACE 35 | ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/boards/split-pico 36 | ) 37 | 38 | target_compile_options(pico-firmware-common INTERFACE -DPICO_ENTER_USB_BOOT_ON_EXIT=1) 39 | target_compile_definitions(pico-firmware-common INTERFACE PICO) 40 | 41 | 42 | 43 | add_executable(pico-firmware-left) 44 | target_link_libraries(pico-firmware-left pico-firmware-common) 45 | target_compile_definitions(pico-firmware-left PRIVATE LEFT_HAND_SIDE) 46 | target_compile_definitions(pico-firmware-left PRIVATE PRIMARY) 47 | pico_enable_stdio_uart(pico-firmware-left 0) 48 | pico_enable_stdio_usb(pico-firmware-left 1) 49 | pico_add_extra_outputs(pico-firmware-left) 50 | 51 | 52 | add_executable(pico-firmware-right) 53 | target_link_libraries(pico-firmware-right pico-firmware-common) 54 | target_compile_definitions(pico-firmware-right PRIVATE RIGHT_HAND_SIDE) 55 | target_compile_definitions(pico-firmware-right PRIVATE SECONDARY) 56 | pico_enable_stdio_uart(pico-firmware-right 0) 57 | pico_enable_stdio_usb(pico-firmware-right 1) 58 | pico_add_extra_outputs(pico-firmware-right) 59 | -------------------------------------------------------------------------------- /src/plugin/TapHoldPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "../KeyDefines.h" 2 | #include "TapHoldPlugin.h" 3 | 4 | 5 | //click & double click keys inside this time period to lock the key until pressed again. 6 | #define TAPHOLD_TIMEOUT 200000 7 | 8 | TapHoldPlugin::TapHoldPlugin(uint8_t* scanCodes, uint8_t* holdCodes, int nCodes):KeyPlugin(scanCodes, nCodes) { 9 | } 10 | 11 | bool TapHoldPlugin::inputEvent(InputEvent* event, KeyboardState* kbState) { 12 | if(event->type == SCANCODE) { 13 | if ( event->state == KEY_PRESSED) { 14 | if(isKey(event->scancode)) { 15 | pressedTS = micros(); 16 | pressedScanCode = event->scancode; 17 | state = PRESSED; 18 | //now swallow the key pressed event. 19 | return false; 20 | } 21 | } else if (event->state == KEY_RELEASED) { 22 | if(event->scancode == pressedScanCode) { 23 | //if in key and we're NOT in the HOLDING state then do the 24 | //tap KEY_PRESSED, the KEY_RELEASED will be handled normally 25 | //if we're in key and we ARE in the HOLDING state then we do the 26 | //hold action KEY_RELEASED, presuming it's already been pressed ... 27 | if(state == PRESSED) { 28 | InputEvent* pressed = kbState->inputEventPool.getInputEvent(SCANCODE); 29 | pressed->scancode = pressedScanCode; 30 | pressed->state = KEY_PRESSED; 31 | kbState->runWireHandlers(pressed); 32 | } else if (state == HOLDING ){ 33 | //we're past the timeout so we've already DONE the press (hopefully) 34 | //so we just have to handle the release. We'll cheat a bit ... 35 | event->scancode = holdScanCode; 36 | } 37 | //annnnd we're all done, reset the state here to wait for the next key. 38 | state = WAITING; 39 | } 40 | } 41 | } else if (event->type == TIMER) { 42 | //check time against time out for active keys, 43 | //if the timeout > TAPHOLD_TIMEOUT then we do the hold action KEY_PRESSED 44 | //and update the state from PRESSED to HOLDING 45 | if(state == PRESSED) { 46 | Serial.println("STATE PRESSED"); 47 | } 48 | if(state == PRESSED && (event->timestamp - pressedTS > TAPHOLD_TIMEOUT)) { 49 | //more than TAPHOLD_TIMEOUT has passed so lets hit pressed for the holdkey 50 | state = HOLDING; 51 | InputEvent* pressed = kbState->inputEventPool.getInputEvent(SCANCODE); 52 | pressed->state = KEY_PRESSED; 53 | pressed->scancode = holdScanCode; 54 | kbState->runWireHandlers(pressed); 55 | } 56 | } 57 | return true; 58 | } 59 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | 7 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 8 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 9 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 10 | endif () 11 | 12 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 13 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 14 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 15 | endif () 16 | 17 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 18 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 19 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 20 | endif () 21 | 22 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 23 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 24 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 25 | 26 | if (NOT PICO_SDK_PATH) 27 | if (PICO_SDK_FETCH_FROM_GIT) 28 | include(FetchContent) 29 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 30 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 31 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 32 | endif () 33 | FetchContent_Declare( 34 | pico_sdk 35 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 36 | GIT_TAG master 37 | ) 38 | if (NOT pico_sdk) 39 | message("Downloading Raspberry Pi Pico SDK") 40 | FetchContent_Populate(pico_sdk) 41 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 42 | endif () 43 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 44 | else () 45 | message(FATAL_ERROR 46 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 47 | ) 48 | endif () 49 | endif () 50 | 51 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 52 | if (NOT EXISTS ${PICO_SDK_PATH}) 53 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 54 | endif () 55 | 56 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 57 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 58 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 59 | endif () 60 | 61 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 62 | 63 | include(${PICO_SDK_INIT_CMAKE_FILE}) 64 | -------------------------------------------------------------------------------- /src/boards/teensy-65/board.cpp: -------------------------------------------------------------------------------- 1 | #include "board.h" 2 | #include "../../wire/USBWireHandler.h" 3 | #include "../../input/MatrixInput.h" 4 | #include "../../plugin/FnPlugin.h" 5 | #include "../../wire/USBMIDIHandler.h" 6 | 7 | unsigned char rowPins[NUM_ROWS] = {13,21,20,18,19}; 8 | unsigned char colPins[NUM_COLS] = {12,11,10,9,8,7,6,5,4,3,2,1,17,16,15,14}; 9 | 10 | unsigned char keyMaps[NUM_KEYMAPS][NUM_ROWS][NUM_COLS] = {{ 11 | { KC_ESCAPE, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINUS, KC_EQUAL, KC_BACKSPACE, KC_INSERT, KC_HOME}, 12 | { KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BRACKET_LEFT, KC_BRACKET_RIGHT, KC_NONE, KC_DELETE, KC_END}, 13 | { KC_FUNCTION, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SEMICOLON, KC_APOSTROPHE,KC_EUROPE_1, KC_RETURN, KC_SPECIAL, KC_PAGE_UP}, 14 | { KC_SHIFT_LEFT,KC_EUROPE_2, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMMA, KC_PERIOD, KC_SLASH,KC_SHIFT_RIGHT, KC_NONE, KC_ARROW_UP, KC_PAGE_DOWN}, 15 | { KC_CONTROL_LEFT,KC_GUI_LEFT, KC_ALT_LEFT, KC_NONE, KC_NONE, KC_NONE, KC_SPACE, KC_NONE, KC_NONE, KC_NONE,KC_ALT_RIGHT, KC_FUNCTION,KC_CONTROL_RIGHT, KC_ARROW_LEFT, KC_ARROW_DOWN, KC_ARROW_RIGHT} 16 | },{ 17 | { KC_GRAVE, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,TRNS,TRNS,TRNS}, 18 | {TRNS, KC_NONE, KC_ARROW_UP, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_INSERT, KC_HOME, KC_PAGE_UP, KC_NONE, KC_NONE, KC_NONE, KC_NONE,TRNS,TRNS}, 19 | {TRNS, KC_ARROW_LEFT, KC_ARROW_DOWN, KC_ARROW_RIGHT, KC_NONE, KC_NONE, KC_NONE, KC_DELETE, KC_END, KC_PAGE_DOWN, KC_NONE, KC_NONE, KC_NONE,TRNS, KC_FUNCTION,TRNS}, 20 | {TRNS, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE, KC_NONE,TRNS, KC_NONE,TRNS,TRNS}, 21 | {TRNS,TRNS,TRNS, KC_NONE, KC_NONE, KC_NONE,TRNS, KC_NONE, KC_NONE, KC_NONE,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS} 22 | }}; 23 | 24 | //plugins config for the board 25 | //first Input devices, then KeyPlugins, then WireHandlers 26 | 27 | InputDevice* inputDevices[] = { 28 | new MatrixInput() 29 | }; 30 | 31 | //keylists for the plugins 32 | //int sticKeyList[] = { KC_FUNCTION,MODIFIER KC_SHIFT}; 33 | 34 | //our USBMidiHandler is simulataneously a Wire plugin AND a key plugin 35 | //the keyPlugin half of it handles activation and mode switching, the 36 | //wire half handles the actual note transmission on USB. 37 | //we create ONE instance and add it to the list of KeyPlugins AND the 38 | //list of Wire Plugins. 39 | uint8_t midiModeKeyList[] = {KC_SPECIAL}; 40 | USBMIDIHandler* midiHandler = new USBMIDIHandler(midiModeKeyList,1); 41 | 42 | uint8_t fnKeyList[] = {KC_FUNCTION}; 43 | KeyPlugin* keyPlugins[] = { 44 | //new SticKeyPlugin(sticKeyList,2), 45 | new FnPlugin(fnKeyList,1), 46 | midiHandler 47 | }; 48 | 49 | 50 | WireHandler* wireHandlers[] = { 51 | midiHandler, 52 | new USBWireHandler() 53 | }; 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/boards/split-pico/board.cpp: -------------------------------------------------------------------------------- 1 | #include "board.h" 2 | #include "../../wire/TinyUSBWireHandler.h" 3 | #include "../../input/MatrixInput.h" 4 | #include "../../plugin/FnPlugin.h" 5 | #include "../../plugin/PicoRebootPlugin.h" 6 | #include "../../wire/I2CWireHandler.h" 7 | 8 | #if defined(LEFT_HAND_SIDE) 9 | unsigned char rowPins[NUM_ROWS] = {13,12,11,10,9}; 10 | unsigned char colPins[NUM_COLS] = {15,14,8,7,6,5,4,3,2}; 11 | #elif defined(RIGHT_HAND_SIDE) 12 | unsigned char rowPins[NUM_ROWS] = {1,2,3,4,5}; 13 | unsigned char colPins[NUM_COLS] = {13,12,11,10,9,8,7,6,0}; 14 | #endif 15 | 16 | #if defined(LEFT_HAND_SIDE) 17 | uint8_t keyMaps[NUM_KEYMAPS][NUM_ROWS][NUM_COLS] = {{ 18 | { KC_F1, KC_F2, KC_ESCAPE, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6}, 19 | { KC_F3, KC_F4, KC_TAB, KC_NONE, KC_Q, KC_W, KC_E, KC_R, KC_T}, 20 | { KC_F5, KC_F6, KC_FUNCTION, KC_NONE, KC_A, KC_S, KC_D, KC_F, KC_G}, 21 | { KC_F7, KC_F8, KC_SHIFT_LEFT, KC_EUROPE_2, KC_Z, KC_X, KC_C, KC_V, KC_B}, 22 | { KC_F9, KC_F10, KC_CONTROL_LEFT, KC_FUNCTION, KC_GUI_LEFT, KC_ALT_LEFT, KC_NONE, KC_SPACE, KC_NONE} 23 | },{ 24 | { KC_REBOOT, KC_NONE, KC_GRAVE, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6}, 25 | { KC_NONE, KC_NONE, TRNS, KC_NONE,TRNS, KC_ARROW_UP,TRNS,TRNS,TRNS}, 26 | { KC_NONE, KC_NONE, KC_FUNCTION, KC_NONE, KC_ARROW_LEFT, KC_ARROW_DOWN, KC_ARROW_RIGHT,TRNS,TRNS}, 27 | { KC_NONE, KC_NONE, KC_SHIFT_LEFT,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS}, 28 | { KC_F11, KC_12, KC_CONTROL_LEFT, KC_FUNCTION, KC_GUI_LEFT, KC_ALT_LEFT, KC_NONE, KC_SPACE, KC_NONE} 29 | }}; 30 | #elif defined(RIGHT_HAND_SIDE) 31 | uint8_t keyMaps[NUM_KEYMAPS][NUM_ROWS][NUM_COLS] = {{ 32 | { KC_7, KC_NONE, KC_8, KC_9, KC_0, KC_MINUS, KC_EQUAL, KC_BACKSPACE, KC_INSERT}, 33 | { KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BRACKET_LEFT, KC_BRACKET_RIGHT, KC_RETURN, KC_DELETE}, 34 | { KC_H, KC_J, KC_K, KC_L, KC_SEMICOLON, KC_APOSTROPHE, KC_EUROPE_1, KC_NONE, KC_HOME}, 35 | { KC_N, KC_M, KC_COMMA, KC_PERIOD, KC_SLASH, KC_NONE, KC_SHIFT_RIGHT, KC_ARROW_UP, KC_END}, 36 | { KC_SPACE, KC_NONE, KC_NONE, KC_ALT_RIGHT, KC_FUNCTION, KC_CONTROL_RIGHT, KC_ARROW_LEFT, KC_ARROW_DOWN, KC_ARROW_RIGHT} 37 | },{ 38 | { KC_F7, KC_NONE, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, TRNS, KC_REBOOT}, 39 | {TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS}, 40 | {TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, KC_PAGE_UP}, 41 | {TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, KC_PAGE_DOWN}, 42 | {TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS, TRNS}, 43 | }}; 44 | #endif 45 | 46 | uint8_t rebootList[] = { KC_REBOOT}; 47 | uint8_t fnKeyList[] = {KC_FUNCTION}; 48 | 49 | 50 | KeyPlugin* keyPlugins[] = { 51 | new FnPlugin(fnKeyList,1), 52 | new PicoRebootPlugin(rebootList,1) 53 | }; 54 | 55 | #if defined(PRIMARY) 56 | bool primary = true; 57 | I2CWireHandler* I2CIOController = new I2CWireHandler(primary); 58 | WireHandler* wireHandlers[] = { 59 | I2CIOController, 60 | new TinyUSBWireHandler() 61 | }; 62 | #elif defined(SECONDARY) 63 | bool primary = false; 64 | I2CWireHandler* I2CIOController = new I2CWireHandler(primary); 65 | WireHandler* wireHandlers[] = { 66 | I2CIOController, 67 | }; 68 | #endif 69 | 70 | InputDevice* inputDevices[] = { 71 | new MatrixInput(), 72 | I2CIOController 73 | }; -------------------------------------------------------------------------------- /src/tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //-------------------------------------------------------------------- 34 | // COMMON CONFIGURATION 35 | //-------------------------------------------------------------------- 36 | 37 | // defined by compiler flags for flexibility 38 | #ifndef CFG_TUSB_MCU 39 | #error CFG_TUSB_MCU must be defined 40 | #endif 41 | 42 | #if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 43 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 44 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) 45 | #else 46 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 47 | #endif 48 | 49 | #ifndef CFG_TUSB_OS 50 | #define CFG_TUSB_OS OPT_OS_PICO 51 | #endif 52 | 53 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 54 | // #define CFG_TUSB_DEBUG 0 55 | 56 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 57 | * Tinyusb use follows macros to declare transferring memory so that they can be put 58 | * into those specific section. 59 | * e.g 60 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 61 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 62 | */ 63 | #ifndef CFG_TUSB_MEM_SECTION 64 | #define CFG_TUSB_MEM_SECTION 65 | #endif 66 | 67 | #ifndef CFG_TUSB_MEM_ALIGN 68 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 69 | #endif 70 | 71 | //-------------------------------------------------------------------- 72 | // DEVICE CONFIGURATION 73 | //-------------------------------------------------------------------- 74 | 75 | #ifndef CFG_TUD_ENDPOINT0_SIZE 76 | #define CFG_TUD_ENDPOINT0_SIZE 64 77 | #endif 78 | 79 | //------------- CLASS -------------// 80 | #define CFG_TUD_HID 1 81 | #define CFG_TUD_CDC 1 82 | #define CFG_TUD_MSC 0 83 | #define CFG_TUD_MIDI 0 84 | #define CFG_TUD_VENDOR 0 85 | 86 | // HID buffer size Should be sufficient to hold ID (if any) + Data 87 | #define CFG_TUD_HID_BUFSIZE 16 88 | 89 | 90 | //------------- CDC -------------// 91 | 92 | // CDC FIFO size of TX and RX 93 | #define CFG_TUD_CDC_RX_BUFSIZE 64 94 | #define CFG_TUD_CDC_TX_BUFSIZE 64 95 | 96 | // CDC Endpoint transfer buffer size, more is faster 97 | #define CFG_TUD_CDC_EP_BUFSIZE 64 98 | 99 | 100 | #ifdef __cplusplus 101 | } 102 | #endif 103 | 104 | #endif /* _TUSB_CONFIG_H_ */ 105 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(PICO) 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pico/stdlib.h" 8 | #include "pico/bootrom.h" 9 | 10 | #include "bsp/board.h" 11 | #include "tusb.h" 12 | #include "usb_descriptors.h" 13 | #elif defined(TEENSY) 14 | #include 15 | #endif 16 | 17 | #include "KeyDefines.h" 18 | #include "KeyboardState.h" 19 | #include "wire/WireHandler.h" 20 | #if defined(PICO) 21 | #include "wire/TinyUSBWireHandler.h" 22 | #include "plugin/PicoRebootPlugin.h" 23 | #include "wire/I2CWireHandler.h" 24 | #elif defined(TEENSY) 25 | #include "wire/USBWireHandler.h" 26 | #include "wire/SerialWireHandler.h" 27 | #endif 28 | 29 | #include "plugin/KeyPlugin.h" 30 | #include "plugin/FnPlugin.h" 31 | //#include "plugin/SticKeyPlugin.h" 32 | //#include "plugin/MacroPlugin.h" 33 | //#include "plugin/TapHoldPlugin.h" 34 | #include "input/InputDevice.h" 35 | #include "input/MatrixInput.h" 36 | 37 | #include "board.h" 38 | 39 | KeyboardState *keyboardState; 40 | 41 | extern uint8_t keyMaps[NUM_KEYMAPS][NUM_ROWS][NUM_COLS]; 42 | extern InputDevice* inputDevices[]; 43 | extern KeyPlugin* keyPlugins[]; 44 | extern WireHandler* wireHandlers[]; 45 | 46 | void doInputs() { 47 | //now run our input handlers. 48 | for(int inputDevice = 0; inputDevice < NUM_INPUT_DEVICES; inputDevice ++) { 49 | if(!inputDevices[inputDevice]->scan(keyboardState)) { 50 | return; 51 | } 52 | } 53 | } 54 | 55 | unsigned long get_micros() { 56 | #if defined(PICO) 57 | return time_us_32(); 58 | #elif defined(TEENSY) 59 | return micros(); 60 | #endif 61 | } 62 | 63 | void doTimerTick() { 64 | InputEvent* timerEvent = keyboardState->inputEventPool.getInputEvent(TIMER); 65 | timerEvent->timestamp = get_micros(); 66 | keyboardState->inputEvent(timerEvent); 67 | } 68 | 69 | void setup() { 70 | 71 | #if defined(TEENSY) 72 | Serial.begin(9600); //needs platformio.ini or teensy setup to be updated to add serial USB functionality. 73 | #endif 74 | //TODO HAL serial comms layer (plugin) 75 | keyboardState = new KeyboardState(keyMaps, NUM_KEYMAPS, 76 | keyPlugins, NUM_KEYPLUGINS, 77 | wireHandlers, NUM_WIREHANDLERS); 78 | } 79 | 80 | void loop() { 81 | static unsigned long scanTick = get_micros(); 82 | static unsigned long timerTick = get_micros(); 83 | 84 | //lets do a timer tick if necessary 85 | unsigned long elapsed = get_micros() - timerTick; 86 | if (elapsed >= TIMER_TICK_PERIOD ) { 87 | timerTick = get_micros() - (elapsed - TIMER_TICK_PERIOD); 88 | doTimerTick(); 89 | } 90 | //now check if we have to run our inputs 91 | elapsed = get_micros() - scanTick; 92 | if (elapsed >= SCAN_PERIOD) { 93 | scanTick = get_micros() - (elapsed - SCAN_PERIOD); 94 | doInputs(); 95 | } 96 | } 97 | 98 | #if defined(PICO) 99 | // Invoked when received GET_REPORT control request 100 | // Application must fill buffer report's content and return its length. 101 | // Return zero will cause the stack to STALL request 102 | uint16_t tud_hid_get_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) { 103 | // TODO not Implemented 104 | (void) report_id; 105 | (void) report_type; 106 | (void) buffer; 107 | (void) reqlen; 108 | 109 | return 0; 110 | } 111 | 112 | // Invoked when received SET_REPORT control request or 113 | // received data on OUT endpoint ( Report ID = 0, Type = 0 ) 114 | void tud_hid_set_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { 115 | // TODO set LED based on CAPLOCK, NUMLOCK etc... 116 | (void) report_id; 117 | (void) report_type; 118 | (void) buffer; 119 | (void) bufsize; 120 | } 121 | 122 | //--------------------------------------------------------------------+ 123 | // USB CDC 124 | //--------------------------------------------------------------------+ 125 | void cdc_task(void) 126 | { 127 | // connected and there are data available 128 | if ( tud_cdc_available() ) 129 | { 130 | // read datas 131 | char buf[64]; 132 | uint32_t count = tud_cdc_read(buf, sizeof(buf)); 133 | (void) count; 134 | } 135 | } 136 | 137 | int main() { 138 | stdio_init_all(); 139 | tusb_init(); 140 | setup(); 141 | while(true) { 142 | tud_task(); 143 | cdc_task(); 144 | loop(); 145 | } 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /src/input/MatrixInput.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(PICO) 3 | #include 4 | #include "pico/stdlib.h" 5 | #elif defined(TEENSY) 6 | #include 7 | #endif 8 | 9 | #include "MatrixInput.h" 10 | 11 | extern uint8_t rowPins[NUM_ROWS]; 12 | extern uint8_t colPins[NUM_COLS]; 13 | 14 | MatrixInput::MatrixInput() { 15 | //set the row pins to 'input' which is the default 'disabled' state 16 | #if defined(PICO) 17 | for(int row=0; row < NUM_ROWS; row++) { 18 | gpio_init(rowPins[row]); 19 | gpio_set_dir(rowPins[row], GPIO_IN); 20 | gpio_pull_up(rowPins[row]); 21 | } 22 | for (int col=0; col < NUM_COLS; col++) { 23 | gpio_init(colPins[col]); 24 | gpio_set_dir(colPins[col], GPIO_IN); 25 | gpio_pull_up(colPins[col]); 26 | } 27 | #elif defined(TEENSY) 28 | for(int row=0; row < NUM_ROWS; row++) { 29 | pinMode(rowPins[row], INPUT); 30 | } 31 | for (int col=0; col < NUM_COLS; col++) { 32 | pinMode(colPins[col], INPUT_PULLUP); 33 | } 34 | #endif 35 | } 36 | 37 | bool MatrixInput::scan(KeyboardState* keyboardState) { 38 | 39 | //First loop runs through each of the rows, 40 | for (int row=0; row < NUM_ROWS; row++) { 41 | //for each row pin, set to OUTPUT and LOW 42 | #if defined(PICO) 43 | gpio_set_dir(rowPins[row], GPIO_OUT); 44 | gpio_put(rowPins[row], false); 45 | //needed for some reason on the PICO, but not the teensy. 46 | //not to sure what difference a 1us difference makes, but 47 | //if it's not done then the first colum often gives duplicates 48 | //as the previous row pin hasn't fully reset to an input yet 49 | busy_wait_us_32(1); 50 | #elif defined(TEENSY) 51 | pinMode(rowPins[row], OUTPUT); 52 | digitalWrite(rowPins[row], LOW); 53 | #endif 54 | //now iterate through each of the columns, set to input_pullup, 55 | //the Row is output and low, and we have input pullup on the column pins, 56 | //so a '1' is an un pressed switch, and a '0' is a pressed switch. 57 | for (int col=0; col < NUM_COLS; col++) { 58 | #if defined(PICO) 59 | bool boolVal = gpio_get(colPins[col]); 60 | unsigned char value = boolVal ? KEY_RELEASED : KEY_PRESSED; 61 | #elif defined(TEENSY) 62 | unsigned char value = digitalRead(colPins[col]); 63 | #endif 64 | //if the value is different from the stored value, then reset the count and set the stored value. 65 | if(value == KEY_PRESSED && keyboardState->keyState[row][col] == KEY_RELEASED) { 66 | keyboardState->keyState[row][col] = KEY_PRESSED; 67 | keyboardState->keyIterCount[row][col] = 0; 68 | } else if (value == KEY_RELEASED && keyboardState->keyState[row][col] == KEY_PRESSED) { 69 | keyboardState->keyState[row][col] = KEY_RELEASED; 70 | keyboardState->keyIterCount[row][col] = 0; 71 | } else { 72 | //Stored value is the same as the current value, this is where our debounce magic happens. 73 | //if the keyIterCount < debounce iter then increment the keyIterCount and move on 74 | //if it's == debounce iter then trigger the key & increment it so the trigger doesn't happen again. 75 | //if it's > debounce iter then we do nothing, except check for the FN key being pressed. 76 | if(keyboardState->keyIterCount[row][col] < DEBOUNCE_ITER) { 77 | keyboardState->keyIterCount[row][col] ++; 78 | } else if (keyboardState->keyIterCount[row][col] == DEBOUNCE_ITER) { 79 | keyboardState->keyIterCount[row][col] ++; 80 | uint8_t scanCode = keyboardState->getScanCode(row,col); 81 | InputEvent* inputEvent = keyboardState->inputEventPool.getInputEvent(SCANCODE); 82 | inputEvent->scancode = scanCode; 83 | inputEvent->state = keyboardState->keyState[row][col]; 84 | keyboardState->inputEvent(inputEvent); 85 | #if defined(PICO) 86 | printf("%i %i %i\n",scanCode,row,col); 87 | #elif defined(TEENSY) 88 | Serial.print("Debounced "); 89 | Serial.print(scanCode); 90 | Serial.print(" "); 91 | Serial.print(inputEvent->state); 92 | Serial.println(""); 93 | #endif 94 | } 95 | } 96 | } 97 | //now just reset the pin mode (effectively disabling it) 98 | #if defined(PICO) 99 | gpio_set_dir(rowPins[row], GPIO_IN); 100 | #elif defined(TEENSY) 101 | pinMode(rowPins[row], INPUT); 102 | #endif 103 | } 104 | return true; 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mechware 2 | Modular and configurable plugin based firmware for mechanical keyboard. 3 | 4 | As per merge of PICO branch, will build for Teensy/Arduino on platformIO (edit platformio.ini file as appropriate) or as part of the PICO SDK, with cmake, similar to the usual PICO SDK CLI based build. 5 | 6 | Individual board configurations, with keymaps, and input/plugin/wire plugin lists (as below) are in named directorys, and consist of a 'board.h' and a 'board.cpp' file. The named directory (i.e. at the time of writing there are 'teensy-65' and a 'split-pico' configurations) should be added to the include paths and the cpp file included in the build. The other named directories should be excluded if necessary. As part of the Teensy/Arduino build the 'boards' directory is excluded, and the specific teensy-65 folder included. As part of the cmake build the 'split-pico' directory is explicitly included in the include path, and the corresponding 'board.cpp' is added to the sources. 7 | 8 | -------------------------------------------------------------- 9 | 10 | There are 3 different types of plugins, input, wire, and general plugins. The plugins are registered on startup, and run sequentially in the specified order, each one returning a boolean value indicating whether to terminate the chain at that point. 11 | 12 | **input/** contains device input classes, currently one which performs the scan of the keyboard matrix and does press/release detection and debounce. Further input classes might include I2C or Serial input classes, responsible for reading values from the other half of a split keyboard for example, and inserting them into the chain. 13 | 14 | The Input classes call into the KeyboardState class which translates the row/column into a scan code by looking up the relevent Key Map. This scan code is used to call the scan code plugins. These are initialised with one or more scancodes, and can check the those scan codes during the plugin execution to determine functionality. 15 | 16 | **plugin/** contains a variety of functional plugin types, each gets called in turn with key events passed up from the input/ plugin chain, and can perform operations. 17 | Currently contains the following functional plugins, 18 | * FnPlugin : handles Fn(s) key on my current board, pressing the key raises the current Keymap level (currently there are only two in my config), releasing the key lowers it (to the base level again). A similar 'LevelPlugin' could be used to handle two keys, to raise and lower through arbitrary key map levels. 19 | * SticKeyPlugin : checks for double clicks on specific keys, and if registered, will return 'false' to swallow the relevent 'KEY_RELEASE' event, those resulting in the key never being un pressed. I have it setup on Fn keys and the left Shift key to allow me to toggle the keyboard into the Function layer without having to hold the Fn key down, and on the left shift to approximate the Capslock (which has been replaced by another Fn key on the left) 20 | * MacroPlugin : If held down, will record up to 100 keystrokes, returning false as it does so, then if 'clicked' will replay those keystrokes. Holding it down for more than 2 seconds without pressing anything else will clear the current buffer. This is a good example of some fairly sophisticated time based functionality in a plugin. Also a bit of a WIP, at the moment it will only handle 1 key as the macro key correctly. 21 | 22 | **wire/** if all the scan code plugins return true, the wire plugin chain is called. There are two at the moment, 23 | * USBWireHandler: outputs key press and release events on USB so the keyboard can function as, well, a keyboard. 24 | * SerialWireHandler I was using this as an alternative to the USB handler while debugging, it's useful sometimes when your keyboard-in-progress DOESN'T act like a keyboard, when you screw stuff up. 25 | 26 | **Multi type plugins/** Plugins can extend more than one type. The MIDI Handler is a case in point, it extends the KeyPlugin and WireHandler subclasses,and added to both lists of plugins (wire and key plugins). The KeyPlugins bit handles the switch between modes, the WireHandler portion handles the actual MIDI note on / off communication. 27 | 28 | ---------------------------------------------------------------------------------- 29 | 30 | Currently the Arduino/Teensy build relies on the Arduino Keyboard Libraries (and the MIDI plugin uses the Arduino MIDI libraries). The PICO build relies on the TinyUSB implementation included in the PICO SDK, so behaviour is a little different (and a little more low level, the TinyUSBWireHandler has to handle more of the key state itself). The Arduino build should probably be moved over to TinyUSB as well for consistencies sakes. 31 | 32 | **TODO** 33 | * Currently if the plugins want to do something that involves pressing a key they have to call the wirehandler chain directly with the scancodes and key state. They should instead be sticking them onto an existing queue of keys, all of which are sent through the scan code plugins. This would facilitate some other behaviours around timing that would be useful. 34 | * Allow chords to trigger plugins i.e. CTRL+ALT+P might printscreen etc etc 35 | 36 | --- 37 | 38 | Originally put together with vscode and its platformio plugin. Current Keyboard has a Teensy LC brain, Relevent platformio.ini is included in the repo. As above, was branched to build out PICO support, and PICO branch has been merged back into main so should support PICO if built using appropriate PICO SDK. 39 | 40 | Initial design and simple firmware for the keyboard is here https://github.com/dairequinlan/the-keyboard 41 | 42 | Build log and description here https://www.dairequinlan.com/tags/the-keyboard/ 43 | -------------------------------------------------------------------------------- /src/wire/USBMIDIHandler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../KeyDefines.h" 3 | #include "WireHandler.h" 4 | #include "USBMIDIHandler.h" 5 | 6 | struct KeyNote { 7 | int scancode; 8 | uint8_t note; 9 | }; 10 | 11 | #define NUM_SONOME_NOTES 47 12 | struct KeyNote sonome[NUM_SONOME_NOTES] = { 13 | { KC_1, 24}, 14 | { KC_2, 31}, 15 | { KC_3, 38}, 16 | { KC_4, 45}, 17 | { KC_5, 52}, 18 | { KC_6, 59}, 19 | { KC_7, 66}, 20 | { KC_8, 73}, 21 | { KC_9, 80}, 22 | { KC_0, 87}, 23 | { KC_MINUS, 94}, 24 | { KC_EQUAL, 101}, 25 | 26 | { KC_Q, 28}, 27 | { KC_W, 35}, 28 | { KC_E, 42}, 29 | { KC_R, 49}, 30 | { KC_T, 56}, 31 | { KC_Y, 63}, 32 | { KC_U, 70}, 33 | { KC_I, 77}, 34 | { KC_O, 84}, 35 | { KC_P, 91}, 36 | { KC_BRACKET_LEFT, 98}, 37 | { KC_BRACKET_RIGHT, 105}, 38 | 39 | { KC_A, 32}, 40 | { KC_S, 39}, 41 | { KC_D, 46}, 42 | { KC_F, 53}, 43 | { KC_G, 60}, 44 | { KC_H, 67}, 45 | { KC_J, 74}, 46 | { KC_K, 81}, 47 | { KC_L, 88}, 48 | { KC_SEMICOLON, 95}, 49 | { KC_APOSTROPHE, 102}, 50 | {KC_EUROPE_1, 109}, 51 | 52 | {KC_EUROPE_2, 29}, 53 | { KC_Z, 36}, 54 | { KC_X, 43}, 55 | { KC_C, 50}, 56 | { KC_V, 57}, 57 | { KC_B, 64}, 58 | { KC_N, 71}, 59 | { KC_M, 78}, 60 | { KC_COMMA, 85}, 61 | { KC_PERIOD, 92}, 62 | { KC_SLASH, 99} 63 | }; 64 | 65 | #define NUM_JANKO_NOTES 47 66 | struct KeyNote janko[NUM_JANKO_NOTES] = { 67 | { KC_1, 62}, 68 | { KC_2, 64}, 69 | { KC_3, 66}, 70 | { KC_4, 68}, 71 | { KC_5, 70}, 72 | { KC_6, 72}, 73 | { KC_7, 74}, 74 | { KC_8, 76}, 75 | { KC_9, 78}, 76 | { KC_0, 80}, 77 | { KC_MINUS, 82}, 78 | { KC_EQUAL, 84}, 79 | 80 | { KC_Q, 63}, 81 | { KC_W, 65}, 82 | { KC_E, 67}, 83 | { KC_R, 69}, 84 | { KC_T, 71}, 85 | { KC_Y, 73}, 86 | { KC_U, 75}, 87 | { KC_I, 77}, 88 | { KC_O, 79}, 89 | { KC_P, 81}, 90 | { KC_BRACKET_LEFT, 83}, 91 | { KC_BRACKET_RIGHT, 85}, 92 | 93 | { KC_A, 52}, 94 | { KC_S, 54}, 95 | { KC_D, 56}, 96 | { KC_F, 58}, 97 | { KC_G, 60}, 98 | { KC_H, 62}, 99 | { KC_J, 64}, 100 | { KC_K, 66}, 101 | { KC_L, 68}, 102 | { KC_SEMICOLON, 70}, 103 | { KC_APOSTROPHE, 72}, 104 | {KC_EUROPE_1, 74}, 105 | 106 | {KC_EUROPE_2, 51}, 107 | { KC_Z, 53}, 108 | { KC_X, 55}, 109 | { KC_C, 57}, 110 | { KC_V, 59}, 111 | { KC_B, 61}, 112 | { KC_N, 63}, 113 | { KC_M, 65}, 114 | { KC_COMMA, 67}, 115 | { KC_PERIOD, 69}, 116 | { KC_SLASH, 71} 117 | }; 118 | 119 | #define NUM_CHROMATIC_NOTES 19 120 | struct KeyNote chromatic[NUM_CHROMATIC_NOTES] = { 121 | { KC_APOSTROPHE, 71}, 122 | { KC_BRACKET_LEFT, 70}, 123 | { KC_SEMICOLON, 69}, 124 | { KC_P,68}, 125 | { KC_L,67}, 126 | { KC_O,66}, 127 | { KC_K,65}, 128 | { KC_J,64}, 129 | { KC_U,63}, 130 | { KC_H,62}, 131 | { KC_Y,61}, 132 | { KC_G,60}, 133 | { KC_F,59}, 134 | { KC_R,58}, 135 | { KC_D,57}, 136 | { KC_E,56}, 137 | { KC_S,55}, 138 | { KC_W,54}, 139 | { KC_A,53} 140 | }; 141 | 142 | USBMIDIHandler::USBMIDIHandler(uint8_t *scanCodes, int nCodes):KeyPlugin(scanCodes, nCodes){ 143 | usbMIDI.begin(); 144 | } 145 | 146 | uint8_t USBMIDIHandler::getNote(MIDIMode mode, int scancode) { 147 | 148 | int size = 0; 149 | KeyNote* which = NULL; 150 | 151 | switch(mode) { 152 | case JANKO: 153 | size = NUM_JANKO_NOTES; 154 | which = janko; 155 | break; 156 | case SONOME: 157 | size = NUM_SONOME_NOTES; 158 | which = sonome; 159 | break; 160 | case CHROMATIC: 161 | default: 162 | size = NUM_CHROMATIC_NOTES; 163 | which = chromatic; 164 | } 165 | 166 | for(int i = 0; i < size; i++) { 167 | if(scancode == which[i].scancode) { 168 | return which[i].note; 169 | } 170 | } 171 | 172 | return 0; 173 | } 174 | 175 | bool USBMIDIHandler::inputEvent(InputEvent* event, KeyboardState* kbState) { 176 | 177 | if(event->type == SCANCODE) { 178 | //do the switcheroo with modes if the appropriate key is hit. 179 | if(nCodes > 0 && event->scancode == scanCodes[0] 180 | && event->state == KEY_RELEASED) { 181 | switch(mode) { 182 | case DISABLED: mode = JANKO; 183 | break; 184 | case JANKO: mode = SONOME; 185 | break; 186 | case SONOME: mode = CHROMATIC; 187 | break; 188 | case CHROMATIC: mode = DISABLED; 189 | break; 190 | } 191 | Serial.print("MIDI MODE: "); 192 | Serial.println(mode); 193 | return false; 194 | } 195 | 196 | if(mode != DISABLED) { 197 | uint8_t note = getNote(mode, event->scancode); 198 | 199 | if(event->state == KEY_PRESSED) { 200 | if (note != 0) usbMIDI.sendNoteOn(note, 99, 1); 201 | } else { 202 | if (note != 0) usbMIDI.sendNoteOff(note, 99, 1); 203 | } 204 | return false; 205 | } 206 | } 207 | 208 | return true; 209 | } 210 | -------------------------------------------------------------------------------- /src/KeyDefines.h: -------------------------------------------------------------------------------- 1 | //stick some global key defines here, useful for a bunch of the other modules. 2 | #ifndef GLOBAL_KEY_DEFINES 3 | #define GLOBAL_KEY_DEFINES 4 | 5 | #define KEY_RELEASED 1 6 | #define KEY_PRESSED 0 7 | 8 | //number of iterations of identical keyscan values before we trigger a keypress 9 | #define DEBOUNCE_ITER 5 10 | 11 | //microseconds between each scan. SCAN_PERIOD * DEBOUNCE_ITER = minimum response time 12 | #define SCAN_PERIOD 3000 13 | //do a timer event every 20 milliseconds. 14 | #define TIMER_TICK_PERIOD 20000 15 | 16 | //some global key defines for function / transparent keys / custom defined etc. 17 | //we're going to define the lowest bound of our 'special' keys as KEY_SPECIAL and 18 | //nothing >= KEY_SPECIAL will ever be sent over the USB link as it's a keyboard 19 | //firmware specific thing 20 | #define KC_SPECIAL 0xFC 21 | #define KC_REBOOT (KC_SPECIAL+1) 22 | #define TRNS (KC_SPECIAL+2) 23 | #define KC_FUNCTION (KC_SPECIAL+3) 24 | 25 | 26 | //General HID keydefines, these will work out of the box for anything that uses TinyUSB, 27 | //the mods have to be special cased for the Arduino Keyboard lib as that uses that 16 bit 28 | //keycode so the individual USB Wire handlers have to handle the special cases 29 | 30 | #ifndef KC_NONE 31 | #define KC_NONE 0x00 32 | #define KC_A 0x04 33 | #define KC_B 0x05 34 | #define KC_C 0x06 35 | #define KC_D 0x07 36 | #define KC_E 0x08 37 | #define KC_F 0x09 38 | #define KC_G 0x0A 39 | #define KC_H 0x0B 40 | #define KC_I 0x0C 41 | #define KC_J 0x0D 42 | #define KC_K 0x0E 43 | #define KC_L 0x0F 44 | #define KC_M 0x10 45 | #define KC_N 0x11 46 | #define KC_O 0x12 47 | #define KC_P 0x13 48 | #define KC_Q 0x14 49 | #define KC_R 0x15 50 | #define KC_S 0x16 51 | #define KC_T 0x17 52 | #define KC_U 0x18 53 | #define KC_V 0x19 54 | #define KC_W 0x1A 55 | #define KC_X 0x1B 56 | #define KC_Y 0x1C 57 | #define KC_Z 0x1D 58 | #define KC_1 0x1E 59 | #define KC_2 0x1F 60 | #define KC_3 0x20 61 | #define KC_4 0x21 62 | #define KC_5 0x22 63 | #define KC_6 0x23 64 | #define KC_7 0x24 65 | #define KC_8 0x25 66 | #define KC_9 0x26 67 | #define KC_0 0x27 68 | #define KC_RETURN 0x28 69 | #define KC_ESCAPE 0x29 70 | #define KC_BACKSPACE 0x2A 71 | #define KC_TAB 0x2B 72 | #define KC_SPACE 0x2C 73 | #define KC_MINUS 0x2D 74 | #define KC_EQUAL 0x2E 75 | #define KC_BRACKET_LEFT 0x2F 76 | #define KC_BRACKET_RIGHT 0x30 77 | #define KC_BACKSLASH 0x31 78 | #define KC_EUROPE_1 0x32 79 | #define KC_SEMICOLON 0x33 80 | #define KC_APOSTROPHE 0x34 81 | #define KC_GRAVE 0x35 82 | #define KC_COMMA 0x36 83 | #define KC_PERIOD 0x37 84 | #define KC_SLASH 0x38 85 | #define KC_CAPS_LOCK 0x39 86 | #define KC_F1 0x3A 87 | #define KC_F2 0x3B 88 | #define KC_F3 0x3C 89 | #define KC_F4 0x3D 90 | #define KC_F5 0x3E 91 | #define KC_F6 0x3F 92 | #define KC_F7 0x40 93 | #define KC_F8 0x41 94 | #define KC_F9 0x42 95 | #define KC_F10 0x43 96 | #define KC_F11 0x44 97 | #define KC_F12 0x45 98 | #define KC_PRINT_SCREEN 0x46 99 | #define KC_SCROLL_LOCK 0x47 100 | #define KC_PAUSE 0x48 101 | #define KC_INSERT 0x49 102 | #define KC_HOME 0x4A 103 | #define KC_PAGE_UP 0x4B 104 | #define KC_DELETE 0x4C 105 | #define KC_END 0x4D 106 | #define KC_PAGE_DOWN 0x4E 107 | #define KC_ARROW_RIGHT 0x4F 108 | #define KC_ARROW_LEFT 0x50 109 | #define KC_ARROW_DOWN 0x51 110 | #define KC_ARROW_UP 0x52 111 | #define KC_NUM_LOCK 0x53 112 | #define KC_KEYPAD_DIVIDE 0x54 113 | #define KC_KEYPAD_MULTIPLY 0x55 114 | #define KC_KEYPAD_SUBTRACT 0x56 115 | #define KC_KEYPAD_ADD 0x57 116 | #define KC_KEYPAD_ENTER 0x58 117 | #define KC_KEYPAD_1 0x59 118 | #define KC_KEYPAD_2 0x5A 119 | #define KC_KEYPAD_3 0x5B 120 | #define KC_KEYPAD_4 0x5C 121 | #define KC_KEYPAD_5 0x5D 122 | #define KC_KEYPAD_6 0x5E 123 | #define KC_KEYPAD_7 0x5F 124 | #define KC_KEYPAD_8 0x60 125 | #define KC_KEYPAD_9 0x61 126 | #define KC_KEYPAD_0 0x62 127 | #define KC_KEYPAD_DECIMAL 0x63 128 | #define KC_EUROPE_2 0x64 129 | #define KC_APPLICATION 0x65 130 | #define KC_POWER 0x66 131 | #define KC_KEYPAD_EQUAL 0x67 132 | #define KC_F13 0x68 133 | #define KC_F14 0x69 134 | #define KC_F15 0x6A 135 | #define KC_CONTROL_LEFT 0xE0 136 | #define KC_SHIFT_LEFT 0xE1 137 | #define KC_ALT_LEFT 0xE2 138 | #define KC_GUI_LEFT 0xE3 139 | #define KC_CONTROL_RIGHT 0xE4 140 | #define KC_SHIFT_RIGHT 0xE5 141 | #define KC_ALT_RIGHT 0xE6 142 | #define KC_GUI_RIGHT 0xE7 143 | #endif //keydefines 144 | 145 | #endif -------------------------------------------------------------------------------- /src/KeyboardState.cpp: -------------------------------------------------------------------------------- 1 | #if defined(PICO) 2 | #include 3 | #include "tusb.h" 4 | #endif 5 | 6 | #include "board.h" 7 | #include "KeyboardState.h" 8 | 9 | int pressed[256] = {0}; 10 | 11 | KeyboardState::KeyboardState( 12 | uint8_t keyMaps[2][NUM_ROWS][NUM_COLS] , int nKeyMaps, 13 | KeyPlugin* keyPlugins[], int nKeyPlugins, 14 | WireHandler* wireHandlers[], int nWireHandlers) { 15 | this->keyMaps = keyMaps; 16 | this->nKeyMaps = nKeyMaps; 17 | this->keyPlugins = keyPlugins; 18 | this->nKeyPlugins = nKeyPlugins; 19 | this->wireHandlers = wireHandlers; 20 | this->nWireHandlers = nWireHandlers; 21 | //set the initial values on the iter count and state arrays. 22 | for (int row = 0; row < NUM_ROWS; row++) { 23 | for (int col = 0; col < NUM_COLS; col++) { 24 | //initial iter value is debounce + 1 so that a key transition isn't immediately detected on startup. 25 | keyIterCount[row][col] = DEBOUNCE_ITER + 1; 26 | keyState[row][col] = KEY_RELEASED; 27 | } 28 | } 29 | } 30 | 31 | //functions to raise and lower the active keymap 32 | //for the Fn key this will likely just be 0 and 1 but for multi layer keebs 33 | //this could be arbitrary so may as well bake this in from the start 34 | void KeyboardState::raise() { 35 | if(activeKeyMapIndex < (nKeyMaps-1)) { //we can do a raise op 36 | activeKeyMapIndex ++; 37 | resetKeyStates(keyMaps[activeKeyMapIndex-1], keyMaps[activeKeyMapIndex]); 38 | } 39 | } 40 | 41 | void KeyboardState::lower() { 42 | if(activeKeyMapIndex > 0) { //we can do a lower op 43 | activeKeyMapIndex --; 44 | resetKeyStates(keyMaps[activeKeyMapIndex+1], keyMaps[activeKeyMapIndex]); 45 | } 46 | } 47 | 48 | //gets the scancode from the currently active keymap 49 | uint8_t KeyboardState::getScanCode(int row, int col) { 50 | return getScanCode(keyMaps[activeKeyMapIndex],row,col); 51 | } 52 | 53 | //gets the scancode given a keymap and row/col 54 | //given the layer and row/col, get the appropriate scancode 55 | uint8_t KeyboardState::getScanCode(uint8_t keyMap[NUM_ROWS][NUM_COLS], int row, int col) { 56 | uint8_t scanCode = keyMap[row][col]; 57 | int trnsIndex = activeKeyMapIndex; 58 | while(scanCode == TRNS && trnsIndex >= 0) { 59 | scanCode = keyMaps[trnsIndex--][row][col]; 60 | } 61 | return scanCode; 62 | } 63 | 64 | //input event raised by one of the input plugins 65 | void KeyboardState::inputEvent(InputEvent* event) { 66 | //first plugins, and bail out of the loop and method 67 | //if any of them return false. 68 | 69 | for(int plugin = 0; plugin < nKeyPlugins; plugin ++) { 70 | if(!keyPlugins[plugin]->inputEvent(event, this)) { 71 | event->clear(); 72 | return; 73 | } 74 | } 75 | //now our wire handler(s). We'll do this in a separate method 76 | //because the plugins can actually call this same method and 77 | //in that case we want all the wire handlers run as though there 78 | //was a key event. 79 | runWireHandlers(event); 80 | event->clear(); 81 | } 82 | 83 | //run the wire handlers in order, bail if any of them return false; 84 | //this can be run from 85 | // 1. the normal key event method above, 86 | // 2. plugins that transmit something directly on the wire 87 | // 3. a couple of other methods here like the reset 88 | void KeyboardState::runWireHandlers(InputEvent* event) { 89 | for(int wireHandler = 0; wireHandler < nWireHandlers; wireHandler++ ){ 90 | if(!wireHandlers[wireHandler]->inputEvent(event, this)) { 91 | //TODO have to call clear here as well because this 'runWireHandlers' 92 | // is called from everywhere to add keys to the events. 93 | // should be removed when we're adding the keys to a queue. 94 | // (which means they'll ALL go through the 'inputEvent' above) 95 | event->clear(); 96 | return; 97 | } 98 | } 99 | //ditto on comment above. 100 | event->clear(); 101 | } 102 | 103 | //clear all the keystate, if it's 'pressed' then we want 104 | //to do a 'KEY_RELEASED' event on the wire 105 | void KeyboardState::clearKeyStates() { 106 | for (int row = 0; row < NUM_ROWS; row ++) { 107 | for (int col = 0; col < NUM_COLS; col++) { 108 | if(keyState[row][col] == KEY_PRESSED) { 109 | runWireHandlers(new InputEvent(KEY_RELEASED, getScanCode(row,col))); 110 | } 111 | keyIterCount[row][col] = DEBOUNCE_ITER + 1; 112 | keyState[row][col] = KEY_RELEASED; 113 | } 114 | } 115 | } 116 | 117 | //Pressing the FN key could potentially shift the scan code between the key being pressed 118 | //and being released. If the FN key is hit then any pressed keys have to be reset to be in the 119 | //'released' state and their iter counts set to DEBOUNCE_ITER+1. 120 | //Quick improvement: Only do this if the scan codes are different in the two maps. This means that 121 | //any keys that are the same between the layers like the modifiers will remain pressed. 122 | void KeyboardState::resetKeyStates(uint8_t fromKeyMap[NUM_ROWS][NUM_COLS], uint8_t toKeyMap[NUM_ROWS][NUM_COLS]) { 123 | //set the initial values on the iter count and state arrays. 124 | for (int row = 0; row < NUM_ROWS; row++) { 125 | for (int col = 0; col < NUM_COLS; col++) { 126 | //only reset if it's not a 'dupe' key i.e. same as previous layer either explicitly or with the TRNS keycode. 127 | if(fromKeyMap[row][col] != TRNS && toKeyMap[row][col] != TRNS && toKeyMap[row][col] != fromKeyMap[row][col]) { 128 | keyIterCount[row][col] = DEBOUNCE_ITER + 1; 129 | //if it's currently PRESSED then we have to 'release' the 'from' map keycode, and 'press' the 'to' keycode 130 | if(keyState[row][col] == KEY_PRESSED) { 131 | uint8_t fromKeyCode = getScanCode(fromKeyMap,row,col); 132 | uint8_t toKeyCode = getScanCode(toKeyMap,row,col); 133 | runWireHandlers(new InputEvent(KEY_RELEASED, fromKeyCode)); 134 | runWireHandlers(new InputEvent(KEY_PRESSED, toKeyCode)); 135 | } 136 | } 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/wire/I2CWireHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "../KeyDefines.h" 2 | #include 3 | #include "pico/stdlib.h" 4 | #include "hardware/i2c.h" 5 | #include "hardware/irq.h" 6 | #include "pico/util/queue.h" 7 | #include "WireHandler.h" 8 | #include "I2CWireHandler.h" 9 | 10 | 11 | queue_t localEventQueue; 12 | queue_t receivedEventQueue; 13 | 14 | // Interrupt handler 15 | void i2c1_irq_handler() { 16 | 17 | // Get interrupt status 18 | uint32_t status = i2c1->hw->intr_stat; 19 | //is a write request? Or a read request ? 20 | if (status & I2C_IC_INTR_STAT_R_RX_FULL_BITS) { 21 | InputEvent event; 22 | printf("..."); 23 | i2c_read_raw_blocking(i2c1, (uint8_t *)&event, sizeof(class InputEvent)); 24 | event.source = PRIMARY_I2C_ADDRESS; 25 | printf("got %d %d\n", event.state, event.scancode); 26 | queue_try_add(&receivedEventQueue, &event); 27 | printf("Added to queue, size: %d", queue_get_level(&receivedEventQueue)); 28 | } else if (status & I2C_IC_INTR_STAT_R_RD_REQ_BITS) { 29 | //Create an empty event here, if the queue is non empty then 30 | //it'll be populated with the head of the queue, otherwise 31 | //we'll just send the empty event, which will make Primary stop 32 | //requesting events. 33 | InputEvent event; 34 | if(!queue_is_empty(&localEventQueue)){ 35 | //pop the head of the queue off and load up the event with it. 36 | queue_try_remove(&localEventQueue, &event); 37 | printf("writing to primary: %d %d \n", event.state, event.scancode); 38 | } 39 | event.source = SECONDARY_I2C_ADDRESS; 40 | i2c_write_raw_blocking(i2c1, (uint8_t *)&event, sizeof(class InputEvent)); 41 | i2c1->hw->clr_rd_req; 42 | } 43 | } 44 | 45 | 46 | 47 | I2CWireHandler::I2CWireHandler(bool primary):WireHandler() { 48 | printf("Initialising I2C Wire Handler"); 49 | 50 | //this is the master wire handler 51 | //OR to slave, address 0x76 52 | busSpeed = i2c_init(i2c1, I2C_BUS_SPEED); 53 | gpio_set_function(27, GPIO_FUNC_I2C); 54 | gpio_set_function(26, GPIO_FUNC_I2C); 55 | gpio_pull_up(27); 56 | gpio_pull_up(26); 57 | 58 | this->primary = primary; 59 | if(!primary) { 60 | i2c_set_slave_mode(i2c1,true, SECONDARY_I2C_ADDRESS); 61 | 62 | // Enable the I2C interrupts we want to process 63 | i2c1->hw->intr_mask = I2C_IC_INTR_STAT_R_RX_FULL_BITS | I2C_IC_INTR_MASK_M_RD_REQ_BITS; 64 | // Set up the interrupt handler to service I2C interrupts 65 | irq_set_exclusive_handler(I2C1_IRQ, i2c1_irq_handler); 66 | 67 | //setup queues for the secondary, local queue to hold events for the 68 | //primary to query, and the received queue to hold events SENT by the 69 | //primary that we'll put through the inputEvent queue in the scan method. 70 | queue_init(&localEventQueue, sizeof(class InputEvent), 10); 71 | queue_init(&receivedEventQueue, sizeof(class InputEvent), 10); 72 | // Enable I2C interrupt 73 | irq_set_enabled(I2C1_IRQ, true); 74 | } 75 | } 76 | 77 | //This is the wire handler bit. 78 | 79 | bool I2CWireHandler::inputEvent(InputEvent* event, KeyboardState* kbState) { 80 | //This is the wire handler input Event. 81 | //if MASTER then we send the event to the secondarys IFF the source isn't 82 | // already the secondary. 83 | //if SECONDARY then we add to a buffer and wait for the MASTER to call 84 | //the SCAN method as below at which point the buffer will be dumped 85 | //out to master. 86 | if(event->type == SCANCODE){ 87 | printf("scancode %d , busSpeed: %d , source %d \n", event->scancode, busSpeed, event->source); 88 | if(primary) { 89 | printf("Primary\n"); 90 | if (event->source != SECONDARY_I2C_ADDRESS) { 91 | printf("About to write\n"); 92 | int connStatus = i2c_write_timeout_us(i2c1, SECONDARY_I2C_ADDRESS, (uint8_t *)event,sizeof(class InputEvent),false,I2C_TIMEOUT_US); 93 | if (connStatus == PICO_ERROR_GENERIC) { 94 | printf("I2C ERR\n" ); 95 | } else if (connStatus == PICO_ERROR_TIMEOUT) { 96 | printf("I2C Timeout\n"); 97 | } else { 98 | printf("sent %d to secondary. Bytes: %d. Sizeof: %d \n", event->scancode, connStatus, sizeof(class InputEvent)); 99 | } 100 | } 101 | } else { 102 | if (event->source != PRIMARY_I2C_ADDRESS) { 103 | queue_try_add(&localEventQueue, event); 104 | } 105 | } 106 | } 107 | return true; 108 | } 109 | 110 | //This is the input device bit. 111 | 112 | bool I2CWireHandler::scan(KeyboardState *keyboardState) { 113 | //if PRIMARY then we poll the secondary(s) 114 | //if SECONDARY then we 'accept' a connection on the master if 115 | //there's something there we ingest and call our handlers. 116 | if(primary) { 117 | InputEvent event; 118 | //we'll do a read timeout here in case the bus goes down, but the 119 | //way this is _supposed_ to work there will always be an event on 120 | //the secondary to query 121 | int returned = i2c_read_timeout_us(i2c1, SECONDARY_I2C_ADDRESS, (uint8_t *)&event,sizeof(InputEvent),false,I2C_TIMEOUT_US); 122 | if (returned == PICO_ERROR_GENERIC) { 123 | //printf("I2C ERR\n" ); 124 | } else if (returned == PICO_ERROR_TIMEOUT) { 125 | 126 | } else { 127 | //IFF it's an actual keycode, then we can get a proper input event 128 | //from our pool, copy this event into it, and pass it up the chain. 129 | if(event.type == SCANCODE) { 130 | while (event.scancode != 0) { 131 | printf("event %d : %d \n",event.scancode, event.state); 132 | InputEvent* inputEvent = keyboardState->inputEventPool.getInputEvent(event); 133 | keyboardState->inputEvent(inputEvent); 134 | i2c_read_timeout_us(i2c1, SECONDARY_I2C_ADDRESS, (uint8_t *)&event,sizeof(InputEvent),false,I2C_TIMEOUT_US); 135 | } 136 | } 137 | } 138 | } else { //secondary, pop any events of the recieved events queue and do the inputevent 139 | InputEvent event; 140 | while (!queue_is_empty(&receivedEventQueue)){ 141 | //pop the head of the queue off and load up the event with it. 142 | queue_try_remove(&receivedEventQueue, &event); 143 | printf(" Playing event into keyboardState %d %d ", event.state, event.scancode); 144 | InputEvent* inputEvent = keyboardState->inputEventPool.getInputEvent(event); 145 | keyboardState->inputEvent(inputEvent); 146 | } 147 | } 148 | return true; 149 | } 150 | -------------------------------------------------------------------------------- /src/usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "tusb.h" 27 | #include "usb_descriptors.h" 28 | 29 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 30 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 31 | * 32 | * Auto ProductID layout's Bitmap: 33 | * [MSB] HID | MSC | CDC [LSB] 34 | */ 35 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 36 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 37 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 38 | 39 | //--------------------------------------------------------------------+ 40 | // Device Descriptors 41 | //--------------------------------------------------------------------+ 42 | tusb_desc_device_t const desc_device = 43 | { 44 | .bLength = sizeof(tusb_desc_device_t), 45 | .bDescriptorType = TUSB_DESC_DEVICE, 46 | .bcdUSB = 0x0200, 47 | /*.bDeviceClass = 0x00, 48 | .bDeviceSubClass = 0x00, 49 | .bDeviceProtocol = 0x00,*/ 50 | .bDeviceClass = TUSB_CLASS_MISC, 51 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 52 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 53 | 54 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 55 | 56 | .idVendor = 0xCafe, 57 | .idProduct = USB_PID, 58 | .bcdDevice = 0x0100, 59 | 60 | .iManufacturer = 0x01, 61 | .iProduct = 0x02, 62 | .iSerialNumber = 0x03, 63 | 64 | .bNumConfigurations = 0x01 65 | }; 66 | 67 | // Invoked when received GET DEVICE DESCRIPTOR 68 | // Application return pointer to descriptor 69 | uint8_t const *tud_descriptor_device_cb(void) { 70 | return (uint8_t const *) &desc_device; 71 | } 72 | 73 | //--------------------------------------------------------------------+ 74 | // HID Report Descriptor 75 | //--------------------------------------------------------------------+ 76 | 77 | uint8_t const desc_hid_report[] = 78 | { 79 | TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)), 80 | TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_MOUSE)) 81 | }; 82 | 83 | // Invoked when received GET HID REPORT DESCRIPTOR 84 | // Application return pointer to descriptor 85 | // Descriptor contents must exist long enough for transfer to complete 86 | uint8_t const *tud_hid_descriptor_report_cb(void) { 87 | return desc_hid_report; 88 | } 89 | 90 | //--------------------------------------------------------------------+ 91 | // Configuration Descriptor 92 | //--------------------------------------------------------------------+ 93 | 94 | //CDC descriptor enums 95 | enum 96 | { 97 | ITF_NUM_HID = 0, 98 | ITF_NUM_CDC_0, 99 | ITF_NUM_CDC_0_DATA, 100 | ITF_NUM_TOTAL 101 | }; 102 | 103 | //CDC Added in here 104 | 105 | #define EPNUM_CDC_0_NOTIF 0x81 106 | #define EPNUM_CDC_0_OUT 0x02 107 | #define EPNUM_CDC_0_IN 0x82 108 | 109 | //HID #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN) 110 | //CDC #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN) 111 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) 112 | 113 | #define EPNUM_HID 0x84 114 | 115 | uint8_t const desc_configuration[] = 116 | { 117 | // Config number, interface count, string index, total length, attribute, power in mA 118 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 119 | 120 | // Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval 121 | TUD_HID_DESCRIPTOR(ITF_NUM_HID, 0, HID_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, 122 | CFG_TUD_HID_BUFSIZE, 10), 123 | //adding CDC record in 124 | // 1st CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. 125 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, 4, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64) 126 | }; 127 | 128 | // Invoked when received GET CONFIGURATION DESCRIPTOR 129 | // Application return pointer to descriptor 130 | // Descriptor contents must exist long enough for transfer to complete 131 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { 132 | (void) index; // for multiple configurations 133 | return desc_configuration; 134 | } 135 | 136 | //--------------------------------------------------------------------+ 137 | // String Descriptors 138 | //--------------------------------------------------------------------+ 139 | 140 | // array of pointer to string descriptors 141 | char const *string_desc_arr[] = 142 | { 143 | (const char[]) {0x09, 0x04}, // 0: is supported language is English (0x0409) 144 | "TinyUSB", // 1: Manufacturer 145 | "TinyUSB Device", // 2: Product 146 | "123456", // 3: Serials, should use chip ID 147 | //added in for CDC vvv 148 | "TinyUSB CDC", // 4: CDC Interface 149 | }; 150 | 151 | static uint16_t _desc_str[32]; 152 | 153 | // Invoked when received GET STRING DESCRIPTOR request 154 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 155 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { 156 | (void) langid; 157 | 158 | uint8_t chr_count; 159 | 160 | if (index == 0) { 161 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 162 | chr_count = 1; 163 | } else { 164 | // Convert ASCII string into UTF-16 165 | 166 | if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) return NULL; 167 | 168 | const char *str = string_desc_arr[index]; 169 | 170 | // Cap at max char 171 | chr_count = strlen(str); 172 | if (chr_count > 31) chr_count = 31; 173 | 174 | for (uint8_t i = 0; i < chr_count; i++) { 175 | _desc_str[1 + i] = str[i]; 176 | } 177 | } 178 | 179 | // first byte is length (including header), second byte is string type 180 | _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); 181 | 182 | return _desc_str; 183 | } 184 | 185 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------