Azoteq IQS5xx Trackpad

The Azoteq IQS5xx-B000 family (IQS550, IQS572, IQS525) are I²C capacitive trackpad controllers, commonly used in keyboards via Azoteq's TPS43 and TPS65 trackpad modules.

Note
  • Currently only relative single-finger cursor movement is reported. Gestures, multi-finger absolute positions, pressure, area, and raw channel data are read from the IC but not yet published as RMK events.
  • Scaling is not supported yet; cursor movements will likely feel fast and imprecise.
  • An RDY (ready) pin is strongly recommended. Without it, the driver falls back to timed polling and may stall the I²C bus through clock-stretching if it polls mid-cycle. See RDY vs polling.
  • Each [[input_device.iqs5xx]] claims its own I²C peripheral. Sharing a bus with another I²C device (e.g. an OLED) isn't supported yet.
  • Persisting parameters to the IC's non-volatile memory (which requires toggling NRST) isn't supported; configuration is rewritten on every boot.

Hardware

  • SDA / SCL — I²C bus, 7-bit address 0x74.
  • RDY — active-high digital output the device drives high during the I²C communication window. Connect to a GPIO that supports async edge waits (embedded_hal_async::digital::Wait).
  • NRST — active-low reset. Not used by this driver, but required if you want to persist parameters to the IC's non-volatile memory (out of scope here).

toml configuration

[[input_device.iqs5xx]]
name = "trackpad0"
id = 0 # optional 0-255. Used for debug prints. Defaults to 0.

i2c.instance = "I2C0"  # RP2040: I2C0 / I2C1.  nRF52: TWISPI0 / TWISPI1 / TWISPI2.
i2c.sda = "PIN_4"
i2c.scl = "PIN_5"

# Optional: RDY (data-ready) pin. Strongly recommended.
rdy = "PIN_15"

# Axis tweaks applied in PointingProcessor.
# proc_invert_x = true
# proc_invert_y = true
# proc_swap_xy = true

Split

To add the trackpad to the central or a peripheral:

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

# resp.
[[split.peripheral.input_device.iqs5xx]]
name = ...

For split keyboards the device runs on whichever side it's wired to; the matching PointingProcessor is generated on the central automatically.

Rust configuration

Construct the device directly. For a split keyboard, add the device to whichever side (central.rs or peripheral.rs) the trackpad is physically wired to.

use embassy_rp::gpio::{Input, Pull};
use embassy_rp::i2c::{Config, I2c};
use rmk::input_device::iqs5xx::Iqs5xx;
use rmk::input_device::pointing::{PointingProcessor, PointingProcessorConfig};

// 1. Bring up the I2C bus the trackpad is on.
let mut i2c_cfg = Config::default();
i2c_cfg.frequency = 400_000;
let i2c = I2c::new_async(p.I2C0, p.PIN_5, p.PIN_4, Irqs, i2c_cfg);

// 2. Configure the RDY pin (recommended). Use `None` if you don't have one.
let rdy = Some(Input::new(p.PIN_15, Pull::None));

// 3. Construct the device. The first argument is an RMK pointing-device id;
//    pick any 0-255, just don't reuse it for another pointing device.
const POINTING_DEV_ID: u8 = 0;
let mut trackpad = Iqs5xx::new(POINTING_DEV_ID, i2c, rdy);

// 4. Add a PointingProcessor on the central side to convert motion events
//    into mouse reports. Axis tweaks (invert / swap) live here.
let proc_config = PointingProcessorConfig {
    // invert_x: true,
    // invert_y: true,
    // swap_xy: true,
    ..Default::default()
};
let mut trackpad_proc = PointingProcessor::new(&keymap, proc_config);

run_all!(trackpad, trackpad_proc, /* matrix, ... */);
Note

PointingProcessor must run on the central side, even if the trackpad is wired to a peripheral. The peripheral runs the Iqs5xx device and forwards events over the split link; the central converts them to USB/BLE HID reports.

RDY vs polling

The IQS5xx alternates between scanning the touch panel and an I²C communication window. When a window is open it drives RDY high.

  • With RDY: the driver waits for RDY high before issuing I²C reads, so transactions complete inside the window with no clock-stretching. The driver puts the IC into "event mode" so the device only opens a window when it actually has touch data, which keeps idle bus traffic minimal.
  • Without RDY (rdy = None / no rdy in TOML): the driver issues reads on a fixed ~15 ms cadence. If a read lands mid-scan the IC clock-stretches SCL until the current cycle ends, freezing any device sharing the bus. The driver compensates with a conservative report interval and a longer per-transaction timeout, but you may still see latency spikes — particularly during long holds.

If your PCB doesn't route RDY, hand-soldering a jumper to a spare GPIO is generally worth it.

References