From 11ac8c4782a01c27268854e4289a0059b23e4a96 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Tue, 7 Jun 2022 19:43:23 -0400 Subject: [PATCH 01/50] fix(build): Fix for proper string variable check. * Properly load variable for comparison for shield name substring calculations. --- app/cmake/ZephyrBuildConfig.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cmake/ZephyrBuildConfig.cmake b/app/cmake/ZephyrBuildConfig.cmake index 469f5761..931dd7ac 100644 --- a/app/cmake/ZephyrBuildConfig.cmake +++ b/app/cmake/ZephyrBuildConfig.cmake @@ -122,7 +122,7 @@ if(DEFINED SHIELD) while(NOT S_PIECES STREQUAL "") list(POP_BACK S_PIECES) list(JOIN S_PIECES "_" s_substr) - if ("%{s_substr}" STREQUAL "" OR "${s_substr}" STREQUAL ${shield_dir_name}) + if ("${s_substr}" STREQUAL "" OR "${s_substr}" STREQUAL "${shield_dir_name}") break() endif() list(APPEND shield_candidate_names ${s_substr}) From 4af3d272fcbfa363673981fc19fc15338b2432ae Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 5 Jan 2022 04:03:50 +0000 Subject: [PATCH 02/50] fix(keymaps): Handle matching `then-layer`s. * Proporly handle multiple conditonal layers w/ the same target `then-layer` values. * Move handling to work callback, to avoid re-entrance for cascading layers enabling other layers. --- app/src/conditional_layer.c | 58 ++++++++++++--- .../events.patterns | 3 + .../keycode_events.snapshot | 20 +++++ .../native_posix_64.keymap | 73 +++++++++++++++++++ .../events.patterns | 3 + .../keycode_events.snapshot | 16 ++++ .../native_posix_64.keymap | 69 ++++++++++++++++++ .../tri-layer-lt/events.patterns | 3 + .../tri-layer-lt/keycode_events.snapshot | 8 ++ .../tri-layer-lt/native_posix_64.keymap | 56 ++++++++++++++ 10 files changed, 297 insertions(+), 12 deletions(-) create mode 100644 app/tests/conditional-layer/same-layer-reached-both-ways/events.patterns create mode 100644 app/tests/conditional-layer/same-layer-reached-both-ways/keycode_events.snapshot create mode 100644 app/tests/conditional-layer/same-layer-reached-both-ways/native_posix_64.keymap create mode 100644 app/tests/conditional-layer/same-layer-reached-differently/events.patterns create mode 100644 app/tests/conditional-layer/same-layer-reached-differently/keycode_events.snapshot create mode 100644 app/tests/conditional-layer/same-layer-reached-differently/native_posix_64.keymap create mode 100644 app/tests/conditional-layer/tri-layer-lt/events.patterns create mode 100644 app/tests/conditional-layer/tri-layer-lt/keycode_events.snapshot create mode 100644 app/tests/conditional-layer/tri-layer-lt/native_posix_64.keymap diff --git a/app/src/conditional_layer.c b/app/src/conditional_layer.c index 4beb87e3..1728a7f4 100644 --- a/app/src/conditional_layer.c +++ b/app/src/conditional_layer.c @@ -7,6 +7,7 @@ #define DT_DRV_COMPAT zmk_conditional_layers #include +#include #include #include @@ -19,6 +20,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +static K_SEM_DEFINE(conditional_layer_sem, 1, 1); + // Conditional layer configuration that activates the specified then-layer when all if-layers are // active. With two if-layers, this is referred to as "tri-layer", and is commonly used to activate // a third "adjust" layer if and only if the "lower" and "raise" layers are both active. @@ -66,22 +69,53 @@ static void conditional_layer_deactivate(int8_t layer) { } } -// On layer state changes, examines each conditional layer config to determine if then-layer in the -// config should activate based on the currently active set of if-layers. static int layer_state_changed_listener(const zmk_event_t *ev) { - for (int i = 0; i < NUM_CONDITIONAL_LAYER_CFGS; i++) { - const struct conditional_layer_cfg *cfg = CONDITIONAL_LAYER_CFGS + i; - zmk_keymap_layers_state_t mask = cfg->if_layers_state_mask; + static bool conditional_layer_updates_needed; - // Activate then-layer if and only if all if-layers are already active. Note that we - // reevaluate the current layer state for each config since activation of one layer can also - // trigger activation of another. - if ((zmk_keymap_layer_state() & mask) == mask) { - conditional_layer_activate(cfg->then_layer); - } else { - conditional_layer_deactivate(cfg->then_layer); + conditional_layer_updates_needed = true; + + // Semaphore ensures we don't re-enter the loop in the middle of doing update, and + // ensures that "waterfalling layer updates" are all processed to trigger subsequent + // nested conditional layers properly. + if (k_sem_take(&conditional_layer_sem, K_NO_WAIT) < 0) { + return 0; + } + + while (conditional_layer_updates_needed) { + int8_t max_then_layer = -1; + uint32_t then_layers = 0; + uint32_t then_layer_state = 0; + + conditional_layer_updates_needed = false; + + // On layer state changes, examines each conditional layer config to determine if then-layer + // in the config should activate based on the currently active set of if-layers. + for (int i = 0; i < NUM_CONDITIONAL_LAYER_CFGS; i++) { + const struct conditional_layer_cfg *cfg = CONDITIONAL_LAYER_CFGS + i; + zmk_keymap_layers_state_t mask = cfg->if_layers_state_mask; + then_layers |= BIT(cfg->then_layer); + max_then_layer = MAX(max_then_layer, cfg->then_layer); + + // Activate then-layer if and only if all if-layers are already active. Note that we + // reevaluate the current layer state for each config since activation of one layer can + // also trigger activation of another. + if ((zmk_keymap_layer_state() & mask) == mask) { + then_layer_state |= BIT(cfg->then_layer); + } + } + + for (uint8_t layer = 0; layer <= max_then_layer; layer++) { + if ((BIT(layer) & then_layers) != 0U) { + if ((BIT(layer) & then_layer_state) != 0U) { + conditional_layer_activate(layer); + } else { + conditional_layer_deactivate(layer); + } + } } } + + k_sem_give(&conditional_layer_sem); return 0; } diff --git a/app/tests/conditional-layer/same-layer-reached-both-ways/events.patterns b/app/tests/conditional-layer/same-layer-reached-both-ways/events.patterns new file mode 100644 index 00000000..14ded795 --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-both-ways/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*conditional_layer/cl/p diff --git a/app/tests/conditional-layer/same-layer-reached-both-ways/keycode_events.snapshot b/app/tests/conditional-layer/same-layer-reached-both-ways/keycode_events.snapshot new file mode 100644 index 00000000..49fc0f87 --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-both-ways/keycode_events.snapshot @@ -0,0 +1,20 @@ +mo_pressed: position 2 layer 1 +mo_pressed: position 3 layer 2 +cl_activate: layer 4 +mo_pressed: position 1 layer 3 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +mo_released: position 1 layer 3 +mo_released: position 3 layer 2 +cl_deactivate: layer 4 +mo_released: position 2 layer 1 +mo_pressed: position 1 layer 3 +mo_pressed: position 2 layer 1 +cl_activate: layer 4 +mo_pressed: position 3 layer 2 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +mo_released: position 3 layer 2 +mo_released: position 2 layer 1 +cl_deactivate: layer 4 +mo_released: position 1 layer 3 diff --git a/app/tests/conditional-layer/same-layer-reached-both-ways/native_posix_64.keymap b/app/tests/conditional-layer/same-layer-reached-both-ways/native_posix_64.keymap new file mode 100644 index 00000000..c94dcef2 --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-both-ways/native_posix_64.keymap @@ -0,0 +1,73 @@ +#include +#include +#include + +/ { + conditional_layers { + compatible = "zmk,conditional-layers"; + conditional_layer_1 { + if-layers = <1 2>; + then-layer = <4>; + }; + conditional_layer_2 { + if-layers = <1 3>; + then-layer = <4>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp A &mo 3 + &mo 1 &mo 2 + >; + }; + layer_1 { + bindings = < + &kp B &trans + &trans &trans + >; + }; + layer_2 { + bindings = < + &kp C &trans + &trans &trans + >; + }; + layer_3 { + bindings = < + &kp D &trans + &trans &trans + >; + }; + layer_4 { + bindings = < + &kp E &trans + &trans &trans + >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(0,1,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; diff --git a/app/tests/conditional-layer/same-layer-reached-differently/events.patterns b/app/tests/conditional-layer/same-layer-reached-differently/events.patterns new file mode 100644 index 00000000..14ded795 --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-differently/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*conditional_layer/cl/p diff --git a/app/tests/conditional-layer/same-layer-reached-differently/keycode_events.snapshot b/app/tests/conditional-layer/same-layer-reached-differently/keycode_events.snapshot new file mode 100644 index 00000000..86371d2f --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-differently/keycode_events.snapshot @@ -0,0 +1,16 @@ +mo_pressed: position 2 layer 1 +mo_pressed: position 3 layer 2 +cl_activate: layer 4 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +mo_released: position 3 layer 2 +cl_deactivate: layer 4 +mo_released: position 2 layer 1 +mo_pressed: position 1 layer 3 +mo_pressed: position 2 layer 1 +cl_activate: layer 4 +kp_pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00 +mo_released: position 2 layer 1 +cl_deactivate: layer 4 +mo_released: position 1 layer 3 diff --git a/app/tests/conditional-layer/same-layer-reached-differently/native_posix_64.keymap b/app/tests/conditional-layer/same-layer-reached-differently/native_posix_64.keymap new file mode 100644 index 00000000..fd127d05 --- /dev/null +++ b/app/tests/conditional-layer/same-layer-reached-differently/native_posix_64.keymap @@ -0,0 +1,69 @@ +#include +#include +#include + +/ { + conditional_layers { + compatible = "zmk,conditional-layers"; + conditional_layer_1 { + if-layers = <1 2>; + then-layer = <4>; + }; + conditional_layer_2 { + if-layers = <1 3>; + then-layer = <4>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp A &mo 3 + &mo 1 &mo 2 + >; + }; + layer_1 { + bindings = < + &kp B &trans + &trans &trans + >; + }; + layer_2 { + bindings = < + &kp C &trans + &trans &trans + >; + }; + layer_3 { + bindings = < + &kp D &trans + &trans &trans + >; + }; + layer_4 { + bindings = < + &kp E &trans + &trans &trans + >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,0,10) + ZMK_MOCK_RELEASE(0,1,10) + >; +}; diff --git a/app/tests/conditional-layer/tri-layer-lt/events.patterns b/app/tests/conditional-layer/tri-layer-lt/events.patterns new file mode 100644 index 00000000..6a0e3bdc --- /dev/null +++ b/app/tests/conditional-layer/tri-layer-lt/events.patterns @@ -0,0 +1,3 @@ +s/.*hid_listener_keycode/kp/p +s/.*mo_keymap_binding/mo/p +s/.*conditional_layer/cl/p \ No newline at end of file diff --git a/app/tests/conditional-layer/tri-layer-lt/keycode_events.snapshot b/app/tests/conditional-layer/tri-layer-lt/keycode_events.snapshot new file mode 100644 index 00000000..cb452df5 --- /dev/null +++ b/app/tests/conditional-layer/tri-layer-lt/keycode_events.snapshot @@ -0,0 +1,8 @@ +mo_pressed: position 2 layer 1 +mo_pressed: position 3 layer 2 +cl_activate: layer 3 +kp_pressed: usage_page 0x07 keycode 0x0A implicit_mods 0x00 explicit_mods 0x00 +kp_released: usage_page 0x07 keycode 0x0A implicit_mods 0x00 explicit_mods 0x00 +mo_released: position 3 layer 2 +cl_deactivate: layer 3 +mo_released: position 2 layer 1 diff --git a/app/tests/conditional-layer/tri-layer-lt/native_posix_64.keymap b/app/tests/conditional-layer/tri-layer-lt/native_posix_64.keymap new file mode 100644 index 00000000..7a091609 --- /dev/null +++ b/app/tests/conditional-layer/tri-layer-lt/native_posix_64.keymap @@ -0,0 +1,56 @@ +#include +#include +#include + +< { + flavor = "balanced"; +}; + +/ { + conditional_layers { + compatible = "zmk,conditional-layers"; + tri_layer { + if-layers = <1 2>; + then-layer = <3>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp A &kp B + < 1 I < 2 J + >; + }; + layer_1 { + bindings = < + &kp C &kp D + &trans &trans + >; + }; + layer_2 { + bindings = < + &kp E &kp F + &trans &trans + >; + }; + layer_3 { + bindings = < + &kp G &kp H + &trans &trans + >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_PRESS(0,0,10) + ZMK_MOCK_RELEASE(0,0,10) + ZMK_MOCK_RELEASE(1,1,10) + ZMK_MOCK_RELEASE(1,0,10) + >; +}; From 53bec710d869440508ebc03fe99bf72d7a8466bc Mon Sep 17 00:00:00 2001 From: WSTRN <45993771+WSTRN@users.noreply.github.com> Date: Fri, 10 Jun 2022 01:54:36 +0800 Subject: [PATCH 03/50] fix(displays): Proper battery charge icon sizing --- app/src/display/widgets/battery_status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/display/widgets/battery_status.c b/app/src/display/widgets/battery_status.c index d74cc8ed..3dfcdb47 100644 --- a/app/src/display/widgets/battery_status.c +++ b/app/src/display/widgets/battery_status.c @@ -76,7 +76,7 @@ ZMK_SUBSCRIPTION(widget_battery_status, zmk_usb_conn_state_changed); int zmk_widget_battery_status_init(struct zmk_widget_battery_status *widget, lv_obj_t *parent) { widget->obj = lv_label_create(parent, NULL); - lv_obj_set_size(widget->obj, 40, 15); + lv_obj_set_size(widget->obj, 43, 15); sys_slist_append(&widgets, &widget->node); From 3d2bd017472c57f4b3860033f989a44f094febaa Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 8 Jun 2022 03:44:29 +0000 Subject: [PATCH 04/50] fix(split): Raise release events on disconnect. * When a peripheral disconnects from a centraly, raise position events to release any active positions from that peripheral. --- app/src/split/bluetooth/central.c | 36 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 2f02faaf..e94a59ae 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -54,6 +54,16 @@ static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_ K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4); +void peripheral_event_work_callback(struct k_work *work) { + struct zmk_position_state_changed ev; + while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) { + LOG_DBG("Trigger key position state change for %d", ev.position); + ZMK_EVENT_RAISE(new_zmk_position_state_changed(ev)); + } +} + +K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback); + int peripheral_slot_index_for_conn(struct bt_conn *conn) { for (int i = 0; i < ZMK_BLE_SPLIT_PERIPHERAL_COUNT; i++) { if (peripherals[i].conn == conn) { @@ -92,6 +102,22 @@ int release_peripheral_slot(int index) { } slot->state = PERIPHERAL_SLOT_STATE_OPEN; + // Raise events releasing any active positions from this peripheral + for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) { + for (int j = 0; j < 8; j++) { + if (slot->position_state[i] & BIT(j)) { + uint32_t position = (i * 8) + j; + struct zmk_position_state_changed ev = {.source = index, + .position = position, + .state = false, + .timestamp = k_uptime_get()}; + + k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT); + k_work_submit(&peripheral_event_work); + } + } + } + for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) { slot->position_state[i] = 0U; slot->changed_positions[i] = 0U; @@ -136,16 +162,6 @@ int confirm_peripheral_slot_conn(struct bt_conn *conn) { return 0; } -void peripheral_event_work_callback(struct k_work *work) { - struct zmk_position_state_changed ev; - while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) { - LOG_DBG("Trigger key position state change for %d", ev.position); - ZMK_EVENT_RAISE(new_zmk_position_state_changed(ev)); - } -} - -K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback); - static uint8_t split_central_notify_func(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data, uint16_t length) { From db437574615237daeadda3f80b6e733522029c9b Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Tue, 14 Jun 2022 13:29:52 -0700 Subject: [PATCH 05/50] fix(shields): Fix indentation in two_percent_milk.keymap --- .../two_percent_milk/two_percent_milk.keymap | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.keymap b/app/boards/shields/two_percent_milk/two_percent_milk.keymap index 71152de2..04dc4c0d 100644 --- a/app/boards/shields/two_percent_milk/two_percent_milk.keymap +++ b/app/boards/shields/two_percent_milk/two_percent_milk.keymap @@ -4,19 +4,19 @@ * SPDX-License-Identifier: MIT */ - #include - #include - #include +#include +#include +#include - / { - keymap { - compatible = "zmk,keymap"; +/ { + keymap { + compatible = "zmk,keymap"; - default_layer { - bindings = < - &kp X - &kp Z - >; - }; - }; - }; \ No newline at end of file + default_layer { + bindings = < + &kp X + &kp Z + >; + }; + }; +}; \ No newline at end of file From d7bd81e5c4b24b664e1f1172f6e0f757dd2e3ca1 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Thu, 11 Nov 2021 18:36:29 +0000 Subject: [PATCH 06/50] fix(usb): Correctly detect USB connection status. * Fix detection of USB power vs. configuration, to ensure endpoint selection works properly with power-only USB attached. --- app/src/usb.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/usb.c b/app/src/usb.c index 8d2b62f1..5f170ee6 100644 --- a/app/src/usb.c +++ b/app/src/usb.c @@ -31,16 +31,16 @@ enum usb_dc_status_code zmk_usb_get_status() { return usb_status; } enum zmk_usb_conn_state zmk_usb_get_conn_state() { LOG_DBG("state: %d", usb_status); switch (usb_status) { + case USB_DC_SUSPEND: + case USB_DC_CONFIGURED: + return ZMK_USB_CONN_HID; + case USB_DC_DISCONNECTED: case USB_DC_UNKNOWN: return ZMK_USB_CONN_NONE; - case USB_DC_ERROR: - case USB_DC_RESET: - return ZMK_USB_CONN_POWERED; - default: - return ZMK_USB_CONN_HID; + return ZMK_USB_CONN_POWERED; } } From edc60e58488c6b867066c79243752cc5fd4bcf8c Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Mon, 20 Jun 2022 18:27:58 -0700 Subject: [PATCH 07/50] fix(docs): Update keycode usage ID for numeric 4 in key-press.md Co-Authored-By: Robert U <978080+urob@users.noreply.github.com> --- docs/docs/behaviors/key-press.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/behaviors/key-press.md b/docs/docs/behaviors/key-press.md index 12d4094f..e8304d77 100644 --- a/docs/docs/behaviors/key-press.md +++ b/docs/docs/behaviors/key-press.md @@ -40,7 +40,7 @@ The "key press" behavior sends standard keycodes on press/release. ### Behavior Binding - Reference: `&kp` -- Parameter: The keycode usage ID from the usage page, e.g. `4` or `A` +- Parameter: The keycode usage ID from the usage page, e.g. `N4` or `A` Example: From 709441412ab538b2a6c061a39d3802ee000f2b42 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 22 Jun 2022 16:21:18 -0400 Subject: [PATCH 08/50] fix: Don't exclude segger, needed for DKs. --- app/west.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/west.yml b/app/west.yml index f7e1ae12..a0c56501 100644 --- a/app/west.yml +++ b/app/west.yml @@ -27,7 +27,6 @@ manifest: - mcuboot - mcumgr - net-tools - - segger - openthread - edtt - trusted-firmware-m From 38e079ef37a871d9abdd932466973d33a310fb97 Mon Sep 17 00:00:00 2001 From: ReFil Date: Thu, 23 Jun 2022 15:15:18 +0100 Subject: [PATCH 09/50] fix(backlight): Improve initial power on behaviour --- app/src/backlight.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/backlight.c b/app/src/backlight.c index e8708642..28a93e73 100644 --- a/app/src/backlight.c +++ b/app/src/backlight.c @@ -91,7 +91,9 @@ static int zmk_backlight_init(const struct device *_arg) { LOG_ERR("Failed to load backlight settings: %d", rc); } #endif - +#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) + state.on = zmk_usb_is_powered(); +#endif return zmk_backlight_update(); } From 90e070b427e28b83eb6b55620e2dee6627075374 Mon Sep 17 00:00:00 2001 From: ReFil <31960031+ReFil@users.noreply.github.com> Date: Sat, 25 Jun 2022 15:56:36 +0100 Subject: [PATCH 10/50] feat(underglow): Add RGB auto off timeout on idle and on usb disconnect Two new options for functionality to enable/disable RGB for USB status or idle events. Co-authored-by: Pete Johanson Co-authored-by: ReFil --- app/Kconfig | 7 +++++ app/src/rgb_underglow.c | 63 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/app/Kconfig b/app/Kconfig index 4bcd88b0..f89d3279 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -243,6 +243,13 @@ config ZMK_RGB_UNDERGLOW_ON_START bool "RGB underglow starts on by default" default y +config ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE + bool "Turn off RGB underglow when keyboard goes into idle state" + +config ZMK_RGB_UNDERGLOW_AUTO_OFF_USB + bool "Turn off RGB underglow when USB is disconnected" + depends on USB_DEVICE_STACK + #ZMK_RGB_UNDERGLOW endif diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 517da1b8..25d4466e 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -19,6 +19,12 @@ #include +#include +#include +#include +#include +#include + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define STRIP_LABEL DT_LABEL(DT_CHOSEN(zmk_underglow)) @@ -265,7 +271,13 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { settings_load_subtree("rgb/underglow"); #endif - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) + state.on = zmk_usb_is_powered(); +#endif + + if (state.on) { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + } return 0; } @@ -444,4 +456,53 @@ int zmk_rgb_underglow_change_spd(int direction) { return zmk_rgb_underglow_save_state(); } +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || \ + IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) +static int rgb_underglow_auto_state(bool *prev_state, bool new_state) { + if (state.on == new_state) { + return 0; + } + if (new_state) { + state.on = *prev_state; + *prev_state = false; + return zmk_rgb_underglow_on(); + } else { + state.on = false; + *prev_state = true; + return zmk_rgb_underglow_off(); + } +} + +static int rgb_underglow_event_listener(const zmk_event_t *eh) { + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) + if (as_zmk_activity_state_changed(eh)) { + static bool prev_state = false; + return rgb_underglow_auto_state(&prev_state, + zmk_activity_get_state() == ZMK_ACTIVITY_ACTIVE); + } +#endif + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) + if (as_zmk_usb_conn_state_changed(eh)) { + static bool prev_state = false; + return rgb_underglow_auto_state(&prev_state, zmk_usb_is_powered()); + } +#endif + + return -ENOTSUP; +} + +ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener); +#endif // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || + // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) +ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); +#endif + +#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) +ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); +#endif + SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); From da209c453eac57b9bb3818a81ad9b20345618c4b Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 20 Jun 2022 03:54:19 +0000 Subject: [PATCH 11/50] refactor(shields): Remove res max Kconfigs * Horizontal/Vertical resolution max is now defaulted from the DTS chosen display automatically, so remove the duplication in our shield Kconfig. --- app/boards/shields/corne/Kconfig.defconfig | 6 ------ app/boards/shields/elephant42/Kconfig.defconfig | 6 ------ app/boards/shields/jorne/Kconfig.defconfig | 6 ------ app/boards/shields/knob_goblin/Kconfig.defconfig | 6 ------ app/boards/shields/kyria/Kconfig.defconfig | 6 ------ app/boards/shields/leeloo/Kconfig.defconfig | 5 ----- app/boards/shields/lily58/Kconfig.defconfig | 6 ------ app/boards/shields/lotus58/Kconfig.defconfig | 6 ------ app/boards/shields/microdox/Kconfig.defconfig | 6 ------ app/boards/shields/murphpad/Kconfig.defconfig | 6 ------ app/boards/shields/nibble/Kconfig.defconfig | 6 ------ app/boards/shields/sofle/Kconfig.defconfig | 6 ------ app/boards/shields/tidbit/Kconfig.defconfig | 6 ------ app/boards/shields/zodiark/Kconfig.defconfig | 6 ------ 14 files changed, 83 deletions(-) diff --git a/app/boards/shields/corne/Kconfig.defconfig b/app/boards/shields/corne/Kconfig.defconfig index 31ca73fd..9160555c 100644 --- a/app/boards/shields/corne/Kconfig.defconfig +++ b/app/boards/shields/corne/Kconfig.defconfig @@ -28,12 +28,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/elephant42/Kconfig.defconfig b/app/boards/shields/elephant42/Kconfig.defconfig index 051e3e89..1e93762c 100644 --- a/app/boards/shields/elephant42/Kconfig.defconfig +++ b/app/boards/shields/elephant42/Kconfig.defconfig @@ -31,12 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/jorne/Kconfig.defconfig b/app/boards/shields/jorne/Kconfig.defconfig index 8d4dfb99..64eb32b8 100644 --- a/app/boards/shields/jorne/Kconfig.defconfig +++ b/app/boards/shields/jorne/Kconfig.defconfig @@ -29,12 +29,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/knob_goblin/Kconfig.defconfig b/app/boards/shields/knob_goblin/Kconfig.defconfig index 07df5996..300fb4eb 100644 --- a/app/boards/shields/knob_goblin/Kconfig.defconfig +++ b/app/boards/shields/knob_goblin/Kconfig.defconfig @@ -21,12 +21,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/kyria/Kconfig.defconfig b/app/boards/shields/kyria/Kconfig.defconfig index 2995daac..0da8a18d 100644 --- a/app/boards/shields/kyria/Kconfig.defconfig +++ b/app/boards/shields/kyria/Kconfig.defconfig @@ -29,12 +29,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 64 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/leeloo/Kconfig.defconfig b/app/boards/shields/leeloo/Kconfig.defconfig index a3f4eff3..a4295d1e 100644 --- a/app/boards/shields/leeloo/Kconfig.defconfig +++ b/app/boards/shields/leeloo/Kconfig.defconfig @@ -31,11 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/lily58/Kconfig.defconfig b/app/boards/shields/lily58/Kconfig.defconfig index 7da758cd..a5e6fbe8 100644 --- a/app/boards/shields/lily58/Kconfig.defconfig +++ b/app/boards/shields/lily58/Kconfig.defconfig @@ -29,12 +29,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/lotus58/Kconfig.defconfig b/app/boards/shields/lotus58/Kconfig.defconfig index d108d7d8..57ae5ef6 100644 --- a/app/boards/shields/lotus58/Kconfig.defconfig +++ b/app/boards/shields/lotus58/Kconfig.defconfig @@ -31,12 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/microdox/Kconfig.defconfig b/app/boards/shields/microdox/Kconfig.defconfig index 461e7c32..7bf40b8b 100644 --- a/app/boards/shields/microdox/Kconfig.defconfig +++ b/app/boards/shields/microdox/Kconfig.defconfig @@ -31,12 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/murphpad/Kconfig.defconfig b/app/boards/shields/murphpad/Kconfig.defconfig index 8e205a8e..9f491564 100644 --- a/app/boards/shields/murphpad/Kconfig.defconfig +++ b/app/boards/shields/murphpad/Kconfig.defconfig @@ -21,12 +21,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/nibble/Kconfig.defconfig b/app/boards/shields/nibble/Kconfig.defconfig index 2df1d812..a1683f3a 100644 --- a/app/boards/shields/nibble/Kconfig.defconfig +++ b/app/boards/shields/nibble/Kconfig.defconfig @@ -25,12 +25,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/sofle/Kconfig.defconfig b/app/boards/shields/sofle/Kconfig.defconfig index bfa7c3a5..69dac3f2 100644 --- a/app/boards/shields/sofle/Kconfig.defconfig +++ b/app/boards/shields/sofle/Kconfig.defconfig @@ -31,12 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/tidbit/Kconfig.defconfig b/app/boards/shields/tidbit/Kconfig.defconfig index 177e2675..013a0a7c 100644 --- a/app/boards/shields/tidbit/Kconfig.defconfig +++ b/app/boards/shields/tidbit/Kconfig.defconfig @@ -22,12 +22,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 32 - config LVGL_VDB_SIZE default 64 diff --git a/app/boards/shields/zodiark/Kconfig.defconfig b/app/boards/shields/zodiark/Kconfig.defconfig index 7780abd2..76bfcbd4 100644 --- a/app/boards/shields/zodiark/Kconfig.defconfig +++ b/app/boards/shields/zodiark/Kconfig.defconfig @@ -31,12 +31,6 @@ endif # ZMK_DISPLAY if LVGL -config LVGL_HOR_RES_MAX - default 128 - -config LVGL_VER_RES_MAX - default 64 - config LVGL_VDB_SIZE default 64 From ef3eb339ede6568c96c095648d2e0f20715e2549 Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Wed, 22 Jun 2022 15:07:05 -0700 Subject: [PATCH 12/50] feat(shields): Add RGB support to 2% Milk Co-Authored-By: treezoob <98245530+treezoob@users.noreply.github.com> --- .../two_percent_milk/boards/nice_nano.overlay | 31 ++++++++++++++++++ .../boards/nice_nano_v2.overlay | 31 ++++++++++++++++++ .../boards/nrfmicro_11.overlay | 32 +++++++++++++++++++ .../boards/nrfmicro_11_flipped.overlay | 31 ++++++++++++++++++ .../boards/nrfmicro_13.overlay | 31 ++++++++++++++++++ .../two_percent_milk/two_percent_milk.conf | 7 ++++ 6 files changed, 163 insertions(+) create mode 100644 app/boards/shields/two_percent_milk/boards/nice_nano.overlay create mode 100644 app/boards/shields/two_percent_milk/boards/nice_nano_v2.overlay create mode 100644 app/boards/shields/two_percent_milk/boards/nrfmicro_11.overlay create mode 100644 app/boards/shields/two_percent_milk/boards/nrfmicro_11_flipped.overlay create mode 100644 app/boards/shields/two_percent_milk/boards/nrfmicro_13.overlay diff --git a/app/boards/shields/two_percent_milk/boards/nice_nano.overlay b/app/boards/shields/two_percent_milk/boards/nice_nano.overlay new file mode 100644 index 00000000..dd7e34c4 --- /dev/null +++ b/app/boards/shields/two_percent_milk/boards/nice_nano.overlay @@ -0,0 +1,31 @@ +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + status = "okay"; + mosi-pin = <9>; + // 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 = <2>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/two_percent_milk/boards/nice_nano_v2.overlay b/app/boards/shields/two_percent_milk/boards/nice_nano_v2.overlay new file mode 100644 index 00000000..dd7e34c4 --- /dev/null +++ b/app/boards/shields/two_percent_milk/boards/nice_nano_v2.overlay @@ -0,0 +1,31 @@ +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + status = "okay"; + mosi-pin = <9>; + // 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 = <2>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/two_percent_milk/boards/nrfmicro_11.overlay b/app/boards/shields/two_percent_milk/boards/nrfmicro_11.overlay new file mode 100644 index 00000000..c2dab5a6 --- /dev/null +++ b/app/boards/shields/two_percent_milk/boards/nrfmicro_11.overlay @@ -0,0 +1,32 @@ +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + status = "okay"; + mosi-pin = <43>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <12>; // 0.12 is not broken out on the nRFMicro + miso-pin = <22>; // 0.22 is not broken out on the nRFMicro + + 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 = <2>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; + diff --git a/app/boards/shields/two_percent_milk/boards/nrfmicro_11_flipped.overlay b/app/boards/shields/two_percent_milk/boards/nrfmicro_11_flipped.overlay new file mode 100644 index 00000000..e53b149a --- /dev/null +++ b/app/boards/shields/two_percent_milk/boards/nrfmicro_11_flipped.overlay @@ -0,0 +1,31 @@ +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + status = "okay"; + mosi-pin = <38>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <12>; // 0.12 is not broken out on the nRFMicro + miso-pin = <22>; // 0.22 is not broken out on the nRFMicro + + 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 = <2>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/two_percent_milk/boards/nrfmicro_13.overlay b/app/boards/shields/two_percent_milk/boards/nrfmicro_13.overlay new file mode 100644 index 00000000..252329b4 --- /dev/null +++ b/app/boards/shields/two_percent_milk/boards/nrfmicro_13.overlay @@ -0,0 +1,31 @@ +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + status = "okay"; + mosi-pin = <43>; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + sck-pin = <12>; // 0.12 is not broken out on the nRFMicro + miso-pin = <22>; // 0.22 is not broken out on the nRFMicro + + 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 = <2>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/two_percent_milk/two_percent_milk.conf b/app/boards/shields/two_percent_milk/two_percent_milk.conf index fb23f20c..4baccacf 100644 --- a/app/boards/shields/two_percent_milk/two_percent_milk.conf +++ b/app/boards/shields/two_percent_milk/two_percent_milk.conf @@ -1,2 +1,9 @@ # Copyright (c) 2022 The ZMK Contributors # SPDX-License-Identifier: MIT + +# Uncomment the following lines to enable RGB Underglow +# CONFIG_ZMK_RGB_UNDERGLOW=y +# CONFIG_WS2812_STRIP=y + +# Uncomment the following line to turn on logging, and set ZMK logging to debug output +# CONFIG_ZMK_USB_LOGGING=y \ No newline at end of file From 91de215bf05be072075095a54c09aaf3c13a04b4 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sat, 2 Jul 2022 20:02:58 -0700 Subject: [PATCH 13/50] fix(docs): Add missing underglow on/off defines --- docs/docs/behaviors/underglow.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/behaviors/underglow.md b/docs/docs/behaviors/underglow.md index a6a47719..c028f832 100644 --- a/docs/docs/behaviors/underglow.md +++ b/docs/docs/behaviors/underglow.md @@ -22,6 +22,8 @@ Here is a table describing the action for each define: | Define | Action | | --------------- | ---------------------------------------------------------------------------------------------- | +| `RGB_ON` | Turns the RGB feature on | +| `RGB_OFF` | Turns the RGB feature off | | `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 | From edec4595aebd332880b988742c234dd61c1b4d9a Mon Sep 17 00:00:00 2001 From: Nick Conway Date: Fri, 1 Jul 2022 15:00:51 -0400 Subject: [PATCH 14/50] fix(behaviors): Fix mod morph description --- app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml index 66b452a5..ed40f936 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml @@ -1,7 +1,7 @@ # Copyright (c) 2020 The ZMK Contributors # SPDX-License-Identifier: MIT -description: Keyboard Reset Behavior +description: Mod Morph Behavior compatible: "zmk,behavior-mod-morph" From 61806435814f76d435166c3ccffcc6df4eb0ff5c Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sat, 2 Jul 2022 19:47:15 -0700 Subject: [PATCH 15/50] fix(shields): Remove uses of deprecated pro_micro_a/d nodes --- app/boards/shields/a_dux/a_dux.dtsi | 34 ++++++++++++++--------------- app/boards/shields/cradio/README.md | 34 ++++++++++++++--------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/app/boards/shields/a_dux/a_dux.dtsi b/app/boards/shields/a_dux/a_dux.dtsi index 4c28d655..4840227c 100644 --- a/app/boards/shields/a_dux/a_dux.dtsi +++ b/app/boards/shields/a_dux/a_dux.dtsi @@ -29,23 +29,23 @@ compatible = "zmk,kscan-gpio-direct"; label = "KSCAN"; input-gpios = - <&pro_micro_d 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_a 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_a 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_a 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_a 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, - <&pro_micro_d 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + <&pro_micro 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 18 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 21 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 19 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 20 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>, + <&pro_micro 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> ; }; diff --git a/app/boards/shields/cradio/README.md b/app/boards/shields/cradio/README.md index b1344293..ee1cab0e 100644 --- a/app/boards/shields/cradio/README.md +++ b/app/boards/shields/cradio/README.md @@ -11,23 +11,23 @@ Some revisions of the aforementioned PCBs have slightly different pin arrangemen /* The position of Q and B keys have been swapped */ &kscan0 { input-gpios - = <&pro_micro_d 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_a 0 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&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_d 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> - , <&pro_micro_d 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + = <&pro_micro 6 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 18 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 19 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 20 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 21 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 15 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 14 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 16 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 3 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 7 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + , <&pro_micro 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> ; }; ``` From 11861a4d303b769a2de615376fbf766e7db433fc Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 6 Mar 2021 20:06:15 -0600 Subject: [PATCH 16/50] docs: Add documentation for config options --- docs/docs/config/behaviors.md | 91 +++++++++ docs/docs/config/combos.md | 42 +++++ docs/docs/config/displays.md | 43 +++++ docs/docs/config/encoders.md | 41 ++++ docs/docs/config/index.md | 237 ++++++++++++++++++++++++ docs/docs/config/keymap.md | 42 +++++ docs/docs/config/kscan.md | 319 ++++++++++++++++++++++++++++++++ docs/docs/config/power.md | 65 +++++++ docs/docs/config/system.md | 66 +++++++ docs/docs/config/underglow.md | 43 +++++ docs/docs/customization.md | 6 +- docs/docs/features/combos.md | 10 +- docs/docs/features/underglow.md | 23 +-- docs/sidebars.js | 12 ++ 14 files changed, 1014 insertions(+), 26 deletions(-) create mode 100644 docs/docs/config/behaviors.md create mode 100644 docs/docs/config/combos.md create mode 100644 docs/docs/config/displays.md create mode 100644 docs/docs/config/encoders.md create mode 100644 docs/docs/config/index.md create mode 100644 docs/docs/config/keymap.md create mode 100644 docs/docs/config/kscan.md create mode 100644 docs/docs/config/power.md create mode 100644 docs/docs/config/system.md create mode 100644 docs/docs/config/underglow.md diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md new file mode 100644 index 00000000..f8a0df7d --- /dev/null +++ b/docs/docs/config/behaviors.md @@ -0,0 +1,91 @@ +--- +title: Behavior Configuration +sidebar_label: Behaviors +--- + +Some behaviors have properties to adjust how they behave. These can also be used as templates to create custom behaviors when none of the built-in behaviors do what you want. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/app/dts/behaviors) folder for all default behaviors. + +## Hold-Tap + +Creates a custom behavior that triggers one behavior when a key is held or a different one when the key is tapped. + +See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for more details and examples. + +### Devicetree + +Applies to: `compatible = "zmk,behavior-hold-tap"` + +| Property | Type | Description | Default | +| ----------------- | ------------- | ------------------------------------------------------------------------------ | ------------------ | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<2>` | | +| `bindings` | phandle array | A list of two behaviors: one for hold and one for tap | | +| `tapping-term-ms` | int | How long in milliseconds the key must be held to trigger a hold | | +| `quick-tap-ms` | int | Tap twice within this period in milliseconds to trigger a tap, even when held | -1 | +| `flavor` | string | Adjusts how the behavior chooses between hold and tap | `"hold-preferred"` | +| `retro-tap` | bool | Triggers the tap behavior on release if no other key was pressed during a hold | false | + +The `flavor` property may be one of: + +- `"hold-preferred"` +- `"balanced"` +- `"tap-preferred"` + +See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for an explanation of each flavor. + +| Node | Behavior | +| ----- | --------------------------------------------- | +| `<` | [Layer-tap](/docs/behaviors/layers#layer-tap) | +| `&mt` | [Mod-tap](/docs/behaviors/mod-tap) | + +## Mod-Morph + +Creates a custom behavior that triggers one behavior when a key is pressed without certain modifiers held or a different one if certain modifiers are held. + +### Devicetree + +Applies to: `compatible = "zmk,behavior-mod-morph"` + +| Property | Type | Description | +| ---------------- | ------------- | ----------------------------------------------------------------------------------- | +| `label` | string | Unique label for the node | +| `#binding-cells` | int | Must be `<0>` | +| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press | +| `mods` | int | A bit field of modifiers which will switch to the morph behavior if any are pressed | + +See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers. + +You can use the following nodes to tweak the default behaviors: + +| Node | Behavior | +| -------- | ------------ | +| `&gresc` | Grave escape | + +## Sticky Key + +Creates a custom behavior that triggers a behavior and keeps it pressed it until another key is pressed and released. + +See the [sticky key behavior](/docs/behaviors/sticky-key) and [sticky layer behavior](/docs/behaviors/sticky-layer) documentation for more details and examples. + +### Devicetree + +Applies to: `compatible = "zmk,behavior-sticky-key"` + +| Property | Type | Description | Default | +| ------------------ | ------------- | ------------------------------------------------------------------------ | ------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must match the number of parameters the `bindings` behavior uses | | +| `bindings` | phandle array | A behavior (without parameters) to trigger | | +| `release-after-ms` | int | Releases the key after this many milliseconds if no other key is pressed | 1000 | +| `quick-release` | bool | Release the sticky key on the next key press instead of release | false | + +You can use the following nodes to tweak the default behaviors: + +| Node | Behavior | +| ----- | -------------------------------------------- | +| `&sk` | [Sticky key](/docs/behaviors/sticky-key) | +| `&sl` | [Sticky layer](/docs/behaviors/sticky-layer) | diff --git a/docs/docs/config/combos.md b/docs/docs/config/combos.md new file mode 100644 index 00000000..fe8877ef --- /dev/null +++ b/docs/docs/config/combos.md @@ -0,0 +1,42 @@ +--- +title: Combo Configuration +sidebar_label: Combos +--- + +See the [Combos feature page](/docs/features/combos) for more details and examples. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------------- | ---- | -------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` | int | Maximum number of combos that can be active at the same time | 4 | +| `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` | int | Maximum number of active combos that use the same key position | 5 | +| `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` | int | Maximum number of keys to press to activate a combo | 4 | + +If `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is 5, you can have 5 separate combos that use position `0`, 5 combos that use position `1`, and so on. + +If you want a combo that triggers when pressing 5 keys, you must set `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` to 5. + +## Devicetree + +Applies to: `compatible = "zmk,combo"` + +Definition file: [zmk/app/dts/bindings/zmk,combos.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ccombos.yaml) + +The `zmk,combos` node itself has no properties. It should have one child node per combo. + +Each child node can have the following properties: + +| Property | Type | Description | Default | +| --------------- | ------------- | ---------------------------------------------------------------------------------- | ------- | +| `bindings` | phandle-array | A [behavior](/docs/features/keymaps#behaviors) to run when the combo is triggered | | +| `key-positions` | array | A list of key position indices for the keys which should trigger the combo | | +| `timeout-ms` | int | All the keys must be pressed within this time in milliseconds to trigger the combo | 50 | +| `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | +| `layers` | array | A list of layers on which the combo may be triggered. `-1` allows all layers. | `<-1>` | + +The `key-positions` array must not be longer than the `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` setting, which defaults to 4. If you want a combo that triggers when pressing 5 keys, then you must change the setting to 5. diff --git a/docs/docs/config/displays.md b/docs/docs/config/displays.md new file mode 100644 index 00000000..93d314a8 --- /dev/null +++ b/docs/docs/config/displays.md @@ -0,0 +1,43 @@ +--- +title: Display Configuration +sidebar_label: Displays +--- + +See the [displays feature page](/docs/features/displays) for more details. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Kconfig + +Definition files: + +- [zmk/app/src/display/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/display/Kconfig) +- [zmk/app/src/display/widgets/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/display/widgets/Kconfig) + +| Config | Type | Description | Default | +| ---------------------------------- | ---- | ---------------------------------------------------- | ------- | +| `CONFIG_ZMK_DISPLAY` | bool | Enable support for displays | n | +| `CONFIG_ZMK_WIDGET_LAYER_STATUS` | bool | Enable a widget to show the highest, active layer | y | +| `CONFIG_ZMK_WIDGET_BATTERY_STATUS` | bool | Enable a widget to show battery charge information | y | +| `CONFIG_ZMK_WIDGET_OUTPUT_STATUS` | bool | Enable a widget to show the current output (USB/BLE) | y | +| `CONFIG_ZMK_WIDGET_WPM_STATUS` | bool | Enable a widget to show words per minute | n | + +If `CONFIG_ZMK_DISPLAY` is enabled, exactly one of the following options must be set to `y`: + +| Config | Type | Description | Default | +| ------------------------------------------- | ---- | ------------------------------ | ------- | +| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | bool | Use the built-in status screen | y | +| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` | bool | Use a custom status screen | n | + +You must also configure the Zephyr driver for your display. Here are the Kconfig options for common displays. + +- [SSD1306](https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_SSD1306.html) + +## Devicetree + +See the Zephyr Devicetree bindings for your display. Here are the bindings for common displays: + +- [SSD1306 (i2c)](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/solomon,ssd1306fb-i2c.html) +- [SSD1306 (spi)](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/solomon,ssd1306fb-spi.html) + +A full list of supported drivers can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/latest/reference/devicetree/bindings.html). diff --git a/docs/docs/config/encoders.md b/docs/docs/config/encoders.md new file mode 100644 index 00000000..28440738 --- /dev/null +++ b/docs/docs/config/encoders.md @@ -0,0 +1,41 @@ +--- +title: Encoder Configuration +sidebar_label: Encoders +--- + +See the [Encoders feature page](/docs/features/encoders) for more details, including instructions for adding encoder support to a board. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## EC11 Encoders + +### Kconfig + +Definition file: [zmk/app/drivers/sensor/ec11/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/sensor/ec11/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------- | ---- | -------------------------------- | ------- | +| `CONFIG_EC11` | bool | Enable EC11 encoders | n | +| `CONFIG_EC11_THREAD_PRIORITY` | int | Priority of the encoder thread | 10 | +| `CONFIG_EC11_THREAD_STACK_SIZE` | int | Stack size of the encoder thread | 1024 | + +If `CONFIG_EC11` is enabled, exactly one of the following options must be set to `y`: + +| Config | Type | Description | +| ----------------------------------- | ---- | ----------------------------------------------- | +| `CONFIG_EC11_TRIGGER_NONE` | bool | No trigger (encoders are disabled) | +| `CONFIG_EC11_TRIGGER_GLOBAL_THREAD` | bool | Process encoder interrupts on the global thread | +| `CONFIG_EC11_TRIGGER_OWN_THREAD` | bool | Process encoder interrupts on their own thread | + +### Devicetree + +Applies to: `compatible = "alps,ec11"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/sensor/alps,ec11.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/sensor/alps%2Cec11.yaml) + +| Property | Type | Description | Default | +| ------------ | ---------- | ------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `a-gpios` | GPIO array | GPIO connected to the encoder's A pin | | +| `b-gpios` | GPIO array | GPIO connected to the encoder's B pin | | +| `resolution` | int | Number of encoder pulses per tick | 1 | diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md new file mode 100644 index 00000000..71058237 --- /dev/null +++ b/docs/docs/config/index.md @@ -0,0 +1,237 @@ +--- +title: Configuration Overview +sidebar_label: Overview +--- + +ZMK has several configuration settings that can be changed to change the behavior of your keyboard. They are set in either Kconfig or Devicetree files. + +This page describes the Kconfig and Devicetree file formats and how to change settings in them. See the other pages in the configuration section for a list of settings you can change. + +:::note +All configuration is currently set at compile time. After changing any settings, you must build new firmware and flash it for the changes to apply. +::: + +## Config File Locations + +ZMK will search multiple folders for the config files described below. There are three primary locations it will search: + +### User Config Folder + +When building with a `zmk-config` folder, ZMK will search the `zmk-config/config` folder for the following config files, where `` is the name of the shield if using a shield, or the name of the board otherwise: + +- `.conf` (Kconfig) +- `.keymap` (Devicetree) + +These files hold your personal settings for the keyboard. They override any configuration set in the board or shield folders. + +When using a split keyboard, you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. + +### Board Folder + +ZMK will search for config files in either of: + +- [`zmk/app/boards/arm/`](https://github.com/zmkfirmware/zmk/tree/main/app/boards/arm) +- `zmk-config/config/boards/arm/` + +...where `` is the name of the board. These files describe the hardware of the board. + +ZMK will search the board folder for the following config files: + +- `_defconfig` (Kconfig) +- `.dts` (Devicetree) +- `.keymap` (Devictree, standalone boards only) + +For more documentation on creating and configuring a new board, see [Zephyr's board porting guide](https://docs.zephyrproject.org/latest/guides/porting/board_porting.html#write-kconfig-files). + +### Shield Folder + +When building with a shield, ZMK will search for config files in either of: + +- [`zmk/app/boards/shields/`](https://github.com/zmkfirmware/zmk/tree/main/app/boards/shields) +- `zmk-config/config/boards/shields/` + +...where `` is the name of the shield. These files describe the hardware of the shield that the board is plugged into. + +ZMK will search the shield folder for the following config files: + +- `.conf` (Kconfig) +- `.overlay` (Devicetree) +- `.keymap` (Devicetree) + +For more documentation on creating and configuring a new shield, see [Zephyr's shield documentation](https://docs.zephyrproject.org/latest/guides/porting/shields.html) and [ZMK's new keyboard shield](/docs/development/new-shield) guide. + +## Kconfig Files + +Kconfig is used to configure global settings such as the keyboard name and enabling certain hardware devices. These typically have a `.conf` file extension and are text files containing `CONFIG_XYZ=value` assignments, with one setting per line. + +Kconfig files look like this: + +``` +CONFIG_ZMK_SLEEP=y +CONFIG_EC11=y +CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y +``` + +The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. + +See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/latest/guides/kconfig/index.html) for more details on Kconfig files. + +### KConfig Value Types + +#### bool + +Either `y` for yes or `n` for no. + +Example: `CONFIG_FOO=y` + +#### int + +An integer. + +Example: `CONFIG_FOO=42` + +#### string + +Text surrounded by double quotes. + +Example: `CONFIG_FOO="foo"` + +## Devicetree Files + +Various Devicetree files are combined to build a tree that describes the hardware for a keyboard. They are also used to define keymaps. Common file extensions for Devicetree files are `.dts`, `.dtsi`, `.overlay`, and `.keymap`. + +Devicetree files look like this: + +```devicetree +/ { + chosen { + zmk,kscan = &kscan0; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + }; +}; +``` + +Devicetree properties apply to specific nodes in the tree instead of globally. The properties that can be set for each node are determined by `.yaml` files in ZMK in the various `dts/bindings` folders. + +See [Zephyr's Devicetree guide](https://docs.zephyrproject.org/latest/guides/dts/index.html) for more details on Devicetree files. + +### Changing Devicetree Properties + +Since Devicetree properties are set for specific nodes in the tree, you will first need to find the node you want to configure. You will typically need to +search through the `.dts` file for you board, `.overlay` file for your shield, or a `.dtsi` file included in by of those files using an `#include` statement. + +A Devicetree node looks like this: + +```devicetree +kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + // more properties and/or nodes... +}; +``` + +The part before the colon, `kscan0`, is a label. This is optional, and it provides a shortcut to allow changing the properties of the node. The part after the colon, `kscan`, is the node's name. The values inside the curly braces are the node's properties. + +The `compatible` property indicates what type of node it is. Search this documentation for the text inside the quotes to see which properties the node +supports. You can also search ZMK for a file whose name is the value of the `compatible` property with a `.yaml` file extension. + +To set a property, see below for examples for common property types, or see [Zephyr's Devicetree documentation](https://docs.zephyrproject.org/latest/guides/dts/intro.html#writing-property-values) for more details on the syntax for properties. + +To change a property for an existing node, first find the node you want to change and find its label. Next, outside of any other node, write an ampersand (`&`) +followed by the node's label, an opening curly brace (`{`), one or more new property values, a closing curly brace (`}`), and a semicolon (`;`). + +For example, to adjust the debouncing of the `zmk,kscan-gpio-matrix` node shown above, you could add this to your keymap: + +```devicetree +&kscan0 { + debounce-period = <7>; +}; +``` + +### Devicetree Property Types + +These are some of the property types you will see most often when working with ZMK. [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/guides/dts/bindings.html) provides more detailed information and a full list of types. + +#### bool + +True or false. To set the property to true, list it with no value. To set it to false, do not list it. + +Example: `property;` + +If a property has already been set to true and you need to override it to false, use the following command to delete the existing property: + +```devicetree +/delete-property/ the-property-name; +``` + +#### int + +A single integer surrounded by angle brackets. Also supports mathematical expressions. + +Example: `property = <42>;` + +#### string + +Text surrounded by double quotes. + +Example: `property = "foo";` + +#### array + +A list of integers surrounded by angle brackets and separated with spaces. Mathematical expressions can be used but must be surrounded by parenthesis. + +Example: `property = <1 2 3 4>;` + +Values can also be split into multiple blocks, e.g. `property = <1 2>, <3 4>;` + +#### phandle + +A single node reference surrounded by angle brackets. + +Example: `property = <&label>` + +#### phandles + +A list of node references surrounded by angle brackets. + +Example: `property = <&label1 &label2 &label3>` + +#### phandle array + +A list of node references and possibly numbers to associate with the node. Mathematical expressions can be used but must be surrounded by parenthesis. + +Example: `property = <&none &mo 1>;` + +Values can also be split into multiple blocks, e.g. `property = <&none>, <&mo 1>;` + +See the documentation for "phandle-array" in [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/guides/dts/bindings.html) +for more details on how parameters are associated with nodes. + +#### GPIO array + +This is just a phandle array. The documentation lists this as a different type to make it clear which properties expect an array of GPIOs. + +Each item in the array should be a label for a GPIO node (the names of which differ between hardware platforms) followed by an index and configuration flags. See [Zephyr's GPIO documentation](https://docs.zephyrproject.org/latest/reference/peripherals/gpio.html) for a full list of flags. + +Example: + +```devicetree +some-gpios = + <&gpio0 0 GPIO_ACTIVE_HIGH>, + <&gpio0 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)> + ; +``` + +#### path + +A path to a node, either as a node reference or as a string. + +Examples: + +``` +property = &label; +property = "/path/to/some/node"; +``` diff --git a/docs/docs/config/keymap.md b/docs/docs/config/keymap.md new file mode 100644 index 00000000..917b82c6 --- /dev/null +++ b/docs/docs/config/keymap.md @@ -0,0 +1,42 @@ +--- +title: Keymap Configuration +sidebar_label: Keymap +--- + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Keymap + +### Devicetree + +Applies to: `compatible = "zmk,keymap"` + +Definition file: [zmk/app/dts/bindings/zmk,keymap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckeymap.yaml) + +The `zmk,keymap` node itself has no properties. It should have one child node per layer of the keymap, starting with the default layer (layer 0). + +Each child node can have the following properties: + +| Property | Type | Description | +| ----------------- | ------------- | ---------------------------------------------------------------------- | +| `label` | string | Unique label for the node | +| `bindings` | phandle-array | List of [key behaviors](/docs/features/keymaps#behaviors), one per key | +| `sensor-bindings` | phandle-array | List of sensor behaviors, one per sensor | + +Items for `bindings` must be listed in the order the keys are defined in the [keyboard scan configuration](/docs/config/kscan). + +Items for `sensor-bindings` must be listed in the order the [sensors](#keymap-sensors) are defined. + +## Keymap Sensors + +### Devicetree + +Applies to: `compatible = "zmk,keymap-sensors"` + +| Property | Type | Description | +| --------- | -------- | -------------------- | +| `sensors` | phandles | List of sensor nodes | + +The following types of nodes can be used as a sensor: + +- [`alps,ec11`](/docs/config/encoders#ec11-encoders) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md new file mode 100644 index 00000000..9b7d1053 --- /dev/null +++ b/docs/docs/config/kscan.md @@ -0,0 +1,319 @@ +--- +title: Keyboard Scan Configuration +sidebar_label: Keyboard Scan +--- + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Common + +### Kconfig + +Definition files: + +- [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) +- [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) + +| Config | Type | Description | Default | +| ----------------------------------- | ---- | ------------------------------------------------------ | ------- | +| `CONFIG_ZMK_KSCAN_GPIO_DRIVER` | bool | Enable GPIO keyboard scan driver to detect key presses | y | +| `CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE` | int | Size of the event queue for kscan events | 4 | +| `CONFIG_ZMK_KSCAN_INIT_PRIORITY` | int | Keyboard scan device driver initialization priority | 40 | + +### Devicetree + +Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/guides/dts/intro.html#aliases-and-chosen-nodes) + +| Property | Type | Description | +| ---------------------- | ---- | ------------------------------------------------------------- | +| `zmk,kscan` | path | The node for the keyboard scan driver to use | +| `zmk,matrix_transform` | path | The node for the [matrix transform](#matrix-transform) to use | + +## Demux Driver + +Keyboard scan driver which works like a regular matrix but uses a demultiplexer to drive the rows or columns. This allows N GPIOs to drive N2 rows or columns instead of just N like with a regular matrix. + +### Devicetree + +Applies to: `compatible = "zmk,kscan-gpio-demux"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-demux.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-demux.yaml) + +| Property | Type | Description | Default | +| ----------------------- | ---------- | -------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `input-gpios` | GPIO array | Input GPIOs | | +| `output-gpios` | GPIO array | Demultiplexer address GPIOs | | +| `debounce-period` | int | Debounce period in milliseconds | 5 | +| `polling-interval-msec` | int | Polling interval in milliseconds | 25 | + +## Direct GPIO Driver + +Keyboard scan driver where each key has a dedicated GPIO. + +### Kconfig + +Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) + +| Config | Type | Description | Default | +| --------------------------------- | ---- | ------------------------------------------------ | ------- | +| `CONFIG_ZMK_KSCAN_DIRECT_POLLING` | bool | Poll for key presses instead of using interrupts | n | + +### Devicetree + +Applies to: `compatible = "zmk,kscan-gpio-direct"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-direct.yaml) + +| Property | Type | Description | Default | +| ----------------- | ---------- | ------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `input-gpios` | GPIO array | Input GPIOs (one per key) | | +| `debounce-period` | int | Debounce period in milliseconds | 5 | + +## Matrix Driver + +Keyboard scan driver where keys are arranged on a matrix with one GPIO per row and column. + +Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) + +| Config | Type | Description | Default | +| --------------------------------- | ---- | ------------------------------------------------ | ------- | +| `CONFIG_ZMK_KSCAN_MATRIX_POLLING` | bool | Poll for key presses instead of using interrupts | n | + +### Devicetree + +Applies to: `compatible = "zmk,kscan-gpio-matrix"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-matrix.yaml) + +| Property | Type | Description | Default | +| ----------------- | ---------- | ------------------------------------------------------------ | ----------- | +| `label` | string | Unique label for the node | | +| `row-gpios` | GPIO array | Matrix row GPIOs in order, starting from the top row | | +| `col-gpios` | GPIO array | Matrix column GPIOs in order, starting from the leftmost row | | +| `debounce-period` | int | Debounce period in milliseconds | 5 | +| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | + +The `diode-direction` property must be one of: + +| Value | Description | +| ----------- | --------------------------------------------------------------------- | +| `"row2col"` | Diodes point from rows to columns (cathodes are connected to columns) | +| `"col2row"` | Diodes point from columns to rows (cathodes are connected to rows) | + +## Composite Driver + +Keyboard scan driver which combines multiple other keyboard scan drivers. + +### Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ----------------------------------- | ---- | ----------------------------- | ------- | +| `CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER` | bool | Enable composite kscan driver | n | + +### Devicetree + +Applies to : `compatible = "zmk,kscan-composite"` + +Definition file: [zmk/app/dts/bindings/zmk,kscan-composite.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk,kscan-composite.yaml) + +| Property | Type | Description | Default | +| -------- | ------ | --------------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `rows` | int | The number rows of in the composite matrix | | +| `cols` | int | The number columns of in the composite matrix | | + +The `zmk,kscan-composite` node should have one child node per keyboard scan driver that should be composited. Each child node can have the following properties: + +| Property | Type | Description | Default | +| --------------- | ------- | ------------------------------------------------------------------------------ | ------- | +| `label` | string | Unique label for the node | | +| `kscan` | phandle | Label of the kscan driver to include | | +| `row-offset` | int | Shifts row 0 of the included driver to a new row in the composite matrix | 0 | +| `column-offset` | int | Shifts column 0 of the included driver to a new column in the composite matrix | 0 | + +### Example Configuration + +For example, consider a macropad with a 3x3 matrix and two direct GPIO keys: + +
+
+Matrix: + + + + + + + + + + +
Col 0Col 1Col 2
Row 0A0A1A2
Row 1A3A4A5
Row 2A6A7A8
+
+ +
+Direct GPIO: + + + + + + + + +
Col 0Col 1
Row 0B0B1
+
+
+ +To combine them, we need to create a composite matrix with enough rows and columns to fit both sets of keys without overlapping, then set row and/or columns offsets to shift them so they do not overlap. + +One possible way to do this is a 3x4 matrix where the direct GPIO keys are shifted to below the matrix keys... + + + + + + + + + + + +
Col 0Col 1Col 2
Row 0A0A1A2
Row 1A3A4A5
Row 2A6A7A8
Row 3B0B1(none)
+ +...which can be configured with the following Devicetree code: + +```devicetree +/ { + chosen { + zmk,kscan = &kscan0; + }; + + kscan0: kscan_composite { + compatible = "zmk,kscan-composite"; + label = "KSCAN0"; + rows = <3>; + columns = <4>; + + // Include the matrix driver + matrix { + kscan = <&kscan1>; + }; + + // Include the direct GPIO driver... + direct { + kscan = <&kscan2>; + row-offset = <3>; // ..and shift it to not overlap + }; + }; + + kscan1: kscan_matrix { + compatible = "zmk,kscan-gpio-matrix"; + // define 3x3 matrix here... + }; + + kscan2: kscan_direct { + compatible = "zmk,kscan-gpio-direct"; + // define 2 direct GPIOs here... + }; +} +``` + +## Mock Driver + +Mock keyboard scan driver that simulates key events. + +### Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------ | ---- | ------------------------ | ------- | +| `CONFIG_ZMK_KSCAN_MOCK_DRIVER` | bool | Enable mock kscan driver | n | + +### Devicetree + +Applies to: `compatible = "zmk,kscan-mock"` + +Definition file: [zmk/app/dts/bindings/zmk,kscan-mock.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ckscan-mock.yaml) + +| Property | Type | Description | Default | +| -------------- | ------ | --------------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `event-period` | int | Milliseconds between each generated event | | +| `events` | array | List of key events to simulate | | +| `rows` | int | The number rows of in the composite matrix | | +| `cols` | int | The number columns of in the composite matrix | | +| `exit-after` | bool | Exit the program after running all events | false | + +The `events` array should be defined using the macros from [dt-bindings/zmk/kscan_mock.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/kscan_mock.h). + +## Matrix Transform + +Defines a mapping from keymap logical positions to physical matrix positions. + +Transforms should be used any time the physical layout of a keyboard's keys does not match the layout of its electrical matrix and/or when not all positions in the matrix are used. This applies to most non-ortholinear boards. + +Transforms can also be used for keyboards with multiple layouts. You can define multiple matrix transform nodes, one for each layout, and users can select which one they want from the `/chosen` node in their keymaps. + +See the [new shield guide](/docs/development/new-shield/#optional-matrix-transform) for more documentation on how to define a matrix transform. + +### Devicetree + +Applies to: `compatible = "zmk,matrix-transform"` + +Definition file: [zmk/app/dts/bindings/zmk,matrix-transform.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Cmatrix-transform.yaml) + +| Property | Type | Description | Default | +| ------------ | ----- | --------------------------------------------------------------------- | ------- | +| `rows` | int | Number of rows in the transformed matrix | | +| `columns` | int | Number of columns in the transformed matrix | | +| `row-offset` | int | Adds an offset to all rows before looking them up in the transform | 0 | +| `col-offset` | int | Adds an offset to all columns before looking them up in the transform | 0 | +| `map` | array | A list of position transforms | | + +The `map` array should be defined using the `RC()` macro from [dt-bindings/zmk/matrix_transform.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/matrix_transform.h). It should have one item per logical position in the keymap. Each item should list the physical row and column that should trigger the key in that position. + +### Example Configuration + +Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-design/page/matrices-and-duplex-matrix), where the matrix has twice as many rows and half as many columns as the keyboard has keys. A matrix transform can be used to correct for this so that keymaps can match the layout of the keys, not the layout of the matrix. + +```devicetree +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + rows = <12>; + columns = <8>; + // define the matrix... + }; + + default_transform: matrix_transform { + compatible = "zmk,matrix-transform"; + rows = <6>; + columns = <16>; + // ESC F1 F2 F3 ... + // ` 1 2 3 ... + // Tab Q W E ... + // Caps A S D ... + // Shift Z X C ... + // Ctrl Alt ... + map = < + RC(0,0) RC(1,0) RC(0,1) RC(1,1) // ... + RC(2,0) RC(3,0) RC(2,1) RC(3,1) // ... + RC(4,0) RC(5,0) RC(4,1) RC(5,1) // ... + RC(6,0) RC(7,0) RC(6,1) RC(7,1) // ... + RC(8,0) RC(8,1) RC(9,1) // ... + RC(10,0) RC(11,0) // ... + >; + }; +}; +``` diff --git a/docs/docs/config/power.md b/docs/docs/config/power.md new file mode 100644 index 00000000..c3dae3de --- /dev/null +++ b/docs/docs/config/power.md @@ -0,0 +1,65 @@ +--- +title: Power Management Configuration +sidebar_label: Power Management +--- + +See [Configuration Overview](/docs/config/index) for instructions on how to +change these settings. + +## Idle/Sleep + +Configuration for entering low power modes when the keyboard is idle. + +In the idle state, peripherals such as displays and lighting are disabled, but the keyboard remains connected to Bluetooth so it can immediately respond when you press a key. + +In the deep sleep state, the keyboard additionally disconnects from Bluetooth. This state uses very little power, but it may take a few seconds to reconnect after waking. + +### Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------- | ---- | ----------------------------------------------------- | ------- | +| `CONFIG_ZMK_IDLE_TIMEOUT` | int | Milliseconds of inactivity before entering idle state | 30000 | +| `CONFIG_ZMK_SLEEP` | bool | Enable deep sleep support | n | +| `CONFIG_ZMK_IDLE_SLEEP_TIMEOUT` | int | Milliseconds of inactivity before entering deep sleep | 900000 | + +## External Power Control + +Driver for enabling or disabling power to peripherals such as displays and lighting. This driver must be configured to use [power management behaviors](/docs/behaviors/power). + +### Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ---------------------- | ---- | ----------------------------------------------- | ------- | +| `CONFIG_ZMK_EXT_POWER` | bool | Enable support to control external power output | y | + +### Devicetree + +Applies to: `compatible = "zmk,ext-power-generic"` + +| Property | Type | Description | +| --------------- | ---------- | ------------------------------------------------------------- | +| `label` | string | Unique label for the node | +| `control-gpios` | GPIO array | List of GPIOs which should be active to enable external power | +| `init-delay-ms` | int | number of milliseconds to delay after initializing the driver | + +## Battery Voltage Divider + +Driver for reading the voltage of a battery using an ADC connected to a voltage divider. + +### Kconfig + +Definition file: [zmk/app/drivers/sensor/battery_voltage_divider/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/sensor/battery_voltage_divider/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------------ | ---- | ------------------------------------------------------------ | ------- | +| `CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER` | bool | Enable battery voltage divider driver for battery monitoring | n | + +### Devicetree + +Applies to: `compatible = "zmk,battery-voltage-divider"` + +See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/voltage-divider.html). diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md new file mode 100644 index 00000000..86bf24c7 --- /dev/null +++ b/docs/docs/config/system.md @@ -0,0 +1,66 @@ +--- +title: System Configuration +sidebar_label: System +--- + +These are general settings that control how the keyboard behaves and which features it supports. Several of these settings come from Zephyr and are not specific to ZMK, but they are listed here because they are relevant to how a keyboard functions. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +### General + +| Config | Type | Description | Default | +| ----------------------------------- | ------ | ----------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard | | +| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | +| `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | +| `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | + +### USB + +| Config | Type | Description | Default | +| --------------------------------- | ------ | --------------------------------------- | --------------- | +| `CONFIG_USB` | bool | Enable USB drivers | | +| `CONFIG_USB_DEVICE_VID` | int | The vendor ID advertised to USB | `0x1D50` | +| `CONFIG_USB_DEVICE_PID` | int | The product ID advertised to USB | `0x615E` | +| `CONFIG_USB_DEVICE_MANUFACTURER` | string | The manufacturer name advertised to USB | `"ZMK Project"` | +| `CONFIG_USB_HID_POLL_INTERVAL_MS` | int | USB polling interval in milliseconds | 9 | +| `CONFIG_ZMK_USB` | bool | Enable ZMK as a USB keyboard | | +| `CONFIG_ZMK_USB_INIT_PRIORITY` | int | USB init priority | 50 | + +### Bluetooth + +See [Zephyr's Bluetooth stack architecture documentation](https://docs.zephyrproject.org/latest/guides/bluetooth/bluetooth-arch.html) +for more information on configuring Bluetooth. + +| Config | Type | Description | Default | +| ----------------------------------------------------- | ---- | ---------------------------------------------------------------------- | ------- | +| `CONFIG_BT` | bool | Enable Bluetooth support | | +| `CONFIG_BT_MAX_CONN` | int | Maximum number of simultaneous Bluetooth connections | 5 | +| `CONFIG_BT_MAX_PAIRED` | int | Maximum number of paired Bluetooth devices | 5 | +| `CONFIG_ZMK_BLE` | bool | Enable ZMK as a Bluetooth keyboard | | +| `CONFIG_ZMK_BLE_INIT_PRIORITY` | int | BLE init priority | 50 | +| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE` | int | Stack size of the BLE notify thread | 512 | +| `CONFIG_ZMK_BLE_THREAD_PRIORITY` | int | Priority of the BLE notify thread | 5 | +| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int | Max number of keyboard HID reports to queue for sending over BLE | 20 | +| `CONFIG_ZMK_BLE_CONSUMER__REPORT_QUEUE_SIZE` | int | Max number of consumer HID reports to queue for sending over BLE | 5 | +| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START` | bool | Clears all bond information from the keyboard on startup | n | +| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Experimental: require typing passkey from host to pair BLE connection | n | +| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | +| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | +| `CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the split peripheral BLE notify thread | 512 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the split peripheral BLE notify thread | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | + +### Logging + +| Config | Type | Description | Default | +| ------------------------ | ---- | ---------------------------------------- | ------- | +| `CONFIG_ZMK_USB_LOGGING` | bool | Enable USB CDC ACM logging for debugging | n | +| `CONFIG_ZMK_LOG_LEVEL` | int | Log level for ZMK debug messages | 4 | diff --git a/docs/docs/config/underglow.md b/docs/docs/config/underglow.md new file mode 100644 index 00000000..256e1b4f --- /dev/null +++ b/docs/docs/config/underglow.md @@ -0,0 +1,43 @@ +--- +title: RGB Underglow Configuration +sidebar_label: RGB Underglow +--- + +See the [RGB Underglow feature page](/docs/features/underglow) for more details, including instructions for adding underglow support to a board. + +See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. + +## Kconfig + +RGB underglow depends on [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/master/drivers/led_strip), which provides additional Kconfig options. + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Config | Type | Description | Default | +| ------------------------------------ | ---- | ----------------------------------------------------- | ------- | +| `CONFIG_ZMK_RGB_UNDERGLOW` | bool | Enable RGB underglow | n | +| `CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER` | bool | Underglow toggling also controls external power | y | +| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP` | int | Hue step in degrees (0-359) used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP` | int | Saturation step in percent used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP` | int | Brightness step in percent used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_START` | int | Default hue in degrees (0-359) | 0 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_START` | int | Default saturation percent (0-100) | 100 | +| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_START` | int | Default brightness in percent (0-100) | 100 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SPD_START` | int | Default effect speed (1-5) | 3 | +| `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START` | int | Default effect index from the effect list (see below) | 0 | +| `CONFIG_ZMK_RGB_UNDERGLOW_ON_START` | bool | Default on state | y | + +Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`: + +| Value | Effect | +| ----- | ----------- | +| 0 | Solid color | +| 1 | Breathe | +| 2 | Spectrum | +| 3 | Swirl | + +## Devicetree + +ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/master/dts/bindings/led_strip). + +See the [RGB underglow feature page](/docs/features/underglow) for examples of the properties that must be set to enable underglow. diff --git a/docs/docs/customization.md b/docs/docs/customization.md index ff5da61d..1ffc38cc 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -9,10 +9,10 @@ with the main `zmk` firmware repository to build your desired firmware. The main working components of ZMK are kept separate from your personal keyboard settings, reducing the amount of file manipulation in the configuration process. This makes flashing ZMK to your keyboard much easier, especially because you don't need to keep an up-to-date copy of zmk on your computer at all times. -On default `zmk-config` folder should contain two files: +By default, the `zmk-config` folder should contain two files: - `.conf` -- ``.keymap +- `.keymap` However, your config folder can also be modified to include a `boards/` directory for keymaps and configurations for multiple boards/shields outside of the default keyboard setting definitions. @@ -23,6 +23,8 @@ The setup script creates a `config/.conf` file that allows you to add ad control what features and options are built into your firmware. Opening that file with your text editor will allow you to see the various config settings that can be commented/uncommented to modify how your firmware is built. +Refer to the [Configuration](/docs/config/index) documentation for more details on this file. + ## Keymap Once you have the basic user config completed, you can find the keymap file in `config/.keymap` and customize from there. diff --git a/docs/docs/features/combos.md b/docs/docs/features/combos.md index 55e1563c..5c73a845 100644 --- a/docs/docs/features/combos.md +++ b/docs/docs/features/combos.md @@ -25,7 +25,7 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod - The name of the combo doesn't really matter, but convention is to start the node name with `combo_`. - The `compatible` property should always be `"zmk,combos"` for combos. -- `timeout-ms` is the length of the window (in milliseconds) in which all keys of the combo must be pressed in order to successfully trigger the combo. +- All the keys must be pressed within `timeout-ms` milliseconds to trigger the combo. - `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board. - `layers = <0 1...>` will allow limiting a combo to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope. - `bindings` is the behavior that is activated when the behavior is pressed. @@ -47,10 +47,4 @@ Key positions are numbered like the keys in your keymap, starting at 0. So, if t Invoking a source-specific behavior such as one of the [reset behaviors](behaviors/reset.md) using a combo will always trigger it on the central side of the keyboard, regardless of the side that the keys corresponding to `key-positions` are on. ::: -### Advanced configuration - -There are three global combo parameters which are set through KConfig. You can set them in the `.conf` file in the same directory as your keymap file. - -- `CONFIG_ZMK_COMBO_MAX_PRESSED_COMBOS` is the number of combos that can be active at the same time. Default 4. -- `CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY` is the maximum number of combos that can be active on a key position. Defaults to 5. (So you can have 5 separate combos that use position `3` for example) -- `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` is the maximum number of keys that need to be pressed to activate a combo. Default 4. If you want a combo that triggers when pressing 5 keys, you'd set this to 5 for example. +See [combo configuration](/docs/config/combos) for advanced configuration options. diff --git a/docs/docs/features/underglow.md b/docs/docs/features/underglow.md index 58b3ef45..d1cd8e20 100644 --- a/docs/docs/features/underglow.md +++ b/docs/docs/features/underglow.md @@ -25,7 +25,8 @@ 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 `CONFIG_ZMK_RGB_UNDERGLOW` and `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 `CONFIG_ZMK_RGB_UNDERGLOW` and `CONFIG_*_STRIP` configuration values in the `.conf` file for your board or shield. +For example: ``` CONFIG_ZMK_RGB_UNDERGLOW=y @@ -33,24 +34,14 @@ CONFIG_ZMK_RGB_UNDERGLOW=y CONFIG_WS2812_STRIP=y ``` +See [Configuration Overview](/docs/config/index) for more instructions on how to +use Kconfig. + 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 | -| ------------------------------------ | ----------------------------------------------- | ------- | -| `CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER` | Underglow toggling also controls external power | y | -| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP` | Hue step in degrees of 360 used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP` | Saturation step in percent used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP` | Brightness step in percent used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_START` | Default hue 0-359 in degrees | 0 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_START` | Default saturation 0-100 in percent | 100 | -| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_START` | Default brightness 0-100 in percent | 100 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SPD_START` | Default effect speed 1-5 | 3 | -| `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START` | Default effect integer from the effect enum | 0 | -| `CONFIG_ZMK_RGB_UNDERGLOW_ON_START` | Default on state | y | +See [RGB underglow configuration](/docs/config/underglow). ## Adding RGB Underglow to a Board @@ -154,7 +145,7 @@ Once you have your `led_strip` properly defined you need to add it to the root d }; ``` -Finally you need to enable the `CONFIG_ZMK_RGB_UNDERGLOW` and `X_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): +Finally you need to enable the `CONFIG_ZMK_RGB_UNDERGLOW` and `CONFIG*_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): ``` CONFIG_ZMK_RGB_UNDERGLOW=y diff --git a/docs/sidebars.js b/docs/sidebars.js index e7d05850..b8e74e0f 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -52,6 +52,18 @@ module.exports = { "codes/power", "codes/keymap-upgrader", ], + Configuration: [ + "config/index", + "config/behaviors", + "config/combos", + "config/displays", + "config/encoders", + "config/keymap", + "config/kscan", + "config/power", + "config/system", + "config/underglow", + ], Development: [ "development/clean-room", "development/documentation", From d36792db2d776c7c9b45db677fb02a8a4fbe4b94 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Thu, 28 Apr 2022 21:26:57 -0500 Subject: [PATCH 17/50] feat(docs): Update kscan config docs --- docs/docs/config/kscan.md | 46 +++++++++++++++------------------------ 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 9b7d1053..37910756 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -14,11 +14,14 @@ Definition files: - [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) - [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) -| Config | Type | Description | Default | -| ----------------------------------- | ---- | ------------------------------------------------------ | ------- | -| `CONFIG_ZMK_KSCAN_GPIO_DRIVER` | bool | Enable GPIO keyboard scan driver to detect key presses | y | -| `CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE` | int | Size of the event queue for kscan events | 4 | -| `CONFIG_ZMK_KSCAN_INIT_PRIORITY` | int | Keyboard scan device driver initialization priority | 40 | +| Config | Type | Description | Default | +| -------------------------------------- | ---- | ---------------------------------------------------- | ------- | +| `CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZE` | int | Size of the event queue for kscan events | 4 | +| `CONFIG_ZMK_KSCAN_INIT_PRIORITY` | int | Keyboard scan device driver initialization priority | 40 | +| `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS` | int | Global debounce time for key press in milliseconds | -1 | +| `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS` | int | Global debounce time for key release in milliseconds | -1 | + +If the debounce press/release values are set to any value other than `-1`, they override the `debounce-press-ms` and `debounce-release-ms` devicetree properties for all keyboard scan drivers which support them. ### Devicetree @@ -87,13 +90,16 @@ Applies to: `compatible = "zmk,kscan-gpio-matrix"` Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-matrix.yaml) -| Property | Type | Description | Default | -| ----------------- | ---------- | ------------------------------------------------------------ | ----------- | -| `label` | string | Unique label for the node | | -| `row-gpios` | GPIO array | Matrix row GPIOs in order, starting from the top row | | -| `col-gpios` | GPIO array | Matrix column GPIOs in order, starting from the leftmost row | | -| `debounce-period` | int | Debounce period in milliseconds | 5 | -| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | +| Property | Type | Description | Default | +| ------------------------- | ---------- | -------------------------------------------------------------------------------------------------- | ----------- | +| `label` | string | Unique label for the node | | +| `row-gpios` | GPIO array | Matrix row GPIOs in order, starting from the top row | | +| `col-gpios` | GPIO array | Matrix column GPIOs in order, starting from the leftmost row | | +| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | +| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | +| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | +| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | +| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_MATRIX_POLLING is enabled. | 10 | The `diode-direction` property must be one of: @@ -106,14 +112,6 @@ The `diode-direction` property must be one of: Keyboard scan driver which combines multiple other keyboard scan drivers. -### Kconfig - -Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) - -| Config | Type | Description | Default | -| ----------------------------------- | ---- | ----------------------------- | ------- | -| `CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER` | bool | Enable composite kscan driver | n | - ### Devicetree Applies to : `compatible = "zmk,kscan-composite"` @@ -227,14 +225,6 @@ One possible way to do this is a 3x4 matrix where the direct GPIO keys are shift Mock keyboard scan driver that simulates key events. -### Kconfig - -Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) - -| Config | Type | Description | Default | -| ------------------------------ | ---- | ------------------------ | ------- | -| `CONFIG_ZMK_KSCAN_MOCK_DRIVER` | bool | Enable mock kscan driver | n | - ### Devicetree Applies to: `compatible = "zmk,kscan-mock"` From bf84481b47fd64bedbfcf665b1dbc09aab5d5029 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 20:20:21 -0500 Subject: [PATCH 18/50] feat(docs): Update behavior config docs --- docs/docs/behaviors/caps-word.md | 2 +- docs/docs/config/behaviors.md | 151 +++++++++++++++++++++++++++---- 2 files changed, 135 insertions(+), 18 deletions(-) diff --git a/docs/docs/behaviors/caps-word.md b/docs/docs/behaviors/caps-word.md index 479e350f..1b743a59 100644 --- a/docs/docs/behaviors/caps-word.md +++ b/docs/docs/behaviors/caps-word.md @@ -5,7 +5,7 @@ sidebar_label: Caps Word ## Summary -The caps word behavior behaves similar to a caps lock, but will automatically deactivate when one of the configured "break keycodes" is pressed, or if the caps word key is pressed again. For smaller keyboards, using [mod-taps](/docs/behaviors/mod-tap), this can help avoid repeated alternating holds when typing words in all caps. +The caps word behavior behaves similar to a caps lock, but will automatically deactivate when any key not in a continue list is pressed, or if the caps word key is pressed again. For smaller keyboards using [mod-taps](/docs/behaviors/mod-tap), this can help avoid repeated alternating holds when typing words in all caps. The modifiers are applied only to to the alphabetic (`A` to `Z`) keycodes, to avoid automatically appliying them to numeric values, etc. diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index f8a0df7d..726f2bb2 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -9,6 +9,35 @@ See [Configuration Overview](/docs/config/index) for instructions on how to chan See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/app/dts/behaviors) folder for all default behaviors. +## Caps Word + +Creates a custom behavior that behaves similar to a caps lock but deactivates when any key not in a continue list is pressed. + +See the [caps word behavior](/docs/behaviors/caps-word) documentation for more details and examples. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-caps-word.yaml) + +Applies to: `compatible = "zmk,behavior-caps-word"` + +| Property | Type | Description | Default | +| ---------------- | ------ | ------------------------------------------------------------------ | -------------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<0>` | | +| `continue-list` | array | List of [key codes](/docs/codes) which do not deactivate caps lock | `` | +| `mods` | int | A bit field of modifiers to apply | `` | + +`continue-list` is treated as if it always includes alphanumeric characters (A-Z, 0-9). + +See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers. + +You can use the following nodes to tweak the default behaviors: + +| Node | Behavior | +| ------------ | -------------------------------------- | +| `&caps_word` | [Caps Word](/docs/behaviors/caps-word) | + ## Hold-Tap Creates a custom behavior that triggers one behavior when a key is held or a different one when the key is tapped. @@ -17,45 +46,113 @@ See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for more det ### Devicetree +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-hold-tap.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-hold-tap.yaml) + Applies to: `compatible = "zmk,behavior-hold-tap"` -| Property | Type | Description | Default | -| ----------------- | ------------- | ------------------------------------------------------------------------------ | ------------------ | -| `label` | string | Unique label for the node | | -| `#binding-cells` | int | Must be `<2>` | | -| `bindings` | phandle array | A list of two behaviors: one for hold and one for tap | | -| `tapping-term-ms` | int | How long in milliseconds the key must be held to trigger a hold | | -| `quick-tap-ms` | int | Tap twice within this period in milliseconds to trigger a tap, even when held | -1 | -| `flavor` | string | Adjusts how the behavior chooses between hold and tap | `"hold-preferred"` | -| `retro-tap` | bool | Triggers the tap behavior on release if no other key was pressed during a hold | false | +| Property | Type | Description | Default | +| ---------------------------- | ------------- | ----------------------------------------------------------------------------------------- | ------------------ | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<2>` | | +| `bindings` | phandle array | A list of two behaviors (without parameters): one for hold and one for tap | | +| `flavor` | string | Adjusts how the behavior chooses between hold and tap | `"hold-preferred"` | +| `tapping-term-ms` | int | How long in milliseconds the key must be held to trigger a hold | | +| `quick-tap-ms` | int | Tap twice within this period (in milliseconds) to trigger a tap, even when held | -1 (disabled) | +| `global-quick-tap` | bool | If enabled, `quick-tap-ms` also applies when tapping another key and then this one. | false | +| `retro-tap` | bool | Triggers the tap behavior on release if no other key was pressed during a hold | false | +| `hold-trigger-key-positions` | array | If set, pressing the hold-tap and then any key position _not_ in the list triggers a tap. | | The `flavor` property may be one of: - `"hold-preferred"` - `"balanced"` - `"tap-preferred"` +- `"tap-unless-interrupted"` See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for an explanation of each flavor. +`hold-trigger-key-positions` is an array of zero-based key position indices. + +You can use the following nodes to tweak the default behaviors: + | Node | Behavior | | ----- | --------------------------------------------- | | `<` | [Layer-tap](/docs/behaviors/layers#layer-tap) | | `&mt` | [Mod-tap](/docs/behaviors/mod-tap) | -## Mod-Morph +## Key Repeat -Creates a custom behavior that triggers one behavior when a key is pressed without certain modifiers held or a different one if certain modifiers are held. +Creates a custom behavior that repeats the whatever key code was last sent. + +See the [key repeat behavior](/docs/behaviors/key-repeat) documentation for more details and examples. ### Devicetree +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-key-repeat.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-key-repeat.yaml) + +Applies to: `compatible = "zmk,behavior-key-repeat"` + +| Property | Type | Description | Default | +| ---------------- | ------ | -------------------------------- | ----------------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<0>` | | +| `usage-pages` | array | List of HID usage pages to track | `` | + +For the `usage-pages` property, use the `HID_USAGE_*` defines from [dt-bindings/zmk/hid_usage_pages.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/hid_usage_pages.h). + +You can use the following nodes to tweak the default behaviors: + +| Node | Behavior | +| ------------- | ---------------------------------------- | +| `&key_repeat` | [Key repeat](/docs/behaviors/key-repeat) | + +## Macro + +Creates a custom behavior which triggers a sequence of other behaviors. + +See the [macro behavior](/docs/behaviors/macros) documentation for more details and examples. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-macro.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-macro.yaml) + +Applies to: `compatible = "zmk,behavior-macro"` + +| Property | Type | Description | Default | +| ---------------- | ------------- | ----------------------------------------------------------------------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<0>` | | +| `bindings` | phandle array | List of behaviors to trigger | | +| `wait-ms` | int | The default time to wait (in milliseconds) before triggering the next behavior. | 100 | +| `tap-ms` | int | The default time to wait (in milliseconds) between the press and release events of a tapped behavior. | 100 | + +The following macro-specific behaviors can be added at any point in the `bindings` list to change how the macro triggers subsequent behaviors. + +| Behavior | Description | +| -------------------------- | ----------------------------------------------------------------------------------------------------- | +| `¯o_tap` | Switches to tap mode | +| `¯o_press` | Switches to press mode | +| `¯o_release` | Switches to release mode | +| `¯o_pause_for_release` | Pauses the macro until the macro key itself is released | +| `¯o_wait_time TIME` | Changes the time to wait (in milliseconds) before triggering the next behavior. | +| `¯o_tap_time TIME` | Changes the time to wait (in milliseconds) between the press and release events of a tapped behavior. | + +## Mod-Morph + +Creates a custom behavior that triggers one of two behaviors depending on whether certain modifiers are held. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-mod-morph.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-mod-morph.yaml) + Applies to: `compatible = "zmk,behavior-mod-morph"` -| Property | Type | Description | -| ---------------- | ------------- | ----------------------------------------------------------------------------------- | -| `label` | string | Unique label for the node | -| `#binding-cells` | int | Must be `<0>` | -| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press | -| `mods` | int | A bit field of modifiers which will switch to the morph behavior if any are pressed | +| Property | Type | Description | +| ---------------- | ------------- | --------------------------------------------------------------------------------- | +| `label` | string | Unique label for the node | +| `#binding-cells` | int | Must be `<0>` | +| `bindings` | phandle array | A list of two behaviors: one for normal press and one for mod morphed press | +| `mods` | int | A bit field of modifiers. The morph behavior is used if any of these are pressed. | See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/modifiers.h) for a list of modifiers. @@ -73,6 +170,8 @@ See the [sticky key behavior](/docs/behaviors/sticky-key) and [sticky layer beha ### Devicetree +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-sticky-key.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-sticky-key.yaml) + Applies to: `compatible = "zmk,behavior-sticky-key"` | Property | Type | Description | Default | @@ -82,6 +181,7 @@ Applies to: `compatible = "zmk,behavior-sticky-key"` | `bindings` | phandle array | A behavior (without parameters) to trigger | | | `release-after-ms` | int | Releases the key after this many milliseconds if no other key is pressed | 1000 | | `quick-release` | bool | Release the sticky key on the next key press instead of release | false | +| `ignore-modifiers` | bool | If enabled, pressing a modifier key does not cancel the sticky key | true | You can use the following nodes to tweak the default behaviors: @@ -89,3 +189,20 @@ You can use the following nodes to tweak the default behaviors: | ----- | -------------------------------------------- | | `&sk` | [Sticky key](/docs/behaviors/sticky-key) | | `&sl` | [Sticky layer](/docs/behaviors/sticky-layer) | + +## Tap Dance + +Creates a custom behavior that triggers a different behavior corresponding to the number of times the key is tapped. + +### Devicetree + +Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-tap-dance.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-tap-dance.yaml) + +Applies to: `compatible = "zmk,behavior-tap-dance"` + +| Property | Type | Description | Default | +| ----------------- | ------------- | -------------------------------------------------------------------------------------------- | ------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<0>` | | +| `bindings` | phandle array | A list of behaviors from which to select | | +| `tapping-term-ms` | int | The maximum time (in milliseconds) between taps before an item from `bindings` is triggered. | 200 | From 71b8f9d4acc0e1a5ba8a077d7efa78756fbe665c Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 20:46:23 -0500 Subject: [PATCH 19/50] feat(docs): Update display config docs --- docs/docs/config/displays.md | 39 ++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/docs/config/displays.md b/docs/docs/config/displays.md index 93d314a8..685540b7 100644 --- a/docs/docs/config/displays.md +++ b/docs/docs/config/displays.md @@ -22,22 +22,39 @@ Definition files: | `CONFIG_ZMK_WIDGET_OUTPUT_STATUS` | bool | Enable a widget to show the current output (USB/BLE) | y | | `CONFIG_ZMK_WIDGET_WPM_STATUS` | bool | Enable a widget to show words per minute | n | -If `CONFIG_ZMK_DISPLAY` is enabled, exactly one of the following options must be set to `y`: +If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set. -| Config | Type | Description | Default | -| ------------------------------------------- | ---- | ------------------------------ | ------- | -| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | bool | Use the built-in status screen | y | -| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` | bool | Use a custom status screen | n | +| Config | Description | +| ------------------------------------------- | ------------------------------ | +| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | Use the built-in status screen | +| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` | Use a custom status screen | -You must also configure the Zephyr driver for your display. Here are the Kconfig options for common displays. +If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set. -- [SSD1306](https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_SSD1306.html) +| Config | Description | +| ----------------------------------------- | ----------------------------------------- | +| `CONFIG_ZMK_DISPLAY_WORK_QUEUE_SYSTEM` | Use the system main thread for UI updates | +| `CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED` | Use a dedicated thread for UI updates | + +Using a dedicated thread requires more memory but prevents displays with slow updates (e.g. E-paper) from delaying key scanning and other processes. If enabled, the following options configure the thread: + +| Config | Type | Description | Default | +| ------------------------------------------------ | ---- | ---------------------------- | ------- | +| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_STACK_SIZE` | int | Stack size for the UI thread | 2048 | +| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY` | int | Priority for the UI thread | 5 | + +You must also configure the driver for your display. ZMK provides the following display drivers: + +- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/display/Kconfig.il0323) + +Zephyr provides several display drivers as well. Search for the name of your display in [Zephyr's Kconfig options](https://docs.zephyrproject.org/latest/kconfig.html) documentation. ## Devicetree -See the Zephyr Devicetree bindings for your display. Here are the bindings for common displays: +See the Devicetree bindings for your display. Here are the bindings for common displays: -- [SSD1306 (i2c)](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/solomon,ssd1306fb-i2c.html) -- [SSD1306 (spi)](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/solomon,ssd1306fb-spi.html) +- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/display/gooddisplay%2Cil0323.yaml) +- [SSD1306 (i2c)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-i2c.html) +- [SSD1306 (spi)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-spi.html) -A full list of supported drivers can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/latest/reference/devicetree/bindings.html). +A full list of drivers provided by Zephyr can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/latest/build/dts/api/bindings.html). From e46eaf5617fd84b18aa6b0512f6407a8c1e28a69 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 21:10:37 -0500 Subject: [PATCH 20/50] feat(docs): Update power config docs --- docs/docs/config/power.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/docs/docs/config/power.md b/docs/docs/config/power.md index c3dae3de..54e44e57 100644 --- a/docs/docs/config/power.md +++ b/docs/docs/config/power.md @@ -50,16 +50,8 @@ Applies to: `compatible = "zmk,ext-power-generic"` Driver for reading the voltage of a battery using an ADC connected to a voltage divider. -### Kconfig - -Definition file: [zmk/app/drivers/sensor/battery_voltage_divider/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/sensor/battery_voltage_divider/Kconfig) - -| Config | Type | Description | Default | -| ------------------------------------ | ---- | ------------------------------------------------------------ | ------- | -| `CONFIG_ZMK_BATTERY_VOLTAGE_DIVIDER` | bool | Enable battery voltage divider driver for battery monitoring | n | - ### Devicetree Applies to: `compatible = "zmk,battery-voltage-divider"` -See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/latest/reference/devicetree/bindings/voltage-divider.html). +See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/latest/build/dts/api/bindings/adc/voltage-divider.html). From e8e6b2a33311651be4b6fa1367a39408f60268ce Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 21:10:49 -0500 Subject: [PATCH 21/50] feat(docs): Update general system config docs --- docs/docs/config/index.md | 2 +- docs/docs/config/system.md | 72 +++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index 71058237..9f24c5a3 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -72,7 +72,7 @@ CONFIG_EC11=y CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y ``` -The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. +The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. Note that options are _not_ prefixed with `CONFIG_` in these files. See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/latest/guides/kconfig/index.html) for more details on Kconfig files. diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 86bf24c7..930ec1e5 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -20,6 +20,32 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ | `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | | `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | +### HID + +| Config | Type | Description | Default | +| ------------------------------------- | ---- | ------------------------------------------------- | ------- | +| `CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE` | int | Number of consumer keys simultaneously reportable | 6 | + +Exactly zero or one of the following options may be set to `y`. The first is used if none are set. + +| Config | Description | +| --------------------------------- | ----------------------------------------------------------------------------------------------------- | +| `CONFIG_ZMK_HID_REPORT_TYPE_HKRO` | Enable `CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE` key roll over. | +| `CONFIG_ZMK_HID_REPORT_TYPE_NKRO` | Enable full N-key roll over. This may prevent the keyboard from working with some BIOS/UEFI versions. | + +If `CONFIG_ZMK_HID_REPORT_TYPE_HKRO` is enabled, it may be configured with the following options: + +| Config | Type | Description | Default | +| ------------------------------------- | ---- | ------------------------------------------------- | ------- | +| `CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE` | int | Number of keyboard keys simultaneously reportable | 6 | + +Exactly zero or one of the following options may be set to `y`. The first is used if none are set. + +| Config | Description | +| --------------------------------------------- | ------------------------------------------------------------------------------------ | +| `CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_FULL` | Enable all consumer key codes, but may have compatibility issues with some host OSes | +| `CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC` | Prevents using some consumer key codes, but allows compatibility with more host OSes | + ### USB | Config | Type | Description | Default | @@ -28,7 +54,7 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ | `CONFIG_USB_DEVICE_VID` | int | The vendor ID advertised to USB | `0x1D50` | | `CONFIG_USB_DEVICE_PID` | int | The product ID advertised to USB | `0x615E` | | `CONFIG_USB_DEVICE_MANUFACTURER` | string | The manufacturer name advertised to USB | `"ZMK Project"` | -| `CONFIG_USB_HID_POLL_INTERVAL_MS` | int | USB polling interval in milliseconds | 9 | +| `CONFIG_USB_HID_POLL_INTERVAL_MS` | int | USB polling interval in milliseconds | 1 | | `CONFIG_ZMK_USB` | bool | Enable ZMK as a USB keyboard | | | `CONFIG_ZMK_USB_INIT_PRIORITY` | int | USB init priority | 50 | @@ -37,26 +63,30 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ See [Zephyr's Bluetooth stack architecture documentation](https://docs.zephyrproject.org/latest/guides/bluetooth/bluetooth-arch.html) for more information on configuring Bluetooth. -| Config | Type | Description | Default | -| ----------------------------------------------------- | ---- | ---------------------------------------------------------------------- | ------- | -| `CONFIG_BT` | bool | Enable Bluetooth support | | -| `CONFIG_BT_MAX_CONN` | int | Maximum number of simultaneous Bluetooth connections | 5 | -| `CONFIG_BT_MAX_PAIRED` | int | Maximum number of paired Bluetooth devices | 5 | -| `CONFIG_ZMK_BLE` | bool | Enable ZMK as a Bluetooth keyboard | | -| `CONFIG_ZMK_BLE_INIT_PRIORITY` | int | BLE init priority | 50 | -| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE` | int | Stack size of the BLE notify thread | 512 | -| `CONFIG_ZMK_BLE_THREAD_PRIORITY` | int | Priority of the BLE notify thread | 5 | -| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int | Max number of keyboard HID reports to queue for sending over BLE | 20 | -| `CONFIG_ZMK_BLE_CONSUMER__REPORT_QUEUE_SIZE` | int | Max number of consumer HID reports to queue for sending over BLE | 5 | -| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START` | bool | Clears all bond information from the keyboard on startup | n | -| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Experimental: require typing passkey from host to pair BLE connection | n | -| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | -| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | -| `CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | -| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the split peripheral BLE notify thread | 512 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the split peripheral BLE notify thread | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | +| Config | Type | Description | Default | +| ----------------------------------------------------- | ---- | ----------------------------------------------------------------------- | ------- | +| `CONFIG_BT` | bool | Enable Bluetooth support | | +| `CONFIG_BT_MAX_CONN` | int | Maximum number of simultaneous Bluetooth connections | 5 | +| `CONFIG_BT_MAX_PAIRED` | int | Maximum number of paired Bluetooth devices | 5 | +| `CONFIG_ZMK_BLE` | bool | Enable ZMK as a Bluetooth keyboard | | +| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START` | bool | Clears all bond information from the keyboard on startup | n | +| `CONFIG_ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE` | int | Max number of consumer HID reports to queue for sending over BLE | 5 | +| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int | Max number of keyboard HID reports to queue for sending over BLE | 20 | +| `CONFIG_ZMK_BLE_INIT_PRIORITY` | int | BLE init priority | 50 | +| `CONFIG_ZMK_BLE_THREAD_PRIORITY` | int | Priority of the BLE notify thread | 5 | +| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE` | int | Stack size of the BLE notify thread | 512 | +| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Experimental: require typing passkey from host to pair BLE connection | n | +| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | +| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | +| `CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | +| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_STACK_SIZE` | int | Stack size of the BLE split central write thread | 512 | +| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE` | int | Max number of behavior run events to queue to send to the peripheral(s) | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the BLE split peripheral notify thread | 650 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the BLE split peripheral notify thread | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | + +Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the same value. On a split keyboard they should only be set for the central and must be set to one greater than the desired number of bluetooth profiles. ### Logging From 2b122acfc38f82f3a681dbac7b6d47c60d6e6268 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 21:21:07 -0500 Subject: [PATCH 22/50] feat(docs): Updating lighting config docs --- docs/docs/config/backlight.md | 36 +++++++++++++++++++++++++++++++++++ docs/docs/config/underglow.md | 6 +++--- docs/sidebars.js | 1 + 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 docs/docs/config/backlight.md diff --git a/docs/docs/config/backlight.md b/docs/docs/config/backlight.md new file mode 100644 index 00000000..814a29a1 --- /dev/null +++ b/docs/docs/config/backlight.md @@ -0,0 +1,36 @@ +--- +title: Backlight Configuration +sidebar_label: Backlight +--- + +See the [backlight feature page](/docs/features/backlight) for more details, including instructions for adding backlight support to a board. + +See [Configuration Overview](/docs/config) for instructions on how to change these settings. + +## Kconfig + +Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) + +| Option | Type | Description | Default | +| ------------------------------------ | ---- | ----------------------------------------------------- | ------- | +| `CONFIG_ZMK_BACKLIGHT` | bool | Enables LED backlight | n | +| `CONFIG_ZMK_BACKLIGHT_BRT_STEP` | int | Brightness step in percent | 20 | +| `CONFIG_ZMK_BACKLIGHT_BRT_START` | int | Default brightness in percent | 40 | +| `CONFIG_ZMK_BACKLIGHT_ON_START` | bool | Default backlight state | y | +| `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE` | bool | Turn off backlight when keyboard goes into idle state | n | +| `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB` | bool | Turn off backlight when USB is disconnected | n | + +## Devicetree + +Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/build/dts/intro.html#aliases-and-chosen-nodes) + +| Property | Type | Description | +| --------------- | ---- | -------------------------------------------- | +| `zmk,backlight` | path | The node for the backlight LED driver to use | + +See the Zephyr devicetree bindings for LED drivers: + +- [gpio-leds](https://docs.zephyrproject.org/latest/build/dts/api/bindings/gpio/gpio-leds.html) +- [pwm-leds](https://docs.zephyrproject.org/latest/build/dts/api/bindings/led/pwm-leds.html) + +See the [backlight feature page](/docs/features/backlight) for examples of the properties that must be set to enable backlighting. diff --git a/docs/docs/config/underglow.md b/docs/docs/config/underglow.md index 256e1b4f..4b742362 100644 --- a/docs/docs/config/underglow.md +++ b/docs/docs/config/underglow.md @@ -5,11 +5,11 @@ sidebar_label: RGB Underglow See the [RGB Underglow feature page](/docs/features/underglow) for more details, including instructions for adding underglow support to a board. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](/docs/config) for instructions on how to change these settings. ## Kconfig -RGB underglow depends on [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/master/drivers/led_strip), which provides additional Kconfig options. +RGB underglow depends on [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/main/drivers/led_strip), which provides additional Kconfig options. Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) @@ -38,6 +38,6 @@ Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`: ## Devicetree -ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip driver](https://github.com/zephyrproject-rtos/zephyr/tree/master/dts/bindings/led_strip). +ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip drivers](https://github.com/zephyrproject-rtos/zephyr/tree/main/dts/bindings/led_strip). See the [RGB underglow feature page](/docs/features/underglow) for examples of the properties that must be set to enable underglow. diff --git a/docs/sidebars.js b/docs/sidebars.js index b8e74e0f..231d4f71 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -54,6 +54,7 @@ module.exports = { ], Configuration: [ "config/index", + "config/backlight", "config/behaviors", "config/combos", "config/displays", From c350f7130beb8153ffc717d7567a7aff0d9734a3 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 21:22:13 -0500 Subject: [PATCH 23/50] fix(docs): Fix links in config pages --- docs/docs/config/backlight.md | 6 +++--- docs/docs/config/behaviors.md | 26 +++++++++++++------------- docs/docs/config/combos.md | 6 +++--- docs/docs/config/displays.md | 4 ++-- docs/docs/config/encoders.md | 4 ++-- docs/docs/config/index.md | 2 +- docs/docs/config/keymap.md | 8 ++++---- docs/docs/config/kscan.md | 4 ++-- docs/docs/config/power.md | 4 ++-- docs/docs/config/system.md | 2 +- docs/docs/config/underglow.md | 6 +++--- docs/docs/customization.md | 2 +- docs/docs/features/underglow.md | 2 +- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/docs/config/backlight.md b/docs/docs/config/backlight.md index 814a29a1..abdbc82b 100644 --- a/docs/docs/config/backlight.md +++ b/docs/docs/config/backlight.md @@ -3,9 +3,9 @@ title: Backlight Configuration sidebar_label: Backlight --- -See the [backlight feature page](/docs/features/backlight) for more details, including instructions for adding backlight support to a board. +See the [backlight feature page](../features/backlight.md) for more details, including instructions for adding backlight support to a board. -See [Configuration Overview](/docs/config) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Kconfig @@ -33,4 +33,4 @@ See the Zephyr devicetree bindings for LED drivers: - [gpio-leds](https://docs.zephyrproject.org/latest/build/dts/api/bindings/gpio/gpio-leds.html) - [pwm-leds](https://docs.zephyrproject.org/latest/build/dts/api/bindings/led/pwm-leds.html) -See the [backlight feature page](/docs/features/backlight) for examples of the properties that must be set to enable backlighting. +See the [backlight feature page](../features/backlight.md) for examples of the properties that must be set to enable backlighting. diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 726f2bb2..4a01dfd3 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -5,7 +5,7 @@ sidebar_label: Behaviors Some behaviors have properties to adjust how they behave. These can also be used as templates to create custom behaviors when none of the built-in behaviors do what you want. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/app/dts/behaviors) folder for all default behaviors. @@ -13,7 +13,7 @@ See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/ap Creates a custom behavior that behaves similar to a caps lock but deactivates when any key not in a continue list is pressed. -See the [caps word behavior](/docs/behaviors/caps-word) documentation for more details and examples. +See the [caps word behavior](../behaviors/caps-word.md) documentation for more details and examples. ### Devicetree @@ -36,13 +36,13 @@ You can use the following nodes to tweak the default behaviors: | Node | Behavior | | ------------ | -------------------------------------- | -| `&caps_word` | [Caps Word](/docs/behaviors/caps-word) | +| `&caps_word` | [Caps Word](../behaviors/caps-word.md) | ## Hold-Tap Creates a custom behavior that triggers one behavior when a key is held or a different one when the key is tapped. -See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for more details and examples. +See the [hold-tap behavior documentation](../behaviors/hold-tap.md) for more details and examples. ### Devicetree @@ -69,7 +69,7 @@ The `flavor` property may be one of: - `"tap-preferred"` - `"tap-unless-interrupted"` -See the [hold-tap behavior documentation](/docs/behaviors/hold-tap) for an explanation of each flavor. +See the [hold-tap behavior documentation](../behaviors/hold-tap.md) for an explanation of each flavor. `hold-trigger-key-positions` is an array of zero-based key position indices. @@ -77,14 +77,14 @@ You can use the following nodes to tweak the default behaviors: | Node | Behavior | | ----- | --------------------------------------------- | -| `<` | [Layer-tap](/docs/behaviors/layers#layer-tap) | -| `&mt` | [Mod-tap](/docs/behaviors/mod-tap) | +| `<` | [Layer-tap](../behaviors/layers.md#layer-tap) | +| `&mt` | [Mod-tap](../behaviors/mod-tap.md) | ## Key Repeat Creates a custom behavior that repeats the whatever key code was last sent. -See the [key repeat behavior](/docs/behaviors/key-repeat) documentation for more details and examples. +See the [key repeat behavior](../behaviors/key-repeat.md) documentation for more details and examples. ### Devicetree @@ -104,13 +104,13 @@ You can use the following nodes to tweak the default behaviors: | Node | Behavior | | ------------- | ---------------------------------------- | -| `&key_repeat` | [Key repeat](/docs/behaviors/key-repeat) | +| `&key_repeat` | [Key repeat](../behaviors/key-repeat.md) | ## Macro Creates a custom behavior which triggers a sequence of other behaviors. -See the [macro behavior](/docs/behaviors/macros) documentation for more details and examples. +See the [macro behavior](../behaviors/macros.md) documentation for more details and examples. ### Devicetree @@ -166,7 +166,7 @@ You can use the following nodes to tweak the default behaviors: Creates a custom behavior that triggers a behavior and keeps it pressed it until another key is pressed and released. -See the [sticky key behavior](/docs/behaviors/sticky-key) and [sticky layer behavior](/docs/behaviors/sticky-layer) documentation for more details and examples. +See the [sticky key behavior](../behaviors/sticky-key.md) and [sticky layer behavior](../behaviors/sticky-layer.md) documentation for more details and examples. ### Devicetree @@ -187,8 +187,8 @@ You can use the following nodes to tweak the default behaviors: | Node | Behavior | | ----- | -------------------------------------------- | -| `&sk` | [Sticky key](/docs/behaviors/sticky-key) | -| `&sl` | [Sticky layer](/docs/behaviors/sticky-layer) | +| `&sk` | [Sticky key](../behaviors/sticky-key.md) | +| `&sl` | [Sticky layer](../behaviors/sticky-layer.md) | ## Tap Dance diff --git a/docs/docs/config/combos.md b/docs/docs/config/combos.md index fe8877ef..479cd8e5 100644 --- a/docs/docs/config/combos.md +++ b/docs/docs/config/combos.md @@ -3,9 +3,9 @@ title: Combo Configuration sidebar_label: Combos --- -See the [Combos feature page](/docs/features/combos) for more details and examples. +See the [Combos feature page](../features/combos.md) for more details and examples. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Kconfig @@ -33,7 +33,7 @@ Each child node can have the following properties: | Property | Type | Description | Default | | --------------- | ------------- | ---------------------------------------------------------------------------------- | ------- | -| `bindings` | phandle-array | A [behavior](/docs/features/keymaps#behaviors) to run when the combo is triggered | | +| `bindings` | phandle-array | A [behavior](../features/keymaps.md#behaviors) to run when the combo is triggered | | | `key-positions` | array | A list of key position indices for the keys which should trigger the combo | | | `timeout-ms` | int | All the keys must be pressed within this time in milliseconds to trigger the combo | 50 | | `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | diff --git a/docs/docs/config/displays.md b/docs/docs/config/displays.md index 685540b7..cf6c07ee 100644 --- a/docs/docs/config/displays.md +++ b/docs/docs/config/displays.md @@ -3,9 +3,9 @@ title: Display Configuration sidebar_label: Displays --- -See the [displays feature page](/docs/features/displays) for more details. +See the [displays feature page](../features/displays.md) for more details. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Kconfig diff --git a/docs/docs/config/encoders.md b/docs/docs/config/encoders.md index 28440738..f6bd6de1 100644 --- a/docs/docs/config/encoders.md +++ b/docs/docs/config/encoders.md @@ -3,9 +3,9 @@ title: Encoder Configuration sidebar_label: Encoders --- -See the [Encoders feature page](/docs/features/encoders) for more details, including instructions for adding encoder support to a board. +See the [Encoders feature page](../features/encoders.md) for more details, including instructions for adding encoder support to a board. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## EC11 Encoders diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index 9f24c5a3..1762839f 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -58,7 +58,7 @@ ZMK will search the shield folder for the following config files: - `.overlay` (Devicetree) - `.keymap` (Devicetree) -For more documentation on creating and configuring a new shield, see [Zephyr's shield documentation](https://docs.zephyrproject.org/latest/guides/porting/shields.html) and [ZMK's new keyboard shield](/docs/development/new-shield) guide. +For more documentation on creating and configuring a new shield, see [Zephyr's shield documentation](https://docs.zephyrproject.org/latest/hardware/porting/shields.html) and [ZMK's new keyboard shield](../development/new-shield.md) guide. ## Kconfig Files diff --git a/docs/docs/config/keymap.md b/docs/docs/config/keymap.md index 917b82c6..b4e81f5e 100644 --- a/docs/docs/config/keymap.md +++ b/docs/docs/config/keymap.md @@ -3,7 +3,7 @@ title: Keymap Configuration sidebar_label: Keymap --- -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Keymap @@ -20,10 +20,10 @@ Each child node can have the following properties: | Property | Type | Description | | ----------------- | ------------- | ---------------------------------------------------------------------- | | `label` | string | Unique label for the node | -| `bindings` | phandle-array | List of [key behaviors](/docs/features/keymaps#behaviors), one per key | +| `bindings` | phandle-array | List of [key behaviors](../features/keymaps.md#behaviors), one per key | | `sensor-bindings` | phandle-array | List of sensor behaviors, one per sensor | -Items for `bindings` must be listed in the order the keys are defined in the [keyboard scan configuration](/docs/config/kscan). +Items for `bindings` must be listed in the order the keys are defined in the [keyboard scan configuration](kscan.md). Items for `sensor-bindings` must be listed in the order the [sensors](#keymap-sensors) are defined. @@ -39,4 +39,4 @@ Applies to: `compatible = "zmk,keymap-sensors"` The following types of nodes can be used as a sensor: -- [`alps,ec11`](/docs/config/encoders#ec11-encoders) +- [`alps,ec11`](encoders.md#ec11-encoders) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 37910756..410f5588 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -3,7 +3,7 @@ title: Keyboard Scan Configuration sidebar_label: Keyboard Scan --- -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Common @@ -250,7 +250,7 @@ Transforms should be used any time the physical layout of a keyboard's keys does Transforms can also be used for keyboards with multiple layouts. You can define multiple matrix transform nodes, one for each layout, and users can select which one they want from the `/chosen` node in their keymaps. -See the [new shield guide](/docs/development/new-shield/#optional-matrix-transform) for more documentation on how to define a matrix transform. +See the [new shield guide](../development/new-shield.md#optional-matrix-transform) for more documentation on how to define a matrix transform. ### Devicetree diff --git a/docs/docs/config/power.md b/docs/docs/config/power.md index 54e44e57..bf0de718 100644 --- a/docs/docs/config/power.md +++ b/docs/docs/config/power.md @@ -3,7 +3,7 @@ title: Power Management Configuration sidebar_label: Power Management --- -See [Configuration Overview](/docs/config/index) for instructions on how to +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Idle/Sleep @@ -26,7 +26,7 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ ## External Power Control -Driver for enabling or disabling power to peripherals such as displays and lighting. This driver must be configured to use [power management behaviors](/docs/behaviors/power). +Driver for enabling or disabling power to peripherals such as displays and lighting. This driver must be configured to use [power management behaviors](../behaviors/power.md). ### Kconfig diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 930ec1e5..cf88c4f5 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -5,7 +5,7 @@ sidebar_label: System These are general settings that control how the keyboard behaves and which features it supports. Several of these settings come from Zephyr and are not specific to ZMK, but they are listed here because they are relevant to how a keyboard functions. -See [Configuration Overview](/docs/config/index) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Kconfig diff --git a/docs/docs/config/underglow.md b/docs/docs/config/underglow.md index 4b742362..6353330d 100644 --- a/docs/docs/config/underglow.md +++ b/docs/docs/config/underglow.md @@ -3,9 +3,9 @@ title: RGB Underglow Configuration sidebar_label: RGB Underglow --- -See the [RGB Underglow feature page](/docs/features/underglow) for more details, including instructions for adding underglow support to a board. +See the [RGB Underglow feature page](../features/underglow.md) for more details, including instructions for adding underglow support to a board. -See [Configuration Overview](/docs/config) for instructions on how to change these settings. +See [Configuration Overview](index.md) for instructions on how to change these settings. ## Kconfig @@ -40,4 +40,4 @@ Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`: ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip drivers](https://github.com/zephyrproject-rtos/zephyr/tree/main/dts/bindings/led_strip). -See the [RGB underglow feature page](/docs/features/underglow) for examples of the properties that must be set to enable underglow. +See the [RGB underglow feature page](../features/underglow.md) for examples of the properties that must be set to enable underglow. diff --git a/docs/docs/customization.md b/docs/docs/customization.md index 1ffc38cc..6fb39f85 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -23,7 +23,7 @@ The setup script creates a `config/.conf` file that allows you to add ad control what features and options are built into your firmware. Opening that file with your text editor will allow you to see the various config settings that can be commented/uncommented to modify how your firmware is built. -Refer to the [Configuration](/docs/config/index) documentation for more details on this file. +Refer to the [Configuration](/docs/config) documentation for more details on this file. ## Keymap diff --git a/docs/docs/features/underglow.md b/docs/docs/features/underglow.md index d1cd8e20..af182fae 100644 --- a/docs/docs/features/underglow.md +++ b/docs/docs/features/underglow.md @@ -34,7 +34,7 @@ CONFIG_ZMK_RGB_UNDERGLOW=y CONFIG_WS2812_STRIP=y ``` -See [Configuration Overview](/docs/config/index) for more instructions on how to +See [Configuration Overview](/docs/config) for more instructions on how to use Kconfig. 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). From 01ffea1b47c3b94d542ba07ae774774ad2734845 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Fri, 29 Apr 2022 21:37:38 -0500 Subject: [PATCH 24/50] feat(docs): Update configuration overview --- docs/docs/config/index.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index 1762839f..a6cb337e 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -3,7 +3,7 @@ title: Configuration Overview sidebar_label: Overview --- -ZMK has several configuration settings that can be changed to change the behavior of your keyboard. They are set in either Kconfig or Devicetree files. +ZMK has many configuration settings that can be changed to change the behavior of your keyboard. They are set in either Kconfig or Devicetree files. This page describes the Kconfig and Devicetree file formats and how to change settings in them. See the other pages in the configuration section for a list of settings you can change. @@ -41,7 +41,7 @@ ZMK will search the board folder for the following config files: - `.dts` (Devicetree) - `.keymap` (Devictree, standalone boards only) -For more documentation on creating and configuring a new board, see [Zephyr's board porting guide](https://docs.zephyrproject.org/latest/guides/porting/board_porting.html#write-kconfig-files). +For more documentation on creating and configuring a new board, see [Zephyr's board porting guide](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html#write-kconfig-files). ### Shield Folder @@ -74,7 +74,7 @@ CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. Note that options are _not_ prefixed with `CONFIG_` in these files. -See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/latest/guides/kconfig/index.html) for more details on Kconfig files. +See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/latest/build/kconfig/index.html) for more details on Kconfig files. ### KConfig Value Types @@ -117,7 +117,7 @@ Devicetree files look like this: Devicetree properties apply to specific nodes in the tree instead of globally. The properties that can be set for each node are determined by `.yaml` files in ZMK in the various `dts/bindings` folders. -See [Zephyr's Devicetree guide](https://docs.zephyrproject.org/latest/guides/dts/index.html) for more details on Devicetree files. +See [Zephyr's Devicetree guide](https://docs.zephyrproject.org/latest/build/dts/index.html) for more details on Devicetree files. ### Changing Devicetree Properties @@ -138,7 +138,7 @@ The part before the colon, `kscan0`, is a label. This is optional, and it provid The `compatible` property indicates what type of node it is. Search this documentation for the text inside the quotes to see which properties the node supports. You can also search ZMK for a file whose name is the value of the `compatible` property with a `.yaml` file extension. -To set a property, see below for examples for common property types, or see [Zephyr's Devicetree documentation](https://docs.zephyrproject.org/latest/guides/dts/intro.html#writing-property-values) for more details on the syntax for properties. +To set a property, see below for examples for common property types, or see [Zephyr's Devicetree documentation](https://docs.zephyrproject.org/latest/build/dts/intro.html#writing-property-values) for more details on the syntax for properties. To change a property for an existing node, first find the node you want to change and find its label. Next, outside of any other node, write an ampersand (`&`) followed by the node's label, an opening curly brace (`{`), one or more new property values, a closing curly brace (`}`), and a semicolon (`;`). @@ -147,13 +147,23 @@ For example, to adjust the debouncing of the `zmk,kscan-gpio-matrix` node shown ```devicetree &kscan0 { - debounce-period = <7>; + debounce-press-ms = <0>; +}; +``` + +If the node you want to edit doesn't have a label, you can also write a new tree and it will be merged with the existing tree, overriding any properties. Adding this to your keymap would be equivalent to the previous example. + +```devicetree +/ { + kscan { + debounce-press-ms = <0>; + }; }; ``` ### Devicetree Property Types -These are some of the property types you will see most often when working with ZMK. [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/guides/dts/bindings.html) provides more detailed information and a full list of types. +These are some of the property types you will see most often when working with ZMK. [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/build/dts/bindings.html) provides more detailed information and a full list of types. #### bool @@ -207,14 +217,14 @@ Example: `property = <&none &mo 1>;` Values can also be split into multiple blocks, e.g. `property = <&none>, <&mo 1>;` -See the documentation for "phandle-array" in [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/guides/dts/bindings.html) +See the documentation for "phandle-array" in [Zephyr's Devicetree bindings documentation](https://docs.zephyrproject.org/latest/build/dts/bindings.html) for more details on how parameters are associated with nodes. #### GPIO array This is just a phandle array. The documentation lists this as a different type to make it clear which properties expect an array of GPIOs. -Each item in the array should be a label for a GPIO node (the names of which differ between hardware platforms) followed by an index and configuration flags. See [Zephyr's GPIO documentation](https://docs.zephyrproject.org/latest/reference/peripherals/gpio.html) for a full list of flags. +Each item in the array should be a label for a GPIO node (the names of which differ between hardware platforms) followed by an index and configuration flags. See [Zephyr's GPIO documentation](https://docs.zephyrproject.org/latest/hardware/peripherals/gpio.html) for a full list of flags. Example: From e0e0928f9c185bd73ad1391605d945e5e3adaace Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Tue, 3 May 2022 21:28:41 -0500 Subject: [PATCH 25/50] fix(docs): Update config docs for review feedback --- docs/docs/config/combos.md | 16 ++++++++-------- docs/docs/config/kscan.md | 10 +++++++++- docs/docs/features/combos.md | 2 +- docs/docs/features/debouncing.md | 10 ++++++++-- docs/docs/features/underglow.md | 2 +- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/docs/docs/config/combos.md b/docs/docs/config/combos.md index 479cd8e5..cd351125 100644 --- a/docs/docs/config/combos.md +++ b/docs/docs/config/combos.md @@ -23,7 +23,7 @@ If you want a combo that triggers when pressing 5 keys, you must set `CONFIG_ZMK ## Devicetree -Applies to: `compatible = "zmk,combo"` +Applies to: `compatible = "zmk,combos"` Definition file: [zmk/app/dts/bindings/zmk,combos.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/zmk%2Ccombos.yaml) @@ -31,12 +31,12 @@ The `zmk,combos` node itself has no properties. It should have one child node pe Each child node can have the following properties: -| Property | Type | Description | Default | -| --------------- | ------------- | ---------------------------------------------------------------------------------- | ------- | -| `bindings` | phandle-array | A [behavior](../features/keymaps.md#behaviors) to run when the combo is triggered | | -| `key-positions` | array | A list of key position indices for the keys which should trigger the combo | | -| `timeout-ms` | int | All the keys must be pressed within this time in milliseconds to trigger the combo | 50 | -| `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | -| `layers` | array | A list of layers on which the combo may be triggered. `-1` allows all layers. | `<-1>` | +| Property | Type | Description | Default | +| --------------- | ------------- | ----------------------------------------------------------------------------------------------------- | ------- | +| `bindings` | phandle-array | A [behavior](../features/keymaps.md#behaviors) to run when the combo is triggered | | +| `key-positions` | array | A list of key position indices for the keys which should trigger the combo | | +| `timeout-ms` | int | All the keys in `key-positions` must be pressed within this time in milliseconds to trigger the combo | 50 | +| `slow-release` | bool | Releases the combo when all keys are released instead of when any key is released | false | +| `layers` | array | A list of layers on which the combo may be triggered. `-1` allows all layers. | `<-1>` | The `key-positions` array must not be longer than the `CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO` setting, which defaults to 4. If you want a combo that triggers when pressing 5 keys, then you must change the setting to 5. diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 410f5588..d81d26b6 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -21,7 +21,7 @@ Definition files: | `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS` | int | Global debounce time for key press in milliseconds | -1 | | `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS` | int | Global debounce time for key release in milliseconds | -1 | -If the debounce press/release values are set to any value other than `-1`, they override the `debounce-press-ms` and `debounce-release-ms` devicetree properties for all keyboard scan drivers which support them. +If the debounce press/release values are set to any value other than `-1`, they override the `debounce-press-ms` and `debounce-release-ms` devicetree properties for all keyboard scan drivers which support them. See the [debouncing documentation](../features/debouncing.md) for more details. ### Devicetree @@ -36,6 +36,10 @@ Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/guides/dts/in Keyboard scan driver which works like a regular matrix but uses a demultiplexer to drive the rows or columns. This allows N GPIOs to drive N2 rows or columns instead of just N like with a regular matrix. +:::note +Currently this driver does not honor the `CONFIG_ZMK_KSCAN_DEBOUNCE_*` settings. +::: + ### Devicetree Applies to: `compatible = "zmk,kscan-gpio-demux"` @@ -54,6 +58,10 @@ Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-demux Keyboard scan driver where each key has a dedicated GPIO. +:::note +Currently this driver does not honor the `CONFIG_ZMK_KSCAN_DEBOUNCE_*` settings. +::: + ### Kconfig Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) diff --git a/docs/docs/features/combos.md b/docs/docs/features/combos.md index 5c73a845..09191896 100644 --- a/docs/docs/features/combos.md +++ b/docs/docs/features/combos.md @@ -25,7 +25,7 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod - The name of the combo doesn't really matter, but convention is to start the node name with `combo_`. - The `compatible` property should always be `"zmk,combos"` for combos. -- All the keys must be pressed within `timeout-ms` milliseconds to trigger the combo. +- All the keys in `key-positions` must be pressed within `timeout-ms` milliseconds to trigger the combo. - `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board. - `layers = <0 1...>` will allow limiting a combo to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope. - `bindings` is the behavior that is activated when the behavior is pressed. diff --git a/docs/docs/features/debouncing.md b/docs/docs/features/debouncing.md index f0022a59..9629131d 100644 --- a/docs/docs/features/debouncing.md +++ b/docs/docs/features/debouncing.md @@ -19,14 +19,20 @@ socket or using some sharp tweezers to bend the contacts back together. ## Debounce Configuration +:::note +Currently only the `zmk,kscan-gpio-matrix` driver supports these options. The other drivers have not yet been updated to use the new debouncing code. +::: + ### Global Options You can set these options in your `.conf` file to control debouncing globally. -Values must be <= 127. +Values must be <= 16383. - `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS`: Debounce time for key press in milliseconds. Default = 5. - `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS`: Debounce time for key release in milliseconds. Default = 5. +If one of these options is set, it overrides the matching per-driver option described below. + For example, this would shorten the debounce time for both press and release: ```ini @@ -37,7 +43,7 @@ CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS=3 ### Per-driver Options You can add these Devicetree properties to a kscan node to control debouncing for -that instance of the driver. Values must be <= 127. +that instance of the driver. Values must be <= 16383. - `debounce-press-ms`: Debounce time for key press in milliseconds. Default = 5. - `debounce-release-ms`: Debounce time for key release in milliseconds. Default = 5. diff --git a/docs/docs/features/underglow.md b/docs/docs/features/underglow.md index af182fae..020701fd 100644 --- a/docs/docs/features/underglow.md +++ b/docs/docs/features/underglow.md @@ -145,7 +145,7 @@ Once you have your `led_strip` properly defined you need to add it to the root d }; ``` -Finally you need to enable the `CONFIG_ZMK_RGB_UNDERGLOW` and `CONFIG*_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): +Finally you need to enable the `CONFIG_ZMK_RGB_UNDERGLOW` and `CONFIG_*_STRIP` configuration values in the `.conf` file of your board (or set a default in the `Kconfig.defconfig`): ``` CONFIG_ZMK_RGB_UNDERGLOW=y From 74b49339800638b745cf0d1b196bb6c7371055be Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 21 May 2022 16:17:11 -0500 Subject: [PATCH 26/50] feat(docs): Clarify descriptions of config files --- docs/docs/config/index.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index a6cb337e..9e35df83 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -22,9 +22,9 @@ When building with a `zmk-config` folder, ZMK will search the `zmk-config/config - `.conf` (Kconfig) - `.keymap` (Devicetree) -These files hold your personal settings for the keyboard. They override any configuration set in the board or shield folders. +These files hold your personal settings for the keyboard. All files are optional. If present, they override any configuration set in the board or shield folders. Otherwise, the default configuration and/or keymap is used. -When using a split keyboard, you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. +When using a split keyboard, you can use a single file without the `_left` or `_right` suffix to configure both sides. For example, `corne.conf` and `corne.keymap` will apply to both `corne_left` and `corne_right`. If a shared config file exists, any left or right files will be ignored. ### Board Folder @@ -38,8 +38,11 @@ ZMK will search for config files in either of: ZMK will search the board folder for the following config files: - `_defconfig` (Kconfig) +- `.conf` (Kconfig) - `.dts` (Devicetree) -- `.keymap` (Devictree, standalone boards only) +- `.keymap` (Devicetree, keyboards with onboard controllers only) + +Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in board folders. For more documentation on creating and configuring a new board, see [Zephyr's board porting guide](https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html#write-kconfig-files). @@ -58,6 +61,8 @@ ZMK will search the shield folder for the following config files: - `.overlay` (Devicetree) - `.keymap` (Devicetree) +Shared config files (excluding any `_left` or `_right` suffix) are not currently supported in shield folders. + For more documentation on creating and configuring a new shield, see [Zephyr's shield documentation](https://docs.zephyrproject.org/latest/hardware/porting/shields.html) and [ZMK's new keyboard shield](../development/new-shield.md) guide. ## Kconfig Files @@ -72,7 +77,7 @@ CONFIG_EC11=y CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y ``` -The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. Note that options are _not_ prefixed with `CONFIG_` in these files. +The list of available settings is determined by various files in ZMK whose names start with `Kconfig`. Files ending with `_defconfig` use the same syntax, but are intended for setting configuration specific to the hardware which users typically won't need to change. Note that options are _not_ prefixed with `CONFIG_` in these files. See [Zephyr's Kconfig documentation](https://docs.zephyrproject.org/latest/build/kconfig/index.html) for more details on Kconfig files. @@ -98,7 +103,14 @@ Example: `CONFIG_FOO="foo"` ## Devicetree Files -Various Devicetree files are combined to build a tree that describes the hardware for a keyboard. They are also used to define keymaps. Common file extensions for Devicetree files are `.dts`, `.dtsi`, `.overlay`, and `.keymap`. +Various Devicetree files are combined to build a tree that describes the hardware for a keyboard. They are also used to define keymaps. + +Devicetree files use various file extensions. These indicate the purpose of the file, but they have no effect on how the file is processed. Common file extensions for Devicetree files include: + +- `.dts`: The base hardware definition. +- `.overlay`: Adds to and/or overrides definitions in a `.dts` file. +- `.keymap`: Holds a keymap and user-specific hardware configuration. +- `.dtsi`: A file which is only intended to be `#include`d from another file. Devicetree files look like this: From 6e67e4a3a52cc962aa4da2024ea42b9af1133583 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 21 May 2022 16:50:20 -0500 Subject: [PATCH 27/50] feat(docs): Update direct GPIO configuration --- docs/docs/config/kscan.md | 45 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index d81d26b6..ac773f18 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -58,10 +58,6 @@ Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-demux Keyboard scan driver where each key has a dedicated GPIO. -:::note -Currently this driver does not honor the `CONFIG_ZMK_KSCAN_DEBOUNCE_*` settings. -::: - ### Kconfig Definition file: [zmk/app/drivers/kscan/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/kscan/Kconfig) @@ -76,11 +72,20 @@ Applies to: `compatible = "zmk,kscan-gpio-direct"` Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-direct.yaml) -| Property | Type | Description | Default | -| ----------------- | ---------- | ------------------------------- | ------- | -| `label` | string | Unique label for the node | | -| `input-gpios` | GPIO array | Input GPIOs (one per key) | | -| `debounce-period` | int | Debounce period in milliseconds | 5 | +| Property | Type | Description | Default | +| ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ----------- | +| `label` | string | Unique label for the node | | +| `input-gpios` | GPIO array | Input GPIOs (one per key) | | +| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | +| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | +| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | +| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | +| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_DIRECT_POLLING` is enabled. | 10 | +| `toggle-mode` | bool | Use toggle switch mode. | n | + +By default, a switch will drain current through the internal pull up/down resistor whenever it is pressed. This is not ideal for a toggle switch, where the switch may be left in the "pressed" state for a long time. Enabling `toggle-mode` will make the driver flip between pull up and down as the switch is toggled to optimize for power. + +`toggle-mode` applies to all switches handled by the instance of the driver. To use a toggle switch with other, non-toggle, direct GPIO switches, create two instances of the direct GPIO driver, one with `toggle-mode` and the other without. Then, use a [composite driver](#composite-driver) to combine them. ## Matrix Driver @@ -98,16 +103,16 @@ Applies to: `compatible = "zmk,kscan-gpio-matrix"` Definition file: [zmk/app/drivers/zephyr/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/kscan/zmk%2Ckscan-gpio-matrix.yaml) -| Property | Type | Description | Default | -| ------------------------- | ---------- | -------------------------------------------------------------------------------------------------- | ----------- | -| `label` | string | Unique label for the node | | -| `row-gpios` | GPIO array | Matrix row GPIOs in order, starting from the top row | | -| `col-gpios` | GPIO array | Matrix column GPIOs in order, starting from the leftmost row | | -| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | -| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | -| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | -| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | -| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_MATRIX_POLLING is enabled. | 10 | +| Property | Type | Description | Default | +| ------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------- | ----------- | +| `label` | string | Unique label for the node | | +| `row-gpios` | GPIO array | Matrix row GPIOs in order, starting from the top row | | +| `col-gpios` | GPIO array | Matrix column GPIOs in order, starting from the leftmost row | | +| `debounce-press-ms` | int | Debounce time for key press in milliseconds. Use 0 for eager debouncing. | 5 | +| `debounce-release-ms` | int | Debounce time for key release in milliseconds. | 5 | +| `debounce-scan-period-ms` | int | Time between reads in milliseconds when any key is pressed. | 1 | +| `diode-direction` | string | The direction of the matrix diodes | `"row2col"` | +| `poll-period-ms` | int | Time between reads in milliseconds when no key is pressed and `CONFIG_ZMK_KSCAN_MATRIX_POLLING` is enabled. | 10 | The `diode-direction` property must be one of: @@ -213,7 +218,7 @@ One possible way to do this is a 3x4 matrix where the direct GPIO keys are shift // Include the direct GPIO driver... direct { kscan = <&kscan2>; - row-offset = <3>; // ..and shift it to not overlap + row-offset = <3>; // ...and shift it to not overlap }; }; From 1646cd7f3048e98fb80b09bc56f458b237cfac61 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 21 May 2022 17:15:41 -0500 Subject: [PATCH 28/50] feat(docs): Add a simpler matrix transform example --- docs/docs/config/kscan.md | 65 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index ac773f18..984fd6ac 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -281,7 +281,70 @@ Definition file: [zmk/app/dts/bindings/zmk,matrix-transform.yaml](https://github The `map` array should be defined using the `RC()` macro from [dt-bindings/zmk/matrix_transform.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/matrix_transform.h). It should have one item per logical position in the keymap. Each item should list the physical row and column that should trigger the key in that position. -### Example Configuration +### Example: Skipping Unused Positions + +Any keyboard which is not a grid of 1 unit keys will likely have some unused positions in the matrix. A matrix transform can be used to skip the unused positions so users don't have to set them to `&none` in keymaps. + +```devicetree +// numpad.overlay +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + rows = <5>; + columns = <4>; + // define the matrix... + }; + + default_transform: matrix_transform { + compatible = "zmk,matrix-transform"; + rows = <5>; + columns = <4>; + // ┌───┬───┬───┬───┐ + // │NUM│ / │ * │ - │ + // ├───┼───┼───┼───┤ + // │ 7 │ 8 │ 9 │ + │ + // ├───┼───┼───┤ │ + // │ 4 │ 5 │ 6 │ │ + // ├───┼───┼───┼───┤ + // │ 1 │ 2 │ 3 │RET│ + // ├───┴───┼───┤ │ + // │ 0 │ . │ │ + // └───────┴───┴───┘ + map = < + RC(0,0) RC(0,1) RC(0,2) RC(0,3) + RC(1,0) RC(1,1) RC(1,2) RC(1,3) + RC(2,0) RC(2,1) RC(2,2) + RC(3,0) RC(3,1) RC(3,2) RC(3,3) + RC(4,0) RC(4,1) + >; + }; +}; +``` + +```devicetree +// numpad.keymap +/ { + keymap { + compatible = "zmk,keymap"; + default { + bindings = < + &kp KP_NUM &kp KP_DIV &kp KP_MULT &kp KP_MINUS + &kp KP_N7 &kp KP_N8 &kp KP_N9 &kp KP_PLUS + &kp KP_N4 &kp KP_N5 &kp KP_N6 + &kp KP_N1 &kp KP_N2 &kp KP_N3 &kp KP_ENTER + &kp KP_N0 &kp KP_DOT + >; + }; + } +}; +``` + +### Example: Non-standard Matrix Consider a keyboard with a [duplex matrix](https://wiki.ai03.com/books/pcb-design/page/matrices-and-duplex-matrix), where the matrix has twice as many rows and half as many columns as the keyboard has keys. A matrix transform can be used to correct for this so that keymaps can match the layout of the keys, not the layout of the matrix. From ae78aa247a99b0b0e7d77cf680c7e419955354ca Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sat, 21 May 2022 17:48:27 -0500 Subject: [PATCH 29/50] feat(docs): Update power and lighting config pages Moved battery configuration to its own page to match the feature page. Documented that external power is disabled when in sleep mode. Clarified that the *_START configs apply on first boot, and any changes after that are persisted. --- docs/docs/config/backlight.md | 4 ++++ docs/docs/config/battery.md | 40 +++++++++++++++++++++++++++++++++++ docs/docs/config/behaviors.md | 6 +++--- docs/docs/config/power.md | 12 +---------- docs/docs/config/underglow.md | 4 ++++ docs/docs/features/battery.md | 2 ++ docs/sidebars.js | 3 ++- 7 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 docs/docs/config/battery.md diff --git a/docs/docs/config/backlight.md b/docs/docs/config/backlight.md index abdbc82b..a3650766 100644 --- a/docs/docs/config/backlight.md +++ b/docs/docs/config/backlight.md @@ -20,6 +20,10 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ | `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE` | bool | Turn off backlight when keyboard goes into idle state | n | | `CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB` | bool | Turn off backlight when USB is disconnected | n | +:::note +The `*_START` settings only determine the initial backlight state. Any changes you make with the [backlight behavior](../behaviors/backlight.md) are saved to flash after a one minute delay and will be used after that. +::: + ## Devicetree Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/build/dts/intro.html#aliases-and-chosen-nodes) diff --git a/docs/docs/config/battery.md b/docs/docs/config/battery.md new file mode 100644 index 00000000..73b4ee92 --- /dev/null +++ b/docs/docs/config/battery.md @@ -0,0 +1,40 @@ +--- +title: Battery Level +sidebar_label: Battery Level +--- + +See the [battery level feature page](../features/battery.md) for more details on configuring a battery sensor. + +See [Configuration Overview](index.md) for instructions on how to change these settings. + +### Devicetree + +Applies to: [`/chosen` node](https://docs.zephyrproject.org/latest/guides/dts/intro.html#aliases-and-chosen-nodes) + +| Property | Type | Description | +| ------------- | ---- | --------------------------------------------- | +| `zmk,battery` | path | The node for the battery sensor driver to use | + +## Battery Voltage Divider Sensor + +Driver for reading the voltage of a battery using an ADC connected to a voltage divider. + +### Devicetree + +Applies to: `compatible = "zmk,battery-voltage-divider"` + +See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/latest/build/dts/api/bindings/adc/voltage-divider.html). + +## nRF VDDH Battery Sensor + +Driver for reading the voltage of a battery using a Nordic nRF52's VDDH pin. This driver has no configuration except for the required `label` property. + +### Devicetree + +Applies to: `compatible = "zmk,battery-nrf-vddh"` + +Definition file: [zmk/app/drivers/zephyr/dts/bindings/sensor/zmk,battery-nrf-vddh.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/drivers/zephyr/dts/bindings/sensor/zmk%2Cbattery-nrf-vddh.yaml) + +| Property | Type | Description | +| -------- | ------ | ------------------------- | +| `label` | string | Unique label for the node | diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 4a01dfd3..72db21d7 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -158,9 +158,9 @@ See [dt-bindings/zmk/modifiers.h](https://github.com/zmkfirmware/zmk/blob/main/a You can use the following nodes to tweak the default behaviors: -| Node | Behavior | -| -------- | ------------ | -| `&gresc` | Grave escape | +| Node | Behavior | +| -------- | ----------------------------------------- | +| `&gresc` | [Grave escape](../behaviors/mod-morph.md) | ## Sticky Key diff --git a/docs/docs/config/power.md b/docs/docs/config/power.md index bf0de718..e09045ed 100644 --- a/docs/docs/config/power.md +++ b/docs/docs/config/power.md @@ -12,7 +12,7 @@ Configuration for entering low power modes when the keyboard is idle. In the idle state, peripherals such as displays and lighting are disabled, but the keyboard remains connected to Bluetooth so it can immediately respond when you press a key. -In the deep sleep state, the keyboard additionally disconnects from Bluetooth. This state uses very little power, but it may take a few seconds to reconnect after waking. +In the deep sleep state, the keyboard additionally disconnects from Bluetooth and any external power output is disabled. This state uses very little power, but it may take a few seconds to reconnect after waking. ### Kconfig @@ -45,13 +45,3 @@ Applies to: `compatible = "zmk,ext-power-generic"` | `label` | string | Unique label for the node | | `control-gpios` | GPIO array | List of GPIOs which should be active to enable external power | | `init-delay-ms` | int | number of milliseconds to delay after initializing the driver | - -## Battery Voltage Divider - -Driver for reading the voltage of a battery using an ADC connected to a voltage divider. - -### Devicetree - -Applies to: `compatible = "zmk,battery-voltage-divider"` - -See [Zephyr's voltage divider documentation](https://docs.zephyrproject.org/latest/build/dts/api/bindings/adc/voltage-divider.html). diff --git a/docs/docs/config/underglow.md b/docs/docs/config/underglow.md index 6353330d..f9d9e15d 100644 --- a/docs/docs/config/underglow.md +++ b/docs/docs/config/underglow.md @@ -36,6 +36,10 @@ Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`: | 2 | Spectrum | | 3 | Swirl | +:::note +The `*_START` settings only determine the initial underglow state. Any changes you make with the [underglow behavior](../behaviors/underglow.md) are saved to flash after a one minute delay and will be used after that. +::: + ## Devicetree ZMK does not have any Devicetree properties of its own. See the Devicetree bindings for [Zephyr's LED strip drivers](https://github.com/zephyrproject-rtos/zephyr/tree/main/dts/bindings/led_strip). diff --git a/docs/docs/features/battery.md b/docs/docs/features/battery.md index 0b4172c2..42ba6d40 100644 --- a/docs/docs/features/battery.md +++ b/docs/docs/features/battery.md @@ -20,6 +20,8 @@ To enable a battery sensor on a new board, add the driver for the sensor to your - `zmk,battery-voltage-divider`: Reads the voltage on an analog input pin. - `zmk,battery-nrf-vddh`: Reads the power supply voltage on a Nordic nRF52's VDDH pin. +See the [battery level configuration page](../config/battery.md) for the configuration supported by each driver provided by ZMK. + Zephyr also provides some drivers for fuel gauge ICs such as the TI bq274xx series and Maxim MAX17xxx series. If you use a battery sensor that does not have an existing driver, you will need to write a new driver that supports the `SENSOR_CHAN_GAUGE_STATE_OF_CHARGE` sensor channel and contribute it to Zephyr or ZMK. Once you have the sensor driver defined, add a `zmk,battery` property to the `chosen` node and set it to reference the sensor node. For example: diff --git a/docs/sidebars.js b/docs/sidebars.js index 231d4f71..7b445a29 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -55,6 +55,7 @@ module.exports = { Configuration: [ "config/index", "config/backlight", + "config/battery", "config/behaviors", "config/combos", "config/displays", @@ -62,8 +63,8 @@ module.exports = { "config/keymap", "config/kscan", "config/power", - "config/system", "config/underglow", + "config/system", ], Development: [ "development/clean-room", From 851c37e14fb1315203caf5f7807cdf4bf922a464 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sun, 3 Jul 2022 14:05:55 -0700 Subject: [PATCH 30/50] fix(docs): Apply suggestions from #722 reviews --- docs/docs/config/behaviors.md | 12 ++++++------ docs/docs/config/kscan.md | 8 ++++---- docs/docs/config/system.md | 13 +++++++------ docs/docs/development/new-shield.md | 6 ++++++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index 72db21d7..a4e847dc 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -21,12 +21,12 @@ Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml](ht Applies to: `compatible = "zmk,behavior-caps-word"` -| Property | Type | Description | Default | -| ---------------- | ------ | ------------------------------------------------------------------ | -------------- | -| `label` | string | Unique label for the node | | -| `#binding-cells` | int | Must be `<0>` | | -| `continue-list` | array | List of [key codes](/docs/codes) which do not deactivate caps lock | `` | -| `mods` | int | A bit field of modifiers to apply | `` | +| Property | Type | Description | Default | +| ---------------- | ------ | ------------------------------------------------------------------ | ------------------------------- | +| `label` | string | Unique label for the node | | +| `#binding-cells` | int | Must be `<0>` | | +| `continue-list` | array | List of [key codes](/docs/codes) which do not deactivate caps lock | `` | +| `mods` | int | A bit field of modifiers to apply | `` | `continue-list` is treated as if it always includes alphanumeric characters (A-Z, 0-9). diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 984fd6ac..77dc23cb 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -134,8 +134,8 @@ Definition file: [zmk/app/dts/bindings/zmk,kscan-composite.yaml](https://github. | Property | Type | Description | Default | | -------- | ------ | --------------------------------------------- | ------- | | `label` | string | Unique label for the node | | -| `rows` | int | The number rows of in the composite matrix | | -| `cols` | int | The number columns of in the composite matrix | | +| `rows` | int | The number of rows in the composite matrix | | +| `cols` | int | The number of columns in the composite matrix | | The `zmk,kscan-composite` node should have one child node per keyboard scan driver that should be composited. Each child node can have the following properties: @@ -249,8 +249,8 @@ Definition file: [zmk/app/dts/bindings/zmk,kscan-mock.yaml](https://github.com/z | `label` | string | Unique label for the node | | | `event-period` | int | Milliseconds between each generated event | | | `events` | array | List of key events to simulate | | -| `rows` | int | The number rows of in the composite matrix | | -| `cols` | int | The number columns of in the composite matrix | | +| `rows` | int | The number of rows in the composite matrix | | +| `cols` | int | The number of columns in the composite matrix | | | `exit-after` | bool | Exit the program after running all events | false | The `events` array should be defined using the macros from [dt-bindings/zmk/kscan_mock.h](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/kscan_mock.h). diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index cf88c4f5..af7bced5 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -13,12 +13,13 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ ### General -| Config | Type | Description | Default | -| ----------------------------------- | ------ | ----------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard | | -| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | -| `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | -| `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | +| Config | Type | Description | Default | +| ------------------------------------ | ------ | ----------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard | | +| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | +| `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | +| `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | +| `CONFIG_ZMK_BATTERY_REPORT_INTERVAL` | int | Battery level report interval in seconds | 60 | ### HID diff --git a/docs/docs/development/new-shield.md b/docs/docs/development/new-shield.md index b46c319d..03a27289 100644 --- a/docs/docs/development/new-shield.md +++ b/docs/docs/development/new-shield.md @@ -157,6 +157,8 @@ this might look something like: }; ``` +See the [Keyboard Scan configuration documentation](../config/kscan.md) for details on configuring the KSCAN driver. + @@ -260,6 +262,8 @@ This is exemplified with the iris .overlay files. ``` +See the [Keyboard Scan configuration documentation](../config/kscan.md) for details on configuring the KSCAN driver. + ### .conf files (Split Shields) While unibody boards only have one .conf file that applies configuration characteristics to the entire keyboard, @@ -341,6 +345,8 @@ Some important things to note: - `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. +See the [matrix transform section](../config/kscan.md#matrix-transform) in the Keyboard Scan configuration documentation for details and more examples of matrix transforms. + ## 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` file. The keymap is configured as an additional devicetree overlay that includes the following: From 19d8c5ba40b3e8767f68db7c9f16e595f638e423 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sun, 3 Jul 2022 14:20:47 -0700 Subject: [PATCH 31/50] feat(docs): Document new underglow Kconfig --- docs/docs/config/underglow.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/docs/config/underglow.md b/docs/docs/config/underglow.md index f9d9e15d..1209e60e 100644 --- a/docs/docs/config/underglow.md +++ b/docs/docs/config/underglow.md @@ -13,19 +13,21 @@ RGB underglow depends on [Zephyr's LED strip driver](https://github.com/zephyrpr Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/Kconfig) -| Config | Type | Description | Default | -| ------------------------------------ | ---- | ----------------------------------------------------- | ------- | -| `CONFIG_ZMK_RGB_UNDERGLOW` | bool | Enable RGB underglow | n | -| `CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER` | bool | Underglow toggling also controls external power | y | -| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP` | int | Hue step in degrees (0-359) used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP` | int | Saturation step in percent used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP` | int | Brightness step in percent used by RGB actions | 10 | -| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_START` | int | Default hue in degrees (0-359) | 0 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_START` | int | Default saturation percent (0-100) | 100 | -| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_START` | int | Default brightness in percent (0-100) | 100 | -| `CONFIG_ZMK_RGB_UNDERGLOW_SPD_START` | int | Default effect speed (1-5) | 3 | -| `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START` | int | Default effect index from the effect list (see below) | 0 | -| `CONFIG_ZMK_RGB_UNDERGLOW_ON_START` | bool | Default on state | y | +| Config | Type | Description | Default | +| ---------------------------------------- | ---- | --------------------------------------------------------- | ------- | +| `CONFIG_ZMK_RGB_UNDERGLOW` | bool | Enable RGB underglow | n | +| `CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER` | bool | Underglow toggling also controls external power | y | +| `CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE` | bool | Turn off RGB underglow when keyboard goes into idle state | n | +| `CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB` | bool | Turn off RGB underglow when USB is disconnected | n | +| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_STEP` | int | Hue step in degrees (0-359) used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_STEP` | int | Saturation step in percent used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP` | int | Brightness step in percent used by RGB actions | 10 | +| `CONFIG_ZMK_RGB_UNDERGLOW_HUE_START` | int | Default hue in degrees (0-359) | 0 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SAT_START` | int | Default saturation percent (0-100) | 100 | +| `CONFIG_ZMK_RGB_UNDERGLOW_BRT_START` | int | Default brightness in percent (0-100) | 100 | +| `CONFIG_ZMK_RGB_UNDERGLOW_SPD_START` | int | Default effect speed (1-5) | 3 | +| `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START` | int | Default effect index from the effect list (see below) | 0 | +| `CONFIG_ZMK_RGB_UNDERGLOW_ON_START` | bool | Default on state | y | Values for `CONFIG_ZMK_RGB_UNDERGLOW_EFF_START`: From 41c9d810967c3be71df9c3727660ce968756ce34 Mon Sep 17 00:00:00 2001 From: Cem Aksoylar Date: Sun, 3 Jul 2022 14:48:09 -0700 Subject: [PATCH 32/50] fix(docs): Update config docs for split Kconfig refactor --- docs/docs/config/system.md | 51 +++++++++++++++------------ docs/docs/development/new-behavior.md | 4 +-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index af7bced5..4784339c 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -64,28 +64,19 @@ Exactly zero or one of the following options may be set to `y`. The first is use See [Zephyr's Bluetooth stack architecture documentation](https://docs.zephyrproject.org/latest/guides/bluetooth/bluetooth-arch.html) for more information on configuring Bluetooth. -| Config | Type | Description | Default | -| ----------------------------------------------------- | ---- | ----------------------------------------------------------------------- | ------- | -| `CONFIG_BT` | bool | Enable Bluetooth support | | -| `CONFIG_BT_MAX_CONN` | int | Maximum number of simultaneous Bluetooth connections | 5 | -| `CONFIG_BT_MAX_PAIRED` | int | Maximum number of paired Bluetooth devices | 5 | -| `CONFIG_ZMK_BLE` | bool | Enable ZMK as a Bluetooth keyboard | | -| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START` | bool | Clears all bond information from the keyboard on startup | n | -| `CONFIG_ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE` | int | Max number of consumer HID reports to queue for sending over BLE | 5 | -| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int | Max number of keyboard HID reports to queue for sending over BLE | 20 | -| `CONFIG_ZMK_BLE_INIT_PRIORITY` | int | BLE init priority | 50 | -| `CONFIG_ZMK_BLE_THREAD_PRIORITY` | int | Priority of the BLE notify thread | 5 | -| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE` | int | Stack size of the BLE notify thread | 512 | -| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Experimental: require typing passkey from host to pair BLE connection | n | -| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | -| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | -| `CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | -| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | -| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_STACK_SIZE` | int | Stack size of the BLE split central write thread | 512 | -| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE` | int | Max number of behavior run events to queue to send to the peripheral(s) | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the BLE split peripheral notify thread | 650 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the BLE split peripheral notify thread | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | +| Config | Type | Description | Default | +| ------------------------------------------- | ---- | --------------------------------------------------------------------- | ------- | +| `CONFIG_BT` | bool | Enable Bluetooth support | | +| `CONFIG_BT_MAX_CONN` | int | Maximum number of simultaneous Bluetooth connections | 5 | +| `CONFIG_BT_MAX_PAIRED` | int | Maximum number of paired Bluetooth devices | 5 | +| `CONFIG_ZMK_BLE` | bool | Enable ZMK as a Bluetooth keyboard | | +| `CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START` | bool | Clears all bond information from the keyboard on startup | n | +| `CONFIG_ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE` | int | Max number of consumer HID reports to queue for sending over BLE | 5 | +| `CONFIG_ZMK_BLE_KEYBOARD_REPORT_QUEUE_SIZE` | int | Max number of keyboard HID reports to queue for sending over BLE | 20 | +| `CONFIG_ZMK_BLE_INIT_PRIORITY` | int | BLE init priority | 50 | +| `CONFIG_ZMK_BLE_THREAD_PRIORITY` | int | Priority of the BLE notify thread | 5 | +| `CONFIG_ZMK_BLE_THREAD_STACK_SIZE` | int | Stack size of the BLE notify thread | 512 | +| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Experimental: require typing passkey from host to pair BLE connection | n | Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the same value. On a split keyboard they should only be set for the central and must be set to one greater than the desired number of bluetooth profiles. @@ -95,3 +86,19 @@ Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the s | ------------------------ | ---- | ---------------------------------------- | ------- | | `CONFIG_ZMK_USB_LOGGING` | bool | Enable USB CDC ACM logging for debugging | n | | `CONFIG_ZMK_LOG_LEVEL` | int | Log level for ZMK debug messages | 4 | + +### Split keyboards + +Following split keyboard settings are defined in [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/Kconfig) (generic) and [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/bluetooth/Kconfig) (bluetooth). + +| Config | Type | Description | Default | +| ----------------------------------------------------- | ---- | ----------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | +| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | +| `CONFIG_ZMK_SPLIT_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | +| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_STACK_SIZE` | int | Stack size of the BLE split central write thread | 512 | +| `CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE` | int | Max number of behavior run events to queue to send to the peripheral(s) | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the BLE split peripheral notify thread | 650 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the BLE split peripheral notify thread | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | diff --git a/docs/docs/development/new-behavior.md b/docs/docs/development/new-behavior.md index 782dc010..af98613e 100644 --- a/docs/docs/development/new-behavior.md +++ b/docs/docs/development/new-behavior.md @@ -347,8 +347,8 @@ endif() For behaviors that do not require central locality, the following options for updating `app/CmakeLists.txt` also exist: - Behavior applies to unibody, or central or peripheral half of keyboard: place `target_sources(app PRIVATE .c)` line _before_ `if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)` -- Behavior applies to _only_ central half of split keyboard: place `target_sources(app PRIVATE .c)` after `if (CONFIG_ZMK_SPLIT_BLE AND CONFIG_ZMK_SPLIT_ROLE_CENTRAL)` -- Behavior applies to _only_ peripheral half of split keyboard: place `target_sources(app PRIVATE .c)` after `if (CONFIG_ZMK_SPLIT_BLE AND (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL))` +- Behavior applies to _only_ central half of split keyboard: place `target_sources(app PRIVATE .c)` after `if (CONFIG_ZMK_SPLIT AND CONFIG_ZMK_SPLIT_ROLE_CENTRAL)` +- Behavior applies to _only_ peripheral half of split keyboard: place `target_sources(app PRIVATE .c)` after `if (CONFIG_ZMK_SPLIT AND (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL))` - Behavior requires certain condition in a keyboard's `.conf` file to be met: use `target_sources_ifdef(CONFIG_ app PRIVATE .c)` instead of `target_sources(.c)` ### Defining common use-cases for the behavior (`.dtsi`) (Optional) From b1ce8a0d3346be78301637a9861e4b70587aa2bb Mon Sep 17 00:00:00 2001 From: "byran.tech" <61983584+Hello9999901@users.noreply.github.com> Date: Tue, 12 Jul 2022 03:47:19 -0400 Subject: [PATCH 33/50] fix(docs): typo fixes --- docs/docs/config/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/config/index.md b/docs/docs/config/index.md index 9e35df83..51fd45b7 100644 --- a/docs/docs/config/index.md +++ b/docs/docs/config/index.md @@ -134,7 +134,7 @@ See [Zephyr's Devicetree guide](https://docs.zephyrproject.org/latest/build/dts/ ### Changing Devicetree Properties Since Devicetree properties are set for specific nodes in the tree, you will first need to find the node you want to configure. You will typically need to -search through the `.dts` file for you board, `.overlay` file for your shield, or a `.dtsi` file included in by of those files using an `#include` statement. +search through the `.dts` file for your board, `.overlay` file for your shield, or a `.dtsi` file included by those files using an `#include` statement. A Devicetree node looks like this: From 08c43feaaff548b4ba7d32a26e72313024a989bc Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Tue, 19 Apr 2022 03:02:00 -0400 Subject: [PATCH 34/50] feat(kscan): Kconfig for optional scan delay. Add optional Kconfig setting to delay scanning after each output column is set, and inputs are read, to allow inputs to "settle" after the last column is set back to inactive. --- app/drivers/kscan/Kconfig | 14 ++++++++++++++ app/drivers/kscan/kscan_gpio_matrix.c | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/app/drivers/kscan/Kconfig b/app/drivers/kscan/Kconfig index c9ace0a3..b76a27b4 100644 --- a/app/drivers/kscan/Kconfig +++ b/app/drivers/kscan/Kconfig @@ -30,6 +30,20 @@ config ZMK_KSCAN_GPIO_MATRIX default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX)) select ZMK_KSCAN_GPIO_DRIVER +if ZMK_KSCAN_GPIO_MATRIX + +config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS + int "Ticks to wait between each output when scanning" + default 0 + help + When iterating over each output to drive it active, read inputs, then set + inactive again, some boards may take time for the previous output to + "settle" before reading inputs for the next active output column. In that + scenario, set this value to a positive value to configure the number of + usecs to wait after reading each column of keys. + +endif # ZMK_KSCAN_GPIO_MATRIX + config ZMK_KSCAN_MOCK_DRIVER bool default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK)) diff --git a/app/drivers/kscan/kscan_gpio_matrix.c b/app/drivers/kscan/kscan_gpio_matrix.c index fc355ca2..1ab4d442 100644 --- a/app/drivers/kscan/kscan_gpio_matrix.c +++ b/app/drivers/kscan/kscan_gpio_matrix.c @@ -250,6 +250,10 @@ static int kscan_matrix_read(const struct device *dev) { LOG_ERR("Failed to set output %i inactive: %i", o, err); return err; } + +#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS > 0 + k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS); +#endif } // Process the new state. From f68692effd96a75627d0b3e96d77f47f37d9f5f7 Mon Sep 17 00:00:00 2001 From: GreenAirplane Date: Wed, 20 Jul 2022 11:17:19 -0400 Subject: [PATCH 35/50] feat(docs): Document behavior queue limit for Macros (#1384) Co-authored-by: Cem Aksoylar Co-authored-by: Dom H --- docs/docs/behaviors/macros.md | 6 ++++++ docs/docs/config/behaviors.md | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/docs/behaviors/macros.md b/docs/docs/behaviors/macros.md index aca9eb7a..1648892e 100644 --- a/docs/docs/behaviors/macros.md +++ b/docs/docs/behaviors/macros.md @@ -134,6 +134,12 @@ bindings ; ``` +### Behavior Queue Limit + +Macros use an internal queue to invoke each behavior in the bindings list when triggered, which has a size of 64 by default. Bindings in "press" and "release" modes correspond to one event in the queue, whereas "tap" mode bindings correspond to two (one for press and one for release). As a result, the effective number of actions processed might be less than 64 and this can cause problems for long macros. + +To prevent issues with longer macros, you can change the size of this queue via the `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` setting in your configuration, [typically through your `.conf` file](../config/index.md). For example, `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE=512` would allow your macro to type about 256 characters. + ## Common Patterns Below are some examples of how the macro behavior can be used for various useful functionality. diff --git a/docs/docs/config/behaviors.md b/docs/docs/config/behaviors.md index a4e847dc..67f5ce74 100644 --- a/docs/docs/config/behaviors.md +++ b/docs/docs/config/behaviors.md @@ -9,6 +9,14 @@ See [Configuration Overview](index.md) for instructions on how to change these s See the [zmk/app/dts/behaviors/](https://github.com/zmkfirmware/zmk/tree/main/app/dts/behaviors) folder for all default behaviors. +## Common + +### Kconfig + +| Config | Type | Description | Default | +| --------------------------------- | ---- | ------------------------------------------------------------------------------------ | ------- | +| `CONFIG_ZMK_BEHAVIORS_QUEUE_SIZE` | int | Maximum number of behaviors to allow queueing from a macro or other complex behavior | 64 | + ## Caps Word Creates a custom behavior that behaves similar to a caps lock but deactivates when any key not in a continue list is pressed. From 54aa3e6a1e1dd26d945edaf9c45672bd093dd744 Mon Sep 17 00:00:00 2001 From: "byran.tech" <61983584+Hello9999901@users.noreply.github.com> Date: Mon, 25 Jul 2022 23:43:56 -0400 Subject: [PATCH 36/50] fix(docs): typo fixes * Update user-setup.md --- 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 faa4bef8..06c168b3 100644 --- a/docs/docs/user-setup.md +++ b/docs/docs/user-setup.md @@ -199,7 +199,7 @@ storage device. Once the flash is complete, the controller should automatically ZMK will automatically advertise itself as connectable if it is not currently connected to a device. You should be able to see your keyboard from the bluetooth scanning view of your laptop or phone / tablet. It is reported by some users that the connections with Android / iOS devices are generally smoother than with laptops, so if you have trouble connecting, you could try to connect from your phone or tablet first to eliminate any potential hardware issues. -ZMK support multiple BLE “profiles”, which allows you to connect to and switch among multiple devices. Please refer to the [Bluetooth behavior](behaviors/bluetooth.md) section for detailed explanations of how to use them. +ZMK supports multiple BLE “profiles”, which allows you to connect to and switch among multiple devices. Please refer to the [Bluetooth behavior](behaviors/bluetooth.md) section for detailed explanations on how to use them. ### Connecting Split Keyboard Halves From c4a47c08def72ae0b096e949f4388e5a539187b6 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 24 Jan 2022 14:34:48 -0500 Subject: [PATCH 37/50] fix(display): Initialize display on queue as well. --- app/src/display/main.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/src/display/main.c b/app/src/display/main.c index 5dfad910..79a054a7 100644 --- a/app/src/display/main.c +++ b/app/src/display/main.c @@ -85,26 +85,20 @@ static void stop_display_updates() { int zmk_display_is_initialized() { return initialized; } -int zmk_display_init() { +void initialize_display(struct k_work *work) { LOG_DBG(""); display = device_get_binding(ZMK_DISPLAY_NAME); if (display == NULL) { LOG_ERR("Failed to find display device"); - return -EINVAL; + return; } -#if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) - k_work_queue_start(&display_work_q, display_work_stack_area, - K_THREAD_STACK_SIZEOF(display_work_stack_area), - CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY, NULL); -#endif - screen = zmk_display_status_screen(); if (screen == NULL) { LOG_ERR("No status screen provided"); - return 0; + return; } lv_scr_load(screen); @@ -112,6 +106,20 @@ int zmk_display_init() { start_display_updates(); initialized = true; +} + +K_WORK_DEFINE(init_work, initialize_display); + +int zmk_display_init() { + LOG_DBG(""); + +#if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) + k_work_queue_start(&display_work_q, display_work_stack_area, + K_THREAD_STACK_SIZEOF(display_work_stack_area), + CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY, NULL); +#endif + + k_work_submit_to_queue(zmk_display_work_q(), &init_work); LOG_DBG(""); return 0; From e3efffa9a885c5d10d7b248b96fe8bc163c070ce Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 25 Apr 2022 22:07:13 -0400 Subject: [PATCH 38/50] refactor(display): Move clear to unblank for EPD driver. --- app/drivers/display/il0323.c | 110 +++++++++++++++++------------------ 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/app/drivers/display/il0323.c b/app/drivers/display/il0323.c index b8117584..f6c9037e 100644 --- a/app/drivers/display/il0323.c +++ b/app/drivers/display/il0323.c @@ -115,28 +115,6 @@ static int il0323_update_display(const struct device *dev) { return 0; } -static int il0323_blanking_off(const struct device *dev) { - struct il0323_data *driver = dev->data; - - if (blanking_on) { - /* Update EPD pannel in normal mode */ - il0323_busy_wait(driver); - if (il0323_update_display(dev)) { - return -EIO; - } - } - - blanking_on = false; - - return 0; -} - -static int il0323_blanking_on(const struct device *dev) { - blanking_on = true; - - return 0; -} - static int il0323_write(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, const void *buf) { struct il0323_data *driver = dev->data; @@ -208,6 +186,58 @@ static int il0323_read(const struct device *dev, const uint16_t x, const uint16_ return -ENOTSUP; } +static int il0323_clear_and_write_buffer(const struct device *dev, uint8_t pattern, bool update) { + struct display_buffer_descriptor desc = { + .buf_size = IL0323_NUMOF_PAGES, + .width = EPD_PANEL_WIDTH, + .height = 1, + .pitch = EPD_PANEL_WIDTH, + }; + uint8_t *line; + + line = k_malloc(IL0323_NUMOF_PAGES); + if (line == NULL) { + return -ENOMEM; + } + + memset(line, pattern, IL0323_NUMOF_PAGES); + for (int i = 0; i < EPD_PANEL_HEIGHT; i++) { + il0323_write(dev, 0, i, &desc, line); + } + + k_free(line); + + if (update == true) { + if (il0323_update_display(dev)) { + return -EIO; + } + } + + return 0; +} + +static int il0323_blanking_off(const struct device *dev) { + struct il0323_data *driver = dev->data; + + if (blanking_on) { + /* Update EPD pannel in normal mode */ + il0323_busy_wait(driver); + if (il0323_clear_and_write_buffer(dev, 0xff, true)) { + return -EIO; + } + } + + blanking_on = false; + + return 0; +} + +static int il0323_blanking_on(const struct device *dev) { + blanking_on = true; + + return 0; +} + static void *il0323_get_framebuffer(const struct device *dev) { LOG_ERR("not supported"); return NULL; @@ -247,36 +277,6 @@ static int il0323_set_pixel_format(const struct device *dev, const enum display_ return -ENOTSUP; } -static int il0323_clear_and_write_buffer(const struct device *dev, uint8_t pattern, bool update) { - struct display_buffer_descriptor desc = { - .buf_size = IL0323_NUMOF_PAGES, - .width = EPD_PANEL_WIDTH, - .height = 1, - .pitch = EPD_PANEL_WIDTH, - }; - uint8_t *line; - - line = k_malloc(IL0323_NUMOF_PAGES); - if (line == NULL) { - return -ENOMEM; - } - - memset(line, pattern, IL0323_NUMOF_PAGES); - for (int i = 0; i < EPD_PANEL_HEIGHT; i++) { - il0323_write(dev, 0, i, &desc, line); - } - - k_free(line); - - if (update == true) { - if (il0323_update_display(dev)) { - return -EIO; - } - } - - return 0; -} - static int il0323_controller_init(const struct device *dev) { struct il0323_data *driver = dev->data; uint8_t tmp[IL0323_TRES_REG_LENGTH]; @@ -350,10 +350,6 @@ static int il0323_controller_init(const struct device *dev) { return -EIO; } - if (il0323_clear_and_write_buffer(dev, 0xff, false)) { - return -1; - } - return 0; } @@ -429,4 +425,4 @@ static struct display_driver_api il0323_driver_api = { }; DEVICE_DT_INST_DEFINE(0, il0323_init, NULL, &il0323_driver, NULL, POST_KERNEL, - CONFIG_APPLICATION_INIT_PRIORITY, &il0323_driver_api); \ No newline at end of file + CONFIG_APPLICATION_INIT_PRIORITY, &il0323_driver_api); From 90b45a1284d11c0003949bb0b57c20a3ecb6d682 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 25 Apr 2022 22:07:21 -0400 Subject: [PATCH 39/50] feat(display): Blank on idle optionally. * Add new defaulted-on Kconfig option to control if displays are blanked/unblanked on idle/activity. --- app/src/display/Kconfig | 4 ++++ app/src/display/main.c | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/display/Kconfig b/app/src/display/Kconfig index 8023025a..ee977556 100644 --- a/app/src/display/Kconfig +++ b/app/src/display/Kconfig @@ -11,6 +11,10 @@ menuconfig ZMK_DISPLAY if ZMK_DISPLAY +config ZMK_DISPLAY_BLANK_ON_IDLE + bool "Blank display on idle" + default y + choice LVGL_TXT_ENC default LVGL_TXT_ENC_UTF8 diff --git a/app/src/display/main.c b/app/src/display/main.c index 79a054a7..9de0ad7f 100644 --- a/app/src/display/main.c +++ b/app/src/display/main.c @@ -111,8 +111,6 @@ void initialize_display(struct k_work *work) { K_WORK_DEFINE(init_work, initialize_display); int zmk_display_init() { - LOG_DBG(""); - #if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) k_work_queue_start(&display_work_q, display_work_stack_area, K_THREAD_STACK_SIZEOF(display_work_stack_area), @@ -125,6 +123,7 @@ int zmk_display_init() { return 0; } +#if IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) int display_event_handler(const zmk_event_t *eh) { struct zmk_activity_state_changed *ev = as_zmk_activity_state_changed(eh); if (ev == NULL) { @@ -148,3 +147,5 @@ int display_event_handler(const zmk_event_t *eh) { ZMK_LISTENER(display, display_event_handler); ZMK_SUBSCRIPTION(display, zmk_activity_state_changed); + +#endif /* IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) */ \ No newline at end of file From 70beff7e624157955ab0c2c1083466eaffec74e9 Mon Sep 17 00:00:00 2001 From: Nick Coutsos Date: Wed, 27 Jul 2022 08:15:46 -0400 Subject: [PATCH 40/50] refactor(docs): Change wording in RGB_COLOR_HSB description Simplify description for second &rgb_ug parameter --- docs/docs/behaviors/underglow.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/behaviors/underglow.md b/docs/docs/behaviors/underglow.md index c028f832..52429e69 100644 --- a/docs/docs/behaviors/underglow.md +++ b/docs/docs/behaviors/underglow.md @@ -41,11 +41,11 @@ Here is a table describing the action for each define: - Reference: `&rgb_ug` - Parameter #1: The RGB action define, e.g. `RGB_TOG` or `RGB_BRI` -- Parameter #2: Only applies to `RGB_COLOR_HSB` and is the HSB values of the color to set within parenthesis and separated by a common (see below for an example) +- Parameter #2: Only applies to `RGB_COLOR_HSB` and is the HSB representation of the color to set (see below for an example) :::note HSB Values -When specifying HSB values you'll need to use `RGB_COLOR_HSB(h, s, b)` in your keymap file. See below for an example. +When specifying HSB values you'll need to use `RGB_COLOR_HSB(h, s, b)` in your keymap file. Value Limits: From 01f51f06dc044f20b38c4cc69403ede2a8cd5aa2 Mon Sep 17 00:00:00 2001 From: HanfG <376840834@qq.com> Date: Wed, 27 Jul 2022 20:17:40 +0800 Subject: [PATCH 41/50] fix(docs): Fix col/row properties in kscan.md example --- docs/docs/config/kscan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/config/kscan.md b/docs/docs/config/kscan.md index 77dc23cb..1f54692d 100644 --- a/docs/docs/config/kscan.md +++ b/docs/docs/config/kscan.md @@ -207,8 +207,8 @@ One possible way to do this is a 3x4 matrix where the direct GPIO keys are shift kscan0: kscan_composite { compatible = "zmk,kscan-composite"; label = "KSCAN0"; - rows = <3>; - columns = <4>; + rows = <4>; + columns = <3>; // Include the matrix driver matrix { From 8bc96ab9fe6121e586208a8d45666ec136002643 Mon Sep 17 00:00:00 2001 From: "byran.tech" <61983584+Hello9999901@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:07:57 -0400 Subject: [PATCH 42/50] feat(docs): added "how is the latency" * Update docs/docs/faq.md Co-authored-by: Kurtis Lew Co-authored-by: Dom H Co-authored-by: Pete Johanson --- docs/docs/faq.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 36ecc010..edb41f3b 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -80,6 +80,14 @@ Please note, many keyboards only have a single PCB which includes the “brains Currently, ZMK only supports wireless split, but wired split is possible and we welcome contributions! +### How is the latency? + +The latency of ZMK is comparable to other firmware offerings. ZMK is equipped with a variety of scanning methods and [debounce algorithms](features/debouncing.md) that can affect the final measured latency. [This video](https://www.youtube.com/watch?v=jWL4nU-vtWs) shows a latency comparison of ZMK and other keyboard firmwares. + +### Any chance for 2.4GHz dongle implementation? + +At this time, there are no current plans to implement 2.4GHz dongle mode. This is because utilizing Nordic's proprietary 2.4GHz low level protocols requires use of the Nordic Connect SDK, which is licensed with a more restrictive license than ZMK's MIT license. However, ZMK does plan to implement dongle mode using BLE (with encryption). This will result in a 3.75ms average latency from the protocol itself. + ### What bootloader does ZMK use? ZMK isn’t designed for any particular bootloader, and supports flashing different boards with different flash utilities (e.g. OpenOCD, nrfjprog, etc.). So if you have any difficulties, please let us know on [Discord](https://zmk.dev/community/discord/invite)! From be0d49b62d7a423512e6e9052eca037b331109b7 Mon Sep 17 00:00:00 2001 From: Jason Hazel Date: Wed, 27 Jul 2022 11:39:42 -0400 Subject: [PATCH 43/50] feat(shields) add support for Spaceman Pancake (#1400) * add support for Spaceman Pancake Co-authored-by: Pete Johanson Co-authored-by: Jason Hazel Co-authored-by: Pete Johanson --- app/boards/shields/pancake/Kconfig.defconfig | 9 ++++ app/boards/shields/pancake/Kconfig.shield | 5 ++ app/boards/shields/pancake/pancake.conf | 1 + app/boards/shields/pancake/pancake.keymap | 57 ++++++++++++++++++++ app/boards/shields/pancake/pancake.overlay | 39 ++++++++++++++ app/boards/shields/pancake/pancake.zmk.yml | 8 +++ 6 files changed, 119 insertions(+) create mode 100644 app/boards/shields/pancake/Kconfig.defconfig create mode 100644 app/boards/shields/pancake/Kconfig.shield create mode 100644 app/boards/shields/pancake/pancake.conf create mode 100644 app/boards/shields/pancake/pancake.keymap create mode 100644 app/boards/shields/pancake/pancake.overlay create mode 100644 app/boards/shields/pancake/pancake.zmk.yml diff --git a/app/boards/shields/pancake/Kconfig.defconfig b/app/boards/shields/pancake/Kconfig.defconfig new file mode 100644 index 00000000..47f5e682 --- /dev/null +++ b/app/boards/shields/pancake/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if SHIELD_PANCAKE + +config ZMK_KEYBOARD_NAME + default "Pancake" + +endif \ No newline at end of file diff --git a/app/boards/shields/pancake/Kconfig.shield b/app/boards/shields/pancake/Kconfig.shield new file mode 100644 index 00000000..784d25a4 --- /dev/null +++ b/app/boards/shields/pancake/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config SHIELD_PANCAKE + def_bool $(shields_list_contains,pancake) \ No newline at end of file diff --git a/app/boards/shields/pancake/pancake.conf b/app/boards/shields/pancake/pancake.conf new file mode 100644 index 00000000..67b8cb5c --- /dev/null +++ b/app/boards/shields/pancake/pancake.conf @@ -0,0 +1 @@ +# CONFIG_ZMK_USB_LOGGING=y \ No newline at end of file diff --git a/app/boards/shields/pancake/pancake.keymap b/app/boards/shields/pancake/pancake.keymap new file mode 100644 index 00000000..99f2aaff --- /dev/null +++ b/app/boards/shields/pancake/pancake.keymap @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#define DEF 0 +#define LWR 1 +#define RSE 2 +#define FNC 3 + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp ESC &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSPC + &kp TAB &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SQT &kp SEMI + &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N &kp M &kp COMMA &kp DOT &kp UP &kp ENTER + &kp LCTRL &kp LALT &kp LGUI &mo FNC &mo LWR &kp SPACE &kp SPACE &mo RSE &kp SLASH &kp LEFT &kp DOWN &kp RIGHT + >; + }; + + lower_layer { + bindings = < + &kp TILDE &kp EXCL &kp AT &kp HASH &kp DLLR &kp PRCNT &kp CARET &kp AMPS &kp STAR &kp LPAR &kp RPAR &kp BSPC + &trans &trans &trans &trans &trans &trans &trans &trans &kp UNDER &kp PLUS &trans &trans + &trans &trans &trans &trans &trans &trans &trans &trans &kp LBRC &kp RBRC &kp C_VOL_UP &trans + &trans &trans &trans &trans &trans &trans &trans &trans &kp QMARK &trans &kp C_VOL_DN &trans + >; + }; + + raise_layer { + bindings = < + &kp GRAVE &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp BSPC + &trans &trans &trans &trans &trans &trans &trans &trans &kp MINUS &kp EQUAL &trans &kp BSLH + &trans &trans &trans &trans &trans &trans &trans &trans &kp LBKT &kp RBKT &kp C_VOL_UP &trans + &trans &trans &trans &trans &trans &trans &trans &trans &trans &trans &kp C_VOL_DN &trans + >; + }; + + function_layer { + bindings = < + &bootloader &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 &trans + &trans &trans &trans &trans &trans &trans &trans &trans &kp MINUS &kp F11 &kp F12 &trans + &bt BT_CLR &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &trans &trans &trans &trans &trans &trans + &out OUT_BLE &out OUT_USB &out OUT_TOG &trans &trans &trans &trans &trans &trans &trans &trans &trans + >; + }; + }; +}; diff --git a/app/boards/shields/pancake/pancake.overlay b/app/boards/shields/pancake/pancake.overlay new file mode 100644 index 00000000..53e8c8c1 --- /dev/null +++ b/app/boards/shields/pancake/pancake.overlay @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + chosen { + zmk,kscan = &kscan0; + }; + + kscan0: kscan_0 { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + diode-direction = "col2row"; + + col-gpios + = <&pro_micro 21 GPIO_ACTIVE_HIGH> + , <&pro_micro 20 GPIO_ACTIVE_HIGH> + , <&pro_micro 19 GPIO_ACTIVE_HIGH> + , <&pro_micro 18 GPIO_ACTIVE_HIGH> + , <&pro_micro 7 GPIO_ACTIVE_HIGH> + , <&pro_micro 8 GPIO_ACTIVE_HIGH> + , <&pro_micro 9 GPIO_ACTIVE_HIGH> + , <&pro_micro 6 GPIO_ACTIVE_HIGH> + , <&pro_micro 5 GPIO_ACTIVE_HIGH> + , <&pro_micro 4 GPIO_ACTIVE_HIGH> + , <&pro_micro 3 GPIO_ACTIVE_HIGH> + , <&pro_micro 2 GPIO_ACTIVE_HIGH> + ; + + row-gpios + = <&pro_micro 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 16 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + }; +}; diff --git a/app/boards/shields/pancake/pancake.zmk.yml b/app/boards/shields/pancake/pancake.zmk.yml new file mode 100644 index 00000000..21cc5444 --- /dev/null +++ b/app/boards/shields/pancake/pancake.zmk.yml @@ -0,0 +1,8 @@ +file_format: "1" +id: pancake +name: Pancake +type: shield +url: https://mkultra.click/pancake-keyboard-kit +requires: [pro_micro] +features: + - keys From eee7e1cd41c8bed81c5abcc0e8986c05e811b251 Mon Sep 17 00:00:00 2001 From: Kurtis Lew Date: Sat, 30 Jul 2022 02:24:49 -0700 Subject: [PATCH 44/50] fix(docs): Update tap-dance and hold-tap documentation Co-Authored-By: Cem Aksoylar --- docs/docs/assets/hold-tap/case1_2.png | Bin 10323 -> 0 bytes docs/docs/assets/hold-tap/case1_2.svg | 21 +++ .../assets/hold-tap/case_hold_preferred.png | Bin 6380 -> 0 bytes .../assets/hold-tap/case_hold_preferred.svg | 23 +++ docs/docs/assets/hold-tap/comparison.png | Bin 158912 -> 0 bytes docs/docs/assets/hold-tap/comparison.svg | 131 ++++++++++++++++ docs/docs/assets/tap-dance/timing_diagram.svg | 140 ++++++++---------- docs/docs/behaviors/hold-tap.md | 134 ++++++++++++++--- docs/docs/behaviors/layers.md | 11 +- docs/docs/behaviors/mod-tap.md | 9 +- docs/docs/behaviors/tap-dance.md | 83 +++++++---- 11 files changed, 421 insertions(+), 131 deletions(-) delete mode 100644 docs/docs/assets/hold-tap/case1_2.png create mode 100644 docs/docs/assets/hold-tap/case1_2.svg delete mode 100644 docs/docs/assets/hold-tap/case_hold_preferred.png create mode 100644 docs/docs/assets/hold-tap/case_hold_preferred.svg delete mode 100644 docs/docs/assets/hold-tap/comparison.png create mode 100644 docs/docs/assets/hold-tap/comparison.svg diff --git a/docs/docs/assets/hold-tap/case1_2.png b/docs/docs/assets/hold-tap/case1_2.png deleted file mode 100644 index 818ac83752dd0c47c590b015ca18923797981c42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10323 zcmdU#cTg1Hw(m!goJ4X~6i_7R=qN!z2?{@wQL+*RNeVcUbIx(ddB`&4obxae zh9PsCbLzf(?x|nB`~G>a>QM#LQ*`g{y?gDo*7vhk*gF+@Vgfn>5C}x9s35Bb0^M-~ zj!*D#fWPfYMfkwSJ!fgfzwm&cPk3gbAkagQqU@``JboePJ@vl3F5DbU>P*R*QI0&g zPu)sP=-4(tkUd7{`i36n+MVQGP|hUa?mU_K>mtS4CFf`UhNULL%W|C`VKFhX5)R9- zzz+;x3S5fvZ(Vj6LXpPuJG>fNZe71P^EeHe#59zkHpMspZ1iJ3M8UUR#K~ebSChJC zX-6K1LmO&(hI@9lowBe;bEUJs!?{JfD#pb?$5Et~8&gbkV_bI)Ay=YrxqKdE$%*@3 zih{_<$e2p9%rCrd@R`p23hyZFKWJ+*jioruFruC;C@3(Qt+C6>&K9x)m*BZ*jgNlV zn{)q_d|~bPh|N803Ea*0yS7mJ+AGUlPtW>yhMet8RoL2^Sz}{kBG~Umd3iYn71d$y z<8RU5zstRTjq~c&D>DlVe!uHaPR`D|ToMjTAP`$znCtEY*@FktUS8sa)ZCqB{Cw{F zdR~})WgQ*bs|yVKvuE2C)u$I1l@80&?HwJ@xVbk1aG4&mvxgiXyJTc!D5$7dpm)YU zfBsx%In2bS{IfHdm_9Tz(tNJYaqx;?c$!qE8B%*P=WUZAeJ!5hKHb4#=k})0E`__p zT-+6@SPf6$hvM71T{{nTfEus-AmzThA{3O2tFnr$aXHst-N-}^M|UGa!*@oO_(2g} z?eN$)_BU{vq1ob3m#A?H#^+|ed9w;C`yGVCV0mct-S!Mq9^L*GOaj?#v53h0SOPLB zsS&+!QDsA|r1L$_I1^&I+O7PnRyR@5TC#8wl0INzd{~uqxwoI{gzz^WJbPg9yZ*JW zf!i8I<>Oo$e<-Yaa~Y-{8bRVDZ#63YbV{ms*@5?nx@F}g^(rCvtz#0zef^z>=(jX_ zLQ%Kl`^-GoxX4V18EW6|motvz{(zOWHAB&yL2G(Y?o^qVfTO;*U>#AbZTUM59e5uS z+suMFz2HzXT%hbWO^rq2)OVwlFAc z(m?U32Yek<@4RUu44r?M?(s8lK8S$?_w1q~(d}*E?L;zC+SWEU_{|jV_W$tdz(qAn zYirY}N}(w#b0b&wrYqMrHvz-JXApbP+q(a%3bNSTX8umX1nhS$+2qAK!ZQ(Zv^ng2 z>B0PfoIETls@wAuE*uVj!ZzVY?5F*9v!)UcA3yzV0=Fyj0)uM#xF`&b?CiWZM4iQF z+V`-rsY$2)NqSck*wCD5#di98N0X%<%?+P0@bL00H<|q`2n51&ue^t14=3A*I)Q+M z#G)+_`*>$OB3&lra4k-i-(l(H>nH}eO=(m28axg@IGEkg@KYHoe9%d*TIP3ijgz3? zH`&U%>Lp< z%Hg5Y=;)|+jjc`-I!7rf5N>w(^2>e#jk*LsL@BW!E%Vrow1G)}Z`4I&TjPl& zuI}*b?yt0&HdDk(?5a|E?G~23SZ}wJC{}MzBRu4*^wfF*HE5niWGg&RBZGyUoqCWl zP%LfpXMKtIxGE>D6q?%~ttssn5f)1G^i7iy1!%nC{86BG+f9O8FDe}gvcK1&&z=r*uhMYSklx0(qHT$FBC zSyB5=+ZAy1d91g^OcmPr;SZnnP!$<0*sA(LUEikKxDZ#~{7^A+xbW%+`(61>S6-V2 zt6oUKYXz^l;d4mC@|CWTu<%{@yz^pnfnBo>=8<9Z**!if9H&i`L6NTS^_-Pv3q~-c zvi7}>rgZI?F6~7>Sg0YV@b^1IGCuqw{Yxz`qNL8Pq?^?rzH)Sj`$Cq7 zPbn6KkvrA8oM^DPM6*YN)GnCKal~$tVyEQC!7z32^r4u+i|fu4mB&vrO!a3A+^?li zx+&*ccsuNT<^|80qBbp$kPYAPTGHv^nfp$PhAdjZL;3C3Z}#^0-&*g|l9BuP6D>Fo zZz3HmQi}dXy-ArRL@%Hg04sz9{I<%y{Ad>YJs<`+YQL5da z-!932eRemX4N1#)71PbEM1IU58J1Ork8bA5-&$eAJe?lhZgD;l#+qH z<2$BjwQG;X#97hoPdK`>ufheh-aGGM;Sd=Or$`Z?soJ+)?jxM<=SDH5iGnX;catSt zzdo*)sDJbbd+vCkb{zFWIkM^Jt2n=)T0;TwMYmbIZH*DX{-t9{i6rUE*|LZh(((7E zo2Tgv2|pb2n?GI7@NThV)J<#4-QbnnNcA-}Iw2EgahE*UPPENZUO6Hgh38c3`)VS1 zz3Q9dM~9}p$mgxwPnuD@1Shs`o{T*wsEv0MR5QVHaU*`VO(%#ixYOs;PzE?W>vB(V zfaY_X6?6?>(%_Oy#cE;KC)kP1P>Sf+X%kj{;jn->UuBns7p}Y4 z9#L0_1g1xpyO?j%2I3GZkYf|>>MFCsNzO1RuYC4&cEAj@e~FCfJZ6~MGHuZ_jAJ^N z0O4anOjkpAoE{J4HpKX6P(HZnNwP-Lh9tW z5JRWroKvsC7SL9VaJg`_AF!ek?$@G`MZ~E~zzrwk-*;jdgW)F2yBhu4mt#aK3`51W zs)e)<>8ne+_r<*~=XL_riGx(JqxQH<&}x*SI+!Igz2~IHL?whA zvIttah%!QL_{CvdaSnV!469V$FVy~e*2jLGLw1T&)wS(XSA&xq)TnP0FcW=IUYyz- zFt4e&gRG==+4KQhd&i+~&LwnzwDj3wz|QYy4+0rD`rag>6V^{_i+%Kp=QX=V&jg+g z4O?JlNNLUyi^AIKm@z&VRYsYiA1=0(>&J92l1TJpcV}cITsCH1nT^qh7JuT!l*KNe zpSZsIw76_*aLsYSuf?~s&;uRI?fw3-(;lQ4WGx@0L;oUw@<;W7I_LrJ!p4tgRYs|o z=4WD!K3I6WG=`;3YqF0(j4*WZq}$OHt&;DlIgYANnSrjkCw}I>PZ9T6x*Nsf6)Zm` zE(BA*bN{E@De?TD6XLXt;{o%Vnfs2|*swcuiScisOL&LPLuna%w)0N%k;%WfwohyD zSxPWQwZ}dMyrlloTT@=_JOK@bFNJ-N)tX)kK)L1;6W_e^=<%98!^R<}oUB|p^hcFU zmWmtBboN0L;I<_a2@X3SQ$h;KGS$oI{o!V8#;qQJ9i7;#lBpE|S^8j9c1@k=mEpgh?!(MsblncM`Vsz6TXT{743l_AkQ%7}- ztG^k1#4Gr~e-zQhR3iITrOp2SqG}IksqT*^N9?6$D;kil2GZQupxKwW2KS4*S$AAN zjYp5q=hr=sJ6m_$eCrdxrT9fBFw?=izZtYI=jGQXPun``4yLRicS%3p{0ys$P)VVa zRdC^qDOaRmoa@NX#QjJBoJy?^2fShDt9|-nERVCX zs4IQ6c3z$adddoh-;&X=TA zWZ1GWudk13z%U1O?Z_?cqt}ohaM{Siu)1N@!>!RX_2^NA9D0VM|Ls(zujf4RD=^jM zbK;0`D4n;^=5Vr`O)|0LXyfCpQT6K5_t5B23#3*FSWAjhU0;8&NT-tfgQlh?&RDR! zk&)5-p~!*<77q^(_4lV*{UQ%T>lS20%bbaGeJ<vHG!Kw_o`VHK?byM1W zUJy7h^8j~#sCnS~UmEfC=&Wt*bVl)8cQ<~frozz2nxT$q5C+~<@bz)+r|OSPZ#Qp# zo$!U-t8F1;R~e#W&JYFD$4w9X*={Ly?MoN=tc$Y(&p0ZKo?gT}eE65IA$@(?H9q``alzWi>w(LRq#Q8BZP zo!xEBv#3leZ@DCYPJML->mk>=?}zx~wX4HfLjP-Tv79ci35ybuttuE%7YO&{vf|=? zZ(Qlzk9zSCol}y+ovj$PKMNoAti&UVT%%U?n<>hKxO<|)yaC&Ki6T>nHu8jI22V0K zM*{XSCxt=cKpGojLxm02_Q37*-{xl4ygE-m+^D8`=!F1DOXA44QFS>pA9v&@8%-U9 z_OOn6S=asb*ei30-mC3x79h;F3p4bpmVHV}ds6xFF0cczWa_rd)xnti5$s zkPq7*fVWeAeT5BpZzAYP8DRl=JwI@b?8lIyEafDb>1pz!4}yz`bwT*jGgAI5zXfM_ zvu3l@x5G+yT1j+U!}CztIpsEPZfC^S?Nn}q+0;Q7EQPygmA}O!qzQVXtTrPH1j#|f zdatW2kVW=BWmoOGayk5q4#-l;$b#ONMf~^VaI@SydfF_bpy1~zo}p?23s-W-D>vUG z@E!FJ?AS0|Pj8*Ag!^Gl8SOyUN$6fuMPxZ-vSbgOSnSX3PGN=pONBjI+;s&=;8VEe zKHM2B|F`k~Hu-Fb($AuF5M_{+m38Q0bk}oh^7jXA3?;w)`0=BF1{+t*z9gj;>*eJo z02Au=#IdolKOGH<5YY=4d4|VQDYkKszBxTTjZRGTn0Aegjs2NVX9XARxlL;c{&tf8 z{tzNh!>XjSv$Hw%8{(V1F=_?|DsSEpR9Xxky6B8bf9&KQCGh#vhHE=pLkt~!4Wvx? zq@)-Z#nHfj{A+!46N}g9+(k`YT}tF65*aD|&!M4pQ5*jyK>lUi|4FO-|8VMuq~Y2Pb&m>$ z++4yKs{S<>arD`JD>;j+Gv{Wwgd)8k#A5DeZ}Bre*)P~ehc3pWCQqlb{}ni`*Wcd* zk999XZ|a0%eqWh{5^3}rqA2!O)Bw{0EHSdGN>DXnG{f34J2>zvgyPTy?Q+M2GlGiC z1d*fsgq?kLeZA}Q>?kZUGP|rSTAs=Cb5Ri|KR-Vn0RfAEK-}5UW*ii%3z-hU!UgdH z!Be+3Dmwc9C<38V=kN%yOh7b^!rUQ!eSKStEnr1O#f0Ln#l_Swgwzk1m{c4c`ED&( zOiU~*HcP&1(Ny=BD~o z7g2M)FhCR6xfQQMaJCy7INt#{4Iz@%%ir;94F}FP$#D0Ck{(Qrq(f-nR zY9FGSx*exKG@m+A$^=I~O!pS4sBk{D9;XWolEEV)SZ0sQE!*9N;NeFuMM<#5eovNh zP!Mf7A2t-Qn{j!)D4ug>&|3J;uC#u~bvU`Ud?_fI^VG;vzxSJBWV%mT3;lBnRrNCO z=#J$jFtZntdTvY#cb7SdIb>bQ@kQ!hwNt~^Ly49TN=6@_bm3y6C9UZwvn?IQ#>&pj zDA-9hD|n9HwnMfifSJTi2hMA@1+O@qp!6bvBY3-HL+)3$qE+Jxk@dst2FV7rg!}Xd zVc)9g!&vfD>|oGHx--O@LFg~7WXs*{kHBTh%GHsj1b5-57^)M^IY5+=nAu+axJWe| zFDhOBzDD^&(2Krsd?2gsfCxDs6YA#6sZ;SeUp?1Ec(+~j@hAffcN*!2L&`LeqYRbQ z5AUa-q>O<=`=cMp0H}lZ@$0V-IXS=Prg()esbDQZ3bHYcD9^E>OlB@P> z?!8%y%9zB&-dig4l~&--$;rkh_(rU><=U&V$yaOjjq@J0cCp1i-S6bEAaTSq{`+y; z7h#EauyCSdVgkks%9F{q^(p`F;--D+L{%CJc_p*ih<3daW;!5JlF9FB#m2>9z5L%( zrt^c)CgA}XpYglRLK5d&7=$XQ5?9HM>tXi$b$aU2*Wa)B_N^(fDCh|&zj?T^d=Yi~ z9FkF)iJ?a5N+J9qB5>`le!R9cjq_K>)bv9qv5 zj^#erG3l)+JbO(>YZ3YKv-brX+F)E4Hx=J08Q5Q#oWmkaYeg4 z3pOgPIlvg^+%@8dCS6Us%v2G!>bocF#eVuFly27cM**Hqr-G_ZH3Tn3b%j%YWd=8wAP15JT#z&00Wax|r_Y+^fI(HNPDCF??`%z+U4{_lk^hcSf`6h`K_gWa;_~t^Mw9&Tou^Bp3>r^pOQb ztpby4!rSv$l)h_OsZM3E^X3qr_sL#_G^2HkuG7Iv*PK&7SL4|Rl)&xaX-lh-^`7vF z?de*0TM8f+7n<)R7He|!zuEc0o~``wnVobKni?z?Wa_yX&2P`K5~uv;*OzQBcS(Hv8~Q`|i()EnWCiA5Z{# zpW8=18ippU5a!JFQR+yA|G%SCo36;Wn}c^B0;u8BB^G?)m=**mL_cT}QEPIGPfTJ8 z7QN42!miDzj34ep<0U)W%RlBM`wK4}eNR{y*{viK^!HEO33))g-Y2$NNVT<}qSku= zs_fW7b}S5T17CdbI5m01*-2a0#FY=wKmG2EJhn5jdw2tkLWR0?qyr#JTMsz8 z&2y?T?F21mDS#(+`Nhg{q5QxDc6*Y&YmcCs{ z8JBM-5cMctweG>9i~dK(t)jqDmQ5d5Cz@9J+oL#8nRb&Rtw>zFqW~cK9F%)}>zlcG zpPo%-_c@XNDW}Up^f#@3La-l05&x$ z`t)IbN}4P-xIV|iCCLJ|Ag5vT9HmfLo$P4Z%x|NKVAY*!bGu6l|IUEg`fQZCJn1_f zUlM@MwTpL8+VF_I$9vghkH(c2`Ig+a7Gh}eXAF0@TH{9oFF?h1fSoLVFy8{*5%cs& zEhI_T`Mfwn3|!0cW?1i}l-TMQ8}`LYSA?Rna<=?8rcW2hT0kMy6>!I}!T6qi7j7e4 z^GSXsH$C039z`c1raO9IM=q;}YO~+I_{*3H^yqgvTE*b5t5$&RQiqt>0PHFEw;+t_u4u!V@>G=gz%6_FIxv%amb~V(&J#F?oNrLger{ zuE_EW2ppi%uC8`0$|n3tiZr}Tz$R;gB6fc;&ou>vS5yLMFsMFYcSt{d@7U2F2cmhG z1|W_1f@Pg-Z|oO79g|$64&b-m`LNj_WM0!1epN#pY3*?KWz_*4ujUNM`g>`|F6AN5 z`J!Iq!VueL=f~RvP?2x)%s%m?;LH1k8jEG@TnG$;Z&8)p0-|@P4nl|1s;S|fl$ua# z7=(Pa%$Rhr#*ee$$ejM$@z&ua8**=CZZfZ}hrF&h$V_k$wcfF_6xN;7tjzVeILBIS zu^4rUR9Q2;IiKV-XiUT=q;j~q!oV`w$P)0wqHftU-~>fok3n|QGtNen;(?D%L%;6+ zCZbH_o?Zb!48X^>!N$VomQm_umI4>iU^%p52Z>VC&cMjvOMKSvU9BsGld_K4bGIt=%fD{s2rYHXuHEF0RrJ( z-}VBW=&Gr;1KA!oY+o1B^rg$BvHqo1@+Yjo&|Rs|SQ3*CGdP zKAg__Kc+Z|%gH%tCXu2>CR} zu)2L00K$t5cZR>nCTsIO-g*x@*`M!U zXaWjE`}B~;bfW|+R>u+KUca8AhFp@a>Q96FGwA{^t^@u`=2`Dd*JBZPEwwp1AT`@k z_3PJ)*eG|o9=2kRQ*gMTcU)PsJ$M9^4S?DNQR?-DmwQozx{-tT-9YJQJYroi)j64u zE)0%7!6P47Q3>#(I>nm4M{?|-%6I%8o8MsyVh6qgPHP&Qyj z;vJvr?v>Xaxi?G^;}OItp0t9Dfm;k#GrYi3WO^yLNm!zxAxFEr zXz1UyGgxLt3QH3|dj!-p>A$#bDb2)-#V1R(7040$DJt9WdGrM--K1jlDoVdYhBJZs z(QQp&pMb@Y5x4+)Te1Vvme{6k0k=;FCAs|vbpt#LKwZZ#E?zjMxmF2acEMRdwha8T ztdRUYB=M06#!cc4Kq#h_rW8IN+&#?~_*_HSZt{C-D3oH4X>HX!z0bmO(%xq>Q?Rr> zBTn`+uIYHkIgw(5M7UlE4iU}E*RO+{Cq~nb-4p+NUH0!;M})Rqi44#x(s;g8c<#{4Glki`UHr$xx2VI{K~ciWq4dtTZo4aoU#l83z-c9p(YUVwSZs;ND2-WaIQE3T@F&wiUw z;f^x6)iMC0nkdrQe>!g`XhKbP7C`#q)ZcEl#<+5N3m{+1%gdl!?HR)4)!Q+y&lou+ zx7Hx=apWNm2}3B{tYGp-YAR{#@iqboUd6SILL;I2vLzC1arl+fb4F!a8X9-*)6spX zEy~FW?g$|j(e+WzCWD1miS|S>t{x48*+oPWVq+h0QV{|R^n{I#-=q_7rWRrdp|70& z3WSF70u7*0kV4L`VpOgj8Wv{U{u$q8f3DtsK6qow&1oNh!NLzSJD=5LwmLj@h|CKj1{39LCNgoM##@TZXN ze8cy}t9^Gs3>{=3fo>n|s*hwzNl7ld6Onm&Pe~X=TLCX42DJA8iGkf8>UQkEgMXP7 z6%SzZLqM}m*@xnzSs5>;2~Roe_U^1wQjEZz>l{}TMIB9=YJf-=tqQ)XvS0hu zw%if&jE844KR1Pim-oIupj1*kH)sh62zq;ajSkn~340=}tY6~eDS+9kn47c81QX?7 zWUFaw_X0Qjb9lHiB!O}{AcLg5dcei*=5Rq%Tdo&an&lgeCO#Y)^auo z`DZS2b&k#iR^zdJR9)#7)oXTDxh%P`uV3#OogJ=IP*WR@SqMWF6%;-?&^_Vhjh>pC z^6C!?3elB0dMHLSK7aXg5CXmo7%$YSw3`(sAS8Uo$$9&hPtVW!t;e2r ze`EeO{psLy$G00lDfO}lg@{!YYWVbEl@{zPX*rz2`uuqW0B6g+G4`58+OL4MP|s01 za7p6&;oB9a5HV4rF98^4uMcLv=L)k(+WJ$-CNDq#z`>j=;dX7l0_`Be_H(^JzDj3{ z`kjfZSx5<+SFy6P6409T+SnGNe9XcX3^y}@$Fhnht4K>rZ;fX6%OP*OE$nN_X=oIH z{xN7VH#0MH+KD69`uaMsp#^mpC#3=0$`#zw0tOa;qY1OHI&GPn4J@m;!AcS#q@eW_ZTN#y}Y> zI56-o3G<7u)3$6+pN0bBi_d9I!NkPmXEhrT4}cT28$I~gRZ}`GDu3syrdkxI^C=BY z@LN4$;YL*3X!#yN7sr(OLLIe>HIpSN$@u90y_e*Faw?a8q-ODDh2GNMK$jp$QBFm+ JMA|6uKLDAAvPJ*^ diff --git a/docs/docs/assets/hold-tap/case1_2.svg b/docs/docs/assets/hold-tap/case1_2.svg new file mode 100644 index 00000000..697d7346 --- /dev/null +++ b/docs/docs/assets/hold-tap/case1_2.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/assets/hold-tap/case_hold_preferred.png b/docs/docs/assets/hold-tap/case_hold_preferred.png deleted file mode 100644 index 6d7fd43bb58681629387932f0053995d7d5d0c33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6380 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 diff --git a/docs/docs/assets/hold-tap/case_hold_preferred.svg b/docs/docs/assets/hold-tap/case_hold_preferred.svg new file mode 100644 index 00000000..70b27014 --- /dev/null +++ b/docs/docs/assets/hold-tap/case_hold_preferred.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/assets/hold-tap/comparison.png b/docs/docs/assets/hold-tap/comparison.png deleted file mode 100644 index 419b2040015ecf851e6605950e9e1a48472b8094..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158912 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

v=mX|&cA`aBl<}f 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

PS-t8+}tN=<%$fuLj)8^>@g5<-1abqat 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 diff --git a/docs/docs/assets/hold-tap/comparison.svg b/docs/docs/assets/hold-tap/comparison.svg new file mode 100644 index 00000000..eef79f39 --- /dev/null +++ b/docs/docs/assets/hold-tap/comparison.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/assets/tap-dance/timing_diagram.svg b/docs/docs/assets/tap-dance/timing_diagram.svg index ab02dcaf..3bf0b137 100644 --- a/docs/docs/assets/tap-dance/timing_diagram.svg +++ b/docs/docs/assets/tap-dance/timing_diagram.svg @@ -1,83 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/behaviors/hold-tap.md b/docs/docs/behaviors/hold-tap.md index d0255c70..528b9aec 100644 --- a/docs/docs/behaviors/hold-tap.md +++ b/docs/docs/behaviors/hold-tap.md @@ -3,6 +3,9 @@ title: Hold-Tap Behavior sidebar_label: Hold-Tap --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## Summary Hold-tap is the basis for other behaviors such as layer-tap and mod-tap. @@ -13,11 +16,11 @@ Simply put, the hold-tap key will output the 'hold' behavior if it's held for a The graph below shows how the hold-tap decides between a 'tap' and a 'hold'. -![Simple behavior](../assets/hold-tap/case1_2.png) +![Simple behavior](../assets/hold-tap/case1_2.svg) By default, the hold-tap is configured to also select the 'hold' functionality if another key is tapped while it's active: -![Hold preferred behavior](../assets/hold-tap/case1_2.png) +![Hold preferred behavior](../assets/hold-tap/case_hold_preferred.svg) We call this the 'hold-preferred' flavor of hold-taps. While this flavor may work very well for a ctrl/escape key, it's not very well suited for home-row mods or layer-taps. That's why there are two more flavors to choose from: 'tap-preferred' and 'balanced'. @@ -30,11 +33,11 @@ We call this the 'hold-preferred' flavor of hold-taps. While this flavor may wor When the hold-tap key is released and the hold behavior has not been triggered, the tap behavior will trigger. -![Hold-tap comparison](../assets/hold-tap/comparison.png) +![Hold-tap comparison](../assets/hold-tap/comparison.svg) ### Basic usage -For basic usage, please see [mod-tap](mod-tap.md) and [layer-tap](layers.md) pages. +For basic usage, please see the [mod-tap](mod-tap.md) and [layer-tap](layers.md#layer-tap) pages. ### Advanced Configuration @@ -50,9 +53,9 @@ In QMK, unlike ZMK, this functionality is enabled by default, and you turn it of #### `global-quick-tap` -If global quick tap is enabled, then `quick-tap-ms` will apply not only when the given hold-tap is tapped but for any key tap before it. This effectively disables the hold tap when typing quickly, which can be quite useful for home row mods. It can also have the effect of removing the input delay when typing quickly. +If `global-quick-tap` is enabled, then `quick-tap-ms` will apply not only when the given hold-tap is tapped, but for any key tapped before it. This effectively disables the hold-tap when typing quickly, which can be quite useful for homerow mods. It can also have the effect of removing the input delay when typing quickly. -For example, the following hold-tap configuration enables global quick tap with a 125 millisecond term. +For example, the following hold-tap configuration enables `global-quick-tap` with a 125 millisecond `quick-tap-ms` term. ``` gqt: global-quick-tap { @@ -69,11 +72,11 @@ gqt: global-quick-tap { If you press `&kp A` and then `&gqt LEFT_SHIFT B` **within** 125 ms, then `ab` will be output. Importantly, `b` will be output immediately since it was within the `quick-tap-ms`. This quick-tap behavior will work for any key press, whether it is within a behavior like hold-tap, or a simple `&kp`. This means the `&gqt LEFT_SHIFT B` binding will only have its underlying hold-tap behavior if it is pressed 125 ms **after** a key press. -Note that the higher the `quick-tap-ms` the harder it will be to use the hold behavior, making this less applicable for something like capitalizing letter while typing normally. However, if the hold behavior isn't used during fast typing, then it can be an effective way to mitigate misfires. +Note that the greater the value of `quick-tap-ms` is, the harder it will be to invoke the hold behavior, making this feature less applicable for use-cases like capitalizing letters while typing normally. However, if the hold behavior isn't used during fast typing, then it can be an effective way to mitigate misfires. #### `retro-tap` -If retro tap is enabled, the tap behavior is triggered when releasing the hold-tap key if no other key was pressed in the meantime. +If `retro-tap` is enabled, the tap behavior is triggered when releasing the hold-tap key if no other key was pressed in the meantime. For example, if you press `&mt LEFT_SHIFT A` and then release it without pressing another key, it will output `a`. @@ -85,16 +88,20 @@ For example, if you press `&mt LEFT_SHIFT A` and then release it without pressin #### Positional hold-tap and `hold-trigger-key-positions` -- Including `hold-trigger-key-positions` in your hold-tap definition turns on the positional hold-tap feature. -- With positional hold-tap enabled, if you press any key **NOT** listed in `hold-trigger-key-positions` before `tapping-term-ms` expires, it will produce a tap. -- In all other situations, positional hold-tap will not modify the behavior of your hold-tap. -- Positional hold-tap is useful with home-row modifiers. If you have a home-row modifier key in the left hand for example, by including only keys positions from the right hand in `hold-trigger-key-positions`, you will only get hold behaviors during cross-hand key combinations. -- Note that `hold-trigger-key-positions` is an array of key position indexes. Key positions are numbered according to your keymap, starting with 0. So if the first key in your keymap is Q, this key is in position 0. The next key (probably W) will be in position 1, et cetera. -- See the following example, which uses a hold-tap behavior definition, configured with the `hold-preferred` flavor, and with positional hold-tap enabled: +Including `hold-trigger-key-positions` in your hold-tap definition turns on the positional hold-tap feature. With positional hold-tap enabled, if you press any key **NOT** listed in `hold-trigger-key-positions` before `tapping-term-ms` expires, it will produce a tap. + +In all other situations, positional hold-tap will not modify the behavior of your hold-tap. Positional hold-tap is useful when used with home-row modifiers: for example, if you have a home-row modifier key in the left hand, by including only key positions from the right hand in `hold-trigger-key-positions`, you will only get hold behaviors during cross-hand key combinations. + +:::info +Note that `hold-trigger-key-positions` is an array of key position indexes. Key positions are numbered sequentially according to your keymap, starting with 0. So if the first key in your keymap is Q, this key is in position 0. The next key (probably W) will be in position 1, et cetera. +::: + +See the following example, which uses a hold-tap behavior definition, configured with the `hold-preferred` flavor, and with positional hold-tap enabled: ``` #include #include + / { behaviors { pht: positional_hold_tap { @@ -125,15 +132,26 @@ For example, if you press `&mt LEFT_SHIFT A` and then release it without pressin - The sequence `(pht_down, W_down, W_up, pht_up)` produces `W`. The normal hold behavior (LEFT_SHIFT) **is NOT** modified into a tap behavior (Q) by positional hold-tap because the first key pressed after the hold-tap key is the `W key`, which is in position 1, which **IS** included in `hold-trigger-key-positions`. - If the `LEFT_SHIFT / Q key` is held by itself for longer than `tapping-term-ms`, a hold behavior is produced. This is because positional hold-tap only modifies the behavior of a hold-tap if another key is pressed before the `tapping-term-ms` period expires. -#### Home row mods +### Example Use-Cases + + + + The following are suggested hold-tap configurations that work well with home row mods: ##### Option 1: cross-hand only modifiers, using `tap-unless-interrupted` and positional hold-tap (`hold-trigger-key-positions`) -``` +```dtsi title="Homerow Mods: Cross-hand Example" #include #include + / { behaviors { lh_pht: left_hand_positional_hold_tap { @@ -162,7 +180,7 @@ The following are suggested hold-tap configurations that work well with home row ##### Option 2: `tap-preferred` -``` +```dtsi title="Homerow Mods: Tap-Preferred Example" #include #include @@ -188,12 +206,11 @@ The following are suggested hold-tap configurations that work well with home row }; }; }; - ``` ##### Option 3: `balanced` -``` +```dtsi title="Homerow Mods: Balanced Example" #include #include @@ -219,9 +236,84 @@ The following are suggested hold-tap configurations that work well with home row }; }; }; - ``` -#### Comparison to QMK + + + + +A popular method of implementing Autoshift in ZMK involves a C-preprocessor macro, commonly defined as `AS(keycode)`. This macro applies the `LSHIFT` modifier to the specified `keycode` when `AS(keycode)` is held, and simply performs a [keypress](key-press.md), `&kp keycode`, when the `AS(keycode)` binding is tapped. This simplifies the use of Autoshift in a keymap, as the complete hold-tap bindings for each desired Autoshift key, as in `&as LS() &as LS() ... &as LS() `, can be quite cumbersome to use when applied to a large portion of the keymap. + +```dtsi title="Hold-Tap Example: Autoshift" +#include +#include + +#define AS(keycode) &as LS(keycode) keycode // Autoshift Macro + +/ { + behaviors { + as: auto_shift { + compatible = "zmk,behavior-hold-tap"; + label = "AUTO_SHIFT"; + #binding-cells = <2>; + tapping_term_ms = <135>; + quick_tap_ms = <0>; + flavor = "tap-preferred"; + bindings = <&kp>, <&kp>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + AS(Q) AS(W) AS(E) AS(R) AS(T) AS(Y) // Autoshift applied for QWERTY keys + >; + }; + }; +}; +``` + + + + + +This hold-tap example implements a [toggle-layer](layers.md/#toggle-layer) when the keybind is tapped and a [momentary-layer](layers.md/#momentary-layer) when it is held. Similarly to the Autoshift and Sticky Hold use-cases, a `TOG_MO(layer)` macro is defined such that the `&tog` and `&mo` behaviors can target a single layer. + +```dtsi title="Hold-Tap Example: Toggle layer on Tap, Momentary layer on Hold" +#include +#include + +#define TOG_MO(layer) &tog_mo layer layer // Macro to apply toggle-layer-on-tap/momentary-layer-on-hold to a specific layer + +/ { + behaviors { + tog_mo: behavior_mo_tog { + compatible = "zmk,behavior-hold-tap"; + label = "mo_tog"; + #binding-cells = <2>; + flavor = "hold-preferred"; + tapping-term-ms = <200>; + bindings = <&tog>, <&mo>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &tog_mo 2 1 // &mo 2 on hold, &tog 1 on tap + TOG_MO(3) // &mo 3 on hold, &tog 3 on tap + >; + }; + }; +}; +``` + + + + + +### Comparison to QMK The hold-preferred flavor works similar to the `HOLD_ON_OTHER_KEY_PRESS` setting in QMK. The 'balanced' flavor is similar to the `PERMISSIVE_HOLD` setting, and the `tap-preferred` flavor is similar to `IGNORE_MOD_TAP_INTERRUPT`. diff --git a/docs/docs/behaviors/layers.md b/docs/docs/behaviors/layers.md index 694b516a..0818aaae 100644 --- a/docs/docs/behaviors/layers.md +++ b/docs/docs/behaviors/layers.md @@ -43,7 +43,7 @@ Example: ## Layer-tap -The "layer-tap" behavior enables a layer when a key is held, and output another key when the key is only tapped for a short time. For more information on the inner workings of layer-tap, see [hold-tap](hold-tap.md). +The "layer-tap" behavior enables a layer when a key is held, and outputs a [keypress](key-press.md) when the key is only tapped for a short time. ### Behavior Binding @@ -57,6 +57,15 @@ Example: < LOWER SPACE ``` +:::info +Functionally, the layer-tap is a [hold-tap](hold-tap.md) of the ["tap-preferred" flavor](hold-tap.md/#flavors) and a [`tapping-term-ms`](hold-tap.md/#tapping-term-ms) of 200 that takes in a [`momentary layer`](#momentary-layer) and a [keypress](key-press.md) as its "hold" and "tap" parameters, respectively. + +For users who want to send a different [keycode](../codes/index.mdx) depending on if the same key is held or tapped, see [Mod-Tap](mod-tap.md). + +Similarly, for users looking to create a keybind like the layer-tap that depending on how long the key is held, invokes behaviors like [sticky keys](sticky-key.md) or [key toggles](key-toggle.md), see [Hold-Tap](hold-tap.md). + +::: + ## To Layer The "to layer" behavior enables a layer and disables _all_ other layers _except_ the default layer. diff --git a/docs/docs/behaviors/mod-tap.md b/docs/docs/behaviors/mod-tap.md index cc2b1ce0..5f4fa506 100644 --- a/docs/docs/behaviors/mod-tap.md +++ b/docs/docs/behaviors/mod-tap.md @@ -44,6 +44,11 @@ You can configure a different tapping term in your keymap: }; ``` -### Additional information +:::info +Under the hood, the mod-tap is simply a [hold-tap](hold-tap.md) of the ["hold-preferred" flavor](hold-tap.md/#flavors) with a [`tapping-term-ms`](hold-tap.md/#tapping-term-ms) of 200 that takes in two [keypresses](key-press.md) as its "hold" and "tap" parameters. This means that the mod-tap can be used to invoke **any** [keycode](../codes/index.mdx), and is not limited to only activating [modifier keys](../codes/modifiers.mdx) when it is held. -The mod-tap is a [hold-tap](hold-tap.md) under the hood with the "hold-preferred" flavor and tapping-term-ms 200. +For users who want to momentarily access a specific [layer](../features/keymaps#layers) while a key is held and send a keycode when the same key is tapped, see [Layer-Tap](layers.md/#layer-tap). + +Similarly, for users looking to create a keybind like the mod-tap that invokes behaviors _other_ than [keypresses](key-press.md), like [sticky keys](sticky-key.md) or [key toggles](key-toggle.md), see [Hold-Tap](hold-tap.md). + +::: diff --git a/docs/docs/behaviors/tap-dance.md b/docs/docs/behaviors/tap-dance.md index af49ca3c..65d5e765 100644 --- a/docs/docs/behaviors/tap-dance.md +++ b/docs/docs/behaviors/tap-dance.md @@ -3,39 +3,41 @@ title: Tap-Dance Behavior sidebar_label: Tap-Dance --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + ## Summary -A tap-dance key invokes a different behavior (e.g. `kp`) corresponding -to how many times it is pressed. For example, you could configure a -tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. -The expandability of the number of [`bindings`](#bindings) attached to a -particular tap-dance is a great way to add more functionality to a single key, -especially for keyboards with a limited number of keys. -Tap-dances are completely custom, so for every unique tap-dance key, -a new tap-dance must be defined in your keymap's `behaviors`. +A tap-dance key invokes a different behavior (e.g. `kp`) corresponding to how many times it is pressed. For example, you could configure a tap-dance key that acts as `LSHIFT` if tapped once, or Caps _Lock_ if tapped twice. The expandability of the number of [`bindings`](#bindings) attached to a particular tap-dance is a great way to add more functionality to a single key, especially for keyboards with a limited number of keys. Tap-dances are completely custom, so for every unique tap-dance key,a new tap-dance must be defined in your keymap's `behaviors`. -Tap-dances are designed to resolve immediately when interrupted by another keypress. -Meaning, when a keybind is pressed other than any active tap-dances, -the tap-dance will activate according to the current value of its -counter before the interrupting keybind is registered. +Tap-dances are designed to resolve immediately when interrupted by another keypress. Meaning, when a keybind is pressed other than any active tap-dances, the tap-dance will activate according to the current value of its counter before the interrupting keybind is registered. ### Configuration #### `tapping-term-ms` -Defines the maximum elapsed time after the last tap-dance keybind press -before a binding is selected from [`bindings`](#bindings). -Default value is `200`ms. +Defines the maximum elapsed time after the last tap-dance keybind press before a binding is selected from [`bindings`](#bindings). Default value is `200`ms. #### `bindings` -An array of one or more keybinds. This list can include [any ZMK keycode](../codes/) and bindings for ZMK behaviors. +An array of one or more keybinds. This list can include [any ZMK keycode](../codes/) and any listed ZMK behavior, like [hold-taps](hold-tap.md), or [sticky keys](sticky-key.md). The index of a keybind in the `bindings` array corresponds to the number of times the tap-dance binding is pressed. For example, in the [basic tap-dance counter](#basic-example-counter) shown below, `&kp N2` is the second binding in the array of `bindings`: we then see an output of `2` when the `td0` binding is pressed twice. -#### Example Usage +The number of bindings in this array also determines the tap-dance's maximum number of keypresses. When a tap-dance reaches its maximum number of keypresses, it will immediately invoke the last behavior in its list of `bindings`, rather than waiting for [`tapping-term-ms`](#tapping-term-ms) to expire before the output is displayed. -This example configures a tap-dance named `td0` that outputs the number of times it is pressed from 1-3. +### Example Usage -``` + + + + +This example configures a tap-dance named `td0` that outputs the number of times its binding is pressed from 1-3. + +```title="Basic Tap-Dance Example: Counter" #include #include @@ -67,10 +69,41 @@ The following image describes the behavior of this particular tap-dance. ![Timing Diagram](../assets/tap-dance/timing_diagram.svg) :::note -Alphanumeric [`key press`](key-press.md) bindings, like those used for `td0`, -will release as soon as an interrupting key press occurs. -For instance, if a modifier key like `LSHIFT` were to replace the `N1` -binding in the last example above, it would remain pressed until `td0`'s -binding is released and the output would instead be `J`. Any following -alphanumeric key presses would be capitalized as long as `td0` is held down. +Alphanumeric [`key press`](key-press.md) bindings, like those used for `td0`, will release as soon as an interrupting key press occurs. For instance, if a modifier key like `LSHIFT` were to replace the `N1` binding in the last example above, it would remain pressed until `td0`'s binding is released and the output would instead be `J`. Any following alphanumeric key presses would be capitalized as long as `td0` is held down. ::: + + + + + +This example configures a mod-tap inside a tap-dance named `td_mt` that outputs `CAPSLOCK` on a single tap, `LSHIFT` on a single press and hold, and `LCTRL` when the tap-dance is pressed twice. + +```title="Advanced Tap-Dance Example: Nested Mod-Tap" +#include +#include + +/ { + behaviors { + td_mt: tap_dance_mod_tap { + compatible = "zmk,behavior-tap-dance"; + label = "TAP_DANCE_MOD_TAP"; + #binding-cells = <0>; + tapping-term-ms = <200>; + bindings = <&mt LSHIFT CAPSLOCK>, <&kp LCTRL>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &td_mt + >; + }; + }; +}; +``` + + + From fc511e40cc1a274473a753c959f8d7e5fcc317d0 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Wed, 3 Aug 2022 20:09:50 -0400 Subject: [PATCH 45/50] fix(behaviors): Fixing erroneous combo triggering, hold-taps sticking * This is a very simple fix to a rather complicated issue. Essentially, hold-taps will "release" (raise) their captured keys before actually telling the event manager they have captured a key. This means the event manager ends up assigning the `last_listener_index` to the hold-tap subscription rather than the combo. So when the combo calls `ZMK_EVENT_RELEASE` it raises after the hold-tap instead of after the combo as the combo code expects. * The corresponding test (which fails without this change) has also been added. * An event can be captured and released in the same event handler, before the last_listener_index would have been updated. This causes some handlers to be triggered multiple times. * The solution is to update the last_listener_index before calling the next event handler, so capturing and releasing within an event handler is harmless. * Also see discussion at https://github.com/zmkfirmware/zmk/pull/1401 * If our handler dedides our undedided hold-tap, return early before continuing. * Fix incorrect pointer logic, resulting in combo candidate filtering leaving incorrect timeout details. Co-authored-by: Andrew Rae Co-authored-by: okke --- app/src/behaviors/behavior_hold_tap.c | 4 ++ app/src/combo.c | 2 +- app/src/event_manager.c | 2 +- .../combos-and-holdtaps-3/events.patterns | 1 + .../keycode_events.snapshot | 5 ++ .../native_posix_64.keymap | 40 ++++++++++++++++ .../combos-and-holdtaps-4/events.patterns | 1 + .../keycode_events.snapshot | 4 ++ .../native_posix_64.keymap | 46 +++++++++++++++++++ 9 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 app/tests/combo/combos-and-holdtaps-3/events.patterns create mode 100644 app/tests/combo/combos-and-holdtaps-3/keycode_events.snapshot create mode 100644 app/tests/combo/combos-and-holdtaps-3/native_posix_64.keymap create mode 100644 app/tests/combo/combos-and-holdtaps-4/events.patterns create mode 100644 app/tests/combo/combos-and-holdtaps-4/keycode_events.snapshot create mode 100644 app/tests/combo/combos-and-holdtaps-4/native_posix_64.keymap diff --git a/app/src/behaviors/behavior_hold_tap.c b/app/src/behaviors/behavior_hold_tap.c index 413806b4..f09006ed 100644 --- a/app/src/behaviors/behavior_hold_tap.c +++ b/app/src/behaviors/behavior_hold_tap.c @@ -612,6 +612,10 @@ static int position_state_changed_listener(const zmk_event_t *eh) { decide_hold_tap(undecided_hold_tap, HT_TIMER_EVENT); } + if (undecided_hold_tap == NULL) { + return ZMK_EV_EVENT_BUBBLE; + } + if (!ev->state && find_captured_keydown_event(ev->position) == NULL) { // no keydown event has been captured, let it bubble. // we'll catch modifiers later in modifier_state_changed_listener diff --git a/app/src/combo.c b/app/src/combo.c index 13ed1709..e434ae17 100644 --- a/app/src/combo.c +++ b/app/src/combo.c @@ -211,7 +211,7 @@ static int filter_timed_out_candidates(int64_t timestamp) { if (candidate->timeout_at > timestamp) { // reorder candidates so they're contiguous candidates[num_candidates].combo = candidate->combo; - candidates[num_candidates].timeout_at = candidates->timeout_at; + candidates[num_candidates].timeout_at = candidate->timeout_at; num_candidates++; } else { candidate->combo = NULL; diff --git a/app/src/event_manager.c b/app/src/event_manager.c index eef5d839..471432a8 100644 --- a/app/src/event_manager.c +++ b/app/src/event_manager.c @@ -25,6 +25,7 @@ int zmk_event_manager_handle_from(zmk_event_t *event, uint8_t start_index) { if (ev_sub->event_type != event->event) { continue; } + event->last_listener_index = i; ret = ev_sub->listener->callback(event); switch (ret) { case ZMK_EV_EVENT_BUBBLE: @@ -35,7 +36,6 @@ int zmk_event_manager_handle_from(zmk_event_t *event, uint8_t start_index) { goto release; case ZMK_EV_EVENT_CAPTURED: LOG_DBG("Listener captured the event"); - event->last_listener_index = i; // Listeners are expected to free events they capture return 0; default: diff --git a/app/tests/combo/combos-and-holdtaps-3/events.patterns b/app/tests/combo/combos-and-holdtaps-3/events.patterns new file mode 100644 index 00000000..833100f6 --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-3/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/combos-and-holdtaps-3/keycode_events.snapshot b/app/tests/combo/combos-and-holdtaps-3/keycode_events.snapshot new file mode 100644 index 00000000..843832dd --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-3/keycode_events.snapshot @@ -0,0 +1,5 @@ +pressed: usage_page 0x07 keycode 0xE5 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/combo/combos-and-holdtaps-3/native_posix_64.keymap b/app/tests/combo/combos-and-holdtaps-3/native_posix_64.keymap new file mode 100644 index 00000000..d4053793 --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-3/native_posix_64.keymap @@ -0,0 +1,40 @@ +#include +#include +#include + +&mt { + flavor = "hold-preferred"; +}; + +/ { + combos { + compatible = "zmk,combos"; + combo_one { + timeout-ms = <40>; + key-positions = <0 1>; + bindings = <&kp X>; + }; + }; + + keymap { + compatible = "zmk,keymap"; + label = "Default keymap"; + + default_layer { + bindings = < + &kp A &kp B + &mt RSHFT RET &kp C + >; + }; + }; +}; + +&kscan { + events = < + ZMK_MOCK_PRESS(1,0,10) + ZMK_MOCK_PRESS(0,1,10) + ZMK_MOCK_PRESS(1,1,10) + ZMK_MOCK_RELEASE(0,1,50) + ZMK_MOCK_RELEASE(1,1,50) + >; +}; diff --git a/app/tests/combo/combos-and-holdtaps-4/events.patterns b/app/tests/combo/combos-and-holdtaps-4/events.patterns new file mode 100644 index 00000000..833100f6 --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-4/events.patterns @@ -0,0 +1 @@ +s/.*hid_listener_keycode_//p \ No newline at end of file diff --git a/app/tests/combo/combos-and-holdtaps-4/keycode_events.snapshot b/app/tests/combo/combos-and-holdtaps-4/keycode_events.snapshot new file mode 100644 index 00000000..f84bc761 --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-4/keycode_events.snapshot @@ -0,0 +1,4 @@ +pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 +pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00 +released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00 diff --git a/app/tests/combo/combos-and-holdtaps-4/native_posix_64.keymap b/app/tests/combo/combos-and-holdtaps-4/native_posix_64.keymap new file mode 100644 index 00000000..ba6cecc6 --- /dev/null +++ b/app/tests/combo/combos-and-holdtaps-4/native_posix_64.keymap @@ -0,0 +1,46 @@ +#include +#include +#include + + +#define ZMK_COMBO(name, combo_bindings, keypos, combo_term) \ +/ { \ + combos { \ + compatible = "zmk,combos"; \ + combo_ ## name { \ + key-positions = ; \ + bindings = ; \ + timeout-ms = ; \ + }; \ + }; \ +}; + +ZMK_COMBO(qmark, &kp QMARK, 0 3, 30) +ZMK_COMBO(dllr, &kp DLLR, 1 3, 50) +ZMK_COMBO(tilde, &kp TILDE, 3 4, 50) + +/ { + keymap { + compatible = "zmk,keymap"; + label = "Default keymap"; + + default_layer { + bindings = < + &none &none + &kp A &mt LSHFT T + &none + >; + }; + }; +}; + +&kscan { + rows = <3>; + columns = <2>; + events = < + ZMK_MOCK_PRESS(1,1,500) + ZMK_MOCK_PRESS(1,0,100) + ZMK_MOCK_RELEASE(1,0,500) + ZMK_MOCK_RELEASE(1,1,0) + >; +}; \ No newline at end of file From f238001904a220a08b53606fff034e747b0dbd77 Mon Sep 17 00:00:00 2001 From: Anton <14187674+antosha417@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:37:06 +0300 Subject: [PATCH 46/50] doc(keymaps): fix typo (#1425) --- docs/docs/features/keymaps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/features/keymaps.md b/docs/docs/features/keymaps.md index 59577d8f..6d4e5f2c 100644 --- a/docs/docs/features/keymaps.md +++ b/docs/docs/features/keymaps.md @@ -58,7 +58,7 @@ in the stack _also_ get the event. Binding a behavior at a certain key position may include up to two extra parameters that are used to alter the behavior when that specific key position is activated/deactivated. For example, when binding -the "key press" (`kp`) behavior at a certain key position, you must specific _which_ keycode should +the "key press" (`kp`) behavior at a certain key position, you must specify _which_ keycode should be used for that key position. ``` From b21ddcf79ab11a6631e6e992aa4ec291ae09db2f Mon Sep 17 00:00:00 2001 From: "byran.tech" <61983584+Hello9999901@users.noreply.github.com> Date: Fri, 19 Aug 2022 10:29:12 -0400 Subject: [PATCH 47/50] fix(docs): added concrete number for Bluetooth advertising name length * Update new-shield.md * Update config docs. --- docs/docs/config/system.md | 2 +- docs/docs/development/new-shield.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 4784339c..a5347b96 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -15,7 +15,7 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ | Config | Type | Description | Default | | ------------------------------------ | ------ | ----------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard | | +| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard (max 16 characters) | | | `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | | `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | | `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | diff --git a/docs/docs/development/new-shield.md b/docs/docs/development/new-shield.md index 03a27289..565f424c 100644 --- a/docs/docs/development/new-shield.md +++ b/docs/docs/development/new-shield.md @@ -77,7 +77,7 @@ which controls the display name of the device over USB and BLE. The updated new default values should always be wrapped inside a conditional on the shield config name defined in the `Kconfig.shield` file. Here's the simplest example file. :::warning -Do not make the keyboard name too long, otherwise the bluetooth advertising might fail and you will not be able to find your keyboard from your laptop / tablet. +The keyboard name must be less than or equal to 16 characters in length, otherwise the bluetooth advertising might fail and you will not be able to find your keyboard from your device. ::: ``` From 391f80f06952c288e2f66c23eaa742644fd927e1 Mon Sep 17 00:00:00 2001 From: Shreyas <69219163+shrekale@users.noreply.github.com> Date: Mon, 22 Aug 2022 00:27:47 -0400 Subject: [PATCH 48/50] feat(hid): Add C_AC_DESKTOP_SHOW_ALL_APPLICATIONS * support for C_AC_DESKTOP_SHOW_ALL_APPLICATIONS Co-authored-by: Shreyas Kale --- app/include/dt-bindings/zmk/keys.h | 4 ++++ docs/src/data/groups.js | 1 + docs/src/data/hid.js | 25 +++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/include/dt-bindings/zmk/keys.h b/app/include/dt-bindings/zmk/keys.h index 32e78795..d144ebb3 100644 --- a/app/include/dt-bindings/zmk/keys.h +++ b/app/include/dt-bindings/zmk/keys.h @@ -1404,6 +1404,10 @@ #define C_AC_DESKTOP_SHOW_ALL_WINDOWS \ (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_AC_DESKTOP_SHOW_ALL_WINDOWS)) +/* Consumer AC Desktop Show All Applications */ +#define C_AC_DESKTOP_SHOW_ALL_APPLICATIONS \ + (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_AC_DESKTOP_SHOW_ALL_APPLICATIONS)) + /* Consumer Keyboard Input Assist Previous */ #define C_KEYBOARD_INPUT_ASSIST_PREVIOUS \ (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_PREVIOUS)) diff --git a/docs/src/data/groups.js b/docs/src/data/groups.js index f7ced161..0eb15d27 100644 --- a/docs/src/data/groups.js +++ b/docs/src/data/groups.js @@ -47,6 +47,7 @@ export default { "C_AC_DEL", "C_AC_VIEW_TOGGLE", "C_AC_DESKTOP_SHOW_ALL_WINDOWS", + "C_AC_DESKTOP_SHOW_ALL_APPLICATIONS", "C_VOICE_COMMAND", ], applications: [ diff --git a/docs/src/data/hid.js b/docs/src/data/hid.js index f2bde565..ac90ea80 100644 --- a/docs/src/data/hid.js +++ b/docs/src/data/hid.js @@ -7251,7 +7251,7 @@ export default [ windows: null, linux: true, android: true, - macos: null, + macos: true, ios: null, }, footnotes: {}, @@ -7713,7 +7713,28 @@ export default [ windows: null, linux: true, android: null, - macos: null, + macos: true, + ios: null, + }, + footnotes: {}, + }, + { + names: ["C_AC_DESKTOP_SHOW_ALL_APPLICATIONS"], + description: "Desktop Show All Applications", + context: "Consumer AC", + clarify: true, + usages: [ + { + application: consumerApplication, + item: usage(consumerPage, 0x2a2), + }, + ], + documentation: "https://usb.org/sites/default/files/hut1_2.pdf#page=153", + os: { + windows: null, + linux: true, + android: null, + macos: true, ios: null, }, footnotes: {}, From ce7a0e2b6c72ac19008d9974be8b14c0455b9f0f Mon Sep 17 00:00:00 2001 From: Elliot Pahl Date: Tue, 6 Sep 2022 08:07:02 +1000 Subject: [PATCH 49/50] feat(shields): Add Eternal Keypad * Add Eternal Keypad Co-authored-by: Duccio --- .../shields/eternal_keypad/Kconfig.defconfig | 9 +++ .../shields/eternal_keypad/Kconfig.shield | 8 +++ app/boards/shields/eternal_keypad/README.md | 10 ++++ .../boards/nice_nano_v2.overlay | 38 +++++++++++++ .../eternal_keypad/eternal_keypad.conf | 9 +++ .../eternal_keypad/eternal_keypad.dtsi | 53 ++++++++++++++++++ .../eternal_keypad/eternal_keypad.keymap | 56 +++++++++++++++++++ .../eternal_keypad/eternal_keypad.overlay | 7 +++ .../eternal_keypad/eternal_keypad.zmk.yml | 9 +++ .../eternal_keypad_lefty.keymap | 56 +++++++++++++++++++ .../eternal_keypad_lefty.overlay | 7 +++ .../eternal_keypad_lefty.zmk.yml | 9 +++ 12 files changed, 271 insertions(+) create mode 100644 app/boards/shields/eternal_keypad/Kconfig.defconfig create mode 100644 app/boards/shields/eternal_keypad/Kconfig.shield create mode 100644 app/boards/shields/eternal_keypad/README.md create mode 100644 app/boards/shields/eternal_keypad/boards/nice_nano_v2.overlay create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad.conf create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad.dtsi create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad.keymap create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad.overlay create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad.zmk.yml create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad_lefty.keymap create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad_lefty.overlay create mode 100644 app/boards/shields/eternal_keypad/eternal_keypad_lefty.zmk.yml diff --git a/app/boards/shields/eternal_keypad/Kconfig.defconfig b/app/boards/shields/eternal_keypad/Kconfig.defconfig new file mode 100644 index 00000000..4d4195ef --- /dev/null +++ b/app/boards/shields/eternal_keypad/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if SHIELD_ETERNAL_KEYPAD || SHIELD_ETERNAL_KEYPAD_LEFTY + +config ZMK_KEYBOARD_NAME + default "Eternal Keypad" + +endif diff --git a/app/boards/shields/eternal_keypad/Kconfig.shield b/app/boards/shields/eternal_keypad/Kconfig.shield new file mode 100644 index 00000000..4a59379e --- /dev/null +++ b/app/boards/shields/eternal_keypad/Kconfig.shield @@ -0,0 +1,8 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config SHIELD_ETERNAL_KEYPAD + def_bool $(shields_list_contains,eternal_keypad) + +config SHIELD_ETERNAL_KEYPAD_LEFTY + def_bool $(shields_list_contains,eternal_keypad_lefty) diff --git a/app/boards/shields/eternal_keypad/README.md b/app/boards/shields/eternal_keypad/README.md new file mode 100644 index 00000000..4b217c5f --- /dev/null +++ b/app/boards/shields/eternal_keypad/README.md @@ -0,0 +1,10 @@ +# Eternal Keypad + +Eternal Keypad is an open-source input device for gaming that can be assembled for both right and left hand mouse users. + +Firmware is described in the [Eternal Keypad](https://github.com/duckyb/eternal-keypad) repository [README](https://github.com/duckyb/eternal-keypad#firmware). + +Two keymaps are included: + +- eternal_keypad (default) +- eternal_keypad_lefty (for left handed users) diff --git a/app/boards/shields/eternal_keypad/boards/nice_nano_v2.overlay b/app/boards/shields/eternal_keypad/boards/nice_nano_v2.overlay new file mode 100644 index 00000000..61950d18 --- /dev/null +++ b/app/boards/shields/eternal_keypad/boards/nice_nano_v2.overlay @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +&spi1 { + compatible = "nordic,nrf-spim"; + /* 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 = <8>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + color-mapping = ; + }; +}; + +/ { + chosen { + zmk,underglow = &led_strip; + }; +}; diff --git a/app/boards/shields/eternal_keypad/eternal_keypad.conf b/app/boards/shields/eternal_keypad/eternal_keypad.conf new file mode 100644 index 00000000..65fa2955 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad.conf @@ -0,0 +1,9 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +# Uncomment to turn on logging, and set ZMK logging to debug output +# CONFIG_ZMK_USB_LOGGING=y + +# Uncomment the following lines to enable RGB underglow +# CONFIG_ZMK_RGB_UNDERGLOW=y +# CONFIG_WS2812_STRIP=y diff --git a/app/boards/shields/eternal_keypad/eternal_keypad.dtsi b/app/boards/shields/eternal_keypad/eternal_keypad.dtsi new file mode 100644 index 00000000..6319d9e0 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad.dtsi @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + label = "KSCAN"; + + diode-direction = "col2row"; + + row-gpios + = <&pro_micro 9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + + col-gpios + = <&pro_micro 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 16 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 15 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 18 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 19 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 20 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + , <&pro_micro 21 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> + ; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <8>; + rows = <5>; + map = < + RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,5) RC(0,6) RC(0,7) + RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,6) RC(1,7) + 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(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,6) RC(3,7) + RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,5) RC(4,7) + >; + }; +}; diff --git a/app/boards/shields/eternal_keypad/eternal_keypad.keymap b/app/boards/shields/eternal_keypad/eternal_keypad.keymap new file mode 100644 index 00000000..0f3eda7b --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad.keymap @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +#define BASE 0 +#define ARROW 1 +#define FUNC 2 + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp ESC &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp N7 + &kp F13 &kp ENTER &kp A &kp S &kp D &kp F &kp G &kp N8 + &kp F14 &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N9 + &kp F15 &kp LCTRL &sl FUNC &kp LALT &kp SPACE < ARROW N0 + >; + }; + + arrow_layer { + bindings = < + &bt BT_SEL 0 &bt BT_SEL 1 &trans &trans &trans &out OUT_USB &out OUT_BLE + &trans &trans &kp UP &trans &rgb_ug RGB_TOG &rgb_ug RGB_HUI &rgb_ug RGB_HUD + &bt BT_CLR &trans &kp LEFT &kp DOWN &kp RIGHT &trans &rgb_ug RGB_BRI &rgb_ug RGB_BRD + &reset &trans &trans &trans &trans &trans &rgb_ug RGB_EFF &rgb_ug RGB_EFR + &bootloader &trans &trans &trans &trans &trans + >; + }; + + function_layer { + bindings = < + &trans &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 + &trans &kp P &kp O &kp I &kp U &kp Y &kp F7 + &bt BT_CLR &kp BSPC &kp SEMI &kp L &kp K &kp J &kp H &kp F8 + &reset &trans &kp LGUI &kp M &kp N &kp F12 &kp F11 &kp F9 + &bootloader &trans &trans &trans &trans &kp F10 + >; + }; + }; +}; diff --git a/app/boards/shields/eternal_keypad/eternal_keypad.overlay b/app/boards/shields/eternal_keypad/eternal_keypad.overlay new file mode 100644 index 00000000..4828baf7 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "eternal_keypad.dtsi" diff --git a/app/boards/shields/eternal_keypad/eternal_keypad.zmk.yml b/app/boards/shields/eternal_keypad/eternal_keypad.zmk.yml new file mode 100644 index 00000000..c5c86bc7 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad.zmk.yml @@ -0,0 +1,9 @@ +file_format: "1" +id: eternal_keypad +name: Eternal Keypad +type: shield +url: https://github.com/duckyb/eternal-keypad +requires: [pro_micro] +features: + - keys + - underglow diff --git a/app/boards/shields/eternal_keypad/eternal_keypad_lefty.keymap b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.keymap new file mode 100644 index 00000000..e1bddbad --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.keymap @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +#define BASE 0 +#define ARROW 1 +#define FUNC 2 + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + }; + + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp ESC &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp N7 + &kp F13 &kp ENTER &kp D &kp S &kp A &kp F &kp G &kp N8 + &kp F14 &kp LSHFT &kp Z &kp X &kp C &kp V &kp B &kp N9 + &kp F15 &kp LCTRL &sl FUNC &kp LALT &kp SPACE < ARROW N0 + >; + }; + + arrow_layer { + bindings = < + &bt BT_SEL 0 &bt BT_SEL 1 &trans &trans &trans &out OUT_USB &out OUT_BLE + &trans &trans &kp UP &trans &rgb_ug RGB_TOG &rgb_ug RGB_HUI &rgb_ug RGB_HUD + &bt BT_CLR &trans &kp RIGHT &kp DOWN &kp RIGHT &trans &rgb_ug RGB_BRI &rgb_ug RGB_BRD + &reset &trans &trans &trans &trans &trans &rgb_ug RGB_EFF &rgb_ug RGB_EFR + &bootloader &trans &trans &trans &trans &trans + >; + }; + + function_layer { + bindings = < + &trans &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 + &trans &kp P &kp O &kp I &kp U &kp Y &kp F7 + &bt BT_CLR &kp BSPC &kp SEMI &kp L &kp K &kp J &kp H &kp F8 + &reset &trans &kp LGUI &kp M &kp N &kp F12 &kp F11 &kp F9 + &bootloader &trans &trans &trans &trans &kp F10 + >; + }; + }; +}; diff --git a/app/boards/shields/eternal_keypad/eternal_keypad_lefty.overlay b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.overlay new file mode 100644 index 00000000..0c5599a1 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 202 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include "eternal_keypad.dtsi" diff --git a/app/boards/shields/eternal_keypad/eternal_keypad_lefty.zmk.yml b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.zmk.yml new file mode 100644 index 00000000..9f5fb578 --- /dev/null +++ b/app/boards/shields/eternal_keypad/eternal_keypad_lefty.zmk.yml @@ -0,0 +1,9 @@ +file_format: "1" +id: eternal_keypad_lefty +name: Eternal Keypad Lefty +type: shield +url: https://github.com/duckyb/eternal-keypad +requires: [pro_micro] +features: + - keys + - underglow From 41bfc56e137df2b30e2208fd0b9afafd4d4db4b6 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 6 Sep 2022 00:15:24 +0200 Subject: [PATCH 50/50] feat(board): add puchi_ble_v1 to boards * feat(board): Add Puchi-BLE v1 board --- app/boards/arm/puchi_ble/CMakeLists.txt | 6 + app/boards/arm/puchi_ble/Kconfig | 3 + app/boards/arm/puchi_ble/Kconfig.board | 8 ++ app/boards/arm/puchi_ble/Kconfig.defconfig | 28 ++++ .../arm/puchi_ble/arduino_pro_micro_pins.dtsi | 59 +++++++++ app/boards/arm/puchi_ble/board.cmake | 6 + app/boards/arm/puchi_ble/pinmux.c | 29 +++++ app/boards/arm/puchi_ble/puchi_ble_v1.dts | 121 ++++++++++++++++++ app/boards/arm/puchi_ble/puchi_ble_v1.yaml | 15 +++ app/boards/arm/puchi_ble/puchi_ble_v1.zmk.yml | 10 ++ .../arm/puchi_ble/puchi_ble_v1_defconfig | 24 ++++ 11 files changed, 309 insertions(+) create mode 100644 app/boards/arm/puchi_ble/CMakeLists.txt create mode 100644 app/boards/arm/puchi_ble/Kconfig create mode 100644 app/boards/arm/puchi_ble/Kconfig.board create mode 100644 app/boards/arm/puchi_ble/Kconfig.defconfig create mode 100644 app/boards/arm/puchi_ble/arduino_pro_micro_pins.dtsi create mode 100644 app/boards/arm/puchi_ble/board.cmake create mode 100644 app/boards/arm/puchi_ble/pinmux.c create mode 100644 app/boards/arm/puchi_ble/puchi_ble_v1.dts create mode 100644 app/boards/arm/puchi_ble/puchi_ble_v1.yaml create mode 100644 app/boards/arm/puchi_ble/puchi_ble_v1.zmk.yml create mode 100644 app/boards/arm/puchi_ble/puchi_ble_v1_defconfig diff --git a/app/boards/arm/puchi_ble/CMakeLists.txt b/app/boards/arm/puchi_ble/CMakeLists.txt new file mode 100644 index 00000000..12cf9b1c --- /dev/null +++ b/app/boards/arm/puchi_ble/CMakeLists.txt @@ -0,0 +1,6 @@ + +if(CONFIG_PINMUX) +zephyr_library() +zephyr_library_sources(pinmux.c) +zephyr_library_include_directories(${ZEPHYR_BASE}/drivers) +endif() \ No newline at end of file diff --git a/app/boards/arm/puchi_ble/Kconfig b/app/boards/arm/puchi_ble/Kconfig new file mode 100644 index 00000000..719c3845 --- /dev/null +++ b/app/boards/arm/puchi_ble/Kconfig @@ -0,0 +1,3 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + diff --git a/app/boards/arm/puchi_ble/Kconfig.board b/app/boards/arm/puchi_ble/Kconfig.board new file mode 100644 index 00000000..07638885 --- /dev/null +++ b/app/boards/arm/puchi_ble/Kconfig.board @@ -0,0 +1,8 @@ +# Puchi-BLE board configuration + +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +config BOARD_PUCHI_BLE_v1 + bool "puchi_ble_v1" + depends on SOC_NRF52840_QIAA diff --git a/app/boards/arm/puchi_ble/Kconfig.defconfig b/app/boards/arm/puchi_ble/Kconfig.defconfig new file mode 100644 index 00000000..94d12c17 --- /dev/null +++ b/app/boards/arm/puchi_ble/Kconfig.defconfig @@ -0,0 +1,28 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if BOARD_PUCHI_BLE_v1 + +config BOARD + default "puchi_ble" + +if USB_DEVICE_STACK + +config USB_NRFX + default y + +endif # USB_DEVICE_STACK + +config BT_CTLR + default BT + +config ZMK_BLE + default y + +config ZMK_USB + default y + +config PINMUX + default y + +endif # BOARD_PUCHI_BLE_v1 diff --git a/app/boards/arm/puchi_ble/arduino_pro_micro_pins.dtsi b/app/boards/arm/puchi_ble/arduino_pro_micro_pins.dtsi new file mode 100644 index 00000000..ed3317f1 --- /dev/null +++ b/app/boards/arm/puchi_ble/arduino_pro_micro_pins.dtsi @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + + +/ { + pro_micro: connector { + compatible = "arduino-pro-micro"; + #gpio-cells = <2>; + gpio-map-mask = <0xffffffff 0xffffffc0>; + gpio-map-pass-thru = <0 0x3f>; + gpio-map + = <0 0 &gpio0 8 0> /* D0 */ + , <1 0 &gpio0 6 0> /* D1 */ + , <2 0 &gpio0 15 0> /* D2 */ + , <3 0 &gpio0 17 0> /* D3 */ + , <4 0 &gpio0 20 0> /* D4/A6 */ + , <5 0 &gpio0 13 0> /* D5 */ + , <6 0 &gpio0 24 0> /* D6/A7 */ + , <7 0 &gpio0 9 0> /* D7 */ + , <8 0 &gpio0 10 0> /* D8/A8 */ + , <9 0 &gpio1 6 0> /* D9/A9 */ + , <10 0 &gpio1 11 0> /* D10/A10 */ + , <16 0 &gpio0 28 0> /* D16 */ + , <14 0 &gpio0 3 0> /* D14 */ + , <15 0 &gpio1 13 0> /* D15 */ + , <18 0 &gpio0 2 0> /* D18/A0 */ + , <19 0 &gpio0 29 0> /* D19/A1 */ + , <20 0 &gpio0 31 0> /* D20/A2 */ + , <21 0 &gpio0 30 0> /* D21/A3 */ + ; + }; + + pro_micro_a: connector_a { + compatible = "arduino-pro-micro"; + #gpio-cells = <2>; + gpio-map-mask = <0xffffffff 0xffffffc0>; + gpio-map-pass-thru = <0 0x3f>; + gpio-map + = <0 0 &gpio0 2 0> /* D18/A0 */ + , <1 0 &gpio0 29 0> /* D19/A1 */ + , <2 0 &gpio0 31 0> /* D20/A2 */ + , <3 0 &gpio0 30 0> /* D21/A3 */ + , <6 0 &gpio0 20 0> /* D4/A6 */ + , <7 0 &gpio0 24 0> /* D6/A7 */ + , <8 0 &gpio0 10 0> /* D8/A8 */ + , <9 0 &gpio1 6 0> /* D9/A9 */ + , <10 0 &gpio1 11 0> /* D10/A10 */ + ; + }; +}; + + +pro_micro_d: &pro_micro {}; +pro_micro_i2c: &i2c0 {}; +pro_micro_spi: &spi0 {}; +pro_micro_serial: &uart0 {}; diff --git a/app/boards/arm/puchi_ble/board.cmake b/app/boards/arm/puchi_ble/board.cmake new file mode 100644 index 00000000..3b5c4aea --- /dev/null +++ b/app/boards/arm/puchi_ble/board.cmake @@ -0,0 +1,6 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") +include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) diff --git a/app/boards/arm/puchi_ble/pinmux.c b/app/boards/arm/puchi_ble/pinmux.c new file mode 100644 index 00000000..78cea314 --- /dev/null +++ b/app/boards/arm/puchi_ble/pinmux.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include + +static int pinmux_puchi_ble_init(const struct device *port) { + ARG_UNUSED(port); + +#if CONFIG_BOARD_PUCHI_BLE_v1 + const struct device *p0 = device_get_binding("GPIO_0"); +#if CONFIG_BOARD_PUCHI_BLE_CHARGER + gpio_pin_configure(p0, 5, GPIO_OUTPUT); + gpio_pin_set(p0, 5, 0); +#else + gpio_pin_configure(p0, 5, GPIO_INPUT); +#endif +#endif + return 0; +} + +SYS_INIT(pinmux_puchi_ble_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/boards/arm/puchi_ble/puchi_ble_v1.dts b/app/boards/arm/puchi_ble/puchi_ble_v1.dts new file mode 100644 index 00000000..e324cccc --- /dev/null +++ b/app/boards/arm/puchi_ble/puchi_ble_v1.dts @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/dts-v1/; +#include +#include "arduino_pro_micro_pins.dtsi" + +/ { + model = "puchi_ble"; + compatible = "puchi_ble"; + + chosen { + zephyr,code-partition = &code_partition; + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,console = &cdc_acm_uart; + zmk,battery = &vbatt; + }; + + leds { + compatible = "gpio-leds"; + blue_led: led_0 { + gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; + label = "Blue LED"; + }; + }; + + ext-power { + compatible = "zmk,ext-power-generic"; + label = "EXT_POWER"; + control-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; + }; + + vbatt: vbatt { + compatible = "zmk,battery-voltage-divider"; + label = "BATTERY"; + io-channels = <&adc 2>; + output-ohms = <2000000>; + full-ohms = <(2000000 + 820000)>; + }; +}; + +&adc { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&i2c0 { + compatible = "nordic,nrf-twi"; + sda-pin = <15>; + scl-pin = <17>; +}; + +&uart0 { + compatible = "nordic,nrf-uarte"; + tx-pin = <6>; + rx-pin = <8>; +}; + +&usbd { + status = "okay"; + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + label = "CDC_ACM_0"; + }; +}; + + +&flash0 { + /* + * For more information, see: + * http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html + */ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + sd_partition: partition@0 { + label = "softdevice"; + reg = <0x00000000 0x00026000>; + }; + code_partition: partition@26000 { + label = "code_partition"; + reg = <0x00026000 0x000c6000>; + }; + + /* + * The flash starting at 0x000ec000 and ending at + * 0x000f3fff is reserved for use by the application. + */ + + /* + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@ec000 { + label = "storage"; + reg = <0x000ec000 0x00008000>; + }; + + boot_partition: partition@f4000 { + label = "adafruit_boot"; + reg = <0x000f4000 0x0000c000>; + }; + }; +}; diff --git a/app/boards/arm/puchi_ble/puchi_ble_v1.yaml b/app/boards/arm/puchi_ble/puchi_ble_v1.yaml new file mode 100644 index 00000000..18770722 --- /dev/null +++ b/app/boards/arm/puchi_ble/puchi_ble_v1.yaml @@ -0,0 +1,15 @@ +identifier: puchi_ble_v1 +name: puchi_ble_v1 +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/puchi_ble/puchi_ble_v1.zmk.yml b/app/boards/arm/puchi_ble/puchi_ble_v1.zmk.yml new file mode 100644 index 00000000..f3114008 --- /dev/null +++ b/app/boards/arm/puchi_ble/puchi_ble_v1.zmk.yml @@ -0,0 +1,10 @@ +file_format: "1" +id: puchi_ble_v1 +name: Puchi-BLE V1 +type: board +arch: arm +outputs: + - usb + - ble +url: https://keycapsss.com/keyboard-parts/mcu-controller/202/puchi-ble-wireless-microcontroller +exposes: [pro_micro] diff --git a/app/boards/arm/puchi_ble/puchi_ble_v1_defconfig b/app/boards/arm/puchi_ble/puchi_ble_v1_defconfig new file mode 100644 index 00000000..e32886d0 --- /dev/null +++ b/app/boards/arm/puchi_ble/puchi_ble_v1_defconfig @@ -0,0 +1,24 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_PUCHI_BLE_v1=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +CONFIG_USE_DT_CODE_PARTITION=y +CONFIG_BUILD_OUTPUT_UF2=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y +CONFIG_CLOCK_CONTROL_NRF=y +CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y