PMW3610 Optical Mouse Sensor

PMW3610 is a low‑power optical mouse sensor.

Note
  • Currently, only implemented for nRF52 and RP2040 with a single-wire (SDIO) half-duplex SPI.
  • Set motion pin for better power efficiency. If omitted, the sensor is polled.
  • By default, report rate is limited to 125 Hz to prevent flooding the event channel, which causes latency issues especially over BLE.

toml configuration

Warning

spi.mosi and spi.miso must be the same pin, or one of them empty.

[[input_device.pmw3610]]
name = "trackball0"
id = 0 # optional number between 0-255. Ids are used for debug prints. Set to 0 if omitted.

spi.instance = "bitbang0"
spi.sck = "P0_05"
spi.mosi = "P0_04"
spi.miso = "P0_04"
spi.cs = "P0_09"
# or spi = { instance = "bitbang0", sck = "P0_05", mosi = "P0_04", miso = "P0_04", cs = "P0_09" }

motion = "P0_02" # Optional. If omitted, the sensor is polled.

report_hz = 125 # Optional: Report rate in Hz

force_awake = false
smart_mode = true
cpi = 800
# invert / swap axis on a sensor level
invert_x = true
# invert_y = true
# swap_xy = true
# invert / swap axis in software (in PointingProcessor)
# proc_invert_x = true
# proc_invert_y = true
# proc_swap_xy = true

Split

To add the sensor to the central or peripheral use

[[split.central.input_device.pmw3610]]
name = ...

# resp.
[[split.peripheral.input_device.pmw3610]]
name = ...
Multi-device ID assignment

PointingEvent carries a device_id so that each PointingProcessor can be paired with a specific sensor. If you have sensors on both halves of a split keyboard, assign distinct ids to avoid the central treating them as the same device:

# Central side sensor
[[split.central.input_device.pmw3610]]
id = 0

# Peripheral side sensor
[[split.peripheral.input_device.pmw3610]]
id = 1

The peripheral forwards events to the central with the device_id preserved. The generated PointingProcessorConfig on the central will automatically use the matching device_id for each sensor.

Breaking change

Adding device_id to PointingEvent changes the serialized binary format used by the split protocol. Both halves of a split keyboard must be flashed with the same firmware version at the same time.

Rust configuration

Define a PointingDevice and add it to run_all! macro. For a split keyboard this must be added to the file (central.rs or peripheral.rs) corresponding to the side the sensor is connected to.

use embassy_rp::gpio::{Output, Flex, Level};
use rmk::input_device::pmw3610::{BitBangSpiBus, Pmw3610, Pmw3610Config};
use rmk::input_device::pointing::PointingDevice;

let pmw3610_config = Pmw3610Config {
    res_cpi: 800,
    // force_awake: true,
    smart_mode: true,
    swap_xy: true,
    // invert_x: true,
    // invert_y: true,
    ..Default::default()
};

let pmw3610_sck = Output::new(p.P0_05, Level::High, OutputDrive::Standard);
let pmw3610_sdio = Flex::new(p.P0_04);
let pmw3610_cs = Output::new(p.P0_09, Level::High, OutputDrive::Standard);
let pmw3610_motion = Some(Input::new(p.P0_02, Pull::Up));
// or if you want to omit the motion pin:
// let pmw3610_motion = Option::<Input<'static>>::None;

let pmw3610_spi = BitBangSpiBus::new(pmw3610_sck, pmw3610_sdio);

const POINTING_DEV_ID: u8 = 0; // this ID can be anything from 0-255. Just make sure you don't use the same number twice for different sensors to avoid confusion.
let mut pmw3610_device = PointingDevice::<Pmw3610<_, _, _>>::new(
    POINTING_DEV_ID
    pmw3610_spi,
    pmw3610_cs,
    pmw3610_motion,
    pmw3610_config,
);

// If you want to customize the report rate (Hz):
// let mut pmw3610_device = Pmw3610Device::with_report_hz(
//     POINTING_DEV_ID,
//     pmw3610_spi,
//     pmw3610_cs,
//     pmw3610_motion,
//     pmw3610_config,
//     125,
// );

run_all!(matrix, pmw3610_device),

And define a PointingProcessor and add it to run_all! macro to process the events.

Warning

This should be added to the central.rs-File even if the sensor is on split peripheral.

    use rmk::input_device::pointing::{ PointingProcessor, PointingProcessorConfig };

    let pmw3610_proc_config = PointingProcessorConfig {
        // invert_x: true, // invert axis if neccesary
        // invert_y: true,
        // swap_y: true,
        ..Default::default()
    };

    let mut pmw3360_processor = PointingProcessor::new(&keymap, pmw3610_proc_config);

run_all!(pmw3610_processor, /* other processors and devices */)

Pointing Modes

The PointingProcessor has four modes (Cursor, Scroll, Sniper, Caret) with per‑layer configuration. See the PointingProcessor page for all options.