Rotary encoders

A rotary encoder is a common input device that can be used for volume control, page scrolling, and other functions.

toml configuration

You can define a rotary encoder in your keyboard.toml:

[[input_device.encoder]]
pin_a = "P0_30"
pin_b = "P0_31"

# Whether to use the MCU's internal pull-up resistor, default to false
internal_pullup = false

# Working mode of the encoder
# Available modes:
# - default: resolution = 1
# - resolution: custom resolution, requires specifying resolution and reverse parameters
phase = "resolution"

# `resolution` represents the number of steps generated per detent.
#
# When your encoder datasheet lists:
#   - detent = number of mechanical detent positions  
#   - pulse  = number of full quadrature cycles (A/B cycles)  
#
# Then the relationship is:
#   resolution = (pulse × 4) / detent
# because each full quadrature cycle (pulse) produces 4 edge transitions.
#
# For example — in the ALPS EC11E series (https://tech.alpsalpine.com/cms.media/product_catalog_ec_01_ec11e_en_611f078659.pdf):
#   detent = 30, pulse = 15 → resolution = (15 × 4) / 30 = 2
resolution = 2

# Or you can specify detent and pulse to calculate resolution automatically
resolution = { detent = 30, pulse = 15 }

# Whether the direction of the rotary encoder is reversed.
reverse = false

Multiple encoders can be added directly, the encoder index is determined by the order:

# Encoder 0
[[input_device.encoder]]
pin_a = "P0_01"
pin_b = "P0_02"
phase = "default"
# Encoder 1
[[input_device.encoder]]
pin_a = "P0_03"
pin_b = "P0_04"
phase = "default"

Defining Encoder Actions in keyboard.toml:

# The map of actions/keycodes triggered by the encoder.
# This is typically an array of layers, where each layer contains two actions:
# [Action for Clockwise (CW) rotation, Action for Counter-Clockwise (CCW) rotation].
#
# The outer array index corresponds to the keyboard layer (e.g., [0] is base layer).
#
# Layer 0
# - Encoder 0: CW -> VolUp, CCW -> VolDn
# - Encoder 1: CW -> PgDn, CCW -> PgUp
encoder_map = [["VolUp", "VolDn"], ["PgDn", "PgUp"]]
# Layer 1
# - Encoder 0: No action ("_")
# - Encoder 1: CW -> Briu (Brightness up), CCW -> Brid (Brightness down)
encoder_map = [["_", "_"], ["Briu", "Brid"]]

Rust configuration

With Rust, you can define a rotary encoder as the following:

use rmk::input_device::rotary_encoder::RotaryEncoder;
    use rmk::input_device::rotary_encoder::DefaultPhase;
    let pin_a = Input::new(p.P1_06, embassy_nrf::gpio::Pull::None);
    let pin_b = Input::new(p.P1_04, embassy_nrf::gpio::Pull::None);
    let mut encoder = RotaryEncoder::with_phase(pin_a, pin_b, DefaultPhase, encoder_id);

You can also use the resolution based phase:

use rmk::input_device::rotary_encoder::RotaryEncoder;
    let pin_a = Input::new(p.P1_06, embassy_nrf::gpio::Pull::None);
    let pin_b = Input::new(p.P1_04, embassy_nrf::gpio::Pull::None);
    // Create an encoder with resolution = 2, reversed = false
    let mut encoder = RotaryEncoder::with_resolution(pin_a, pin_b, 2, false, encoder_id)

Then add the encoder to the device list of run_device.

join3(
        run_devices! (
            (matrix, encoder) => EVENT_CHANNEL,
        ),
        keyboard.run(), // Keyboard is special
        run_rmk(&keymap, driver, storage, rmk_config, sd),
    )
    .await;

Defining Encoder Actions in keymap.rs:

pub const fn get_default_keymap() -> [[[KeyAction; COL]; ROW]; NUM_LAYER] {
    [
    ... // Standard keymap definition
    ]
}

pub const fn get_default_encoder_map() -> [[EncoderAction; NUM_ENCODER]; NUM_LAYER] {
    [
        // Layer 0
        [
            // Encoder 0: (Clockwise, Counter-Clockwise)
            encoder!(k!(KbVolumeUp), k!(KbVolumeDown)), 
            // Encoder 1:
            encoder!(k!(KbVolumeUp), k!(KbVolumeDown)), 
        ],
        ...
    ]
}