From fd3ce4ba1cbd521b60020395696611cfa5810ab0 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 6 Aug 2020 23:22:02 -0400 Subject: [PATCH 01/48] Encoder Kconfig fixes. --- app/drivers/zephyr/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/drivers/zephyr/Kconfig b/app/drivers/zephyr/Kconfig index b22c4b1c..0237846a 100644 --- a/app/drivers/zephyr/Kconfig +++ b/app/drivers/zephyr/Kconfig @@ -25,7 +25,7 @@ menuconfig EC11 if EC11 -choice EC11_TRIGGER +choice prompt "Trigger mode" default EC11_TRIGGER_NONE help @@ -46,6 +46,9 @@ config EC11_TRIGGER_OWN_THREAD endchoice +config EC11_TRIGGER + bool + config EC11_THREAD_PRIORITY int "Thread priority" depends on EC11_TRIGGER_OWN_THREAD From 04606317292bcfb62e6884291495cb76fa656f47 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 6 Aug 2020 23:28:34 -0400 Subject: [PATCH 02/48] Refactor to simpler keymaps. --- app/boards/arm/planck/planck_rev6.keymap | 16 +--- .../clueboard_california.keymap | 16 +--- app/boards/shields/kyria/kyria.keymap | 57 +++++++---- app/boards/shields/lily58/lily58.keymap | 22 +---- app/dts/bindings/zmk,keymap.yaml | 20 ++-- app/dts/bindings/zmk,layers.yaml | 18 ---- app/src/keymap.c | 94 ++++--------------- 7 files changed, 76 insertions(+), 167 deletions(-) delete mode 100644 app/dts/bindings/zmk,layers.yaml diff --git a/app/boards/arm/planck/planck_rev6.keymap b/app/boards/arm/planck/planck_rev6.keymap index f474ec92..af1dc7c6 100644 --- a/app/boards/arm/planck/planck_rev6.keymap +++ b/app/boards/arm/planck/planck_rev6.keymap @@ -1,23 +1,11 @@ #include #include -#include / { - chosen { - zmk,keymap = &keymap0; - }; - - keymap0: keymap { + keymap { compatible = "zmk,keymap"; - label ="Default Planck Keymap"; - layers = <&default>; - }; - layers { - compatible = "zmk,layers"; - - default: layer_0 { - label = "DEFAULT"; + default_layer { // ----------------------------------------------------------------------------------------- // | TAB | Q | W | E | R | T | Y | U | I | O | P | BSPC | // | ESC | A | S | D | F | G | H | J | K | L | ; | ' | diff --git a/app/boards/shields/clueboard_california/clueboard_california.keymap b/app/boards/shields/clueboard_california/clueboard_california.keymap index c2fa7f8f..7a84b119 100644 --- a/app/boards/shields/clueboard_california/clueboard_california.keymap +++ b/app/boards/shields/clueboard_california/clueboard_california.keymap @@ -1,23 +1,11 @@ -#include -#include #include +#include / { - chosen { - zmk,keymap = &keymap0; - }; - keymap0: keymap { compatible = "zmk,keymap"; - label ="Default Kyria Keymap"; - layers = <&default>; - }; - layers { - compatible = "zmk,layers"; - - default: layer_0 { - label = "DEFAULT"; + default_layer { bindings = < &kp NUM_9 &kp NUM_8 &kp NUM_7 &kp NUM_6 diff --git a/app/boards/shields/kyria/kyria.keymap b/app/boards/shields/kyria/kyria.keymap index 7f12f597..614e13bb 100644 --- a/app/boards/shields/kyria/kyria.keymap +++ b/app/boards/shields/kyria/kyria.keymap @@ -2,34 +2,55 @@ #include / { - chosen { - zmk,keymap = &keymap0; - }; - - keymap0: keymap { + keymap { compatible = "zmk,keymap"; - label ="Default Kyria Keymap"; - layers = <&default>; - }; - layers { - compatible = "zmk,layers"; - - default: layer_0 { - label = "DEFAULT"; + default_layer { // --------------------------------------------------------------------------------------------------------------------------------- // | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | // | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | // | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | // | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL - &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &mo 1 &mo 2 &kp SPC &kp RET &kp BKSP &kp RALT >; sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; }; + + lower_layer { +// --------------------------------------------------------------------------------------------------------------------------------- +// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | +// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | +// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | +// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | + bindings = < + &kp ESC &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &kp BSLH + &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &trans &kp RET &kp SPC &kp SPC &kp BKSP &kp RALT + >; + + // sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; + }; + + raise_layer { +// --------------------------------------------------------------------------------------------------------------------------------- +// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | +// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | +// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | +// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | + bindings = < + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &kp RET &trans &kp SPC &kp SPC &kp BKSP &kp RALT + >; + + // sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; + }; }; -}; +}; \ No newline at end of file diff --git a/app/boards/shields/lily58/lily58.keymap b/app/boards/shields/lily58/lily58.keymap index a1987707..71bf4adb 100644 --- a/app/boards/shields/lily58/lily58.keymap +++ b/app/boards/shields/lily58/lily58.keymap @@ -1,23 +1,11 @@ #include #include -#include / { - chosen { - zmk,keymap = &keymap0; - }; - - keymap0: keymap { + keymap { compatible = "zmk,keymap"; - label ="Default Lily58 Keymap"; - layers = <&default &lower &raise>; - }; - layers { - compatible = "zmk,layers"; - - default: layer_0 { - label = "DEFAULT"; + default_layer { // ------------------------------------------------------------------------------------------------------------ // | ESC | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | ` | // | TAB | Q | W | E | R | T | | Y | U | I | O | P | - | @@ -33,8 +21,7 @@ >; }; - lower: layer_1 { - label = "LOWER"; + lower_layer { // ------------------------------------------------------------------------------------------------------------ // | | | | | | | | | | | | | | // | F1 | F2 | F3 | F4 | F5 | F6 | | F7 | F8 | F9 | F10 | F11 | F12 | @@ -50,8 +37,7 @@ >; }; - raise: layer_3 { - label = "RAISE"; + raise_layer { // ------------------------------------------------------------------------------------------------------------ // | | | | | | | | | | | | | | // | ` | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | | diff --git a/app/dts/bindings/zmk,keymap.yaml b/app/dts/bindings/zmk,keymap.yaml index b9517a34..fb6d9147 100644 --- a/app/dts/bindings/zmk,keymap.yaml +++ b/app/dts/bindings/zmk,keymap.yaml @@ -3,15 +3,13 @@ description: | compatible: "zmk,keymap" -properties: - transform: - type: phandle - required: false +child-binding: + description: "A layer to be used in a keymap" - label: - type: string - required: true - - layers: - type: phandles - required: true + properties: + bindings: + type: phandle-array + required: true + sensor-bindings: + type: phandle-array + required: false diff --git a/app/dts/bindings/zmk,layers.yaml b/app/dts/bindings/zmk,layers.yaml deleted file mode 100644 index 1a3592e9..00000000 --- a/app/dts/bindings/zmk,layers.yaml +++ /dev/null @@ -1,18 +0,0 @@ -description: | - Allows defining the various keymap layers for use. - -compatible: "zmk,layers" - -child-binding: - description: "A layer to be used in a keymap" - - properties: - label: - type: string - required: true - bindings: - type: phandle-array - required: true - sensor-bindings: - type: phandle-array - required: false diff --git a/app/src/keymap.c b/app/src/keymap.c index 24e249dd..ec512405 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -17,96 +17,41 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static u32_t zmk_keymap_layer_state = 0; static u8_t zmk_keymap_layer_default = 0; -#define ZMK_KEYMAP_NODE DT_CHOSEN(zmk_keymap) -#define ZMK_KEYMAP_LAYERS_LEN DT_PROP_LEN(ZMK_KEYMAP_NODE, layers) +#define DT_DRV_COMPAT zmk_keymap + +#define LAYER_CHILD_LEN(node) 1+ +#define ZMK_KEYMAP_NODE DT_DRV_INST(0) +#define ZMK_KEYMAP_LAYERS_LEN (DT_INST_FOREACH_CHILD(0, LAYER_CHILD_LEN) 0) #define LAYER_NODE(l) DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, l) #define _TRANSFORM_ENTRY(idx, layer) \ - { .behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, layer), bindings, idx)), \ - .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(LAYER_NODE(layer), bindings, idx, param1), (0), (DT_PHA_BY_IDX(LAYER_NODE(layer), bindings, idx, param1))), \ - .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(LAYER_NODE(layer), bindings, idx, param2), (0), (DT_PHA_BY_IDX(LAYER_NODE(layer), bindings, idx, param2))), \ + { .behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(layer, bindings, idx)), \ + .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(layer, bindings, idx, param1), (0), (DT_PHA_BY_IDX(layer, bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(layer, bindings, idx, param2), (0), (DT_PHA_BY_IDX(layer, bindings, idx, param2))), \ }, -#define TRANSFORMED_LAYER(idx) \ - { UTIL_LISTIFY(DT_PROP_LEN(DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, idx), bindings), _TRANSFORM_ENTRY, idx) } +#define TRANSFORMED_LAYER(node) \ + { UTIL_LISTIFY(DT_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, node) }, static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 0) - TRANSFORMED_LAYER(0), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 1) - TRANSFORMED_LAYER(1), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 2) - TRANSFORMED_LAYER(2), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 3) - TRANSFORMED_LAYER(3), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 4) - TRANSFORMED_LAYER(4), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 5) - TRANSFORMED_LAYER(5), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 6) - TRANSFORMED_LAYER(6), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 7) - TRANSFORMED_LAYER(7), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 8) - TRANSFORMED_LAYER(8), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 9) - TRANSFORMED_LAYER(9), -#endif + DT_INST_FOREACH_CHILD(0, TRANSFORMED_LAYER) }; #if ZMK_KEYMAP_HAS_SENSORS #define _TRANSFORM_SENSOR_ENTRY(idx, layer) \ - { .behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, layer), sensor_bindings, idx)), \ - .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(LAYER_NODE(layer), sensor_bindings, idx, param1), (0), (DT_PHA_BY_IDX(LAYER_NODE(layer), sensor_bindings, idx, param1))), \ - .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(LAYER_NODE(layer), sensor_bindings, idx, param2), (0), (DT_PHA_BY_IDX(LAYER_NODE(layer), sensor_bindings, idx, param2))), \ + { .behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(layer, sensor_bindings, idx)), \ + .param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(layer, sensor_bindings, idx, param1), (0), (DT_PHA_BY_IDX(layer, sensor_bindings, idx, param1))), \ + .param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(layer, sensor_bindings, idx, param2), (0), (DT_PHA_BY_IDX(layer, sensor_bindings, idx, param2))), \ }, -#define SENSOR_LAYER(idx) \ - COND_CODE_1(DT_NODE_HAS_PROP(DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, idx), sensor_bindings), \ - ({ UTIL_LISTIFY(DT_PROP_LEN(DT_PHANDLE_BY_IDX(ZMK_KEYMAP_NODE, layers, idx), sensor_bindings), _TRANSFORM_SENSOR_ENTRY, idx) }), \ - (NULL)) +#define SENSOR_LAYER(node) \ + COND_CODE_1(DT_NODE_HAS_PROP(node, sensor_bindings), \ + ({ UTIL_LISTIFY(DT_PROP_LEN(node, sensor_bindings), _TRANSFORM_SENSOR_ENTRY, node) }), \ + ({})), static struct zmk_behavior_binding zmk_sensor_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_SENSORS_LEN] = { -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 0) - SENSOR_LAYER(0), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 1) - SENSOR_LAYER(1), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 2) - SENSOR_LAYER(2), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 3) - SENSOR_LAYER(3), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 4) - SENSOR_LAYER(4), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 5) - SENSOR_LAYER(5), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 6) - SENSOR_LAYER(6), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 7) - SENSOR_LAYER(7), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 8) - SENSOR_LAYER(8), -#endif -#if DT_PROP_HAS_IDX(ZMK_KEYMAP_NODE, layers, 9) - SENSOR_LAYER(9), -#endif + DT_INST_FOREACH_CHILD(0, SENSOR_LAYER) }; #endif /* ZMK_KEYMAP_HAS_SENSORS */ @@ -131,6 +76,7 @@ int zmk_keymap_layer_deactivate(u8_t layer) int zmk_keymap_position_state_changed(u32_t position, bool pressed) { + LOG_DBG("Searching %d layers for a binding", ZMK_KEYMAP_LAYERS_LEN); for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= zmk_keymap_layer_default; layer--) { if ((zmk_keymap_layer_state & BIT(layer)) == BIT(layer) || layer == zmk_keymap_layer_default) From 01b8b724c12b2dc21340fbaa37ed0946f4b7c39c Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 6 Aug 2020 23:54:18 -0400 Subject: [PATCH 03/48] Remove debugging line that snuck in. --- app/src/keymap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/keymap.c b/app/src/keymap.c index ec512405..aee05773 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -76,7 +76,6 @@ int zmk_keymap_layer_deactivate(u8_t layer) int zmk_keymap_position_state_changed(u32_t position, bool pressed) { - LOG_DBG("Searching %d layers for a binding", ZMK_KEYMAP_LAYERS_LEN); for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= zmk_keymap_layer_default; layer--) { if ((zmk_keymap_layer_state & BIT(layer)) == BIT(layer) || layer == zmk_keymap_layer_default) From b4ae91a649832e16fbd0c940dc96f9eb3a8b7e39 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 6 Aug 2020 23:57:11 -0400 Subject: [PATCH 04/48] Revert some personal Kyria keymap changes. --- app/boards/shields/kyria/kyria.keymap | 40 +++------------------------ 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/app/boards/shields/kyria/kyria.keymap b/app/boards/shields/kyria/kyria.keymap index 614e13bb..1d6b9bff 100644 --- a/app/boards/shields/kyria/kyria.keymap +++ b/app/boards/shields/kyria/kyria.keymap @@ -12,45 +12,13 @@ // | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | // | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL - &kp LGUI &kp DEL &kp RET &kp SPC &mo 1 &mo 2 &kp SPC &kp RET &kp BKSP &kp RALT + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT >; sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; }; - - lower_layer { -// --------------------------------------------------------------------------------------------------------------------------------- -// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | -// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | -// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | -// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | - bindings = < - &kp ESC &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &kp BSLH - &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL - &kp LGUI &kp DEL &kp RET &kp SPC &trans &kp RET &kp SPC &kp SPC &kp BKSP &kp RALT - >; - - // sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; - }; - - raise_layer { -// --------------------------------------------------------------------------------------------------------------------------------- -// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | -// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | -// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | -// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | - bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL - &kp LGUI &kp DEL &kp RET &kp SPC &kp RET &trans &kp SPC &kp SPC &kp BKSP &kp RALT - >; - - // sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; - }; }; }; \ No newline at end of file From 2c734c913390dd4b2ff49295b3e1107fa397aed8 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Thu, 6 Aug 2020 23:59:20 -0400 Subject: [PATCH 05/48] Whitespace tweaks. --- app/boards/shields/kyria/kyria.keymap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/boards/shields/kyria/kyria.keymap b/app/boards/shields/kyria/kyria.keymap index 1d6b9bff..6caf0733 100644 --- a/app/boards/shields/kyria/kyria.keymap +++ b/app/boards/shields/kyria/kyria.keymap @@ -12,9 +12,9 @@ // | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | // | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT >; From d57c271804328460470d13218a309e92a9397d22 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 00:00:35 -0400 Subject: [PATCH 06/48] Whitespace at EOF fix. --- app/boards/shields/kyria/kyria.keymap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/boards/shields/kyria/kyria.keymap b/app/boards/shields/kyria/kyria.keymap index 6caf0733..f689ef62 100644 --- a/app/boards/shields/kyria/kyria.keymap +++ b/app/boards/shields/kyria/kyria.keymap @@ -21,4 +21,4 @@ sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; }; }; -}; \ No newline at end of file +}; From ec4c12501ce8ca4184689e114d8e3465c8f1daf2 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 09:31:12 -0400 Subject: [PATCH 07/48] Fix up the keymap docs after the DT refactor. --- docs/docs/feature/keymaps.md | 52 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/docs/docs/feature/keymaps.md b/docs/docs/feature/keymaps.md index 3eee1458..133ce935 100644 --- a/docs/docs/feature/keymaps.md +++ b/docs/docs/feature/keymaps.md @@ -102,24 +102,39 @@ ALl the remaining keymap nodes will be nested inside of the root devicetree node }; ``` -### Layers +### Keymap Node + +Nested under the devicetree root, is the keymap node. The node _name_ itself is not critical, but the node **MUST** have a property +`compatible = "zmk,keymap"` in order to be used by ZMK. ``` - layers { - compatible = "zmk,layers"; + keymap { + compatible = "zmk,keymap"; - default: layer_0 { - label = "DEFAULT"; + // Layer nodes go here! + }; +``` + +### Layers + +Each layer of your keymap will be nested under the keymap node. Here is a sample +that defines just one layer for this keymap: + +``` + keymap { + compatible = "zmk,keymap"; + + default_layer { // --------------------------------------------------------------------------------------------------------------------------------- // | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | // | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | // | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | // | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | bindings = < - &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH - &kp TAB &kp A &kp S &mt MOD_LCTL D &mt MOD_LSFT F &kp G &kp H &mt MOD_LSFT J &mt MOD_LCTL K &kp L &kp SCLN &kp QUOT - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL - &kp LGUI &kp DEL &kp RET &kp SPC &mo 1 &mo 2 &kp SPC &kp RET &kp BKSP &kp RALT + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT >; sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; @@ -127,23 +142,12 @@ ALl the remaining keymap nodes will be nested inside of the root devicetree node }; ``` -### Keymap +Each layer should have: -``` - keymap0: keymap { - compatible = "zmk,keymap"; - label ="Default Kyria Keymap"; - layers = <&default>; - }; -``` +1. A `bindings` property this will be a list of behaviour bindings, one for each key position for the keyboard. +1. (Optional) A `sensor-bindings` property that will be a list of behavior bindings for each sensor on the keyboard. (Currently, only encoders are supported as sensor hardware, but in the future devices like trackpoints would be supported the same way) -### Chosen Keymap - -``` - chosen { - zmk,keymap = &keymap0; - }; -``` +For the full set of possible behaviors, start at the [Key Press](/docs/behavior/key-press) behavior. ### Complete Example From 5c10517c29696fdc4ba7b7574fbde0e7a68a4589 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 09:36:48 -0400 Subject: [PATCH 08/48] Show the complete keymap example inline. --- docs/docs/feature/keymaps.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/docs/feature/keymaps.md b/docs/docs/feature/keymaps.md index 133ce935..3b8ebb88 100644 --- a/docs/docs/feature/keymaps.md +++ b/docs/docs/feature/keymaps.md @@ -151,4 +151,31 @@ For the full set of possible behaviors, start at the [Key Press](/docs/behavior/ ### Complete Example -You can see a complete example if you see the [stock Kyria keymap](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/kyria.keymap). +Putting this all together, a complete [`kyria.keymap`](https://github.com/zmkfirmware/zmk/blob/main/app/boards/shields/kyria/kyria.keymap) looks like: + +``` +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { +// --------------------------------------------------------------------------------------------------------------------------------- +// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | +// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | +// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | +// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | + bindings = < + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT + >; + + sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; + }; + }; +}; +``` From ccb9b9309a37f55e0aca3c79aa1d78214a42f701 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 10:02:44 -0400 Subject: [PATCH 09/48] Script fixes, ability to prepopulate a keymap. * Use read w/ -i for initial default values. * Prompt for fetching keymap for customization, and grab latest from `main` if opted in. * Don't copy `prj.conf` from the template, pull the `.conf` from `main` also. --- docs/static/setup.sh | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index 23ab42bc..a1229948 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -7,6 +7,7 @@ title="ZMK Config Setup:" # TODO: Check for git being installed +# TODO: Check for curl being installed # TODO: Check for user.name and user.email git configs being set prompt="Pick an MCU board:" @@ -41,7 +42,7 @@ options=("Kyria" "Lily58") PS3="$prompt " # TODO: Add support for "Other" and linking to docs on adding custom shields in user config repos. # select opt in "${options[@]}" "Other" "Quit"; do -select opt in "${options[@]}" "Other" "Quit"; do +select opt in "${options[@]}" "Quit"; do case "$REPLY" in @@ -56,12 +57,16 @@ select opt in "${options[@]}" "Other" "Quit"; do esac done -read -p "GitHub Username (leave empty to skip GitHub repo creation): " github_user +read -e -p "Copy in the stock keymap for customization? [Yn]: " copy_keymap + +if [ -z "$copy_keymap" ] || [ "$copy_keymap" == "Y" ] || [ "$copy_keymap" == "y" ]; then copy_keymap="yes"; fi + +read -e -p "GitHub Username (leave empty to skip GitHub repo creation): " github_user if [ -n "$github_user" ]; then - read -p "GitHub Repo Name [zmk-config]: " repo_name + read -e -i "zmk-config" -p "GitHub Repo Name: " repo_name if [ -z "$repo_name" ]; then repo_name="zmk-config"; fi - read -p "GitHub Repo [https://github.com/${github_user}/${repo_name}.git]: " github_repo + read -e -i "https://github.com/${github_user}/${repo_name}.git" -p "GitHub Repo: " github_repo if [ -z "$github_repo" ]; then github_repo="https://github.com/${github_user}/${repo_name}.git"; fi else @@ -72,6 +77,11 @@ echo "" echo "Preparing a user config for:" echo "* MCU Board: ${board}" echo "* Shield: ${shield}" +if [ "$copy_keymap" == "yes" ]; then + echo "* Copy Keymap?: ✓" +else + echo "* Copy Keymap?: ❌" +fi if [ -n "$github_repo" ]; then echo "* GitHub Repo To Push (please create this in GH first!): ${github_repo}" fi @@ -87,14 +97,22 @@ fi git clone --single-branch $repo_path ${repo_name} cd ${repo_name} +pushd config + +curl -O "https://raw.githubusercontent.com/zmkfirmware/zmk/main/app/boards/shields/${shield}/${shield}.conf" + +if [ "$copy_keymap" == "yes" ]; then + curl -O "https://raw.githubusercontent.com/zmkfirmware/zmk/main/app/boards/shields/${shield}/${shield}.keymap" +fi + +popd + sed -i \ -e "s/BOARD_NAME/$board/" \ -e "s/SHIELD_NAME/$shield/" \ -e "s/KEYBOARD_TITLE/$shield_title/" \ .github/workflows/build.yml -mv config/prj.conf "config/${shield}.conf" - rm -rf .git git init . git add . From 726fb4b76fdcf814787f6b9474bdc40b42c80699 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 15:04:20 -0400 Subject: [PATCH 10/48] Better default configs. --- app/boards/shields/kyria/kyria.conf | 6 ++++++ app/boards/shields/lily58/lily58.conf | 2 ++ 2 files changed, 8 insertions(+) diff --git a/app/boards/shields/kyria/kyria.conf b/app/boards/shields/kyria/kyria.conf index e69de29b..f4ab45ad 100644 --- a/app/boards/shields/kyria/kyria.conf +++ b/app/boards/shields/kyria/kyria.conf @@ -0,0 +1,6 @@ +# Uncomment these two line to add support for encoders to your firmware +# CONFIG_EC11=y +# CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y + +# Uncomment the following line to enable the Kyria OLED Display +# CONFIG_ZMK_DISPLAY=y diff --git a/app/boards/shields/lily58/lily58.conf b/app/boards/shields/lily58/lily58.conf index e69de29b..73f0d9de 100644 --- a/app/boards/shields/lily58/lily58.conf +++ b/app/boards/shields/lily58/lily58.conf @@ -0,0 +1,2 @@ +# Uncomment the following line to enable the Lily58 OLED Display +# CONFIG_ZMK_DISPLAY=y From 14eef91da1d2f9f93ba66d9728408b1a12d37758 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 15:05:20 -0400 Subject: [PATCH 11/48] Better user setup docs. --- .../assets/user-setup/firmware-archive.png | Bin 0 -> 13312 bytes .../assets/user-setup/github-actions-link.png | Bin 0 -> 10573 bytes docs/docs/user-setup.md | 65 ++++++++++++++++-- docs/sidebars.js | 2 +- 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 docs/docs/assets/user-setup/firmware-archive.png create mode 100644 docs/docs/assets/user-setup/github-actions-link.png diff --git a/docs/docs/assets/user-setup/firmware-archive.png b/docs/docs/assets/user-setup/firmware-archive.png new file mode 100644 index 0000000000000000000000000000000000000000..9cb6f7deba370ec5496b488bce762db4b353da7c GIT binary patch literal 13312 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 H;`RRmo)2{u literal 0 HcmV?d00001 diff --git a/docs/docs/assets/user-setup/github-actions-link.png b/docs/docs/assets/user-setup/github-actions-link.png new file mode 100644 index 0000000000000000000000000000000000000000..c12483b1154d3401614e6183a28b6abb79f68479 GIT binary patch literal 10573 zcmcI}Wl)^Ym+lZWxclG|++Bmay9Rf64>G|axO;F7?k>R{!r%~GgFAPA|Ju7-Te~0b z*1fl=p{7nv_uFru(|sO^R8f*fK_o;3001bmG7@S408|d-djkR-LFVq`Y(Zw>ZDR=lc!L)+>|D({P{Q8baCATP;r-gXgwnEr*9{bw zsjn6CbQO}VNYpVSJrWefT({=r>U+CeCVJ~vo&t-NgIBGYuGFOnY_3X14yL_dQgldy z@=B*pa{Pr!wE~5bdG{`i^E`y(+4+p$y|)~Yb@Qgf-`W$O7`}JH2fmn-u59MTC;jpF z%}u*P?)T^E&W#VlKDCCO)2ns zx7Ic9PrqN_2l&xfoR~0wy^U42;rNeF$$whBwwF(No`oN6y3c?0{HgD99n5lKk$K(o z4KcQN!^&g>;F*&k>h#%P4By4@t>$N{=ozp|dgqA}%}BJT@ecU%!kA z=mOdj=bsJ!u$_Cyv=nn(VK^#^v2G^>Js`fH?p$>Ccde>Z{dWVjlzeNQrOR>5Qs$UiGy`l?@g`P_{69vI0#`WivWV3=vsP!hnH zCK(jVE+lJ&33>uDCj|EraS>#yk61@FTpj7Gk4H!L2~tM7%1csTIKeRJTrE{!IIdu- z)aYMk=eB36+EVrGD)s!zil$QaZJL@&CM6whmkKqrDm;Pwe&@x@8|`-+(s`7zF>p(> z-{ORa(>Xw+qDeYQ&$L!rdOj(d+WPHJgm#XH4dXQ(4(kE&LdyJ)WLTv@q1PA=#gGDQ zttvf^CdYw&kR8va>xqN5Q_t>{*Md=5pwBCu?Ufjl(D$q78z9#d++qv*j;k7w16^KH z)Bp&DkVD+?yEysJFFs2bs;EL58eL$P^B@LvziHxeir49z=(%(&2pklq|HSbpMEpKa zlo_zDz1IEt%sX!tZ7^5!kS{jlxTD?j*ra4OgV(8)rzPkS|7{)c-X-cjR;{Vj;g!PS z6RSB77D|Ej7D{I8tMgj7BOW3ggkv+UW}u6j_3`CCS@VbibTJ>gu2#4$c{uWY59$rh zue2bv2NzO~aMAv$+1p1IP&YcR8B|IL7&rgmch;F$jnOm@M{`IPrM~?tkL>**pwOxdvv67SR!(*Cdb@`u(3h*cDc(gjOOTuf?#7vK&_mJIfu!L zy+zQ;!t`K5dd}2egwXC(8>~Z~mwAz>|1_$IMpuTt$*52>L@praMP7f;Y?vTOm%UlV zsB0sXHDSxh^&nBnsHWjzA@?&yV9C0}runDbT05)dEwdDJ%e?|d)V_~nsTk2)MN_+P z{)JKjSM={SGCipYyYIYN4y%d>ORcU>Qc~7;*E*C~^?iH=Sf1rQ9Sz?Ya&`(gln5K@ zr{>&kvZL{(ND^aC@}lW(TRJ3rvr{I%y36|+tPa_g!+RQYHZ1#vvkP~zognnwP< z-Dq7658EQ!cxvPiBi+kg%j(5FO|9c_eojQTcKedAY3$QP_oF19^4J5f6^ajh{H5AcpW#-w3(s}*#Px>*vSUQr0F@-AU z#}Zdcz&ti(wkl-F_H-NGC>7MFx*Ys5WeNL6?2nsQEr#Rb4(;zG?wifLd3!~UNPUU^<@%GqvCxG-ULBWp0R`h)s*nBo zz~v-gYeEz_hY3Ia8P}Qz>+H}99#e@Z|5FxrkoQn>wN!-{!ca0)e)D7nN{^|zm$1Ux zXP-JMH|37zv}6^b;NGqzC%yDK6?jm76V`8nd5(6Ot#rr!ih@wqRm zP!3z1x`rKQh*7MOxcGRP>mykSt*$lIezC)${)Dftx(t!KklOegz}wfEG8N}(9|j=Z zV~O2hV>2HUM%nQ=x@|k5{iqtMYHkr|_xV#wGnme+$TnJOFd$OVmJ=Kh*rnRE(*c+yh?b$y4 zK!0wyG2?l+_`-lIZ*gI=#lZPynu9XX`1V7m@8^NrpBaYl6$Bmk>uTaer^`-m%SkFy z6@tt*R@1K!gcNO+P_?UlNngZph~1I-q)|pILz~(A;Z80!7w<)-`dtKa9f*X%zA9gV zg{HffMQTHCks0{zl!VLpt<=q}*;ZMSM>H>lGK713Dq5YGaEMD^@DDQ_u`Zz}umvi^ z8%1L>k8sk(O_UWzVHm6M`LH`xZ*5D7T1CG*>OE~k17UF=<=FeK@PoaM?CDt-X*5bD#BgFL+^fK`?o2#6p%s5I4f0alBo=zwn) z;b2(NwZ@<#vHcZEExFJ$;w<@_@X17pi`XRG7ewIi%(fyN=TEu9%|8{Tg(6dVRW6j4 z@yvU^TsxOw3>Cr8%VsCs#Kw_EMKrchRz=afwJ8p1CJir)(yXJDYO~5Gr{FvEAY|5ijIlDP;BrngMf;qOj2%zON#LC(&kM0Kqfd0myJOj<@I+#780TW!Xi4aty7 zTD=V&r%5!_#ECEYxH!w8gO_Yze`nC!(O@n5Vc>1=%PaO`kLhi%lSz-nZDfa&g;mV$ zP!WsQ6}V0oK6|CsY!?^apL>?(^4Vn|_8>xrY$jLWu$(uSo13E5*~#9udY*|)Om7-} z8ew}G{qgLWgDpd0++xrlx9PW1_iTueWlnX4mr$hGc{Jez=j)^3WuVk%GVGIM za1B>W%f7G@=~qsVlujsA)Zl~<7PWqz0t*|fJyw~ouK|1%3jU?#hi@e?E~OAf-7 zPsNpJF2OID3%Q%)zJ1<}Q!n+KFPu;i+MFJ-I&ZsQb91!XaLc>yJ_y*XOg2{0L8hQ6 z|3`@cp0^`md77#RPG{MTXs zME;v$|4c;jUx)n@`Pb9_pA7p?%l?n2{qrLKzU+U0+JAoAvSSTzgyWX^K&VSJWKn+u z!8r~N3?Cj$Kz?m4e1MS9;n9i7Om8qN50AK6c`|&=aZ2Zl|5{wkaM5pC@7%;EsXMya zxxtPzaDI5COO9ZP+#@V#h*9rW-rk=0?%XXnyp)!mot@#eJAu++o7ayU8TrNsz1#>P zzBSYiqvbsz;GUID$}4FK8TjO+|9W=+yJ32-sd<~%ikrrtFPDq7;@TiSTU5*QPNd!m zcve!^K%j9DN0+sH9C;Ii9P?(HJ>!3um}|uFdv&Gn*DnOMa+p-yeKChm%)%rE1tynB z2%_eHNc;V=xt)TIMac}%T3Nlzlo)`#yTT2Ul$wirn+LVbj)2SI%xippjotH&wrHCM zOG;*DBk!rT3FJG^=DL;^^#>AY=m32D!-2VeVmBZVofs3bbW$Ik5El**(Mc33{93iu zz6qPd?^V$|gq|b@o$D)%~gzR5v{uCCvtLfD(ZGHKp?Ply`3vLIr&)XP|)v=cxY(Idg+uY zHG~I;*Eks_PNEPb4u999H95Gkf$IC@o6c=U!_LuQ-giCJs%K}r+HFSbd9hCGTXW!d z(f2yj$K#4jN%`J;os2D0jOl(h2LeUGY-4yiGq59`trGUnO=#TcS!!{=OdE;?1_}$G zE)(&C6&YSPz?Wn+aAqUQWee@G#cIEQBZCG`GpuJjAA2Si=7Z3=<8@Lm;cCB4wBx!GNP<>y> zKu_NvCaeBEIXi}Ay<=ctUDFY$zq(yl$BLCo#BKj8L|eJ@bP@NJOp2NJ{Pj_#mG+v7 zPM6u;=yn6VROsU-L&SqCq-|gjb6UknMMXm)L(%e>!0)&+P3u5ki=a`XO19G(Lod zg#~;q8i^k;S(?xj`bJ)$4O!;Pmg7T7B8H zu%JQg?lDAD$|N)WtM`VW0plMd z&k2JhD=S0ukwnSj_v&ij+JwTkuwBUVY*D`%q+*$D@ zQD7?L_4uo+YmFzPLB{0#JkoetpgQHfXMKG=`WERYMzqFyvH0_n%ey-$z~G?Z zU~B|}I|p02vwozk?C&N&mZ@BU@NhKJ?b@EV5XdXV@Ouu{nJnQ8SRyYhEQH;TD5r-d zCv&d#1SxwnU&V`rxeAy*s3_!eVgOj3-o(~A?n~=7Oj<$<9lCD*$bPsxb^AO6LW+p= zi3kb#@QHyzT)TyOVsetrbHW-FBrj%4QKFPZ&A19i9T{X9q2zxe$>tW=HJE|@^XHGK zrY24oBU9a%4eh-pYb;ExpH}fmQ@JAX@Mt6mcpjLMxC$K}1GJRVriU}+f+Gldu}|2L zeS`)WprGL~>tQ7&vRYJBflLOTNL9b5#Z!-K@9bCWvvS&pex2X>>Pt)%Z5*^3o1c#q z5gRS;;=-l@DJjtw3;D){#i47fX>=T%*s?Nf;=eZ`;~PS*U16wi=TXJu+G7>6V`J8L zOZqc2Goos)?2u7gQW7w6-oAZj_s}y-OH#IanPpzeMn>Ir_O^PiiF{MfdJkf%nwlWU z?FdwFvcQd){P91o1PkjIN|H{j8*`2_C%=|XGJ6A0X; zQTXTBlv`%{XA6*qeV<`%Z6o<^7v4im98x}SMWNp(Ix;cAxJ`V`?}@zH!Y`h(9(ux6 zuJM71iOJl;f}BZF(CE;@$N!27D!NBEDr`_Nnn+ks%Gq7?}my zYCEJB10xU?Z#r)XQUn-*)Y#YEt}uXyhlick`;Vy+h!3zZ2=VYJRsH~8hm8+e+oZdV z#-b^AR;m?|)i>S7l{RNOvey}o_k3T(dpKn~D1@q@>#<@f18x>-{mhsD-^p z>*JVrcb?t{IbLB0rlusOJ3r`tjgCSA+}YX14yJaFq&Yb`zpSm`59h?rR-`xR|H1y&hT3J`-`-Qt2tl%J#jyGeHuA3u&0K zG>VxKM$?m%LsvMJlG7GeR%UnEZDLz8RCPQaLaFPeNLwlaoL`KE4OfMMQ6)%gFGpNaR#GpW6YX2+<52jUuwK`uh>eu5>W^rL{Ea!iQMh0GC zTp4xtzJ=CU=NmkJtYR@ng#+LdHuuHYs7uz$Qt3ITb74*A3QKFab3&k#4h22Bu`haA zn{LVm5hV3T99Dh(YsexH6j4sof+j-z$zAVL*Tu0fq@2hLOp0y!s1=2|mzH=vz@%SK zOAa3Mk??tY<+eRjnT)C1t)KpeUx1?X3JWPFvU<=*;{3*y62s%2XBKhPX%zdrg*v`x zrNI|X8Oqzn00rnzyR=lXwt2==qjOhx8X*u;FFNG!HOnwk@a1aF`orJ`>e*(%{lyXt zbz5eZ21W3}wm2(S%B?TB$q%jj*`&HoX>c;Da3dg%86IhJ5m4v$%-sgQsN!?mK4GvU z{43~-jfCfqVqu{qW^4=#my3~UtSH|^^C_(Y^bDUHJif7xsIS&+zZtx?W>8jMzTIN% z6;Ur{QqF$1gkl(D!I7PNc}r)*|B2p)Uq)G3`@!Vw)PCDz5V#r8Q85>^WgIm6)|!2q z?OJP~U2X79yys%%H_LFHga$V8UzdKf0HLm9u36Bx0_At8-;FB-M>sG6W|0!!*$F9+ z;JhApCU}571gDee>)zts{-CQB@5>SJa=u<-GHEj#K^5Ej)jg2{B5X`d#Do+)2Ciq5 z)Vqi`i4Z+uvYmsF1U4^Vrb!;Jjl{X8#&ORP(y|e*d*0*_hym%GE+6>aty%O~o;HM3 z&t&|&M|q%XbedthH%8C{sbNg|((rk?$_kEy?2zA@E}a>T=_;A*?Cb_C4^==X1YYM= ztj>?o&03iPn!|EnA}Qo9OTvAM>D-2VMj)%s&y zpADZL>)kpuM`q1NB=dT10*E|3Jdp8cNNJTG#%g{|Wq+2EqWPf~$;&xV6Op=kv99kZ zQjLO)?js;q)~@~f(1QknF=>_u7VH2YJNj0~GLhf*#Oa*0jnUi8h)eRzGk2~JQQ zp(lkoiH94w+r_x#_eD+%6?fr#aN~3UW2dI#diSJJKi!@nt*0{SCccw%EjG2VJ8rat zp6}&-LsxY4$E(fEgftLxIy?Ozas?x&g@M$12eU;Zg%;4zLtNcp#OyRh&*CO7PL0-$ zaa3e6MK124{kG)4fTZ;N**3O$Mf**{XqP$r@6Yd8>w|a zo252|N$ZM~P-bQwqY}77-&69Ks5liC{Hr5mmuqyITnQA~J(;|6QrnrZh4Yd%*qaqw zieFxpQ|z|4aRPr;RoQRLxlSBT&y<_NpFZsI{L|0YIt2|{y+7hX@T~I5QjvK0olff) z;VQZH%jyV7t#iM;U_JR?3LBul#GNeHQgU(Oba&H&Jon#TpT7JpeBWMI3qc!dqS+eqBK^%rB}|Z3CukWreSJ ztog;K6w{r%u0Pf;g-=&OQSqnSvzED)6(toXG9I%*|6@Lxp#Kems_JYyrw@HLch$({ zHn%1}K50CDT)Z^0Gz0=M8+L{vr7`dN&%pjDDcLz~BMUQZgS#log@CPr#B0PS-zTxd z!J!dt1_N&$W@3)mDbgvZwLz)9*lW%Xi=Sx;j}q3>4%PvPye$4 z3tWRmt=CuBLEqLo4Q5>(6!0rn>#5fa(8wUmdwYXwCB_UnJoIyg3Z$g?fK{phs=a69 zcG6Bf4zfR{WT<@Go03t%T38_Lx_Z$JQy2{}2j7lAqhYDTJx(mg-u=o&+8 z8w>U3F5nF=$KxDQP#J>W0ifraihLxzzb`&=O&fj_&#l&Nr>~J=VG={Z842WsXc|d+ zM^Sc25C(;~%&?lhRH0d^_b3RVtaXj7BUcvk&SN7)Esh2QP-$K1f;NSp4hstl8SDee zdt1%?%DC+#m~;3qza-Bol>R8!p5;{I@})B~ag4(mPyGOxo11gKFuJjGQkRs3Es|P6 z>RJ*TUn8UODIVE>fQ8dK7J>j~VxZ;j&SL`(Dh(3@6{Es-cja-!vg+(r>SUHP6Qj;{ z564@nB%ZjDbbMNQY_!vtZ}f*5cMzU)!+8MjT^MjEbUl_vIdmBllYUX+T!)4I zw^Oi&=snX_R@KST5SI9(xHyuT(C@x}FmdQTv|+x49g1KatNcT%WeL6rxJK#i$IXd% z%WFsf*3N3ng@&-ZR`nZdrB+aQL>6^V-#-XM_}TxI;h>9teg3m%(~rzzn3c)V`Xa6Q zxw*Q-tYUm_Qz{5z(rPt~ORkZ4NXxwCy>g@q5f?LV>n<9we;^jos@5$G6&I82q1W@> z)J(|80QIUttw4Aa@{==JV^b5(8wZ307^UL&KcSHfMvU3A$Rnvb(7yQcGQ)>Q+_k-L zeJ+R&R8_^8*f=5Y)bh+;fpAy`u8}V`lnxG(p(5)-f)a)qP`b(k>wbeG>z6(m&+4^y zV~qrdCYB%PNi20*53^kLmP*CME|%c|&fsZdd~^CRCHIUPHwc;k zUGG42*M2%;l=fPfm8p!01yNGIkuJS`;_ovan;ajw1NQu?(3#v2>+o~{EoZ(7x$={< zsd5g<><-6bKtT4Ym*egUgh<>RakQNckB9_6o1m>MO5)V^pA-$N(Vd>!gZ3tI6bAX0 zXA8hNDB0X;_N!A<&EV^r$6P@=+c+S*w=+lti?wRv0g9z|x0u;m*60jz8v2W{OcKcaC(-FK!TJ_pPfIAxv41_wdTg#yv{zub+T1Vv0{wx_EfqSBL?G_^ zGoCY;1+eP%7Sd)siMNy{NA2CyLaqFRN3<#K4SV>;M!n$2k1&WTz^GTR;qF+egGL19 z){turk$2eV=3SUx+GkLCY<6HeH#^emu3mhMx95h&WfbK4M?CZL-43R#=l30~ZEPv1 zsbRHiLH}ISD1@B7EtOi4``_6g{i-BCe};lXA&BwbV|4^R!)^w(5Mt3N_T8Q= z&z^*Kd)G>P9UdQx6&E1p4vO?G+AV)iOG5(!9T?L^;;EZ3_xUGqXAy zeDm3Fx)vY=RlKyjbr`hxLoP4gKl{v#c|v$$4F@4J<8xx1GdG(1yc1%sSL&_pqj3I~ z=H{j+cRY%Yj&Pui2z@dN?)C?F8cf|xNLt!p@`Hnc-0$M z5DWuXDW9g(`Ta!7vLZj?zB&mnRT;uNIguK4xuSG!UcjPop?u=eQ0?2>LML{03%@9z zmyuJF!ZS0o2VZie1~$kv7C_)a@Ax$c0O`th3IuA zR?lp4em-NdHe^E(jdWXw9tM|DkMS7%(hfP~pv^g-6$$Niu$uo1ma2D?^+u=KJr|*T z@!@hyd>Oavx&L?ToML%gvytX%#GnW{b#<&}(s-a|Xx}=&lT*oxevm#vsd3>sha; z;W>2`t8c?0AtGL0&Qks(NJ>gdewS&Ssc~3E?C=u~$_)t%qx~*Z%sP{HL68P%HV_ep zf=LKkPHyZUNYm_q@)#Yn_&qlvc-Cwf@!sHv+DA|O)hDoGavym?y0BX+>rixps&p&K znlXcjwqcT*n%d>zU$ai{1SFt?L!-?+Tr8pH3w0huCwzF|E#pxOuQ6iav{`Ef=aDlp zp~RDjKo}!8BzJ#3PnKMhJeQjPlqzG8;)S1PH{G05y}iG(Z}<- zrk%ME@g{$Db$#-(`D?$)*YdVy6R9Ua-7pTdfz71f8xD2RM_MUcN=^<85!4;dXx_oH z;spE})R5-wnl$5+XN9LxI}IK(vZL`{p0sk=+4W^_zoAT z4Cq~.conf` file that allows you to add additional configuration options to +control what features and options are built into your firmware. Opening that file with your text editor you should see +various config settings that can be commented/uncommented to modify how your firmware is built. + +### Keymap + +Once you have the basic user config completed, you can find the file in `config/.keymap` and customize from there. +Refer to the [Keymap](/docs/feature/keymaps) documentation to learn more. + +### Publishing + +After making any changes you want, you should commit the changes and then push them to GitHub. That will trigger a new +GitHub Actions job to build your firmware which you can download once it completes. + +:::note +If you need to, a review of [Learn The Basics Of Git In Under 10 Minutes](https://www.freecodecamp.org/news/learn-the-basics-of-git-in-under-10-minutes-da548267cc91/) will help you get these steps right. +::: diff --git a/docs/sidebars.js b/docs/sidebars.js index f6256725..43d4ea7c 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -1,6 +1,6 @@ module.exports = { someSidebar: { - "Getting Started": ["intro", "hardware", "user-setup", "faq"], + "Getting Started": ["intro", "hardware", "faq", "user-setup"], Features: [ "feature/keymaps", "feature/displays", From 5418918e138650273435c8d8ec93259722dcc982 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 15:15:04 -0400 Subject: [PATCH 12/48] Explicitly use bash. --- docs/docs/user-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/user-setup.md b/docs/docs/user-setup.md index 90886dc8..11b47f6a 100644 --- a/docs/docs/user-setup.md +++ b/docs/docs/user-setup.md @@ -69,14 +69,14 @@ values={[ ``` -sh -c "$(curl -fsSL https://zmkfirmware.dev/setup.sh)" +bash -c "$(curl -fsSL https://zmkfirmware.dev/setup.sh)" ``` ``` -sh -c "$(wget https://zmkfirmware.dev/setup.sh -O -)" +bash -c "$(wget https://zmkfirmware.dev/setup.sh -O -)" ``` From 4b310abb3de8fed1080ced9d0466a21a4955463a Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 15:30:37 -0400 Subject: [PATCH 13/48] Remove broken import. --- docs/docs/user-setup.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/user-setup.md b/docs/docs/user-setup.md index 11b47f6a..a2790201 100644 --- a/docs/docs/user-setup.md +++ b/docs/docs/user-setup.md @@ -6,7 +6,6 @@ sidebar_label: Installing ZMK import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import Image from '@theme/IdealImage'; Unlike other keyboard firmwares, ZMK Firmware has been built from the ground up to allow users to manage their own keyboard configurations, including keymaps, specific hardware details, etc. all outside of the From 3b841c65bb264ec4a512ac564a0c4d0daa636cee Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 15:58:09 -0400 Subject: [PATCH 14/48] Typo fix (thanks @Nicell). --- docs/docs/user-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/user-setup.md b/docs/docs/user-setup.md index a2790201..b79b5735 100644 --- a/docs/docs/user-setup.md +++ b/docs/docs/user-setup.md @@ -176,7 +176,7 @@ To flash the firmware, first put your board into bootloader mode by double click or the one that is part of your keyboard). The controller should appear in your OS as a new USB storage device. Once this happens, copy the correct UF2 file (e.g. left or right if working on a split), and paste it onto the root of that USB mass -storage device. One the flash is complete, the controller should automatically restart, and load your newfly flashed firmware. +storage device. Once the flash is complete, the controller should automatically restart, and load your newfly flashed firmware. ## Customization From dd8deceed02b9d439eca3bc10fdde5b5d0fb9c6e Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 16:29:52 -0400 Subject: [PATCH 15/48] Add basic layer behavior docs. --- docs/docs/behavior/layers.md | 41 ++++++++++++++++++++++++++++++++++++ docs/sidebars.js | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 docs/docs/behavior/layers.md diff --git a/docs/docs/behavior/layers.md b/docs/docs/behavior/layers.md new file mode 100644 index 00000000..2388cafe --- /dev/null +++ b/docs/docs/behavior/layers.md @@ -0,0 +1,41 @@ +--- +title: Layers +--- + +## Summary + +Often, you may want a certain key position to alter which layers are enabled, change the default layer, etc. +Some of those behaviors are still in the works; the ones that are working now are documented here. + +## Defines To Refer To Layers + +When working with layers, you may have several different key positions with bindings that enable/disable those layers. +To make it easier to refer to those layers in your key bindings, and to change which layers are where later, you can +add a set of `#define`s at the top of your keymap file, and use those layer in your keymap. + +For example, if you have three layers, you can add the following to the top of your keymap: + +``` +#define DEFAULT 0 +#define LOWER 1 +#define RAISE 2 +``` + +This allows you to use those defines, e.g. `LOWER` later in your keymap. + +## Momentary Layer + +The "momentary layer" behavior allows you to enable a layer while a certain key is pressed. Immediately upon +activation of the key, the layer is enabled, and immediately open release of the key, the layer is disabled +again. + +### Behavior Binding + +- Reference: `&mo` +- Parameter: The layer number to enable/disable, e.g. `1` + +Example: + +``` +&mo LOWER +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 43d4ea7c..7b04864a 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -7,7 +7,7 @@ module.exports = { "feature/encoders", "feature/underglow", ], - Behaviors: ["behavior/key-press"], + Behaviors: ["behavior/key-press", "behavior/layers"], Development: [ "dev-clean-room", "dev-setup", From b25133cbefbeecbdde0ba6206d9cd54d7f2c1a12 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 16:45:11 -0400 Subject: [PATCH 16/48] Add a layer diagram. * Original diagram from Thomas Baart, used with permission. --- .../assets/features/keymaps/layer-diagram.png | Bin 0 -> 53975 bytes docs/docs/feature/keymaps.md | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 docs/docs/assets/features/keymaps/layer-diagram.png diff --git a/docs/docs/assets/features/keymaps/layer-diagram.png b/docs/docs/assets/features/keymaps/layer-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..7b42daae87c7ac600d39c81252f7f1ab932ac334 GIT binary patch literal 53975 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

9^W zdcOPKS!MUj-W!^X9YI0c%=6tz3Ms6RJRq~ljsdniEhPpmt-Pkm!s34=Aa+<42+O zy&4bun@VJbFYNtso4JS{F!Ja*))#@z*!@2@6UN~!;(7K2aF0nVv`>2~J<98T-pUje z0}wAU@wu_&*{+ps;jRlhP7<1D=o0>KCpskED<5i0@a9=b3^%~8gE6tftgUD9N@fh9sM5grqHjvP`BJS#>z*IACRzsNidUKGnouGH~$z zN$uB1fWTJ{;>I`u{NRx^NFlxFAnQ?GgCS-dfOyh@!uFpa+=dJ*wE`-Jw+!@1gn6ba z4*E^-o>(}o{yPcyK7mNcKMBR5g&uya({kJUB}9kzNd^xNIzD*#TNJaGq2&J>FG?IS z=wVj#_gLNmP_#iEm~kHmoeEkxt{lZbxLWhY9Oqw4@m7=nn}|M&2F@GzC^Jp(aHAL!v1Do8w3F){FP*h7YE2GlS# zN{sh^E$3U&Vu^dEs3?%D{*w7{;5royjb8}$A3xsCf=6})Zj@ttA4A8|Sd+Da^D7Y&4>L;m zCJoGwQC|Y{pDbMmbf}1qbKV591mXtF8~-Cqwm_DC_L7DFJHs$Da4d@?tRvtL=HR_= zb3h}{fj>s*FE&rY<_%vvNH6}Qc`+V*(G^Yp0Qh5V@E$8KuzgKHa_5OoepMo>iD#y8 z`Tm`XD|9O4KUKIufD{hi!@>cYzyjVnmK2`aq=xwkcu8RYBRK`~l61vtU=QP9gRO+P zH$g-r2uYRF3mQGnYAaM}y(SHzx3Y!QtmGlsB!6ehU5xGk`UcpW9zI_2)fJ`((6; z7q2#mi~b%us!wuY`T`|8bX4*Tm@~L7tv)naPyPlcoEQ?4kS|YIh|~jkTi#V9SHFEwJ(kOGe=fl5Q<_b(tlr>01D#{FM2M9=G;4Ta_R-k^V{M5X+t51sM7 z|9i%(<=~8y;e+^pxBV4ELKbX0bFSGRjYd&4_QdAwtZMh$yvgp;*sSc)-8;tH4M#z* zi^|r?rC<|w3;D0{kAM278XwdiEp4iAcP2P(chjZXM22$Pa(#^ATfN5Dq~cn0>{FI$ zdXTGfS&pA!+!?Wh7W4TC(7{ws`L=rXrsUe+t+hmnMnRk~Z!eK%D7N`w}F#sbd$2!a*U$XVv{z0 zucy28#rMS014I#`BB(k}7#FbbMcAN32KxSec6GNqaf`J|{E2o`#UE5wMlE^_&sl_m z;c#pkMH%sHcHy6{|I>g`)HG@k-$oqZ%(cYf4J-xJ-m^QLYAyYhX;Ql2Pr(r(3YQPP zMH3sRC*deyiDGKElbaizIjW%1RiU9z?@!@e%qF0H^USvK!oSDkkSm_mMA06tg1dUw z^Q_ z_@wW3p)gu-^-Ry*C`37R;(=&4QcqW!T<$T4IpgwYyh4%;;uiOU)m+YRDv(l-FYP$I}1r%^6 zHF>}62|G%iBB8aQAHI;D94a85nsU*1H>$7`IXz9>jwB=J%PuIlR_+(LMwO326N^L3 z{Dq1JhQ`bH9_`N=qw{;y2OuOv;EGY{-D9`;4`o_oySp2^tkEPWZW$e0|)mm^+_+lQ|U!9B=8PDv= zW!PtOV1*xixpBnE?eKDWLoxtyHv&G~;d>T(!#IWT=j8$!XjSkk4Ln{+^@@%m^XsDF zNCm⩔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 NSxH5SLNS9E{|82zP0RoQ literal 0 HcmV?d00001 diff --git a/docs/docs/feature/keymaps.md b/docs/docs/feature/keymaps.md index 3b8ebb88..020df176 100644 --- a/docs/docs/feature/keymaps.md +++ b/docs/docs/feature/keymaps.md @@ -39,11 +39,13 @@ Like many mechanical keyboard firmwares, ZMK keymaps are composed of a collectio minimum of at least one layer that is the default, usually near the bottom of the "stack". Each layer in ZMK contains a set of bindings that bind a certain behavior to a certain key position in that layer. +| ![Diagram of three layers](../assets/features/keymaps/layer-diagram.png) | +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| _A simplified diagram showing three layers. The layout of each layer is the same (they all contain four keys), but the behavior bindings within each layer can be different._ | + In addition to the base default layer (which can be changed), certain bound behaviors may also enable/disable additional layers "on top" of the default layer. -**TODO**: A diagram to help visualize layers - When a key location is pressed/released, the stack of all active layers from "top to bottom" is used, and the event is sent to the behavior bound at that position in each layer, for it to perform whatever actions it wants to in reaction to the event. Those behaviors can choose to "handle" the event, and stop From 5039a508311bcd74c1aaef7542c3b6e1c6a01929 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 7 Aug 2020 23:35:48 -0400 Subject: [PATCH 17/48] Tweak git usage for backwards compatibility. * Closes #76 --- docs/static/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index a1229948..1a72224f 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -120,7 +120,7 @@ git commit -m "Initial User Config." if [ -n "$github_repo" ]; then git remote add origin "$github_repo" - git push --set-upstream origin $(git branch --show-current) + git push --set-upstream origin $(git symbolic-ref --short HEAD) # TODO: Support determing the actions URL when non-https:// repo URL is used. if [ "${github_repo}" != "${github_repo#https://}" ]; then From 85d35c7ff987f58c2f43ae6eae0387d923124d4a Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sat, 8 Aug 2020 12:25:54 -0400 Subject: [PATCH 18/48] Tweak the pip PATH callouts. --- docs/docs/dev-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/dev-setup.md b/docs/docs/dev-setup.md index 9e4d9395..016b14cf 100644 --- a/docs/docs/dev-setup.md +++ b/docs/docs/dev-setup.md @@ -188,7 +188,7 @@ West can be installed by using the `pip` python package manager. pip3 install --user -U west ``` -:::tip pip user packages +:::danger pip user packages If you haven't done so yet, you may need to add the Python Pip user package directory to your `PATH`, e.g.: ``` @@ -306,7 +306,7 @@ cd zmk west init -l app/ ``` -:::note +:::caution Command Not Found? If you encounter errors like `command not found: west` then your `PATH` environment variable is likely missing the Python 3 user packages directory. See the [West Build Command](#west-build-command) section again for links to how to do this From 483530e0ad8c882d4c29f397f620e578efd5ee9d Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sat, 8 Aug 2020 17:10:12 -0400 Subject: [PATCH 19/48] Basic key press/consumer press behavior docs. --- docs/docs/behavior/key-press.md | 58 ++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/docs/behavior/key-press.md b/docs/docs/behavior/key-press.md index 8a69c4f8..08296bb9 100644 --- a/docs/docs/behavior/key-press.md +++ b/docs/docs/behavior/key-press.md @@ -2,4 +2,60 @@ title: Key Presses --- -TODO: Docs on key press behavior +## Summary + +The most basic of behaiors, is the ability to send certain keycode presses and releases in response to activating +a certain key. + +For reference on keycode values, see the [USB HID Usage Tables](https://www.usb.org/document-library/hid-usage-tables-12). + +## Keycode Defines + +To make it easier to encode the HID keycode numeric values, most keymaps include +the [`dt-bindings/zmk/keys.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/keys.h) header +provided by ZMK near the top: + +``` +#include +``` + +Doing so makes a set of defines such as `A`, `NUM_1`, etc. available for use with these behaviors + +:::note +There is an [open issue](https://github.com/zmkfirmware/zmk/issues/21) to provide a more comprehensive, and +complete set of defines for the full keypad and consumer usage pages in the future for ZMK. +::: + +## Keypad Key Press + +The "keypad key press" behavior sends standard keypad keycodes on press/release. + +### Behavior Binding + +- Reference: `&kp` +- Parameter: The keycode usage ID from the keypad usage page, e.g. `4` or `A` + +Example: + +``` +&kp A +``` + +## Consumer Key Press + +The "consumer key press" behavior allows you to send "consumer" usage page keycodes on press/release. +These are mostly used for media and power related keycodes, such as sending "Pause", "Scan Track Next", +"Scan Track Previous", etc. + +There are a subset of the full consumer usage IDs found in the `keys.h` include, prefixed with `M_`, e.g. `M_PREV`. + +### Behavior Binding + +- Reference: `&cp` +- Parameter: The keycode usage ID from the consumer usage page, e.g. `M_PREV` or `M_EJCT` + +Example: + +``` +&cp M_PREV +``` From e2848c66c305442badd2b21a414c1a8fe89cd4fd Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sat, 8 Aug 2020 17:28:00 -0400 Subject: [PATCH 20/48] Fix to archive the .hex files for proton-c build. * Closes #77. --- docs/static/setup.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index 1a72224f..327fc84b 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -113,6 +113,11 @@ sed -i \ -e "s/KEYBOARD_TITLE/$shield_title/" \ .github/workflows/build.yml +if [ "$board" == "proton_c" ]; then + # Proton-C board still fa + sed -i -e "s/uf2/hex/g" .github/workflows/build.yml +fi + rm -rf .git git init . git add . From c92f114efe14e1ad7f7fafbb2f3cc402cefa1bbe Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 8 Aug 2020 17:23:11 -0500 Subject: [PATCH 21/48] Add lighting behavior docs --- docs/docs/behavior/lighting.md | 50 ++++++++++++++++++++++++++++++++++ docs/sidebars.js | 6 +++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/docs/behavior/lighting.md diff --git a/docs/docs/behavior/lighting.md b/docs/docs/behavior/lighting.md new file mode 100644 index 00000000..432960e3 --- /dev/null +++ b/docs/docs/behavior/lighting.md @@ -0,0 +1,50 @@ +--- +title: Lighting +--- + +## Summary + +Lighting is often used for either aesthetics or for the practical purposes of lighting up keys in the dark. +Currently ZMK supports RGB underglow, which can be changed and configured using its behavior. + +## RGB Action Defines + +RGB actions defines are provided through the [`dt-bindings/zmk/rgb.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/rgb.h) header, +which is added at the top of the keymap file: + +``` +#include +``` + +This will allow you to reference the actions defined in this header such as `RGB_TOG`. + +Here is a table describing the action for each define: + +| Define | Action | +|-----------|-----------------------------------------------------------| +| `RGB_TOG` | Toggles the RGB feature on and off | +| `RGB_HUI` | Increases the hue of the RGB feature | +| `RGB_HUD` | Decreases the hue of the RGB feature | +| `RGB_SAI` | Increases the saturation of the RGB feature | +| `RGB_SAD` | Decreases the saturation of the RGB feature | +| `RGB_BRI` | Increases the brightness of the RGB feature | +| `RGB_BRD` | Decreases the brightness of the RGB feature | +| `RGB_SPI` | Increases the speed of the RGB feature effect's animation | +| `RGB_SPD` | Decreases the speed of the RGB feature effect's animation | +| `RGB_EFF` | Cycles the RGB feature's effect forwards | +| `RGB_EFR` | Cycles the RGB feature's effect reverse | + +## RGB Underglow + +The "RGB underglow" behavior completes an RGB action given on press. + +### Behavior Binding + +- Reference: `&rgb_ug` +- Parameter: The RGB action define, e.g. `RGB_TOG` or `RGB_BRI` + +Example: + +``` +&rgb_ug RGB_TOG +``` \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index 7b04864a..1bd0358f 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -7,7 +7,11 @@ module.exports = { "feature/encoders", "feature/underglow", ], - Behaviors: ["behavior/key-press", "behavior/layers"], + Behaviors: [ + "behavior/key-press", + "behavior/layers", + "behavior/lighting", + ], Development: [ "dev-clean-room", "dev-setup", From d901a0061dec7abe5d5cd29c616abcda110df9b1 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 8 Aug 2020 19:02:00 -0500 Subject: [PATCH 22/48] Add underglow feature docs + DT fixes --- .../shields/kyria/boards/nice_nano.overlay | 1 - app/boards/shields/kyria/kyria.conf | 3 + docs/docs/feature/underglow.md | 116 +++++++++++++++++- 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/app/boards/shields/kyria/boards/nice_nano.overlay b/app/boards/shields/kyria/boards/nice_nano.overlay index fe07d172..f51ecfeb 100644 --- a/app/boards/shields/kyria/boards/nice_nano.overlay +++ b/app/boards/shields/kyria/boards/nice_nano.overlay @@ -1,6 +1,5 @@ &spi1 { compatible = "nordic,nrf-spi"; - /* Cannot be used together with i2c0. */ status = "okay"; mosi-pin = <6>; // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. diff --git a/app/boards/shields/kyria/kyria.conf b/app/boards/shields/kyria/kyria.conf index f4ab45ad..eb272137 100644 --- a/app/boards/shields/kyria/kyria.conf +++ b/app/boards/shields/kyria/kyria.conf @@ -4,3 +4,6 @@ # Uncomment the following line to enable the Kyria OLED Display # CONFIG_ZMK_DISPLAY=y + +# Uncomment the following lineto enable RGB underglow +# CONFIG_ZMK_RGB_UNDERGLOW=y diff --git a/docs/docs/feature/underglow.md b/docs/docs/feature/underglow.md index 0bf9a8d9..02aac5d6 100644 --- a/docs/docs/feature/underglow.md +++ b/docs/docs/feature/underglow.md @@ -1,5 +1,119 @@ --- title: RGB Underglow +sidebar_label: RGB Underglow --- -TODO: Documentation on RGB underglow. +RGB underglow is a feature used to control "strips" of RGB LEDs. Most of the time this is called underglow and creates a glow underneath the board using a ring of LEDs around the edge, hence the name. However, this can be extended to be used to control anything from a single LED to a long string of LEDs anywhere on the keyboard. + +ZMK supports all the RGB LEDs supported by Zephyr. Here's the current list supported: + +- WS2812-ish (WS2812B, WS2813, SK6812, or compatible) +- APA102 +- LPD880x (LPD8803, LPD8806, or compatible) + +Of the compatible types, the WS2812 LED family is by far the most popular type. Currently each of these types of LEDs are expected to be run using SPI with a couple of exceptions. + +Here you can see the RGB underglow feature in action using WS2812 LEDs. + + + +## Enabling RGB Underglow + +To enable RGB underglow on your board or shield, simply enable the `ZMK_RGB_UNDERGLOW` configuration value in the `.conf` file of your user config directory as such: + +``` +CONFIG_ZMK_RGB_UNDERGLOW=y +``` + +If your board or shield does not have RGB underglow configured, refer to [Adding RGB Underglow to a Board](#adding-rgb-underglow-to-a-board). + +## Configuring RGB Underglow + +There are various Kconfig options used to configure the RGB underglow feature. These can all be set in the `.conf` file. + +| Option | Description | Default | +| ---------------------------- | ---------------------------------------------- | ------- | +| `ZMK_RGB_UNDERGLOW_HUE_STEP` | Hue step in degrees of 360 used by RGB actions | `10` | +| `ZMK_RGB_UNDERGLOW_SAT_STEP` | Saturation step in percent used by RGB actions | `10` | +| `ZMK_RGB_UNDERGLOW_BRT_STEP` | Brightness step in percent used by RGB actions | `10` | + +## Adding RGB Underglow to a Board + +RGB underglow is always added to a board, not a shield. This is a consequence of needing to configure SPI to control the LEDs. +If you have a shield with RGB underglow, you must add a `boards/` directory within your shield folder to define the RGB underglow individually for each board that supports the shield. +Inside the `boards/` folder, you define a `.overlay` for each different board. +For example, the Kyria shield has a `boards/nice_nano.overlay` file that defines the RGB underglow for the `nice_nano` board specifically. + +The first step to adding support for underglow is to select you SPI output. With nRF52 boards, you can just use `&spi1` and define the pins you want to use. +For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip. + +Here's an example of an nRF52 SPI definition: + +``` +&spi1 { + compatible = "nordic,nrf-spi"; + status = "okay"; + mosi-pin = <6>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <5>; + miso-pin = <7>; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + label = "WS2812"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <4000000>; + + /* WS2812 */ + chain-length = <10>; /* number of LEDs */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + }; +}; +``` + +:::info + +If you are configuring SPI for an nRF52840 (or other nRF52) based board, double check that you are using pins that aren't restricted to low frequency I/O. +Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0). + +::: + +Here's another example for a non-nRF52 board on `spi1`: + +``` +&spi1 { + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + label = "WS2812"; + + /* SPI */ + reg = <0>; + spi-max-frequency = <5250000>; + + /* WS2812 */ + chain-length = <10>; /* number of LEDs */ + spi-one-frame = <0x70>; /* make sure to configure this properly for your SOC */ + spi-zero-frame = <0x40>; /* make sure to configure this properly for your SOC */ + }; +}; +``` + +Once you have your `led_strip` properly defined you need to add it to the root devicetree node `chosen` element: + +``` +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; +``` + +Finally you need to enable the `ZMK_RGB_UNDERGLOW` configuration value in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): + +``` +CONFIG_ZMK_RGB_UNDERGLOW=y +``` From 8d3ac00f31e30c0ed410035067079468afa56126 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 8 Aug 2020 19:02:54 -0500 Subject: [PATCH 23/48] Add missing space to comment --- app/boards/shields/kyria/kyria.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/boards/shields/kyria/kyria.conf b/app/boards/shields/kyria/kyria.conf index eb272137..fa98d1cd 100644 --- a/app/boards/shields/kyria/kyria.conf +++ b/app/boards/shields/kyria/kyria.conf @@ -5,5 +5,5 @@ # Uncomment the following line to enable the Kyria OLED Display # CONFIG_ZMK_DISPLAY=y -# Uncomment the following lineto enable RGB underglow +# Uncomment the following line to enable RGB underglow # CONFIG_ZMK_RGB_UNDERGLOW=y From fd77fdb63d5fe8a031f0adfa1a4f5964e6f339ef Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 8 Aug 2020 19:10:10 -0500 Subject: [PATCH 24/48] Fix video container size --- docs/docs/feature/underglow.md | 4 +++- docs/src/css/custom.css | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/docs/feature/underglow.md b/docs/docs/feature/underglow.md index 02aac5d6..780845af 100644 --- a/docs/docs/feature/underglow.md +++ b/docs/docs/feature/underglow.md @@ -15,7 +15,9 @@ Of the compatible types, the WS2812 LED family is by far the most popular type. Here you can see the RGB underglow feature in action using WS2812 LEDs. - +

+ +
## Enabling RGB Underglow diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index f46a5653..d9cddb85 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -23,3 +23,21 @@ margin: 0 calc(-1 * var(--ifm-pre-padding)); padding: 0 var(--ifm-pre-padding); } + +.video-container { + height: 0; + margin: 0; + margin-bottom: 30px; + overflow: hidden; + padding-bottom: 56.25%; + padding-top: 30px; + position: relative; +} + +.video-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} From 3127192720c9da848e14625c34ce3f6329eb0a0f Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sun, 9 Aug 2020 12:13:39 -0400 Subject: [PATCH 25/48] Invoke called behavior after layer change. * If you press a key with a layer active, then deactivate the layer (e.g. releasing a `&mo`, then release the key, we currently may send the wrong key release event. * Fixes #67. --- app/src/keymap.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/app/src/keymap.c b/app/src/keymap.c index aee05773..ff494f76 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -34,9 +34,6 @@ static u8_t zmk_keymap_layer_default = 0; #define TRANSFORMED_LAYER(node) \ { UTIL_LISTIFY(DT_PROP_LEN(node, bindings), _TRANSFORM_ENTRY, node) }, -static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { - DT_INST_FOREACH_CHILD(0, TRANSFORMED_LAYER) -}; #if ZMK_KEYMAP_HAS_SENSORS #define _TRANSFORM_SENSOR_ENTRY(idx, layer) \ @@ -50,6 +47,21 @@ static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_ ({ UTIL_LISTIFY(DT_PROP_LEN(node, sensor_bindings), _TRANSFORM_SENSOR_ENTRY, node) }), \ ({})), +#endif /* ZMK_KEYMAP_HAS_SENSORS */ + +// State + +// When a behavior handles a key position "down" event, we record that layer +// here so that even if that layer is deactivated before the "up", event, we +// still send the release event to the behavior in that layer also. +static u8_t zmk_keymap_active_behavior_layer[ZMK_KEYMAP_LEN]; + +static struct zmk_behavior_binding zmk_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { + DT_INST_FOREACH_CHILD(0, TRANSFORMED_LAYER) +}; + +#if ZMK_KEYMAP_HAS_SENSORS + static struct zmk_behavior_binding zmk_sensor_keymap[ZMK_KEYMAP_LAYERS_LEN][ZMK_KEYMAP_SENSORS_LEN] = { DT_INST_FOREACH_CHILD(0, SENSOR_LAYER) }; @@ -74,11 +86,18 @@ int zmk_keymap_layer_deactivate(u8_t layer) SET_LAYER_STATE(layer, false); }; +bool is_active_position(u32_t position, u8_t layer) +{ + return (zmk_keymap_layer_state & BIT(layer)) == BIT(layer) + || layer == zmk_keymap_layer_default + || zmk_keymap_active_behavior_layer[position] == layer; +} + int zmk_keymap_position_state_changed(u32_t position, bool pressed) { for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= zmk_keymap_layer_default; layer--) { - if ((zmk_keymap_layer_state & BIT(layer)) == BIT(layer) || layer == zmk_keymap_layer_default) + if (is_active_position(position, layer)) { struct zmk_behavior_binding *binding = &zmk_keymap[layer][position]; struct device *behavior; @@ -104,8 +123,10 @@ int zmk_keymap_position_state_changed(u32_t position, bool pressed) continue; } else if (ret < 0) { LOG_DBG("Behavior returned error: %d", ret); + zmk_keymap_active_behavior_layer[position] = 0; return ret; } else { + zmk_keymap_active_behavior_layer[position] = pressed ? layer : 0; return ret; } } From be57b10c56885315ad56b78cc2f65a955029bccf Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 11:29:16 -0400 Subject: [PATCH 26/48] Initial Corne shield definition. * Also include a build for Clueboard California macropad. --- .github/workflows/build.yml | 5 ++ app/boards/shields/corne/Kconfig.defconfig | 58 ++++++++++++++ app/boards/shields/corne/Kconfig.shield | 8 ++ .../shields/corne/boards/nice_nano.overlay | 29 +++++++ app/boards/shields/corne/corne.conf | 4 + app/boards/shields/corne/corne.dtsi | 79 +++++++++++++++++++ app/boards/shields/corne/corne.keymap | 22 ++++++ app/boards/shields/corne/corne_left.conf | 2 + app/boards/shields/corne/corne_left.overlay | 18 +++++ app/boards/shields/corne/corne_right.conf | 2 + app/boards/shields/corne/corne_right.overlay | 23 ++++++ 11 files changed, 250 insertions(+) create mode 100644 app/boards/shields/corne/Kconfig.defconfig create mode 100644 app/boards/shields/corne/Kconfig.shield create mode 100644 app/boards/shields/corne/boards/nice_nano.overlay create mode 100644 app/boards/shields/corne/corne.conf create mode 100644 app/boards/shields/corne/corne.dtsi create mode 100644 app/boards/shields/corne/corne.keymap create mode 100644 app/boards/shields/corne/corne_left.conf create mode 100644 app/boards/shields/corne/corne_left.overlay create mode 100644 app/boards/shields/corne/corne_right.conf create mode 100644 app/boards/shields/corne/corne_right.overlay diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41091612..5eb79716 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,10 +10,15 @@ jobs: matrix: board: [proton_c, nice_nano] shield: + - corne_left + - corne_right - kyria_left - kyria_right - lily58_left - lily58_right + include: + - board: proton_c + shield: clueboard_california steps: # To use this repository's private action, # you must check out the repository diff --git a/app/boards/shields/corne/Kconfig.defconfig b/app/boards/shields/corne/Kconfig.defconfig new file mode 100644 index 00000000..8878da3d --- /dev/null +++ b/app/boards/shields/corne/Kconfig.defconfig @@ -0,0 +1,58 @@ + +if SHIELD_CORNE_LEFT + +config ZMK_KEYBOARD_NAME + default "Corne Left" + +endif + + +if SHIELD_CORNE_RIGHT + +config ZMK_KEYBOARD_NAME + default "Corne Right" + +endif + +if SHIELD_CORNE_LEFT || SHIELD_CORNE_RIGHT + +config ZMK_SPLIT + default y + +if ZMK_DISPLAY + +config I2C + default y + +config SSD1306 + default y + +config SSD1306_REVERSE_MODE + default y + +endif # ZMK_DISPLAY + +if LVGL + +config LVGL_HOR_RES + default 128 + +config LVGL_VER_RES + default 32 + +config LVGL_VDB_SIZE + default 64 + +config LVGL_DPI + default 148 + +config LVGL_BITS_PER_PIXEL + default 1 + +choice LVGL_COLOR_DEPTH + default LVGL_COLOR_DEPTH_1 +endchoice + +endif # LVGL + +endif diff --git a/app/boards/shields/corne/Kconfig.shield b/app/boards/shields/corne/Kconfig.shield new file mode 100644 index 00000000..3cac86fe --- /dev/null +++ b/app/boards/shields/corne/Kconfig.shield @@ -0,0 +1,8 @@ +# Copyright (c) 2020 Pete Johanson +# SPDX-License-Identifier: MIT + +config SHIELD_CORNE_LEFT + def_bool $(shields_list_contains,corne_left) + +config SHIELD_CORNE_RIGHT + def_bool $(shields_list_contains,corne_right) diff --git a/app/boards/shields/corne/boards/nice_nano.overlay b/app/boards/shields/corne/boards/nice_nano.overlay new file mode 100644 index 00000000..c7c3eb8e --- /dev/null +++ b/app/boards/shields/corne/boards/nice_nano.overlay @@ -0,0 +1,29 @@ +&spi1 { + compatible = "nordic,nrf-spi"; + /* Cannot be used together with i2c0. */ + status = "okay"; + mosi-pin = <6>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <5>; + miso-pin = <7>; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + label = "SK6812mini"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <4000000>; + + /* WS2812 */ + chain-length = <6>; /* There are per-key RGB, but the first 6 are underglow */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/corne/corne.conf b/app/boards/shields/corne/corne.conf new file mode 100644 index 00000000..2723b038 --- /dev/null +++ b/app/boards/shields/corne/corne.conf @@ -0,0 +1,4 @@ +# TODO: Add lines about underglow! + +# Uncomment the following line to enable the Corne OLED Display +# CONFIG_ZMK_DISPLAY=y diff --git a/app/boards/shields/corne/corne.dtsi b/app/boards/shields/corne/corne.dtsi new file mode 100644 index 00000000..da48d62c --- /dev/null +++ b/app/boards/shields/corne/corne.dtsi @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Pete Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <12>; + rows = <4>; +// | SW1 | SW2 | SW3 | SW4 | SW5 | SW6 | | SW6 | SW5 | SW4 | SW3 | SW2 | SW1 | +// | SW7 | SW8 | SW9 | SW10 | SW11 | SW12 | | SW12 | SW11 | SW10 | SW9 | SW8 | SW7 | +// | SW13 | SW14 | SW15 | SW16 | SW17 | SW18 | | SW18 | SW17 | SW16 | SW15 | SW14 | SW13 | +// | SW19 | SW20 | SW21 | | SW21 | SW20 | SW19 | + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) RC(0,12) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) RC(1,12) +RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) RC(2,12) + RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8) + >; + }; + + five_column_transform: keymap_transform_1 { + compatible = "zmk,matrix-transform"; + columns = <10>; + rows = <4>; +// | SW2 | SW3 | SW4 | SW5 | SW6 | | SW6 | SW5 | SW4 | SW3 | SW2 | +// | SW8 | SW9 | SW10 | SW11 | SW12 | | SW12 | SW11 | SW10 | SW9 | SW8 | +// | SW14 | SW15 | SW16 | SW17 | SW18 | | SW18 | SW17 | SW16 | SW15 | SW14 | +// | SW19 | SW20 | SW21 | | SW21 | SW20 | SW19 | + map = < +RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10) +RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10) +RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10) + RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) RC(3,8) + >; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + + diode-direction = "col2row"; + row-gpios + = <&pro_micro_d 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro_d 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro_d 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro_d 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + }; + + // TODO: RGB node(s) +}; + +&pro_micro_i2c { + status = "okay"; + + ssd1306@3c { + compatible = "solomon,ssd1306fb"; + reg = <0x3c>; + label = "DISPLAY"; + width = <128>; + height = <32>; + segment-offset = <0>; + page-offset = <0>; + display-offset = <0>; + multiplex-ratio = <63>; + prechargep = <0x22>; + }; +}; diff --git a/app/boards/shields/corne/corne.keymap b/app/boards/shields/corne/corne.keymap new file mode 100644 index 00000000..2f7f38b9 --- /dev/null +++ b/app/boards/shields/corne/corne.keymap @@ -0,0 +1,22 @@ +#include +#include + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { +// --------------------------------------------------------------------------------------------------------------------------------- +// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | +// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | +// | SHIFT | Z | X | C | V | B | | N | M | , | . | / | CTRL | +// | GUI | DEL | RET | | TAB | BSPC | R-ALT | + bindings = < + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp TAB &kp BKSP &kp RALT + >; + }; + }; +}; diff --git a/app/boards/shields/corne/corne_left.conf b/app/boards/shields/corne/corne_left.conf new file mode 100644 index 00000000..e51dee44 --- /dev/null +++ b/app/boards/shields/corne/corne_left.conf @@ -0,0 +1,2 @@ +CONFIG_ZMK_SPLIT=y +CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL=y \ No newline at end of file diff --git a/app/boards/shields/corne/corne_left.overlay b/app/boards/shields/corne/corne_left.overlay new file mode 100644 index 00000000..399bddd1 --- /dev/null +++ b/app/boards/shields/corne/corne_left.overlay @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 Pete Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include "corne.dtsi" + +&kscan0 { + col-gpios + = <&pro_micro_a 3 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 2 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 1 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 0 GPIO_ACTIVE_HIGH> + , <&pro_micro_d 15 GPIO_ACTIVE_HIGH> + , <&pro_micro_d 14 GPIO_ACTIVE_HIGH> + ; +}; diff --git a/app/boards/shields/corne/corne_right.conf b/app/boards/shields/corne/corne_right.conf new file mode 100644 index 00000000..a835adc1 --- /dev/null +++ b/app/boards/shields/corne/corne_right.conf @@ -0,0 +1,2 @@ +CONFIG_ZMK_SPLIT=y +CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL=y \ No newline at end of file diff --git a/app/boards/shields/corne/corne_right.overlay b/app/boards/shields/corne/corne_right.overlay new file mode 100644 index 00000000..652d5edd --- /dev/null +++ b/app/boards/shields/corne/corne_right.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 Pete Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include "corne.dtsi" + +&default_transform { + col-offset = <6>; +}; + +&kscan0 { + col-gpios + = <&pro_micro_d 14 GPIO_ACTIVE_HIGH> + , <&pro_micro_d 15 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 0 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 1 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 2 GPIO_ACTIVE_HIGH> + , <&pro_micro_a 3 GPIO_ACTIVE_HIGH> + ; +}; + From aebeb7a153eb11e2f53b8def7235a021effd7e1f Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 12:36:12 -0400 Subject: [PATCH 27/48] Updated config note about underglow. --- app/boards/shields/corne/corne.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/boards/shields/corne/corne.conf b/app/boards/shields/corne/corne.conf index 2723b038..c8e3c00c 100644 --- a/app/boards/shields/corne/corne.conf +++ b/app/boards/shields/corne/corne.conf @@ -1,4 +1,5 @@ -# TODO: Add lines about underglow! +# Uncomment the following line to enable the Corne RGB Underglow +# ZMK_RGB_UNDERGLOW=y # Uncomment the following line to enable the Corne OLED Display # CONFIG_ZMK_DISPLAY=y From 851d54ad960cbcb914cef83c9d1f7fcec49ecf21 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 12:37:05 -0400 Subject: [PATCH 28/48] Add Corne to setup.sh script. --- docs/static/setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index 327fc84b..42089434 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -37,7 +37,7 @@ echo "" echo "Keyboard Shield Selection:" prompt="Pick an keyboard:" -options=("Kyria" "Lily58") +options=("Kyria" "Lily58" "Corne") PS3="$prompt " # TODO: Add support for "Other" and linking to docs on adding custom shields in user config repos. @@ -48,6 +48,7 @@ select opt in "${options[@]}" "Quit"; do 1 ) shield_title="Kyria" shield="kyria"; split="y"; break;; 2 ) shield_title="Lily58" shield="lily58"; split="y"; break;; + 3 ) shield_title="Corne" shield="corne"; split="y"; break;; # Add link to docs on adding your own custom shield in your ZMK config! # $(( ${#options[@]}+1 )) ) echo "Other!"; break;; From 3f748143796c96468bc3626b6b77397d5fa31eef Mon Sep 17 00:00:00 2001 From: CrossR Date: Mon, 10 Aug 2020 17:43:57 +0100 Subject: [PATCH 29/48] Quick doc update. --- docs/docs/dev-boards-shields-keymaps.md | 6 +++--- docs/docs/dev-guide-new-shield.md | 12 ++++++------ docs/docs/dev-posix-board.md | 6 +++--- docs/docs/dev-setup.md | 19 ++++++++++++++----- docs/docs/user-setup.md | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/docs/docs/dev-boards-shields-keymaps.md b/docs/docs/dev-boards-shields-keymaps.md index bda7d9ff..cfe52526 100644 --- a/docs/docs/dev-boards-shields-keymaps.md +++ b/docs/docs/dev-boards-shields-keymaps.md @@ -8,11 +8,11 @@ title: Boards, Shields, and Keymaps The foundational elements needed to get a specific keyboard working with ZMK can be broken down into: - A [KSCAN driver](https://docs.zephyrproject.org/2.3.0/reference/peripherals/kscan.html), which uses `compatible="zmk,kscan-gpio-matrix"` for GPIO matrix based keyboards, or uses `compatible="zmk,kscan-gpio-direct"` for small direct wires. -- An optional matrix transform, which defines how the KSCAN row/column events are translated into logical "key positions". This is required for non-rectangular keyboards/matrixes, where the key positions don't naturally follow the row/columns from the GPIO matrix. +- An optional matrix transform, which defines how the KSCAN row/column events are translated into logical "key positions". This is required for non-rectangular keyboards/matrices, where the key positions don't naturally follow the row/columns from the GPIO matrix. - A keymap, which binds each key position to a behavior, e.g. key press, mod-tap, momentary layer, in a set of layers. -These three core architectural elements are defined per-keyboard, and _where_ they are defined depeneds on the specifics of how that -keyboard works. For an overview on the general concepts of boards and shields, please see the [FAQs on boards and sheilds](/docs/faq#why-boards-and-shields--why-not-just-keyboard). +These three core architectural elements are defined per-keyboard, and _where_ they are defined depends on the specifics of how that +keyboard works. For an overview on the general concepts of boards and shields, please see the [FAQs on boards and shields](/docs/faq#why-boards-and-shields--why-not-just-keyboard). ## Self-Contained Keyboard diff --git a/docs/docs/dev-guide-new-shield.md b/docs/docs/dev-guide-new-shield.md index d5620339..11198065 100644 --- a/docs/docs/dev-guide-new-shield.md +++ b/docs/docs/dev-guide-new-shield.md @@ -11,14 +11,14 @@ The high level steps are: - Create a new shield directory. - Add the base Kconfig files. - Add the shield overlay file to define the [KSCAN driver]() for detecting key press/release. -- (Optional) Add the mateix transform for mapping KSCAN row/coluk values to sane key positions. This is needed for non-rectangular keyboards, or where the underlying row/column pin arrangement does not map one to one with logical locations on the keyboard. +- (Optional) Add the matrix transform for mapping KSCAN row/column values to sane key positions. This is needed for non-rectangular keyboards, or where the underlying row/column pin arrangement does not map one to one with logical locations on the keyboard. - Add a default keymap, which users can override in their own configs as needed. It may be helpful to review the upstream [shields documentation](https://docs.zephyrproject.org/2.3.0/guides/porting/shields.html#shields) to get a proper understanding of the underlying system before continuing. ## New Shield Directory -Shields for Zephyr applications go into the `boards/shields/` directory; since ZMK's Zephyr appliction linves in the `app/` subdirectory of the repository, that means the new shield directory should be: +Shields for Zephyr applications go into the `boards/shields/` directory; since ZMK's Zephyr application lives in the `app/` subdirectory of the repository, that means the new shield directory should be: ```bash mkdir app/boards/shields/ @@ -60,7 +60,7 @@ endif ## Shield Overlay -The `.overlay` is the devicetree description of the keyboard shield that is merged with the primary board devicetree description before the build. For ZMK, this file at a minumum should include the [chosen]() node named `zmk,kscan` that refernces a KSCAN driver instance. For a simple 3x3 macropad matrix, +The `.overlay` is the devicetree description of the keyboard shield that is merged with the primary board devicetree description before the build. For ZMK, this file at a minimum should include the [chosen]() node named `zmk,kscan` that references a KSCAN driver instance. For a simple 3x3 macropad matrix, this might look something like: ``` @@ -97,7 +97,7 @@ Internally ZMK translates all row/column events into "key position" events to ma 1. For non rectangular keyboards with thumb clusters, non `1u` locations, etc. A "key position" is the numeric index (zero-based) of a given key, which identifies -the logical key location as percieved by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values. +the logical key location as perceived by the end user. All _keymap_ mappings actually bind behaviors to _key positions_, not to row/column values. _Without_ a matrix transform, that intentionally map each key position to the row/column pair that position corresponds to, the default equation to determine that is: @@ -141,13 +141,13 @@ RC(7,0) RC(7,1) RC(7,2) RC(7,3) RC(7 Some important things to note: -- The `#include ` is critical. The `RC` macro is used to generate the interanl storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK. +- The `#include ` is critical. The `RC` macro is used to generate the internal storage in the matrix transform, and is actually replaced by a C preprocessor before the final devicetree is compiled into ZMK. - `RC(row, column)` is placed sequentially to define what row and column values that position corresponds to. - If you have a keyboard with options for `2u` keys in certain positions, or break away portions, it is a good idea to set the chosen `zmk,matrix_transform` to the default arrangement, and include _other_ possible matrix transform nodes in the devicetree that users can select in their user config by overriding the chosen node. ## Default Keymap -Each keyboard should provide an OOTB default keymap to be used when building the firmware, which can be overriden and customized by user configs. For "shield keyboards", this should be placed in the `app/boards/shields//keymap/keymap.overlay` file. The keymap is configured as an additional devicetree overlay that includes the following: +Each keyboard should provide an OOTB default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `app/boards/shields//keymap/keymap.overlay` file. The keymap is configured as an additional devicetree overlay that includes the following: - A node with `compatible="zmk,layers"` where each child node is a layer with a `bindings` array that binds each key position to a given behavior (e.g. key press, momentarily layer, etc). - A node with `compatible="zmk,keymap"` that references the layers with a `layers` phandle-array property. diff --git a/docs/docs/dev-posix-board.md b/docs/docs/dev-posix-board.md index dc33ea0c..e146bf1f 100644 --- a/docs/docs/dev-posix-board.md +++ b/docs/docs/dev-posix-board.md @@ -10,7 +10,7 @@ flowing into the handler functions. ## Prerequisites -In order to build targetting the `native_posix` board, you need to setup your system +In order to build targeting the `native_posix` board, you need to setup your system with a compiler that can target 32-bit POSIX. On Debian, you can do this with: @@ -21,7 +21,7 @@ apt install -y gcc-multilib ## Building -To do this, you can build ZMK targetting the +To do this, you can build ZMK targeting the `native_posix` board. ``` @@ -36,4 +36,4 @@ Once built, you can run the firmware locally: ## Virtual Key Events -The virtual key presses are hardcoded in `boards/native_posix.overlay` file. should you want to change the sequence to test various actions like Mod-Tap, etc. +The virtual key presses are hardcoded in `boards/native_posix.overlay` file, should you want to change the sequence to test various actions like Mod-Tap, etc. diff --git a/docs/docs/dev-setup.md b/docs/docs/dev-setup.md index 016b14cf..56b9adc1 100644 --- a/docs/docs/dev-setup.md +++ b/docs/docs/dev-setup.md @@ -34,7 +34,7 @@ A unix-like environment with the following base packages installed: -On Debian and Ubuntu, we'll use apt to install our base dependencies: +On Debian and Ubuntu, we'll use `apt` to install our base dependencies: First, if you haven't updated recently, or if this is a new install, you should update to get the latest package information: @@ -200,7 +200,7 @@ source ~/.bashrc ### Toolchain Installation -The toolchain provides the compiler, linker, etc necessary to build for the target +The toolchain provides the compiler, linker, etc., necessary to build for the target platform. @@ -217,7 +217,7 @@ wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZSDK_ rm "zephyr-toolchain-arm-${ZSDK_VERSION}-setup.run" ``` -The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults shouldn normally work as expected. +The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults should normally work as expected. @@ -252,11 +252,20 @@ wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZSDK_ rm "zephyr-toolchain-arm-\${ZSDK_VERSION}-setup.run" ``` -The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults shouldn normally work as expected. +The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults should normally work as expected. +:::note +If you intend to build firmware straight away, make sure to correctly setup the current shell. + +Notes on setting this up can be found in the [Environment Variables](#environment-variables) section. +The transient instructions can be used to setup the current shell, and the automatic instructions can setup any newly made shells automatically. + +The transient instructions must be run to build firmware using the current shell. +::: + #### GNU ARM Embedded Since the Zephyr™ SDK is not available for Windows, we recommending following the steps to install the [GNU ARM Embedded](https://docs.zephyrproject.org/latest/getting_started/toolchain_3rd_party_x_compilers.html#gnu-arm-embedded). @@ -275,7 +284,7 @@ wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZSDK_ rm "zephyr-toolchain-arm-\${ZSDK_VERSION}-setup.run" ``` -The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults shouldn normally work as expected. +The installation will prompt with several questions about installation location, and creating a default `~/.zephyrrc` for you with various variables. The defaults should normally work as expected. diff --git a/docs/docs/user-setup.md b/docs/docs/user-setup.md index b79b5735..be230d28 100644 --- a/docs/docs/user-setup.md +++ b/docs/docs/user-setup.md @@ -36,7 +36,7 @@ Following the steps in this guide, you will: ## Prerequisites -The remainder of this guide assumes the following prequisites: +The remainder of this guide assumes the following prerequisites: 1. You have an active, working [GitHub](https://github.com/) account. 1. You have installed and configured the [`git`](https://git-scm.com/) version control tool. From 030f0dbd074164dbe085a423bdf1de68515d8f02 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 12:51:01 -0400 Subject: [PATCH 30/48] Tweak note about per-key RGB. --- app/boards/shields/corne/corne.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/boards/shields/corne/corne.dtsi b/app/boards/shields/corne/corne.dtsi index da48d62c..70d6495b 100644 --- a/app/boards/shields/corne/corne.dtsi +++ b/app/boards/shields/corne/corne.dtsi @@ -58,7 +58,7 @@ RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10 }; - // TODO: RGB node(s) + // TODO: per-key RGB node(s)? }; &pro_micro_i2c { From 03a945d4234f009db9e6ac95441c45eec8463198 Mon Sep 17 00:00:00 2001 From: CrossR Date: Mon, 10 Aug 2020 17:51:54 +0100 Subject: [PATCH 31/48] Move to correct location. --- docs/docs/dev-setup.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/docs/dev-setup.md b/docs/docs/dev-setup.md index 56b9adc1..5cceb73f 100644 --- a/docs/docs/dev-setup.md +++ b/docs/docs/dev-setup.md @@ -257,15 +257,6 @@ The installation will prompt with several questions about installation location, -:::note -If you intend to build firmware straight away, make sure to correctly setup the current shell. - -Notes on setting this up can be found in the [Environment Variables](#environment-variables) section. -The transient instructions can be used to setup the current shell, and the automatic instructions can setup any newly made shells automatically. - -The transient instructions must be run to build firmware using the current shell. -::: - #### GNU ARM Embedded Since the Zephyr™ SDK is not available for Windows, we recommending following the steps to install the [GNU ARM Embedded](https://docs.zephyrproject.org/latest/getting_started/toolchain_3rd_party_x_compilers.html#gnu-arm-embedded). @@ -289,6 +280,15 @@ The installation will prompt with several questions about installation location, +:::note +If you intend to build firmware straight away, make sure to correctly setup the current shell. + +Notes on setting this up can be found in the [Environment Variables](#environment-variables) section. +The transient instructions can be used to setup the current shell, and the automatic instructions can setup any newly made shells automatically. + +The transient instructions must be run to build firmware using the current shell. +::: + ### Source Code Next, you'll need to clone the ZMK source repository if you haven't already: From 4da2070a5b896fa2c28d77f0dde8508c739d0deb Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 12:54:34 -0400 Subject: [PATCH 32/48] Add Corne to hardware list. --- docs/docs/hardware.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/hardware.md b/docs/docs/hardware.md index 015b21db..951cee04 100644 --- a/docs/docs/hardware.md +++ b/docs/docs/hardware.md @@ -22,6 +22,7 @@ That being said, there are currently only a few specific [boards](/docs/faq#what ## Keyboard Shields - [Kyria](https://splitkb.com/products/kyria-pcb-kit) (`kyria_left` and `kyria_right`) +- [Corne](https://github.com/foostan/crkbd) (`corne_left` and `corne_right`) - [Lily58](https://github.com/kata0510/Lily58) (`lily58_left` and `lily58_right`) ## Other Hardware From b0c648ad57d138c462fac538c7ea25f88b03e443 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Mon, 10 Aug 2020 22:47:18 -0400 Subject: [PATCH 33/48] Revert usage of `read -i` for macOS compat. --- docs/static/setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index 42089434..ce2fe25d 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -64,10 +64,10 @@ if [ -z "$copy_keymap" ] || [ "$copy_keymap" == "Y" ] || [ "$copy_keymap" == "y" read -e -p "GitHub Username (leave empty to skip GitHub repo creation): " github_user if [ -n "$github_user" ]; then - read -e -i "zmk-config" -p "GitHub Repo Name: " repo_name + read -p "GitHub Repo Name [zmk-config]: " repo_name if [ -z "$repo_name" ]; then repo_name="zmk-config"; fi - read -e -i "https://github.com/${github_user}/${repo_name}.git" -p "GitHub Repo: " github_repo + read -p "GitHub Repo [https://github.com/${github_user}/${repo_name}.git]: " github_repo if [ -z "$github_repo" ]; then github_repo="https://github.com/${github_user}/${repo_name}.git"; fi else From 47dbba21849a136797014f4691c999de28073bf3 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 11 Aug 2020 08:40:13 -0400 Subject: [PATCH 34/48] Fix sed -i usage on macOS, accept 'Y'. --- docs/static/setup.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/static/setup.sh b/docs/static/setup.sh index ce2fe25d..27dcb0b3 100644 --- a/docs/static/setup.sh +++ b/docs/static/setup.sh @@ -90,7 +90,7 @@ fi echo "" read -p "Continue? [Yn]: " do_it -if [ -n "$do_it" ] && [ "$do_it" != "y" ]; then +if [ -n "$do_it" ] && [ "$do_it" != "y" ] && [ "$do_it" != "Y" ]; then echo "Aborting..." exit fi @@ -108,7 +108,7 @@ fi popd -sed -i \ +sed -i'.orig' \ -e "s/BOARD_NAME/$board/" \ -e "s/SHIELD_NAME/$shield/" \ -e "s/KEYBOARD_TITLE/$shield_title/" \ @@ -116,9 +116,11 @@ sed -i \ if [ "$board" == "proton_c" ]; then # Proton-C board still fa - sed -i -e "s/uf2/hex/g" .github/workflows/build.yml + sed -i'.orig' -e "s/uf2/hex/g" .github/workflows/build.yml fi +rm .github/workflows/*.yml.orig + rm -rf .git git init . git add . From f1f77ad32ae5358dff54181e7bd41c6cdff22810 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 11 Aug 2020 13:07:15 -0400 Subject: [PATCH 35/48] Add a note about upvoting feature requests. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34854f25..26ab37e8 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ Check out the website to learn more: https://zmkfirmware.dev/ You can also come join our [ZMK Discord Server](https://zmkfirmware.dev/community/discord/invite) -To review planned features, check out the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. +To review planned features, check out the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. Please free to add 👍 to the issue desciprtion of any requests to upvote the feature. From af5e936b89d5586f52597fb82422cfcfa1e2abbf Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 11 Aug 2020 14:37:49 -0400 Subject: [PATCH 36/48] Typo fixes. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26ab37e8..93f4e831 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ Check out the website to learn more: https://zmkfirmware.dev/ You can also come join our [ZMK Discord Server](https://zmkfirmware.dev/community/discord/invite) -To review planned features, check out the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. Please free to add 👍 to the issue desciprtion of any requests to upvote the feature. +To review planned features, check out the [enhancement label](https://github.com/zmkfirmware/zmk/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) in GitHub. Please feel free to add 👍 to the issue description of any requests to upvote the feature. From 78059bbbdb2d53957718f38f37b20216baa51566 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 11 Aug 2020 14:49:43 -0400 Subject: [PATCH 37/48] Fix up some incorrect license headers. --- app/boards/arm/nice_nano/board.cmake | 2 +- app/boards/arm/proton_c/CMakeLists.txt | 2 +- app/boards/arm/proton_c/pinmux.c | 2 +- app/drivers/zephyr/ec11_trigger.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/boards/arm/nice_nano/board.cmake b/app/boards/arm/nice_nano/board.cmake index 12a1d93b..fa847d50 100644 --- a/app/boards/arm/nice_nano/board.cmake +++ b/app/boards/arm/nice_nano/board.cmake @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake) diff --git a/app/boards/arm/proton_c/CMakeLists.txt b/app/boards/arm/proton_c/CMakeLists.txt index d1b8108c..940af1fe 100644 --- a/app/boards/arm/proton_c/CMakeLists.txt +++ b/app/boards/arm/proton_c/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT if(CONFIG_PINMUX) zephyr_library() diff --git a/app/boards/arm/proton_c/pinmux.c b/app/boards/arm/proton_c/pinmux.c index 09f3a349..a6aaae0a 100644 --- a/app/boards/arm/proton_c/pinmux.c +++ b/app/boards/arm/proton_c/pinmux.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 I-SENSE group of ICCS * - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: MIT */ #include diff --git a/app/drivers/zephyr/ec11_trigger.c b/app/drivers/zephyr/ec11_trigger.c index 55acf45e..709d1fbf 100644 --- a/app/drivers/zephyr/ec11_trigger.c +++ b/app/drivers/zephyr/ec11_trigger.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2016 Intel Corporation * - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: MIT */ #define DT_DRV_COMPAT alps_ec11 From a6ef1cddecda77e0e374a59fb76266df6d00f2bd Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 11 Aug 2020 16:55:36 -0400 Subject: [PATCH 38/48] Add analytics. --- docs/docusaurus.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 44acd69c..2c3622f4 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -7,6 +7,10 @@ module.exports = { organizationName: "zmkfirmware", // Usually your GitHub org/user name. projectName: "zmk", // Usually your repo name. themeConfig: { + googleAnalytics: { + trackingID: "UA-145201102-2", + anonymizeIP: true, + }, // sidebarCollapsible: false, navbar: { title: "ZMK Firmware", From 0624c6c54a3292571229727b3e19088bb155e482 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Wed, 12 Aug 2020 10:20:51 -0400 Subject: [PATCH 39/48] State Of The Firmware #1. --- docs/blog/2020-05-24-wip.md | 2 +- docs/blog/2020-08-12-zmk-sotf-1.md | 52 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 docs/blog/2020-08-12-zmk-sotf-1.md diff --git a/docs/blog/2020-05-24-wip.md b/docs/blog/2020-05-24-wip.md index 2ec2956e..eaf2b795 100644 --- a/docs/blog/2020-05-24-wip.md +++ b/docs/blog/2020-05-24-wip.md @@ -5,7 +5,7 @@ author: Pete Johanson author_title: Project Creator author_url: https://gitlab.com/petejohanson author_image_url: https://www.gravatar.com/avatar/2001ceff7e9dc753cf96fcb2e6f41110 -tags: [keyboards, firmeware, oss, ble] +tags: [keyboards, firmware, oss, ble] --- This blog is a work-in-progress as I work on basic docs + blog on this nascent keyboard firmware. diff --git a/docs/blog/2020-08-12-zmk-sotf-1.md b/docs/blog/2020-08-12-zmk-sotf-1.md new file mode 100644 index 00000000..2b695350 --- /dev/null +++ b/docs/blog/2020-08-12-zmk-sotf-1.md @@ -0,0 +1,52 @@ +--- +title: ZMK State Of The Firmware \#1 +author: Pete Johanson +author_title: Project Creator +author_url: https://gitlab.com/petejohanson +author_image_url: https://www.gravatar.com/avatar/2001ceff7e9dc753cf96fcb2e6f41110 +tags: [SOTF, keyboards, firmware, oss, ble] +--- + +Welcome to the first ZMK "State Of The Firmware"! + +With interest and Discord activity growing, it seemed important to lay out the progress made recently, current major bugs/showstoppers, and planned next steps. + +## Recent Activity + +There's been lots of various activity in ZMK land! + +- [Nicell](https://github.com/Nicell) (nice!nano creator) contributed initial [RGB Underglow](/docs/feature/underglow) ([#64](https://github.com/zmkfirmware/zmk/pull/64)) support to ZMK. +- Tons of [documentation](/docs) work. +- Refactoring ([#73](https://github.com/zmkfirmware/zmk/pull/73), [#74](https://github.com/zmkfirmware/zmk/pull/74)) of [keymaps](/docs/feature/keymaps) to make them simpler for users. +- Mod-Tap Behavior (docs coming!) is much improved ([#69](https://github.com/zmkfirmware/zmk/pull/69)) and usable now. +- An initial [`setup.sh`](http://localhost:3000/docs/user-setup#user-config-setup-script) script was created, allowing users to quickly bootstrap a "user config" setup and push it to GitHub, where GitHub Actions will build the firmware for you. +- Corne shield ([#80](https://github.com/zmkfirmware/zmk/pull/80)) shield definition was added. +- Initial [encoder](/docs/feature/encoders) support ([#61](https://github.com/zmkfirmware/zmk/pull/61)) was added. + +## Bugs and Showstoppers + +Despite the flurry of activity, there are still some serious bugs and show stoppers that potential ZMK users should be aware of: + +- [Bluetooth Related](https://github.com/zmkfirmware/zmk/issues/58) - There are several key bugs and fixes needed, including one complete show stopper: + - Fully working split wireless is not working. In particular, both split halves can properly pair, but once they do so, pairing with the _central_ host will not work. Workaround: You can currently have both halves pair, and use USB on the central side (Left side right now for Kyria, Corne, Lily58) and receive HID events over USB. + - BT bond information is not currently stored to the devices, so after powering off or restarting your device, users need to re-pair +- USB - There is one important USB related bug which is a showstopper: + - The Zephyr USB stack does not have a built in queue for endpoint data being written. As a result, HID events sent by ZMK are sometimes [dropped, or lost](https://github.com/zmkfirmware/zmk/issues/84). + +## Next Steps + +There's plenty of places to go next! To help keep folks in the loop for what's next, I've created a [Core Functionality](https://github.com/zmkfirmware/zmk/projects/1) project/kanban board in GitHub, where users should be able to get some visibility into items being focused on. + +Of course, at the top of that list currently is the above bugs/showstoppers, and then from there, I hope to: + +- Work on power management. +- Improve our documentation on various aspects of the system, mostly around: + - End user documentation for understanding how to use ZMK, better installation docs, etc. + - Developer focused documentation, so interested contributors can start building out more behaviors and ZMK functionality. +- Implement true "hold" detection, useful for several behaviors such as Mod-Tap and Layer-Tap. +- Hopefully acquire a proper and official USB Product ID for use for the project. +- Fun things that come up along the way! + +## Thanks! + +A big thanks for everyone who has shown interest in the project, tested things, asked questions, and contributed PRs ([Nicell](https://github.com/Nicell), [CrossR](https://github.com/CrossR), [careyk007](https://github.com/careyk007)). From c3b4525d2574bcd0cd03d91b069bd090c01292da Mon Sep 17 00:00:00 2001 From: Cody McGinnis Date: Thu, 13 Aug 2020 00:19:59 -0400 Subject: [PATCH 40/48] Fix edit url links for the website. --- docs/docusaurus.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 2c3622f4..64f9a454 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -105,12 +105,12 @@ module.exports = { homePageId: "intro", sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. - editUrl: "https://githlab.com/zmkproject/zmk/edit/main/docs/", + editUrl: "https://github.com/zmkfirmware/zmk/edit/main/docs/", }, blog: { showReadingTime: true, // Please change this to your repo. - editUrl: "https://gitlab.com/zmkproject/zmk/edit/main/docs/blog/", + editUrl: "https://github.com/zmkfirmware/zmk/edit/main/docs/", }, theme: { customCss: require.resolve("./src/css/custom.css"), From 8b61beb2bbc62f754db670ad77266f84edde041d Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 14 Aug 2020 16:44:13 -0400 Subject: [PATCH 41/48] Bump max paired connections to 2 for central. --- app/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Kconfig b/app/Kconfig index 4086955a..be5a1e4a 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -106,6 +106,10 @@ if ZMK_SPLIT_BLE_ROLE_CENTRAL config BT_MAX_CONN default 5 +config BT_MAX_PAIRED + # Bump this everywhere once we support switching active connections! + default 2 + endif config ZMK_SPLIT_BLE_ROLE_PERIPHERAL From 7e8a07e693431adfb64d12262b3c7614920c042c Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 14 Aug 2020 16:45:05 -0400 Subject: [PATCH 42/48] Remove use of printk. --- app/src/ble.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/ble.c b/app/src/ble.c index 7d986d71..488491c2 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -31,11 +31,11 @@ static void connected(struct bt_conn *conn, u8_t err) if (err) { - printk("Failed to connect to %s (%u)\n", addr, err); + LOG_WRN("Failed to connect to %s (%u)", log_strdup(addr), err); return; } - printk("Connected %s\n", addr); + LOG_DBG("Connected %s", log_strdup(addr)); bt_conn_le_param_update(conn, BT_LE_CONN_PARAM(0x0006, 0x000c, 30, 400)); @@ -45,7 +45,7 @@ static void connected(struct bt_conn *conn, u8_t err) if (bt_conn_set_security(conn, BT_SECURITY_L2)) { - printk("Failed to set security\n"); + LOG_ERR("Failed to set security"); } } @@ -55,7 +55,7 @@ static void disconnected(struct bt_conn *conn, u8_t reason) bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Disconnected from %s (reason 0x%02x)\n", addr, reason); + LOG_DBG("Disconnected from %s (reason 0x%02x)", log_strdup(addr), reason); } static void security_changed(struct bt_conn *conn, bt_security_t level, @@ -67,11 +67,11 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, if (!err) { - printk("Security changed: %s level %u\n", addr, level); + LOG_DBG("Security changed: %s level %u", log_strdup(addr), level); } else { - printk("Security failed: %s level %u err %d\n", addr, level, + LOG_ERR("Security failed: %s level %u err %d", log_strdup(addr), level, err); } } @@ -107,7 +107,7 @@ static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Passkey for %s: %06u\n", addr, passkey); + LOG_DBG("Passkey for %s: %06u", log_strdup(addr), passkey); } #ifdef CONFIG_ZMK_BLE_PASSKEY_ENTRY @@ -118,7 +118,7 @@ static void auth_passkey_entry(struct bt_conn *conn) bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Passkey entry requested for %s\n", addr); + LOG_DBG("Passkey entry requested for %s", log_strdup(addr)); auth_passkey_entry_conn = bt_conn_ref(conn); } @@ -138,7 +138,7 @@ static void auth_cancel(struct bt_conn *conn) passkey_digit = 0; - printk("Pairing cancelled: %s\n", addr); + LOG_DBG("Pairing cancelled: %s", log_strdup(addr)); } static struct bt_conn_auth_cb zmk_ble_auth_cb_display = { @@ -169,14 +169,14 @@ static void zmk_ble_ready(int err) LOG_DBG("ready? %d", err); if (err) { - printk("Bluetooth init failed (err %d)\n", err); + LOG_ERR("Bluetooth init failed (err %d)", err); return; } err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0); if (err) { - printk("Advertising failed to start (err %d)\n", err); + LOG_ERR("Advertising failed to start (err %d)", err); return; } } @@ -187,7 +187,7 @@ static int zmk_ble_init(struct device *_arg) if (err) { - printk("BLUETOOTH FAILED"); + LOG_ERR("BLUETOOTH FAILED (%d)", err); return err; } From aa4ae90fb7cd7d7e295f9f5d4af8ad63d9bc9715 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 14 Aug 2020 21:31:18 -0500 Subject: [PATCH 43/48] Add missing configuration values for RGB underglow --- app/boards/shields/corne/corne.conf | 1 + app/boards/shields/kyria/kyria.conf | 1 + docs/docs/feature/underglow.md | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/boards/shields/corne/corne.conf b/app/boards/shields/corne/corne.conf index c8e3c00c..e96bf5ac 100644 --- a/app/boards/shields/corne/corne.conf +++ b/app/boards/shields/corne/corne.conf @@ -1,5 +1,6 @@ # Uncomment the following line to enable the Corne RGB Underglow # ZMK_RGB_UNDERGLOW=y +# CONFIG_WS2812_STRIP=y # Uncomment the following line to enable the Corne OLED Display # CONFIG_ZMK_DISPLAY=y diff --git a/app/boards/shields/kyria/kyria.conf b/app/boards/shields/kyria/kyria.conf index fa98d1cd..20aa8c20 100644 --- a/app/boards/shields/kyria/kyria.conf +++ b/app/boards/shields/kyria/kyria.conf @@ -7,3 +7,4 @@ # Uncomment the following line to enable RGB underglow # CONFIG_ZMK_RGB_UNDERGLOW=y +# CONFIG_WS2812_STRIP=y diff --git a/docs/docs/feature/underglow.md b/docs/docs/feature/underglow.md index 780845af..4be752fc 100644 --- a/docs/docs/feature/underglow.md +++ b/docs/docs/feature/underglow.md @@ -21,10 +21,12 @@ Here you can see the RGB underglow feature in action using WS2812 LEDs. ## Enabling RGB Underglow -To enable RGB underglow on your board or shield, simply enable the `ZMK_RGB_UNDERGLOW` configuration value in the `.conf` file of your user config directory as such: +To enable RGB underglow on your board or shield, simply enable the `ZMK_RGB_UNDERGLOW` and `CONFIG_X_STRIP` configuration values in the `.conf` file of your user config directory as such: ``` CONFIG_ZMK_RGB_UNDERGLOW=y +# Use the STRIP config specific tot he LEDs you're using +CONFIG_WS2812_STRIP=y ``` If your board or shield does not have RGB underglow configured, refer to [Adding RGB Underglow to a Board](#adding-rgb-underglow-to-a-board). @@ -114,8 +116,10 @@ Once you have your `led_strip` properly defined you need to add it to the root d }; ``` -Finally you need to enable the `ZMK_RGB_UNDERGLOW` configuration value in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): +Finally you need to enable the `ZMK_RGB_UNDERGLOW` and `CONFIG_X_STRIP` STRIP configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): ``` CONFIG_ZMK_RGB_UNDERGLOW=y +# Use the STRIP config specific tot he LEDs you're using +CONFIG_WS2812_STRIP=y ``` From a03b3ab68c5a81dc7c98749241dbfe80e5f1b09b Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 14 Aug 2020 21:33:33 -0500 Subject: [PATCH 44/48] Fix grammar --- app/boards/shields/corne/corne.conf | 2 +- app/boards/shields/kyria/kyria.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/boards/shields/corne/corne.conf b/app/boards/shields/corne/corne.conf index e96bf5ac..b79385bd 100644 --- a/app/boards/shields/corne/corne.conf +++ b/app/boards/shields/corne/corne.conf @@ -1,4 +1,4 @@ -# Uncomment the following line to enable the Corne RGB Underglow +# Uncomment the following lines to enable the Corne RGB Underglow # ZMK_RGB_UNDERGLOW=y # CONFIG_WS2812_STRIP=y diff --git a/app/boards/shields/kyria/kyria.conf b/app/boards/shields/kyria/kyria.conf index 20aa8c20..7a0b5b6c 100644 --- a/app/boards/shields/kyria/kyria.conf +++ b/app/boards/shields/kyria/kyria.conf @@ -5,6 +5,6 @@ # Uncomment the following line to enable the Kyria OLED Display # CONFIG_ZMK_DISPLAY=y -# Uncomment the following line to enable RGB underglow +# Uncomment the following lines to enable RGB underglow # CONFIG_ZMK_RGB_UNDERGLOW=y # CONFIG_WS2812_STRIP=y From 2863f150063bc4a31cc819efe973b59b45a10d10 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 14 Aug 2020 21:36:11 -0500 Subject: [PATCH 45/48] Fix docs typos --- docs/docs/feature/underglow.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/feature/underglow.md b/docs/docs/feature/underglow.md index 4be752fc..c6517ced 100644 --- a/docs/docs/feature/underglow.md +++ b/docs/docs/feature/underglow.md @@ -21,11 +21,11 @@ Here you can see the RGB underglow feature in action using WS2812 LEDs. ## Enabling RGB Underglow -To enable RGB underglow on your board or shield, simply enable the `ZMK_RGB_UNDERGLOW` and `CONFIG_X_STRIP` configuration values in the `.conf` file of your user config directory as such: +To enable RGB underglow on your board or shield, simply enable the `ZMK_RGB_UNDERGLOW` and `X_STRIP` configuration values in the `.conf` file of your user config directory as such: ``` CONFIG_ZMK_RGB_UNDERGLOW=y -# Use the STRIP config specific tot he LEDs you're using +# Use the STRIP config specific to the LEDs you're using CONFIG_WS2812_STRIP=y ``` @@ -116,10 +116,10 @@ Once you have your `led_strip` properly defined you need to add it to the root d }; ``` -Finally you need to enable the `ZMK_RGB_UNDERGLOW` and `CONFIG_X_STRIP` STRIP configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): +Finally you need to enable the `ZMK_RGB_UNDERGLOW` and `X_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): ``` CONFIG_ZMK_RGB_UNDERGLOW=y -# Use the STRIP config specific tot he LEDs you're using +# Use the STRIP config specific to the LEDs you're using CONFIG_WS2812_STRIP=y ``` From 4acfa8d7ef2009ce966f6e58719d44abb193f659 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Fri, 14 Aug 2020 23:26:03 -0400 Subject: [PATCH 46/48] Add DCDC setting for nano. --- app/boards/arm/nice_nano/Kconfig | 6 ++++++ app/boards/arm/nice_nano/Kconfig.board | 1 + 2 files changed, 7 insertions(+) create mode 100644 app/boards/arm/nice_nano/Kconfig diff --git a/app/boards/arm/nice_nano/Kconfig b/app/boards/arm/nice_nano/Kconfig new file mode 100644 index 00000000..a1904253 --- /dev/null +++ b/app/boards/arm/nice_nano/Kconfig @@ -0,0 +1,6 @@ + +config BOARD_ENABLE_DCDC + bool "Enable DCDC mode" + select SOC_DCDC_NRF52X + default y + depends on BOARD_NICE_NANO diff --git a/app/boards/arm/nice_nano/Kconfig.board b/app/boards/arm/nice_nano/Kconfig.board index 60ee58b5..4fd394f4 100644 --- a/app/boards/arm/nice_nano/Kconfig.board +++ b/app/boards/arm/nice_nano/Kconfig.board @@ -6,3 +6,4 @@ config BOARD_NICE_NANO bool "nice!nano" depends on SOC_NRF52840_QIAA + From 7e1942867b0c7c19f6bd78e6f8a15227fbfc323b Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sat, 15 Aug 2020 20:20:19 -0400 Subject: [PATCH 47/48] Doc fixes for keymaps. --- docs/docs/dev-guide-new-shield.md | 47 +++++++++++-------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/docs/docs/dev-guide-new-shield.md b/docs/docs/dev-guide-new-shield.md index 11198065..963a723c 100644 --- a/docs/docs/dev-guide-new-shield.md +++ b/docs/docs/dev-guide-new-shield.md @@ -147,51 +147,38 @@ Some important things to note: ## Default Keymap -Each keyboard should provide an OOTB default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `app/boards/shields//keymap/keymap.overlay` file. The keymap is configured as an additional devicetree overlay that includes the following: +Each keyboard should provide an OOTB default keymap to be used when building the firmware, which can be overridden and customized by user configs. For "shield keyboards", this should be placed in the `app/boards/shields//.keymap` file. The keymap is configured as an additional devicetree overlay that includes the following: -- A node with `compatible="zmk,layers"` where each child node is a layer with a `bindings` array that binds each key position to a given behavior (e.g. key press, momentarily layer, etc). -- A node with `compatible="zmk,keymap"` that references the layers with a `layers` phandle-array property. -- A chosen node named `zmk,keymap` that references the defined keymap. +- A node with `compatible="zmk,keymap"` where each child node is a layer with a `bindings` array that binds each key position to a given behavior (e.g. key press, momentarily layer, etc). -Here is an example simple keymap for the nice60, with only one layer: +Here is an example simple keymap for the Kyria, with only one layer: ``` #include #include / { - chosen { - zmk,keymap = &keymap0; - }; - - keymap0: keymap { + keymap { compatible = "zmk,keymap"; - label ="Default nice!60 Keymap"; - layers = <&default>; - }; - layers { - compatible = "zmk,layers"; - - default: layer_0 { - label = "DEFAULT"; -// ------------------------------------------------------------------------------------------ -// | ESC | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | = | BKSP | -// | TAB | Q | W | E | R | T | Y | U | I | O | P | [ | ] | "|" | -// | CAPS | A | S | D | F | G | H | J | K | L | ; | ' | ENTER | -// | SHIFT | Z | X | C | V | B | N | M | , | . | / | SHIFT | -// | CTL | WIN | ALT | SPACE | ALT | WIN | MENU | CTL | -// ------------------------------------------------------------------------------------------ + default_layer { +// --------------------------------------------------------------------------------------------------------------------------------- +// | ESC | Q | W | E | R | T | | Y | U | I | O | P | \ | +// | TAB | A | S | D | F | G | | H | J | K | L | ; | ' | +// | SHIFT | Z | X | C | V | B | L SHIFT | L SHIFT | | L SHIFT | L SHIFT | N | M | , | . | / | CTRL | +// | GUI | DEL | RET | SPACE | ESC | | RET | SPACE | TAB | BSPC | R-ALT | bindings = < - &kp ESC &kp NUM_1 &kp NUM_2 &kp NUM_3 &kp NUM_4 &kp NUM_5 &kp NUM_6 &kp NUM_7 &kp NUM_8 &kp NUM_9 &kp NUM_0 &kp MINUS &kp EQL &kp BKSP - &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp LBKT &kp RBKT &kp BSLH - &kp CLCK &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT &kp RET - &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RSFT - &kp LCTL &kp LGUI &kp LALT &kp SPC &kp RALT &kp RGUI &kp GUI &kp RCTL + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SCLN &kp QUOT + &kp LSFT &kp Z &kp X &kp C &kp V &kp B &kp LSFT &kp LSFT &kp LSFT &kp LSFT &kp N &kp M &kp CMMA &kp DOT &kp FSLH &kp RCTL + &kp LGUI &kp DEL &kp RET &kp SPC &kp ESC &kp RET &kp SPC &kp TAB &kp BKSP &kp RALT >; + + sensor-bindings = <&inc_dec_cp M_VOLU M_VOLD &inc_dec_kp PGUP PGDN>; }; }; }; + ``` :::note From ee5900686bdb921beccf87566cb1ba4977059f99 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sat, 15 Aug 2020 21:13:50 -0400 Subject: [PATCH 48/48] Fix the active/pull down flags for the example. --- docs/docs/dev-guide-new-shield.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/dev-guide-new-shield.md b/docs/docs/dev-guide-new-shield.md index 963a723c..8556623d 100644 --- a/docs/docs/dev-guide-new-shield.md +++ b/docs/docs/dev-guide-new-shield.md @@ -81,9 +81,9 @@ this might look something like: ; row-gpios - = <&pro_micro_a 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_a 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_a 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + = <&pro_micro_a 1 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro_a 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro_a 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> ; }; };