RMK Keyboard Firmware

repository·main·Indexed 23 days ago

https://github.com/haobogu/rmk

A modern, feature-rich keyboard firmware written in Rust designed for high performance, low latency, and ease of use. It supports a wide range of microcontrollers and wireless connectivity, including ESP32C6, ESP32C3, ESP32H2, nRF52832, and nRF52840. Users can create firmware using a TOML configuration file with the #[rmk_keyboard] macro or by writing directly against the RMK API in Rust for greater flexibility.

Tokens
85.7K
Snippets
236
Records
389
Agent score
83%

What's inside RMK

  1. Overview of RMK features

    main

    RMK is a feature-rich Rust keyboard firmware built on embassy. Key capabilities include:

    • Wide MCU Support: Supports various series including STM32, nRF, RP2040, and ESP32.
    • Real-time Keymap Editing: Supports Vial or the native Rynk protocol for instant keymap changes, including over BLE.
    • Advanced Functions: Built-in support for layer switching, media controls, system controls, and mouse control.
    • Wireless Support: BLE connectivity with automatic reconnection and multi-device support (tested on nRF52840, ESP32, and Pi Pico W).
    • Easy Configuration: Can be configured using only a keyboard.toml file without writing Rust code, though Rust developers can use the API for maximum flexibility.
    • Performance: Low latency (~2ms wired, ~10ms Bluetooth) and low power consumption (especially with the async_matrix feature enabled).
  2. Overview of RMK keyboard firmware

    main

    RMK is a high-performance, feature-rich keyboard firmware framework written in Rust. It utilizes Rust's async ecosystem to provide modern functionality across various microcontrollers.

    Key capabilities include:

    • Connectivity: Support for both wired (USB) and wireless (BLE) connections, featuring optimized low-power performance and multi-profile support.
    • Split Keyboards: Native support for both wired and wireless split keyboard configurations.
    • Configuration: Keyboard settings are managed through a single TOML file.
    • Advanced Key Features: Supports on-the-fly keymap customization, advanced layer management, macros, and multi-role keys like Tap-Hold, TapDance, and Morse Key.
  3. Configure split keyboard matrix offsets

    main

    In a split configuration, the [layout] section defines the total rows and columns for the entire keyboard. To position individual halves correctly, you must use rows, cols, row_offset, and col_offset within the [split.central] and [[split.peripheral]] sections.

    For example, if a central keyboard has 2 columns and a peripheral starts after it, the peripheral's col_offset should be 2.

    [split.central]
    rows = 2
    cols = 2
    row_offset = 0
    col_offset = 0
    
    [[split.peripheral]]
    rows = 2
    cols = 3
    row_offset = 0
    col_offset = 2
  4. Understand RDY pin vs Polling for IQS5xx

    main

    The Azoteq IQS5xx trackpad alternates between scanning the panel and an I²C communication window. How you handle this window affects bus stability:

    • Using the RDY pin (Recommended): The driver waits for the RDY pin to go high before starting I²C reads. This ensures transactions happen within the communication window, preventing clock-stretching and minimizing idle bus traffic. The IC is put into "event mode".
    • Polling (Without RDY): If rdy is not configured, the driver polls on a fixed ~15 ms cadence. If a poll occurs during a scan cycle, the IC will use clock-stretching on the SCL line to pause the bus. This can freeze other devices on the same I²C bus and cause latency spikes, especially during long touch holds.

    Recommendation: If your hardware lacks a routed RDY pin, it is highly recommended to hand-solder a jumper to a spare GPIO to avoid bus issues.

  5. Implement the JsByteLink contract

    main

    A JsByteLink acts as the byte-stream boundary between the browser's hardware transports (Web Serial/WebHID) and the rynk-wasm protocol state machine. It is not a high-level API, but a raw byte pipe.

    Required Interface

    The object passed to connect(link) must implement the following shape:

    {
      label: "My keyboard", // A required string for the device-picker UI
      async send(bytes) {
        // Receives Uint8Array from Wasm -> delivers to browser transport in order.
        // Must resolve only after the transport has accepted the bytes.
      },
      async recv() {
        // Browser transport -> Uint8Array for Wasm.
        // Must wait until bytes are available or the link is closed.
        // Returns new Uint8Array(0) ONLY for EOF (disconnection).
      },
      async close() {
        // The page is responsible for closing the link.
        // Must be called on every exit path to release locks.
      }
    }

    Critical Rules

    • EOF Handling: recv() must return new Uint8Array(0) to signal EOF. This triggers a Disconnected state in the Wasm API.
    • Ownership: rynk-wasm never closes the link; the host page must manage the lifecycle.
    • Exclusivity: Only rynk-wasm should call recv() after connect() has been called. If you need to probe the protocol version, do so before calling connect().
    • Framing: Any transport-specific framing (like WebHID report padding) must be stripped or handled below this boundary so the Wasm core receives a clean Rynk byte stream.
  6. How the RMK layer system works

    main

    RMK employs a layer-based system for key determination, similar to QMK. When a key is pressed, RMK evaluates layers in order from the highest index to the lowest.

    If a key is defined in a higher layer, that definition is used. If the key is not found in the higher layer, or if the key is set as transparent, RMK falls back to checking the next lower layer. This process continues until a non-transparent key is found or the bottom layer is reached.

  7. Understand the Rynk protocol architecture

    main

    Rynk is a runtime-free host-side client for RMK's native host-communication protocol. It allows a host to read/write keymaps, combos, forks, morse, macros, and behaviors, and observe live status.

    The architecture is split into three distinct layers:

    1. Protocol State Machine (rynk): This crate manages the protocol logic and state. It does not handle I/O or discovery.
    2. Transports (rynk-serial, rynk-ble, etc.): These crates handle device discovery, connection, and byte I/O. They implement the RynkDevice::open method to provide the byte link.
    3. Layout Conversion (rynk-kle): This crate handles converting KLE exports or Vial vial.json files to RMK's [layout] format and decoding layouts into rynk::layout types.

    To communicate with a device, you must use a transport crate to discover a device, then use rynk to manage the session.

  8. Configure keyboard.toml and vial.json for cloud compilation

    main

    When using the cloud compilation method via the rmk-project-template, your firmware is defined by two primary configuration files located in the root of your repository:

    • keyboard.toml: This is the primary configuration file that defines almost everything about your keyboard layout and features.
    • vial.json: This file is used for vial integration. It contains the matrix definitions required for vial to recognize your keyboard and allow on-the-fly keymap updates. You should follow the official vial porting guide to create this file correctly.
  9. Understand the RMK keyboard configuration structure

    main

    RMK divides a keyboard's description into three distinct logical sections in your configuration file:

    1. [matrix]: Defines the electrical wiring (which GPIO pins form the key matrix).
    2. [layout]: Defines the physical arrangement (grid size, the visual map of keys, and shapes).
    3. [keymap]: Defines the logical behavior (what each key does on different layers).

    Crucially, the order of keys in the [layout].map string must exactly match the order of keys in every [[keymap.layer]].keys string. The $n$-th item in the map corresponds to the $n$-th key on every layer.

    [layout]
    rows = 2
    cols = 2
    map = """
    (0,0) (0,1)
    (1,0) (1,1)
    ""
    
    [[keymap.layer]]
    keys = """
    A B
    C D
    ""
  10. How the Watchdog feature works in RMK

    main

    The hardware watchdog is a safety mechanism that resets the MCU if the firmware hangs, preventing the need for a physical unplug. RMK manages this via a dedicated Embassy task that 'feeds' the watchdog at regular intervals.

    Because RMK tasks are joined cooperatively, a tight-loop stall in any sibling task (such as matrix scanning, USB, or BLE) will prevent the watchdog task from running. When this happens, the watchdog timeout expires and the hardware automatically resets the MCU.

    Key Details:

    • The watchdog feature is enabled by default; no manual configuration is required.
    • Supported chips include RP2040, nRF52, and ESP32.
    • STM32 does not currently have automatic watchdog code generation.