瀏覽代碼

[Keyboard] Lagrange handwired keyboard (#11374)

* [Keyboard] Add the Lagrange keyboard

* Covert the master side to use the SPI driver.
Dimitris Papavasiliou 4 年之前
父節點
當前提交
73b8f85816

+ 48 - 0
keyboards/handwired/lagrange/config.h

@@ -0,0 +1,48 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "config_common.h"
+
+/* USB Device descriptor parameter */
+#define VENDOR_ID       0xFEED
+#define PRODUCT_ID      0x2718
+#define DEVICE_VER      0x0001
+#define MANUFACTURER    Dimitris Papavasiliou
+#define PRODUCT         Lagrange
+
+#define EE_HANDS
+#define SPLIT_USB_DETECT
+
+/* key matrix size */
+#define MATRIX_ROWS 14
+#define MATRIX_COLS 6
+
+/* pin-out */
+#define MATRIX_ROW_PINS { E6, F1, F0, F4, F5, F6, F7 }
+#define MATRIX_COL_PINS { B4, B5, D7, B6, C6, D6 }
+#define MATRIX_ROW_PINS_RIGHT { B5, B4, D7, B6, C6, D6, D4 }
+#define MATRIX_COL_PINS_RIGHT { C7, F7, F6, F5, F4, F1 }
+#define UNUSED_PINS
+
+/* COL2ROW or ROW2COL */
+#define DIODE_DIRECTION ROW2COL
+
+#define DEBOUNCE 5
+
+#define LED_CAPS_LOCK_PIN D1
+#define LED_SCROLL_LOCK_PIN D2

+ 21 - 0
keyboards/handwired/lagrange/info.json

@@ -0,0 +1,21 @@
+{
+  "keyboard_name": "Lagrange",
+  "url": "https://github.com/dpapavas/lagrange-keyboard",
+  "maintainer": "dpapavas",
+  "width": 19,
+  "height": 8.5,
+  "layouts": {
+    "LAYOUT": {
+      "layout": [
+        {"x":0, "y":0.75, "w":1.5}, {"x":1.5, "y":0.75}, {"x":2.5, "y":0.375}, {"x":3.5, "y":0}, {"x":4.5, "y":0.5}, {"x":5.5, "y":0.5},     {"x":12.5, "y":0.5}, {"x":13.5, "y":0.5}, {"x":14.5, "y":0}, {"x":15.5, "y":0.375}, {"x":16.5, "y":0.75}, {"x":17.5, "y":0.75, "w":1.5},
+        {"x":0, "y":1.75, "w":1.5}, {"x":1.5, "y":1.75}, {"x":2.5, "y":1.375}, {"x":3.5, "y":1}, {"x":4.5, "y":1.5}, {"x":5.5, "y":1.5},     {"x":12.5, "y":1.5}, {"x":13.5, "y":1.5}, {"x":14.5, "y":1}, {"x":15.5, "y":1.375}, {"x":16.5, "y":1.75}, {"x":17.5, "y":1.75, "w":1.5},
+        {"x":0, "y":2.75, "w":1.5}, {"x":1.5, "y":2.75}, {"x":2.5, "y":2.375}, {"x":3.5, "y":2}, {"x":4.5, "y":2.5}, {"x":5.5, "y":2.5},     {"x":12.5, "y":2.5}, {"x":13.5, "y":2.5}, {"x":14.5, "y":2}, {"x":15.5, "y":2.375}, {"x":16.5, "y":2.75}, {"x":17.5, "y":2.75, "w":1.5},
+        {"x":0, "y":3.75, "w":1.5}, {"x":1.5, "y":3.75}, {"x":2.5, "y":3.375}, {"x":3.5, "y":3}, {"x":4.5, "y":3.5}, {"x":5.5, "y":3.5},     {"x":12.5, "y":3.5}, {"x":13.5, "y":3.5}, {"x":14.5, "y":3}, {"x":15.5, "y":3.375}, {"x":16.5, "y":3.75}, {"x":17.5, "y":3.75, "w":1.5},
+        {"x":0, "y":4.75, "w":1.5}, {"x":2.5, "y":4.375}, {"x":3.5, "y":4},                                                                                                                                    {"x":14.5, "y":4}, {"x":15.5, "y":4.5}, {"x":17.5, "y":4.75, "w":1.5},
+        {"x":5, "y":5, "h":1.25}, {"x":6, "y":5, "h":1.5}, {"x":7, "y":5, "h":1.5}, {"x":8, "y":5.5},                                                                               {"x":10, "y":5.5}, {"x":11, "y":5, "h":1.5}, {"x":12, "y":5, "h":1.5}, {"x":13, "y":5, "h":1.25},
+        {"x":5, "y":7}, {"x":6, "y":6.5}, {"x":7, "y":7},                                                                                                                                                                        {"x":11, "y":7}, {"x":12, "y":6.5}, {"x":13, "y":7},
+                        {"x":6, "y":7.5},                                                                                                                                                                                                         {"x":12, "y":7.5}
+      ]
+    }
+  }
+}

+ 50 - 0
keyboards/handwired/lagrange/keymaps/default/keymap.c

@@ -0,0 +1,50 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include QMK_KEYBOARD_H
+
+#define EQL_ALT MT(MOD_RALT, KC_EQL)
+#define MINS_ALT MT(MOD_LALT, KC_MINS)
+#define HOME_GUI MT(MOD_LGUI, KC_HOME)
+#define RGHT_GUI MT(MOD_RGUI, KC_RGHT)
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+    [0] = LAYOUT(
+                           /* Left hand */                                                                                     /* Right hand */
+
+        KC_GESC,  KC_1,    KC_2,    KC_3,     KC_4,     KC_5,                                                KC_6,    KC_7,    KC_8,     KC_9,     KC_0,    TT(1),
+        KC_TAB,   KC_Q,    KC_W,    KC_E,     KC_R,     KC_T,                                                KC_Y,    KC_U,    KC_I,     KC_O,     KC_P,    KC_BSLS,
+        KC_CAPS,  KC_A,    KC_S,    KC_D,     KC_F,     KC_G,                                                KC_H,    KC_J,    KC_K,     KC_L,     KC_SCLN, KC_QUOT,
+        KC_LSFT,  KC_Z,    KC_X,    KC_C,     KC_V,     KC_B,                                                KC_N,    KC_M,    KC_COMM,  KC_DOT,   KC_SLSH, KC_RSFT,
+
+        KC_LCPO,           KC_INS,  KC_LBRC, MINS_ALT, KC_BSPC,  KC_DEL,  KC_PSCR,   KC_PAUSE,   KC_ENT,   KC_SPC,   EQL_ALT, KC_RBRC, KC_DEL,          KC_RCPC,
+                                             HOME_GUI, KC_PGUP,  KC_END,                         KC_LEFT,  KC_UP,    RGHT_GUI,
+                                                       KC_PGDN,                                            KC_DOWN
+        ),
+
+  [1] = LAYOUT(
+                          /* Left hand */                                                                         /* Right hand */
+
+        KC_TRNS, KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,                                     KC_F6,   KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_TRNS,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_F11,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_F12,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+
+        KC_TRNS,          KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, RESET,   RESET, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,          KC_TRNS,
+                                            KC_TRNS, KC_TRNS, KC_TRNS,                 KC_TRNS, KC_TRNS, KC_TRNS,
+                                                     KC_TRNS,                                   KC_TRNS
+      ),
+};

+ 23 - 0
keyboards/handwired/lagrange/keymaps/dpapavas/config.h

@@ -0,0 +1,23 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#undef TAPPING_TERM
+#define TAPPING_TERM 175
+#define TAPPING_TERM_PER_KEY
+#define PERMISSIVE_HOLD_PER_KEY
+#define IGNORE_MOD_TAP_INTERRUPT

+ 202 - 0
keyboards/handwired/lagrange/keymaps/dpapavas/keymap.c

@@ -0,0 +1,202 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include QMK_KEYBOARD_H
+
+#define CAPS_SFT MT(MOD_LSFT, KC_CAPS)
+#define QUOT_SFT MT(MOD_RSFT, KC_QUOT)
+#define PSCR_SFT MT(MOD_LSFT, KC_PSCR)
+#define PAUSE_SFT MT(MOD_RSFT, KC_PAUSE)
+#define F_SFT MT(MOD_LSFT, KC_F)
+#define J_SFT MT(MOD_RSFT, KC_J)
+#define PGUP_GUI MT(MOD_LGUI, KC_PGUP)
+#define END_GUI MT(MOD_LGUI, KC_END)
+#define UP_GUI MT(MOD_RGUI, KC_UP)
+#define LEFT_GUI MT(MOD_RGUI, KC_LEFT)
+#define EQL_CTL MT(MOD_RCTL, KC_EQL)
+#define MINS_CTL MT(MOD_LCTL, KC_MINS)
+#define BSPC_ALT LALT_T(KC_BSPC)
+#define ENT_ALT LALT_T(KC_ENT)
+#define SPC_ALT RALT_T(KC_SPC)
+#define DEL_ALT RALT_T(KC_DEL)
+
+enum tapdance_keycodes {
+    TD_LEFT,
+    TD_RGHT,
+    TD_C_X
+};
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+  [0] = LAYOUT(
+                           /* Left hand */                                                                                       /* Right hand */
+
+        KC_GRV,   KC_1,    KC_2,    KC_3,     KC_4,     KC_5,                                                   KC_6,    KC_7,    KC_8,     KC_9,     KC_0,    KC_ESC,
+        KC_TAB,   KC_Q,    KC_W,    KC_E,     KC_R,     KC_T,                                                   KC_Y,    KC_U,    KC_I,     KC_O,     KC_P,    KC_BSLS,
+        CAPS_SFT, KC_A,    KC_S,    KC_D,     F_SFT,    KC_G,                                                   KC_H,    J_SFT,   KC_K,     KC_L,     KC_SCLN, QUOT_SFT,
+        PSCR_SFT, KC_Z,    KC_X,    KC_C,     KC_V,     KC_B,                                                   KC_N,    KC_M,    KC_COMM,  KC_DOT,   KC_SLSH, PAUSE_SFT,
+
+        TD(TD_LEFT),       KC_INS,  KC_LBRC, MINS_CTL, BSPC_ALT, DEL_ALT, TD(TD_C_X),   TD(TD_C_X), ENT_ALT,  SPC_ALT,  EQL_CTL, KC_RBRC, KC_DEL,          TD(TD_RGHT),
+                                             KC_HOME,  PGUP_GUI, END_GUI,                           LEFT_GUI, UP_GUI,   KC_RGHT,
+                                                       KC_PGDN,                                               KC_DOWN
+        ),
+
+  [1] = LAYOUT(
+                          /* Left hand */                                                                        /* Right hand */
+
+        KC_TRNS, KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,                                     KC_F6,   KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_TRNS,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_F11,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_F12,
+        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,                                   KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
+
+        KC_TRNS,          KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, RESET,   RESET, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,          KC_TRNS,
+                                            KC_TRNS, KC_TRNS, KC_TRNS,                 KC_TRNS, KC_TRNS, KC_TRNS,
+                                                     KC_TRNS,                                   KC_TRNS
+        ),
+};
+
+/* The following helper macros define tap dances that support
+ * separated press, release, tap and double-tap functions. */
+
+#define STEPS(DANCE) [DANCE] = ACTION_TAP_DANCE_FN_ADVANCED(            \
+        NULL,                                                           \
+        dance_ ## DANCE ## _finished,                                   \
+        dance_ ## DANCE ## _reset)
+
+#define CHOREOGRAPH(DANCE, PRESS, RELEASE, TAP, DOUBLETAP)              \
+    static bool dance_ ## DANCE ## _pressed;                            \
+                                                                        \
+    void dance_ ## DANCE ## _finished(qk_tap_dance_state_t *state, void *user_data) { \
+        if (state->count == 1) {                                        \
+            if (state->pressed) {                                       \
+                dance_ ## DANCE ## _pressed = true;                     \
+                PRESS;                                                  \
+            } else {                                                    \
+                TAP;                                                    \
+            }                                                           \
+        } else if (state->count == 2) {                                 \
+            if (!state->pressed) {                                      \
+                DOUBLETAP;                                              \
+            }                                                           \
+        }                                                               \
+    }                                                                   \
+                                                                        \
+    void dance_ ## DANCE ## _reset(qk_tap_dance_state_t *state, void *user_data) { \
+        if (state->count == 1) {                                        \
+            if (dance_ ## DANCE ## _pressed) {                          \
+                RELEASE;                                                \
+                dance_ ## DANCE ## _pressed = false;                    \
+            }                                                           \
+        }                                                               \
+    }
+
+/* Define dance for left palm key. */
+
+CHOREOGRAPH(TD_LEFT,
+            layer_invert(1),    /* Temporarily toggle layer when held. */
+            layer_invert(1),
+
+            /* Press and release both shifts on tap, to change
+             * keyboard layout (i.e. language). */
+
+            SEND_STRING(SS_DOWN(X_LSFT) SS_DOWN(X_RSFT)
+                        SS_UP(X_LSFT) SS_UP(X_RSFT)),
+
+            layer_invert(1));   /* Toggle layer (permanently) on
+                                 * double-tap. */
+
+/* Define dance for right palm key. */
+
+CHOREOGRAPH(TD_RGHT,
+            layer_invert(1),    /* Same as above */
+            layer_invert(1),
+            /* Send a complex macro: C-x C-s Mod-t up.  (Save in
+             * Emacs, switch to terminal and recall previous command,
+             * hopefully a compile command.) */
+            SEND_STRING(SS_DOWN(X_LCTRL) SS_TAP(X_X) SS_TAP(X_S) SS_UP(X_LCTRL)
+                        SS_DOWN(X_LGUI) SS_TAP(X_T) SS_UP(X_LGUI) SS_TAP(X_UP)),
+            layer_invert(1));
+
+/* This facilitates C-x chords in Emacs.  Used as a modifier along
+ * with, say, the s-key, it saves, by sending C-x C-s.  When tapped it
+ * just sends C-x. */
+
+CHOREOGRAPH(TD_C_X,
+            SEND_STRING(SS_DOWN(X_LCTRL) SS_TAP(X_X)),
+            SEND_STRING(SS_UP(X_LCTRL)),
+            SEND_STRING(SS_DOWN(X_LCTRL) SS_TAP(X_X) SS_UP(X_LCTRL)),);
+
+qk_tap_dance_action_t tap_dance_actions[] = {
+    STEPS(TD_LEFT), STEPS(TD_RGHT), STEPS(TD_C_X)
+};
+
+/* Set a longer tapping term for palm keys to allow comfortable
+ * permanent layer toggle.  Also set an essentially infinite tapping
+ * term for certain mod-tap keys one tends to keep pressed (such as
+ * space, backspace, etc.).  This prevents sending the modifier
+ * keycode by accident (allowing re-tap to get repeated key-press)
+ * and, in combination with permissive hold, they can still be used
+ * fine as modifiers. */
+
+uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) {
+    switch (keycode) {
+        case TD(TD_LEFT):
+        case TD(TD_RGHT):
+             return 250;
+        case BSPC_ALT:
+        case UP_GUI:
+        case LEFT_GUI:
+            return 5000;
+        default:
+            return TAPPING_TERM;
+    }
+}
+
+bool get_permissive_hold(uint16_t keycode, keyrecord_t *record) {
+    switch (keycode) {
+        case TD(TD_LEFT):
+        case TD(TD_RGHT):
+        case BSPC_ALT:
+        case UP_GUI:
+        case LEFT_GUI:
+            return true;
+        default:
+            return false;
+    }
+}
+
+/* Use the first LED to indicate the active layer. */
+
+layer_state_t layer_state_set_user(layer_state_t state) {
+    writePin(D0, (get_highest_layer(state) > 0));
+
+    return state;
+}
+
+/* Cycle through the LEDs after initialization. */
+
+void keyboard_post_init_user(void) {
+    const pin_t pins[] = {D0, D1, D2};
+    uint8_t i, j;
+
+    for (i = 0 ; i < sizeof(pins) / sizeof(pins[0]) + 2 ; i += 1) {
+        for (j = 0 ; j < sizeof(pins) / sizeof(pins[0]) ; j += 1) {
+            setPinOutput(pins[j]);
+            writePin(pins[j], (j == i || j == i - 1));
+        }
+
+        wait_ms(100);
+    }
+}

+ 4 - 0
keyboards/handwired/lagrange/keymaps/dpapavas/rules.mk

@@ -0,0 +1,4 @@
+# Enable additional features.
+
+DEBOUNCE_TYPE = sym_defer_pk
+TAP_DANCE_ENABLE = yes

+ 59 - 0
keyboards/handwired/lagrange/lagrange.c

@@ -0,0 +1,59 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <LUFA/Drivers/USB/USB.h>
+
+#include "lagrange.h"
+
+#ifndef SPLIT_USB_TIMEOUT_POLL
+#    define SPLIT_USB_TIMEOUT_POLL 10
+#endif
+
+/* Instead of timing out, the slave waits indefinitely for the other
+ * side to signal that it has become master.  This avoids both sides
+ * assuming the slave role when the USB port is powered but not
+ * otherwise active (e.g. when the host is turned off, or suspended).
+ * The SPI SS line is used for signaling.  On power-up it is
+ * configured as input with pull-up enabled.  When one side assumes
+ * the master role, it reconfigures the line for SPI, and pulls it low
+ * to select the slave, which doubles as the signal. */
+
+bool is_keyboard_master(void) {
+    static int8_t is_master = -1;
+
+    if (is_master < 0) {
+        while (readPin(SPI_SS_PIN)) {
+            if (USB_Device_IsAddressSet()) {
+                is_master = 1;
+                return is_master;
+            }
+            wait_ms(SPLIT_USB_TIMEOUT_POLL);
+        }
+
+        is_master = 0;
+
+        USB_Disable();
+        USB_DeviceState = DEVICE_STATE_Unattached;
+    }
+
+    return is_master;
+}
+
+void keyboard_pre_init_kb(void) {
+    setPinInputHigh(SPI_SS_PIN);
+
+    keyboard_pre_init_user();
+}

+ 53 - 0
keyboards/handwired/lagrange/lagrange.h

@@ -0,0 +1,53 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "quantum.h"
+
+#if !defined(SPI_SS_PIN)
+#    define SPI_SS_PIN B0
+#endif
+
+#define SPI_SCK_PIN B1
+#define SPI_MOSI_PIN B2
+#define SPI_MISO_PIN B3
+
+#define LAYOUT(                                                                       \
+    l00, l01, l02, l03, l04, l05,                       r05, r04, r03, r02, r01, r00, \
+    l10, l11, l12, l13, l14, l15,                       r15, r14, r13, r12, r11, r10, \
+    l20, l21, l22, l23, l24, l25,                       r25, r24, r23, r22, r21, r20, \
+    l30, l31, l32, l33, l34, l35,                       r35, r34, r33, r32, r31, r30, \
+    l40,      l42, l43, l44, l45, l46, l47,   r47, r46, r45, r44, r43, r42,      r40, \
+                   l50, l51, l52,                       r52, r51, r50,                \
+                        l70,                                 r70)                     \
+    {                                                                                 \
+        {l00, l01, l02, l03, l04, l05},                                               \
+        {l10, l11, l12, l13, l14, l15},                                               \
+        {l20, l21, l22, l23, l24, l25},                                               \
+        {l30, l31, l32, l33, l34, l35},                                               \
+        {l40, KC_NO, l42, l43, l44, l45},                                             \
+        {KC_NO, KC_NO, KC_NO, l50, l51, l46},                                         \
+        {KC_NO, KC_NO, KC_NO, l70, l52, l47},                                         \
+                                                                                      \
+        {r00, r01, r02, r03, r04, r05},                                               \
+        {r10, r11, r12, r13, r14, r15},                                               \
+        {r20, r21, r22, r23, r24, r25},                                               \
+        {r30, r31, r32, r33, r34, r35},                                               \
+        {r40, KC_NO, r42, r43, r44, r45},                                             \
+        {KC_NO, KC_NO, KC_NO, r50, r51, r46},                                         \
+        {KC_NO, KC_NO, KC_NO, r70, r52, r47}                                          \
+    }

+ 21 - 0
keyboards/handwired/lagrange/readme.md

@@ -0,0 +1,21 @@
+# Lagrange
+
+An ergonomic, split, concave keyboard, with convex thumb pads.  See the [project page](https://github.com/dpapavas/lagrange-keyboard) for more info.
+
+![Lagrange](https://github.com/dpapavas/lagrange-keyboard/blob/master/doc/lagrange_keyboard.png?raw=true)
+
+* Keyboard Maintainer: [Dimitris Papavasiliou](https://github.com/dpapavas)
+* Hardware Supported: Lagrange PCB Rev A
+* Hardware Availability: See the build guide on the [project page](https://github.com/dpapavas/lagrange-keyboard).
+
+Make example for this keyboard (after setting up your build environment):
+
+    make handwired/lagrange:default
+
+Flashing example for this keyboard:
+
+    make handwired/lagrange:default:flash
+
+To program the keyboard you'll need to reset it.  This can be accomplished, either through the reset button, accessible via a hole in the bottom cover, or, if you've assigned the `RESET` keycode to a key, by pressing that key.
+
+See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).

+ 27 - 0
keyboards/handwired/lagrange/rules.mk

@@ -0,0 +1,27 @@
+# MCU name
+MCU = atmega32u4
+
+# Bootloader selection
+BOOTLOADER = atmel-dfu
+
+# Build Options
+#   change yes to no to disable
+#
+BOOTMAGIC_ENABLE = no       # Virtual DIP switch configuration
+MOUSEKEY_ENABLE = no        # Mouse keys
+EXTRAKEY_ENABLE = yes       # Audio control and System control
+CONSOLE_ENABLE = yes        # Console for debug
+COMMAND_ENABLE = no         # Commands for debug and configuration
+# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
+SLEEP_LED_ENABLE = no       # Breathing sleep LED during USB suspend
+# if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work
+NKRO_ENABLE = no            # USB Nkey Rollover
+BACKLIGHT_ENABLE = no       # Enable keyboard backlight functionality
+RGBLIGHT_ENABLE = no        # Enable keyboard RGB underglow
+BLUETOOTH_ENABLE = no       # Enable Bluetooth
+AUDIO_ENABLE = no           # Audio output
+UNICODE_ENABLE = yes
+SPLIT_KEYBOARD = yes
+SPLIT_TRANSPORT = custom
+
+SRC += transport.c spi_master.c

+ 175 - 0
keyboards/handwired/lagrange/transport.c

@@ -0,0 +1,175 @@
+/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <spi_master.h>
+
+#include "quantum.h"
+#include "split_util.h"
+#include "timer.h"
+
+#include "lagrange.h"
+
+struct led_context {
+    led_t led_state;
+    layer_state_t layer_state;
+};
+
+uint8_t transceive(uint8_t b) {
+    for (SPDR = b ; !(SPSR & _BV(SPIF)) ; );
+    return SPDR;
+}
+
+/* The SPI bus, doens't have any form of protocol built in, so when
+ * the other side isn't present, any old noise on the line will appear
+ * as matrix data.  To avoid interpreting data as keystrokes, we do a
+ * simple n-way (8-way here) handshake before each scan, where each
+ * side sends a prearranged sequence of bytes. */
+
+void shake_hands(bool master) {
+    const uint8_t m = master ? 0xf8 : 0;
+    const uint8_t a = 0xa8 ^ m, b = 0x50 ^ m;
+
+    uint8_t i;
+
+    i = SPSR;
+    i = SPDR;
+
+    do {
+        /* Cylcling the SS pin on each attempt is necessary, as it
+         * resets the AVR's SPI core and guarantees proper
+         * alignment. */
+
+        if (master) {
+            writePinLow(SPI_SS_PIN);
+        }
+
+        for (i = 0 ; i < 8 ; i += 1) {
+            if (transceive(a + i) != b + i) {
+                break;
+            }
+        }
+
+        if (master) {
+            writePinHigh(SPI_SS_PIN);
+        }
+    } while (i < 8);
+}
+
+bool transport_master(matrix_row_t matrix[]) {
+    const struct led_context context = {
+        host_keyboard_led_state(),
+        layer_state
+    };
+
+    uint8_t i;
+
+    /* Shake hands and then receive the matrix from the other side,
+     * while transmitting LED and layer states. */
+
+    shake_hands(true);
+
+    spi_start(SPI_SS_PIN, 0, 0, 4);
+
+    for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
+        spi_status_t x;
+
+        x = spi_write(i < sizeof(struct led_context) ?
+                      ((uint8_t *)&context)[i] : 0);
+
+        if (x == SPI_STATUS_TIMEOUT) {
+            return false;
+        }
+
+        ((uint8_t *)matrix)[i] = (uint8_t)x;
+    }
+
+    spi_stop();
+
+    return true;
+}
+
+void transport_slave(matrix_row_t matrix[]) {
+    static struct led_context context;
+    struct led_context new_context;
+
+    uint8_t i;
+
+    /* Do the reverse of master above.  Note that timing is critical,
+     * so interrupts must be turned off. */
+
+    cli();
+    shake_hands(false);
+
+    for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
+        uint8_t b;
+
+        b = transceive(((uint8_t *)matrix)[i]);
+
+        if (i < sizeof(struct led_context)) {
+            ((uint8_t *)&new_context)[i] = b;
+        }
+    }
+
+    sei();
+
+    /* Update the layer and LED state if necessary. */
+
+    if (!isLeftHand) {
+        if (context.led_state.raw != new_context.led_state.raw) {
+            context.led_state.raw = new_context.led_state.raw;
+            led_update_kb(context.led_state);
+        }
+
+        if (context.layer_state != new_context.layer_state) {
+            context.layer_state = new_context.layer_state;
+            layer_state_set_kb(context.layer_state);
+        }
+    }
+}
+
+void transport_master_init(void) {
+    /* We need to set the SS pin as output as the handshake logic
+     * above depends on it and the SPI master driver won't do it
+     * before we call spi_start(). */
+
+    writePinHigh(SPI_SS_PIN);
+    setPinOutput(SPI_SS_PIN);
+
+    spi_init();
+
+    shake_hands(true);
+}
+
+void transport_slave_init(void) {
+    /* The datasheet isn't very clear on whether the internal pull-up
+     * is selectable when the SS pin is used by the SPI slave, but
+     * experimentations shows that it is, at least on the ATMega32u4.
+     * We enable the pull-up to guard against the case where both
+     * halves end up as slaves.  In that case the SS pin would
+     * otherwise be floating and free to fluctuate due to picked up
+     * noise, etc. When reading low it would make both halves think
+     * they're asserted making the MISO pin an output on both ends and
+     * leading to potential shorts. */
+
+    setPinInputHigh(SPI_SS_PIN);
+    setPinInput(SPI_SCK_PIN);
+    setPinInput(SPI_MOSI_PIN);
+    setPinOutput(SPI_MISO_PIN);
+
+    SPCR = _BV(SPE);
+
+    shake_hands(false);
+}