Skip to main content

Keyboard Scan Configuration

See Configuration Overview for instructions on how to change these settings.

Common

Kconfig

Definition files:

ConfigTypeDescriptionDefault
CONFIG_ZMK_KSCAN_EVENT_QUEUE_SIZEintSize of the event queue for kscan events4
CONFIG_ZMK_KSCAN_INIT_PRIORITYintKeyboard scan device driver initialization priority40
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MSintGlobal debounce time for key press in milliseconds-1
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MSintGlobal 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. See the debouncing documentation for more details.

Devicetree

Applies to: /chosen node

PropertyTypeDescription
zmk,kscanpathThe node for the keyboard scan driver to use
zmk,matrix-transformpathThe node for the 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.

note

Currently this driver does not honor the CONFIG_ZMK_KSCAN_DEBOUNCE_* settings.

Devicetree

Applies to: compatible = "zmk,kscan-gpio-demux"

Definition file: zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-demux.yaml

PropertyTypeDescriptionDefault
input-gpiosGPIO arrayInput GPIOs
output-gpiosGPIO arrayDemultiplexer address GPIOs
debounce-periodintDebounce period in milliseconds5
polling-interval-msecintPolling interval in milliseconds25

Direct GPIO Driver

Keyboard scan driver where each key has a dedicated GPIO.

Kconfig

Definition file: zmk/app/module/drivers/kscan/Kconfig

ConfigTypeDescriptionDefault
CONFIG_ZMK_KSCAN_DIRECT_POLLINGboolPoll for key presses instead of using interruptsn

Devicetree

Applies to: compatible = "zmk,kscan-gpio-direct"

Definition file: zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-direct.yaml

PropertyTypeDescriptionDefault
input-gpiosGPIO arrayInput GPIOs (one per key). Can be either direct GPIO pin or gpio-key references
debounce-press-msintDebounce time for key press in milliseconds. Use 0 for eager debouncing5
debounce-release-msintDebounce time for key release in milliseconds5
debounce-scan-period-msintTime between reads in milliseconds when any key is pressed1
poll-period-msintTime between reads in milliseconds when no key is pressed and CONFIG_ZMK_KSCAN_DIRECT_POLLING is enabled10
toggle-modeboolUse toggle switch moden
wakeup-sourceboolMark this kscan instance as able to wake the keyboardn

Assuming the switches connect each GPIO pin to the ground, the GPIO flags for the elements in input-gpios should be (GPIO_ACTIVE_LOW | GPIO_PULL_UP):

    kscan0: kscan {
compatible = "zmk,kscan-gpio-direct";
wakeup-source;
input-gpios
= <&pro_micro 4 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
, <&pro_micro 5 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>
;
};

A direct pin defined in the input-gpios property is considered a column when used in a matrix transform; e.g. the 5th pin on the list can be referred to using RC(0,4).

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 enable and disable the internal pull up/down resistor as needed when the switch is toggled to minimise power draw. For toggle-mode to work correctly each pole of the switch needs a dedicated GPIO pin.

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 to combine them. The state of the switch is read on power on, so if the switch is moved whilst the board is off this will get correctly interpreted by the driver.

When using toggle-mode the pull resistors get automatically set by the driver and should not be set in the devicetree via GPIO flags. Assuming the common pole of the switch is connected to ground with an SP3T switch:

    kscan_sp3t_toggle: kscan_sp3t_toggle {
compatible = "zmk,kscan-gpio-direct";
toggle-mode;

input-gpios
= <&pro_micro 4 GPIO_ACTIVE_LOW>
, <&pro_micro 3 GPIO_ACTIVE_LOW>
, <&pro_micro 2 GPIO_ACTIVE_LOW>
;
};

Matrix Driver

Keyboard scan driver where keys are arranged on a matrix with one GPIO per row and column.

Definition file: zmk/app/module/drivers/kscan/Kconfig

ConfigTypeDescriptionDefault
CONFIG_ZMK_KSCAN_MATRIX_POLLINGboolPoll for key presses instead of using interruptsn
CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTSint (ticks)How long to wait before reading input pins after setting output active0
CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTSint (ticks)How long to wait between each output to allow previous output to "settle"0

Devicetree

Applies to: compatible = "zmk,kscan-gpio-matrix"

Definition file: zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-matrix.yaml

PropertyTypeDescriptionDefault
row-gpiosGPIO arrayMatrix row GPIOs in order, starting from the top row
col-gpiosGPIO arrayMatrix column GPIOs in order, starting from the leftmost row
debounce-press-msintDebounce time for key press in milliseconds. Use 0 for eager debouncing5
debounce-release-msintDebounce time for key release in milliseconds5
debounce-scan-period-msintTime between reads in milliseconds when any key is pressed1
diode-directionstringThe direction of the matrix diodes"row2col"
poll-period-msintTime between reads in milliseconds when no key is pressed and CONFIG_ZMK_KSCAN_MATRIX_POLLING is enabled10
wakeup-sourceboolMark this kscan instance as able to wake the keyboardn

The diode-direction property must be one of:

ValueDescription
"row2col"Diodes point from rows to columns (cathodes are connected to columns)
"col2row"Diodes point from columns to rows (cathodes are connected to rows)

Given the diode-direction, the GPIO flags for the elements in row- and col-gpios should be set appropriately. The output pins (e.g. columns for col2row) should have the flag GPIO_ACTIVE_HIGH, and input pins (e.g. rows for col2row) should have the flags (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN):

    kscan0: kscan {
compatible = "zmk,kscan-gpio-matrix";
wakeup-source;
diode-direction = "col2row";
col-gpios
= <&pro_micro 4 GPIO_ACTIVE_HIGH>
, <&pro_micro 5 GPIO_ACTIVE_HIGH>
;
row-gpios
= <&pro_micro 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&pro_micro 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
;
};

Charlieplex Driver

Keyboard scan driver where keys are arranged on a matrix with each GPIO used as both input and output.

  • With interrupt-gpios unset, this allows n pins to drive n*(n-1) keys.
  • With interrupt-gpios set, n pins will drive (n-1)*(n-2) keys, but provide much improved power handling.

Definition file: zmk/app/module/drivers/kscan/Kconfig

ConfigTypeDescriptionDefault
CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BEFORE_INPUTSint (ticks)How long to wait before reading input pins after setting output active0
CONFIG_ZMK_KSCAN_CHARLIEPLEX_WAIT_BETWEEN_OUTPUTSint (ticks)How long to wait between each output to allow previous output to "settle"0

Devicetree

Applies to: compatible = "zmk,kscan-gpio-charlieplex"

Definition file: zmk/app/module/dts/bindings/kscan/zmk,kscan-gpio-charlieplex.yaml

PropertyTypeDescriptionDefault
gpiosGPIO arrayGPIOs used, listed in order.
interrupt-gpiosGPIO arrayA single GPIO to use for interrupt. Leaving this empty will enable continuous polling.
debounce-press-msintDebounce time for key press in milliseconds. Use 0 for eager debouncing.5
debounce-release-msintDebounce time for key release in milliseconds.5
debounce-scan-period-msintTime between reads in milliseconds when any key is pressed.1
poll-period-msintTime between reads in milliseconds when no key is pressed and interrupt-gpois is not set.10
wakeup-sourceboolMark this kscan instance as able to wake the keyboardn

Define the transform with a matrix transform. The row is always the driven pin, and the column always the receiving pin (input to the controller). For example, in RC(5,0) power flows from the 6th pin in gpios to the 1st pin in gpios. Exclude all positions where the row and column are the same as these pairs will never be triggered, since no pin can be both input and output at the same time.

The GPIO flags for the elements in gpios should be GPIO_ACTIVE_HIGH, and interrupt pins set in interrupt-gpios should have the flags (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN).

Composite Driver

Keyboard scan driver which combines multiple other keyboard scan drivers.

Devicetree

Applies to : compatible = "zmk,kscan-composite"

Definition file: zmk/app/dts/bindings/zmk,kscan-composite.yaml

PropertyTypeDescriptionDefault
rowsintThe number of rows in the composite matrix
columnsintThe 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:

PropertyTypeDescriptionDefault
kscanphandleLabel of the kscan driver to include
row-offsetintShifts row 0 of the included driver to a new row in the composite matrix0
col-offsetintShifts column 0 of the included driver to a new column in the composite matrix0
wakeup-sourceboolMark this kscan instance as able to wake the keyboardn

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:

/ {
chosen {
zmk,kscan = &kscan0;
};

kscan0: kscan_composite {
compatible = "zmk,kscan-composite";
rows = <4>;
columns = <3>;

// 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.

Devicetree

Applies to: compatible = "zmk,kscan-mock"

Definition file: zmk/app/dts/bindings/zmk,kscan-mock.yaml

PropertyTypeDescriptionDefault
event-periodintMilliseconds between each generated event
eventsarrayList of key events to simulate
rowsintThe number of rows in the composite matrix
columnsintThe number of columns in the composite matrix
exit-afterboolExit the program after running all eventsfalse

The events array should be defined using the macros from app/module/include/dt-bindings/zmk/kscan_mock.h.

Kscan Sideband Behavior Driver

The Kscan sideband behaviors node can be used to assign behaviors to keys in a manner distinctly separate from the keymap. These assignments and definitions will not be affected by nor have any effect on the keymap.

Devicetree

Applies to: compatible = "zmk,kscan-sideband-behaviors"

Definition file: zmk/app/dts/bindings/kscan/zmk,matrix-transform.yaml

PropertyTypeDescription
kscanphandlePhandle to a kscan containing keys to assign behaviors to
auto-enableboolEnables the sideband instance on startup unconditionally
wakeup-sourceboolMark this kscan instance as able to wake the keyboard

If auto-enable is not set, then the sideband behavior will wait for an external activation source before being enabled, e.g. being assigned as the chosen zmk,kscan. The kscan can contain additional keys, which will be used by the keymap if this node is set as the chosen zmk,kscan and has an appropriate matrix transformation defined.

Each child node should have the following properties:

PropertyTypeDescriptionDefault
rowintThe row index of the key in the kscan to intercept and trigger a behavior for0
columnintThe column index of the key in the kscan to intercept and trigger a behavior for
bindingsphandle-arrayThe behavior that should be triggered when the matching row and column event triggers