~ruther/qmk_firmware

73b8f85816c3209f6213e358a0d3737c7a73d45f — Dimitris Papavasiliou 4 years ago aadea5a
[Keyboard] Lagrange handwired keyboard (#11374)

* [Keyboard] Add the Lagrange keyboard

* Covert the master side to use the SPI driver.
A keyboards/handwired/lagrange/config.h => keyboards/handwired/lagrange/config.h +48 -0
@@ 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

A keyboards/handwired/lagrange/info.json => keyboards/handwired/lagrange/info.json +21 -0
@@ 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}
      ]
    }
  }
}

A keyboards/handwired/lagrange/keymaps/default/keymap.c => keyboards/handwired/lagrange/keymaps/default/keymap.c +50 -0
@@ 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
      ),
};

A keyboards/handwired/lagrange/keymaps/dpapavas/config.h => keyboards/handwired/lagrange/keymaps/dpapavas/config.h +23 -0
@@ 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

A keyboards/handwired/lagrange/keymaps/dpapavas/keymap.c => keyboards/handwired/lagrange/keymaps/dpapavas/keymap.c +202 -0
@@ 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);
    }
}

A keyboards/handwired/lagrange/keymaps/dpapavas/rules.mk => keyboards/handwired/lagrange/keymaps/dpapavas/rules.mk +4 -0
@@ 0,0 1,4 @@
# Enable additional features.

DEBOUNCE_TYPE = sym_defer_pk
TAP_DANCE_ENABLE = yes

A keyboards/handwired/lagrange/lagrange.c => keyboards/handwired/lagrange/lagrange.c +59 -0
@@ 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();
}

A keyboards/handwired/lagrange/lagrange.h => keyboards/handwired/lagrange/lagrange.h +53 -0
@@ 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}                                          \
    }

A keyboards/handwired/lagrange/readme.md => keyboards/handwired/lagrange/readme.md +21 -0
@@ 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).

A keyboards/handwired/lagrange/rules.mk => keyboards/handwired/lagrange/rules.mk +27 -0
@@ 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

A keyboards/handwired/lagrange/transport.c => keyboards/handwired/lagrange/transport.c +175 -0
@@ 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);
}