From 4b89748b5ff561fdca86edef51d1c48ef2d7cb7a Mon Sep 17 00:00:00 2001 From: aiirononeko Date: Mon, 1 Jun 2026 00:27:33 +0900 Subject: [PATCH 1/5] Add minimal IQS9151 trackpad support --- .../docs/configuration/input_device/index.md | 1 + .../configuration/input_device/iqs9151.md | 89 ++++++ rmk-config/src/lib.rs | 71 +++++ rmk-config/src/resolved/hardware.rs | 6 +- rmk-macro/src/codegen/chip/bind_interrupt.rs | 16 + rmk-macro/src/codegen/input_device/iqs9151.rs | 165 ++++++++++ rmk-macro/src/codegen/input_device/mod.rs | 54 ++++ rmk-macro/src/codegen/split/peripheral.rs | 39 ++- rmk/src/input_device/iqs9151.rs | 295 ++++++++++++++++++ rmk/src/input_device/mod.rs | 1 + 10 files changed, 733 insertions(+), 4 deletions(-) create mode 100644 docs/docs/main/docs/configuration/input_device/iqs9151.md create mode 100644 rmk-macro/src/codegen/input_device/iqs9151.rs create mode 100644 rmk/src/input_device/iqs9151.rs diff --git a/docs/docs/main/docs/configuration/input_device/index.md b/docs/docs/main/docs/configuration/input_device/index.md index f34feec47..4888941e3 100644 --- a/docs/docs/main/docs/configuration/input_device/index.md +++ b/docs/docs/main/docs/configuration/input_device/index.md @@ -6,6 +6,7 @@ All input devices are defined in the `[input_device]` table. Currently supported - [Joystick (joystick)](./joystick.md) - [PMW3610 Optical Mouse Sensor (pmw3610)](./pmw3610.md) - [Azoteq IQS5xx Trackpad (iqs5xx)](./iqs5xx.md) +- [Azoteq IQS9151 Trackpad (iqs9151)](./iqs9151.md) Please refer to the corresponding documentation for detailed configuration settings. diff --git a/docs/docs/main/docs/configuration/input_device/iqs9151.md b/docs/docs/main/docs/configuration/input_device/iqs9151.md new file mode 100644 index 000000000..5db19e88d --- /dev/null +++ b/docs/docs/main/docs/configuration/input_device/iqs9151.md @@ -0,0 +1,89 @@ +# Azoteq IQS9151 Trackpad + +The Azoteq IQS9151 is an I²C capacitive trackpad controller used by some +keyboard trackpad modules. + +::: note + +- Currently only relative cursor movement is reported. Gestures, scrolling, + virtual buttons, dynamic scaling, and persistent IC configuration are not + supported. +- The driver performs a product-number check (`0x09bc`) and a minimal runtime + setup. It does not ship a panel-specific Azoteq configuration image. +- An active-low `RDY` 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. Event mode is only enabled when `RDY` is configured. +- Each `[[input_device.iqs9151]]` claims its own I²C peripheral. Sharing a bus + with another I²C device (e.g. an OLED) isn't supported yet. + +::: + +## Hardware + +- `SDA` / `SCL` — I²C bus, 7-bit address `0x56`. +- `RDY` — active-low digital output used to identify the I²C communication + window. Connect to a GPIO that supports async level waits + (`embedded_hal_async::digital::Wait`). + +## `toml` configuration + +```toml +[[input_device.iqs9151]] +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: active-low RDY 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: + +```toml +[[split.central.input_device.iqs9151]] +name = ... + +# resp. +[[split.peripheral.input_device.iqs9151]] +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. + +```rust +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::i2c::{Config, I2c}; +use rmk::input_device::iqs9151::Iqs9151; +use rmk::input_device::pointing::{PointingProcessor, PointingProcessorConfig}; + +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); +let rdy = Some(Input::new(p.PIN_15, Pull::None)); + +const POINTING_DEV_ID: u8 = 0; +let mut trackpad = Iqs9151::new(POINTING_DEV_ID, i2c, rdy); + +let mut trackpad_proc = PointingProcessor::new( + &keymap, + PointingProcessorConfig::default(), +); + +run_all!(trackpad, trackpad_proc, /* matrix, ... */); +``` diff --git a/rmk-config/src/lib.rs b/rmk-config/src/lib.rs index 8473bb383..138ed4b9a 100644 --- a/rmk-config/src/lib.rs +++ b/rmk-config/src/lib.rs @@ -859,6 +859,7 @@ pub struct InputDeviceConfig { pub pmw3610: Option>, pub pmw33xx: Option>, pub iqs5xx: Option>, + pub iqs9151: Option>, } #[derive(Clone, Debug, Default, Deserialize)] @@ -997,6 +998,43 @@ pub struct Iqs5xxI2cConfig { pub scl: String, } +/// Azoteq IQS9151 trackpad configuration. +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Iqs9151Config { + /// Name of the trackpad (used for variable naming). + pub name: String, + /// RMK pointing-device id (0-255). Defaults to 0. + pub id: Option, + /// I²C bus the trackpad is connected to. The bus is dedicated to this + /// device — sharing with other I²C peripherals (e.g. an OLED) is not yet + /// supported via TOML. + pub i2c: Iqs9151I2cConfig, + /// Optional active-low `RDY` pin. Strongly recommended; without it the + /// driver falls back to timed polling and may stall the bus through + /// clock-stretching. + pub rdy: Option, + /// Invert X in the PointingProcessor. + #[serde(default)] + pub proc_invert_x: bool, + /// Invert Y in the PointingProcessor. + #[serde(default)] + pub proc_invert_y: bool, + /// Swap X and Y in the PointingProcessor. + #[serde(default)] + pub proc_swap_xy: bool, +} + +/// I²C bus configuration for the IQS9151. Distinct from the generic +/// `I2cConfig` because the default IQS9151 address is fixed at `0x56`. +#[derive(Clone, Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Iqs9151I2cConfig { + pub instance: String, + pub sda: String, + pub scl: String, +} + #[derive(Clone, Debug, Default, Deserialize)] #[serde(deny_unknown_fields)] pub struct EncoderConfig { @@ -1221,4 +1259,37 @@ subs = 2 assert_eq!(config.event.layer_change.pubs, 2); assert_eq!(config.event.layer_change.subs, 2); } + + #[test] + fn test_iqs9151_input_device_config() { + let user_toml = r#" +[[input_device.iqs9151]] +name = "trackpad" +id = 7 +rdy = "P1_11" +i2c.instance = "TWISPI0" +i2c.sda = "P0_04" +i2c.scl = "P0_05" +proc_invert_x = true +"#; + let config: KeyboardTomlConfig = Config::builder() + .add_source(File::from_str(user_toml, FileFormat::Toml)) + .build() + .unwrap() + .try_deserialize() + .unwrap(); + + let iqs9151 = config.input_device.unwrap().iqs9151.unwrap(); + + assert_eq!(iqs9151.len(), 1); + assert_eq!(iqs9151[0].name, "trackpad"); + assert_eq!(iqs9151[0].id, Some(7)); + assert_eq!(iqs9151[0].rdy.as_deref(), Some("P1_11")); + assert_eq!(iqs9151[0].i2c.instance, "TWISPI0"); + assert_eq!(iqs9151[0].i2c.sda, "P0_04"); + assert_eq!(iqs9151[0].i2c.scl, "P0_05"); + assert!(iqs9151[0].proc_invert_x); + assert!(!iqs9151[0].proc_invert_y); + assert!(!iqs9151[0].proc_swap_xy); + } } diff --git a/rmk-config/src/resolved/hardware.rs b/rmk-config/src/resolved/hardware.rs index 54573d132..63bfc09a8 100644 --- a/rmk-config/src/resolved/hardware.rs +++ b/rmk-config/src/resolved/hardware.rs @@ -9,9 +9,9 @@ pub use crate::chip::{ChipModel, ChipSeries}; pub use crate::communication::{CommunicationConfig, UsbInfo}; pub use crate::{ BleConfig, ChipConfig, CommunicationProtocol, DependencyConfig, DisplayConfig, DisplayDriver, EncoderConfig, - EncoderResolution, I2cConfig, InputDeviceConfig, Iqs5xxConfig, Iqs5xxI2cConfig, JoystickConfig, KeyInfo, - LightConfig, MatrixConfig, MatrixType, OutputConfig, PinConfig, Pmw33xxConfig, Pmw33xxType, Pmw3610Config, - PointingDeviceConfig, SerialConfig, SpiConfig, SplitBoardConfig, SplitConfig, + EncoderResolution, I2cConfig, InputDeviceConfig, Iqs5xxConfig, Iqs5xxI2cConfig, Iqs9151Config, Iqs9151I2cConfig, + JoystickConfig, KeyInfo, LightConfig, MatrixConfig, MatrixType, OutputConfig, PinConfig, Pmw33xxConfig, + Pmw33xxType, Pmw3610Config, PointingDeviceConfig, SerialConfig, SpiConfig, SplitBoardConfig, SplitConfig, }; /// Resolved storage hardware config diff --git a/rmk-macro/src/codegen/chip/bind_interrupt.rs b/rmk-macro/src/codegen/chip/bind_interrupt.rs index f37f01351..247938ec0 100644 --- a/rmk-macro/src/codegen/chip/bind_interrupt.rs +++ b/rmk-macro/src/codegen/chip/bind_interrupt.rs @@ -12,6 +12,7 @@ use syn::ItemMod; use crate::codegen::display::expand_display_interrupt; use crate::codegen::feature::{get_rmk_features, is_feature_enabled}; use crate::codegen::input_device::iqs5xx::expand_iqs5xx_interrupts; +use crate::codegen::input_device::iqs9151::expand_iqs9151_interrupts; /// Expand `bind_interrupt!` stuffs, and other code before `main` function pub(crate) fn expand_bind_interrupt(hardware: &Hardware, item_mod: &ItemMod) -> TokenStream2 { @@ -92,6 +93,19 @@ pub(crate) fn bind_interrupt_default(hardware: &Hardware, item_mod: &ItemMod) -> .unwrap_or(Vec::new()), }; let iqs5xx_interrupt = expand_iqs5xx_interrupts(&chip.series, &iqs5xx_config); + let iqs9151_config = match board { + BoardConfig::UniBody(UniBodyConfig { input_device, .. }) => { + input_device.clone().iqs9151.unwrap_or(Vec::new()) + } + BoardConfig::Split(split_config) => split_config + .central + .input_device + .clone() + .unwrap_or(InputDeviceConfig::default()) + .iqs9151 + .unwrap_or(Vec::new()), + }; + let iqs9151_interrupt = expand_iqs9151_interrupts(&chip.series, &iqs9151_config); match chip.series { rmk_config::resolved::hardware::ChipSeries::Stm32 => { @@ -246,6 +260,7 @@ pub(crate) fn bind_interrupt_default(hardware: &Hardware, item_mod: &ItemMod) -> RTC0 => ::nrf_sdc::mpsl::HighPrioInterruptHandler; #pmw33xx_spi_interrupts #iqs5xx_interrupt + #iqs9151_interrupt #display_interrupt #extern_irqs }); @@ -304,6 +319,7 @@ pub(crate) fn bind_interrupt_default(hardware: &Hardware, item_mod: &ItemMod) -> #dma_irq_0 #pio0_irq_0 #iqs5xx_interrupt + #iqs9151_interrupt #display_interrupt }); #ble_task diff --git a/rmk-macro/src/codegen/input_device/iqs9151.rs b/rmk-macro/src/codegen/input_device/iqs9151.rs new file mode 100644 index 000000000..f5065e5d8 --- /dev/null +++ b/rmk-macro/src/codegen/input_device/iqs9151.rs @@ -0,0 +1,165 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use rmk_config::resolved::hardware::{ChipModel, ChipSeries, Iqs9151Config}; + +use super::Initializer; + +/// Expand IQS9151 device configuration. +/// Returns (device initializers, processor initializers). +pub(crate) fn expand_iqs9151_device( + iqs9151_config: Vec, + chip: &ChipModel, +) -> (Vec, Vec) { + if iqs9151_config.is_empty() { + return (Vec::new(), Vec::new()); + } + + match chip.series { + ChipSeries::Nrf52 | ChipSeries::Rp2040 => {} + _ => { + panic!("IQS9151 is only supported on nRF52 and RP2040 chips"); + } + } + + let mut device_initializers = vec![]; + let mut processor_initializers = vec![]; + + for (idx, sensor) in iqs9151_config.iter().enumerate() { + let sensor_id = sensor.id.unwrap_or(0); + let sensor_name = if sensor.name.is_empty() { + format!("iqs9151_{}_id{}", idx, sensor_id) + } else { + format!("{}_id{}", sensor.name.clone(), sensor_id) + }; + + let device_ident = format_ident!("{}_device", sensor_name); + let i2c_ident = format_ident!("{}_i2c", sensor_name); + let rdy_ident = format_ident!("{}_rdy", sensor_name); + let processor_ident = format_ident!("{}_processor", sensor_name); + let processor_ident_config = format_ident!("{}_config", processor_ident); + + let instance_ident = format_ident!("{}", sensor.i2c.instance.to_uppercase()); + let sda_ident = format_ident!("{}", sensor.i2c.sda); + let scl_ident = format_ident!("{}", sensor.i2c.scl); + + let proc_invert_x = sensor.proc_invert_x; + let proc_invert_y = sensor.proc_invert_y; + let proc_swap_xy = sensor.proc_swap_xy; + + let rdy_init = match (&sensor.rdy, &chip.series) { + (Some(rdy_pin), ChipSeries::Nrf52) => { + let rdy_pin_ident = format_ident!("{}", rdy_pin); + quote! { + let #rdy_ident = Some(::embassy_nrf::gpio::Input::new( + p.#rdy_pin_ident, + ::embassy_nrf::gpio::Pull::None, + )); + } + } + (Some(rdy_pin), ChipSeries::Rp2040) => { + let rdy_pin_ident = format_ident!("{}", rdy_pin); + quote! { + let #rdy_ident = Some(::embassy_rp::gpio::Input::new( + p.#rdy_pin_ident, + ::embassy_rp::gpio::Pull::None, + )); + } + } + (None, ChipSeries::Nrf52) => quote! { + let #rdy_ident: Option<::embassy_nrf::gpio::Input<'static>> = None; + }, + (None, ChipSeries::Rp2040) => quote! { + let #rdy_ident: Option<::embassy_rp::gpio::Input<'static>> = None; + }, + _ => unreachable!(), + }; + + let device_init = match chip.series { + ChipSeries::Nrf52 => quote! { + #rdy_init + static #i2c_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); + let #i2c_ident = #i2c_ident.init([0u8; 16]); + let #i2c_ident = ::embassy_nrf::twim::Twim::new( + p.#instance_ident, + Irqs, + p.#sda_ident, + p.#scl_ident, + ::embassy_nrf::twim::Config::default(), + #i2c_ident, + ); + let mut #device_ident = ::rmk::input_device::iqs9151::Iqs9151::new( + #sensor_id, + #i2c_ident, + #rdy_ident, + ); + }, + ChipSeries::Rp2040 => quote! { + #rdy_init + let #i2c_ident = ::embassy_rp::i2c::I2c::new_async( + p.#instance_ident, + p.#scl_ident, + p.#sda_ident, + Irqs, + ::embassy_rp::i2c::Config::default(), + ); + let mut #device_ident = ::rmk::input_device::iqs9151::Iqs9151::new( + #sensor_id, + #i2c_ident, + #rdy_ident, + ); + }, + _ => unreachable!(), + }; + + device_initializers.push(Initializer { + initializer: device_init, + var_name: device_ident, + }); + + let processor_init = quote! { + let #processor_ident_config = ::rmk::input_device::pointing::PointingProcessorConfig { + invert_x: #proc_invert_x, + invert_y: #proc_invert_y, + swap_xy: #proc_swap_xy, + }; + let mut #processor_ident = ::rmk::input_device::pointing::PointingProcessor::new( + &keymap, + #processor_ident_config, + ); + }; + + processor_initializers.push(Initializer { + initializer: processor_init, + var_name: processor_ident, + }); + } + + (device_initializers, processor_initializers) +} + +/// Generate `bind_interrupts!` entries for the I²C peripherals used by IQS9151 +/// devices on `chip`. Returns an empty token stream if there are no devices. +pub(crate) fn expand_iqs9151_interrupts( + chip_series: &ChipSeries, + iqs9151_config: &[Iqs9151Config], +) -> TokenStream { + if iqs9151_config.is_empty() { + return quote! {}; + } + let entries = iqs9151_config.iter().map(|sensor| { + let instance = format_ident!("{}", sensor.i2c.instance.to_uppercase()); + match chip_series { + ChipSeries::Nrf52 => quote! { + #instance => ::embassy_nrf::twim::InterruptHandler<::embassy_nrf::peripherals::#instance>; + }, + ChipSeries::Rp2040 => { + let irq = format_ident!("{}_IRQ", sensor.i2c.instance.to_uppercase()); + quote! { + #irq => ::embassy_rp::i2c::InterruptHandler<::embassy_rp::peripherals::#instance>; + } + } + _ => quote! {}, + } + }); + quote! { #(#entries)* } +} diff --git a/rmk-macro/src/codegen/input_device/mod.rs b/rmk-macro/src/codegen/input_device/mod.rs index 7ac304dec..8fdd68744 100644 --- a/rmk-macro/src/codegen/input_device/mod.rs +++ b/rmk-macro/src/codegen/input_device/mod.rs @@ -1,6 +1,7 @@ use adc::expand_adc_device; use encoder::expand_encoder_device; use iqs5xx::expand_iqs5xx_device; +use iqs9151::expand_iqs9151_device; use pmw33xx::expand_pmw33xx_device; use pmw3610::expand_pmw3610_device; use proc_macro2::{Ident, TokenStream}; @@ -13,6 +14,7 @@ use rmk_config::resolved::hardware::{ pub(crate) mod adc; pub(crate) mod encoder; pub(crate) mod iqs5xx; +pub(crate) mod iqs9151; pub(crate) mod pmw33xx; pub(crate) mod pmw3610; @@ -295,5 +297,57 @@ pub(crate) fn expand_input_device_config( } } + // generate IQS9151 configuration + let (iqs9151_device_initializers, iqs9151_processor_initializers) = match board { + BoardConfig::UniBody(UniBodyConfig { input_device, .. }) => { + expand_iqs9151_device(input_device.clone().iqs9151.unwrap_or(Vec::new()), chip) + } + BoardConfig::Split(split_config) => expand_iqs9151_device( + split_config + .central + .input_device + .clone() + .unwrap_or(InputDeviceConfig::default()) + .iqs9151 + .unwrap_or(Vec::new()), + chip, + ), + }; + + for initializer in iqs9151_device_initializers { + initialization.extend(initializer.initializer); + let device_name = initializer.var_name; + devices.push(quote! { #device_name }); + } + + for initializer in iqs9151_processor_initializers { + initialization.extend(initializer.initializer); + let processor_name = initializer.var_name; + processors.push(quote! { #processor_name }); + } + + // For split keyboards, also generate processors for IQS9151 devices on peripherals + // The devices run on peripherals, but processors need to run on central to handle the events + if let BoardConfig::Split(split_config) = board { + for peripheral in &split_config.peripheral { + let peripheral_iqs9151_config = peripheral + .input_device + .clone() + .unwrap_or(InputDeviceConfig::default()) + .iqs9151 + .unwrap_or(Vec::new()); + + // Only generate processors (not devices) for peripheral IQS9151 + let (_, peripheral_iqs9151_processors) = + expand_iqs9151_device(peripheral_iqs9151_config, chip); + + for initializer in peripheral_iqs9151_processors { + initialization.extend(initializer.initializer); + let processor_name = initializer.var_name; + processors.push(quote! { #processor_name }); + } + } + } + (initialization, devices, processors) } diff --git a/rmk-macro/src/codegen/split/peripheral.rs b/rmk-macro/src/codegen/split/peripheral.rs index 1de6473d3..4b1a28121 100644 --- a/rmk-macro/src/codegen/split/peripheral.rs +++ b/rmk-macro/src/codegen/split/peripheral.rs @@ -18,6 +18,7 @@ use crate::codegen::import::expand_custom_imports; use crate::codegen::input_device::adc::expand_adc_device; use crate::codegen::input_device::encoder::expand_encoder_device; use crate::codegen::input_device::iqs5xx::{expand_iqs5xx_device, expand_iqs5xx_interrupts}; +use crate::codegen::input_device::iqs9151::{expand_iqs9151_device, expand_iqs9151_interrupts}; use crate::codegen::input_device::pmw33xx::expand_pmw33xx_device; use crate::codegen::input_device::pmw3610::expand_pmw3610_device; use crate::codegen::keyboard_config::read_keyboard_toml_config; @@ -104,6 +105,16 @@ fn expand_bind_interrupt_for_split_peripheral( _ => Vec::new(), }; let iqs5xx_interrupt = expand_iqs5xx_interrupts(&chip.series, &iqs5xx_config_for_irq); + let iqs9151_config_for_irq = match &hardware.board { + BoardConfig::Split(split_config) => split_config.peripheral[peripheral_id] + .input_device + .clone() + .unwrap_or(InputDeviceConfig::default()) + .iqs9151 + .unwrap_or(Vec::new()), + _ => Vec::new(), + }; + let iqs9151_interrupt = expand_iqs9151_interrupts(&chip.series, &iqs9151_config_for_irq); match chip.series { ChipSeries::Nrf52 => { @@ -171,6 +182,7 @@ fn expand_bind_interrupt_for_split_peripheral( RTC0 => ::nrf_sdc::mpsl::HighPrioInterruptHandler; #pmw33xx_spi_interrupts #iqs5xx_interrupt + #iqs9151_interrupt #display_interrupt }); @@ -215,6 +227,7 @@ fn expand_bind_interrupt_for_split_peripheral( PIO0_IRQ_0 => ::embassy_rp::pio::InterruptHandler<::embassy_rp::peripherals::PIO0>; DMA_IRQ_0 => ::embassy_rp::dma::InterruptHandler<::embassy_rp::peripherals::DMA_CH0>, ::embassy_rp::dma::InterruptHandler<::embassy_rp::peripherals::DMA_CH1>; #iqs5xx_interrupt + #iqs9151_interrupt #display_interrupt }); #[::embassy_executor::task] @@ -222,11 +235,15 @@ fn expand_bind_interrupt_for_split_peripheral( runner.run().await } } - } else if !display_interrupt.is_empty() || !iqs5xx_interrupt.is_empty() { + } else if !display_interrupt.is_empty() + || !iqs5xx_interrupt.is_empty() + || !iqs9151_interrupt.is_empty() + { quote! { use ::embassy_rp::bind_interrupts; bind_interrupts!(struct Irqs { #iqs5xx_interrupt + #iqs9151_interrupt #display_interrupt }); } @@ -657,5 +674,25 @@ pub(crate) fn expand_peripheral_input_device_config( devices.push(quote! { #device_name }); } + // generate IQS9151 configuration + let (iqs9151_devices, _iqs9151_processors) = match board { + BoardConfig::Split(split_config) => expand_iqs9151_device( + split_config.peripheral[id] + .input_device + .clone() + .unwrap_or(InputDeviceConfig::default()) + .iqs9151 + .unwrap_or(Vec::new()), + chip, + ), + _ => (vec![], vec![]), + }; + + for initializer in iqs9151_devices { + initializations.extend(initializer.initializer); + let device_name = initializer.var_name; + devices.push(quote! { #device_name }); + } + (initializations, devices, processors) } diff --git a/rmk/src/input_device/iqs9151.rs b/rmk/src/input_device/iqs9151.rs new file mode 100644 index 000000000..2c70e9938 --- /dev/null +++ b/rmk/src/input_device/iqs9151.rs @@ -0,0 +1,295 @@ +//! Azoteq IQS9151 trackpad controller driver. +//! +//! This driver intentionally implements only the generic pointer path: +//! product-number verification, minimal runtime setup, coordinate-frame reads, +//! and relative X/Y movement published as `PointingEvent`. +//! +//! Gestures, virtual keys, scrolling, dynamic scaling, split custom transports, +//! and device-specific configuration images are deliberately out of scope. + +use embassy_time::{Duration, Instant, Timer}; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::i2c::I2c; +use rmk_macro::input_device; + +use crate::event::{Axis, AxisEvent, AxisValType, PointingEvent}; +use crate::fmt::Debug; + +/// Default 7-bit I2C address used by IQS9151. +pub const I2C_ADDR: u8 = 0x56; +/// Expected IQS9151 product number. +pub const PRODUCT_NUMBER: u16 = 0x09bc; + +const ADDR_PRODUCT_NUMBER: u16 = 0x1000; +const ADDR_RELATIVE_X: u16 = 0x1014; +const ADDR_INFO_FLAGS: u16 = 0x1020; +const ADDR_TRACKPAD_FLAGS: u16 = 0x1022; +const ADDR_SYSTEM_CONTROL: u16 = 0x11bc; +const ADDR_CONFIG_SETTINGS: u16 = 0x11be; + +const COORD_BLOCK_START: u16 = ADDR_RELATIVE_X; +const COORD_BLOCK_LENGTH: usize = 0x1c; + +const INFO_SHOW_RESET: u16 = 1 << 7; +const TP_FINGER_COUNT_MASK: u16 = 0x000f; +const TP_MOVEMENT_DETECTED: u16 = 1 << 4; +const SYS_CTRL_ACK_RESET: u16 = 1 << 7; +const CFG_TP_TOUCH_EVENT_EN: u16 = 1 << 13; +const CFG_TP_EVENT_EN: u16 = 1 << 10; +const CFG_EVENT_MODE: u16 = 1 << 8; + +#[input_device(publish = PointingEvent)] +pub struct Iqs9151 +where + I: I2c, + I::Error: Debug, + RDY: Wait, +{ + /// The RMK pointing device id of this device (*not* the I2C bus address). + pointing_device_id: u8, + + i2c: I, + + window_detection: WindowDetection, + + initialized: bool, +} + +/// Manner of detecting a communication-ready window. +pub enum WindowDetection { + /// Wait for a low state of the given GPIO connected to the active-low `RDY` pin. + Rdy(RDY), + + /// Poll every `interval`; the device may clock-stretch if polled mid-cycle. + Poll { last_poll: Instant, interval_ms: u16 }, +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +enum Error { + I2c { tag: &'static str, inner: I2cError }, + InvalidProductNumber(u16), + Pin, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +struct CoordinateFrame { + relative_x: i16, + relative_y: i16, + info_flags: u16, + trackpad_flags: u16, +} + +impl CoordinateFrame { + fn parse(block: &[u8; COORD_BLOCK_LENGTH]) -> Self { + Self { + relative_x: i16::from_le_bytes(unwrap!(block[0x00..0x02].try_into())), + relative_y: i16::from_le_bytes(unwrap!(block[0x02..0x04].try_into())), + info_flags: u16::from_le_bytes(unwrap!( + block[(ADDR_INFO_FLAGS - COORD_BLOCK_START) as usize + ..(ADDR_INFO_FLAGS - COORD_BLOCK_START) as usize + 2] + .try_into() + )), + trackpad_flags: u16::from_le_bytes(unwrap!( + block[(ADDR_TRACKPAD_FLAGS - COORD_BLOCK_START) as usize + ..(ADDR_TRACKPAD_FLAGS - COORD_BLOCK_START) as usize + 2] + .try_into() + )), + } + } + + const fn finger_count(self) -> u8 { + (self.trackpad_flags & TP_FINGER_COUNT_MASK) as u8 + } + + const fn movement_detected(self) -> bool { + (self.trackpad_flags & TP_MOVEMENT_DETECTED) != 0 + } + + const fn show_reset(self) -> bool { + (self.info_flags & INFO_SHOW_RESET) != 0 + } +} + +impl Iqs9151 +where + I: I2c, + I::Error: Debug, + RDY: Wait, +{ + pub fn new(rmk_id: u8, i2c: I, rdy: Option) -> Self { + Self { + i2c, + window_detection: match rdy { + None => WindowDetection::Poll { + last_poll: Instant::now(), + interval_ms: 10, + }, + Some(rdy) => WindowDetection::Rdy(rdy), + }, + initialized: false, + pointing_device_id: rmk_id, + } + } + + async fn wait_ready(&mut self) -> Result<(), Error> { + match self.window_detection { + WindowDetection::Rdy(ref mut rdy) => rdy.wait_for_low().await.map_err(|_| Error::Pin), + WindowDetection::Poll { + ref mut last_poll, + interval_ms, + } => { + Timer::at(last_poll.saturating_add(Duration::from_millis(u64::from(interval_ms)))).await; + *last_poll = Instant::now(); + Ok(()) + } + } + } + + async fn read_u16(&mut self, tag: &'static str, register: u16) -> Result> { + let mut bytes = [0u8; 2]; + self.read_block(tag, register, &mut bytes).await?; + Ok(u16::from_le_bytes(bytes)) + } + + async fn write_u16(&mut self, tag: &'static str, register: u16, value: u16) -> Result<(), Error> { + let register = register.to_le_bytes(); + let value = value.to_le_bytes(); + self.i2c + .write(I2C_ADDR, &[register[0], register[1], value[0], value[1]]) + .await + .map_err(|inner| Error::I2c { tag, inner }) + } + + async fn update_bits_u16( + &mut self, + tag: &'static str, + register: u16, + mask: u16, + value: u16, + ) -> Result<(), Error> { + let current = self.read_u16(tag, register).await?; + self.write_u16(tag, register, (current & !mask) | (value & mask)).await + } + + async fn read_block(&mut self, tag: &'static str, register: u16, bytes: &mut [u8]) -> Result<(), Error> { + self.i2c + .write_read(I2C_ADDR, ®ister.to_le_bytes(), bytes) + .await + .map_err(|inner| Error::I2c { tag, inner }) + } + + async fn init(&mut self) -> Result<(), Error> { + self.wait_ready().await?; + let product_number = self.read_u16("read_product_number", ADDR_PRODUCT_NUMBER).await?; + if product_number != PRODUCT_NUMBER { + return Err(Error::InvalidProductNumber(product_number)); + } + + self.wait_ready().await?; + let info_flags = self.read_u16("read_info_flags", ADDR_INFO_FLAGS).await?; + if (info_flags & INFO_SHOW_RESET) != 0 { + self.wait_ready().await?; + self.update_bits_u16("ack_reset", ADDR_SYSTEM_CONTROL, SYS_CTRL_ACK_RESET, SYS_CTRL_ACK_RESET) + .await?; + } + + self.wait_ready().await?; + let config_mask = CFG_TP_TOUCH_EVENT_EN | CFG_TP_EVENT_EN | CFG_EVENT_MODE; + let config_value = match self.window_detection { + WindowDetection::Rdy(_) => config_mask, + WindowDetection::Poll { .. } => CFG_TP_TOUCH_EVENT_EN | CFG_TP_EVENT_EN, + }; + self.update_bits_u16("configure_events", ADDR_CONFIG_SETTINGS, config_mask, config_value) + .await?; + + self.initialized = true; + info!("iqs9151 {}: initialized", self.pointing_device_id); + Ok(()) + } + + async fn read_coordinate_frame(&mut self) -> Result> { + self.wait_ready().await?; + let mut block = [0u8; COORD_BLOCK_LENGTH]; + self.read_block("read_coordinate_frame", COORD_BLOCK_START, &mut block) + .await?; + Ok(CoordinateFrame::parse(&block)) + } + + async fn read_pointing_event(&mut self) -> PointingEvent { + loop { + if !self.initialized + && let Err(e) = self.init().await + { + error!( + "iqs9151 {} initialization failed: {:?}; will retry in 1 second", + self.pointing_device_id, e, + ); + Timer::after_secs(1).await; + continue; + } + + match self.read_coordinate_frame().await { + Ok(frame) => { + if frame.show_reset() { + self.initialized = false; + continue; + } + debug!( + "iqs9151 {} frame: fingers={} movement={} dx={} dy={}", + self.pointing_device_id, + frame.finger_count(), + frame.movement_detected(), + frame.relative_x, + frame.relative_y, + ); + if frame.relative_x != 0 || frame.relative_y != 0 { + return PointingEvent([ + AxisEvent { + typ: AxisValType::Rel, + axis: Axis::X, + value: frame.relative_x, + }, + AxisEvent { + typ: AxisValType::Rel, + axis: Axis::Y, + value: frame.relative_y, + }, + AxisEvent { + typ: AxisValType::Rel, + axis: Axis::Z, + value: 0, + }, + ]); + } + } + Err(e) => { + error!("iqs9151 {} failure: {:?}", self.pointing_device_id, e); + Timer::after_millis(5).await; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn coordinate_frame_parses_relative_xy_and_flags() { + let mut block = [0u8; COORD_BLOCK_LENGTH]; + block[0x00..0x02].copy_from_slice(&(-12_i16).to_le_bytes()); + block[0x02..0x04].copy_from_slice(&34_i16.to_le_bytes()); + block[0x0c..0x0e].copy_from_slice(&INFO_SHOW_RESET.to_le_bytes()); + block[0x0e..0x10].copy_from_slice(&(TP_MOVEMENT_DETECTED | 2).to_le_bytes()); + + let frame = CoordinateFrame::parse(&block); + + assert_eq!(frame.relative_x, -12); + assert_eq!(frame.relative_y, 34); + assert!(frame.show_reset()); + assert!(frame.movement_detected()); + assert_eq!(frame.finger_count(), 2); + } +} diff --git a/rmk/src/input_device/mod.rs b/rmk/src/input_device/mod.rs index fb1df79e8..848e39f74 100644 --- a/rmk/src/input_device/mod.rs +++ b/rmk/src/input_device/mod.rs @@ -9,6 +9,7 @@ pub mod adc; #[cfg(feature = "_ble")] pub mod battery; pub mod iqs5xx; +pub mod iqs9151; pub mod joystick; pub mod pmw33xx; pub mod pmw3610; From b80d645a30fb77a9d564b2eaad0991074c77f480 Mon Sep 17 00:00:00 2001 From: aiirononeko Date: Mon, 1 Jun 2026 01:28:17 +0900 Subject: [PATCH 2/5] Fix nRF I2C buffer names for Azoteq codegen --- rmk-macro/src/codegen/input_device/iqs5xx.rs | 8 +++++--- rmk-macro/src/codegen/input_device/iqs9151.rs | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/rmk-macro/src/codegen/input_device/iqs5xx.rs b/rmk-macro/src/codegen/input_device/iqs5xx.rs index 932292df8..18199ed92 100644 --- a/rmk-macro/src/codegen/input_device/iqs5xx.rs +++ b/rmk-macro/src/codegen/input_device/iqs5xx.rs @@ -34,6 +34,8 @@ pub(crate) fn expand_iqs5xx_device( let device_ident = format_ident!("{}_device", sensor_name); let i2c_ident = format_ident!("{}_i2c", sensor_name); + let i2c_buf_ident = format_ident!("{}_i2c_buf", sensor_name); + let i2c_buf_ref_ident = format_ident!("{}_i2c_buf_ref", sensor_name); let rdy_ident = format_ident!("{}_rdy", sensor_name); let processor_ident = format_ident!("{}_processor", sensor_name); let processor_ident_config = format_ident!("{}_config", processor_ident); @@ -77,15 +79,15 @@ pub(crate) fn expand_iqs5xx_device( let device_init = match chip.series { ChipSeries::Nrf52 => quote! { #rdy_init - static #i2c_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); - let #i2c_ident = #i2c_ident.init([0u8; 16]); + static #i2c_buf_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); + let #i2c_buf_ref_ident = #i2c_buf_ident.init([0u8; 16]); let #i2c_ident = ::embassy_nrf::twim::Twim::new( p.#instance_ident, Irqs, p.#sda_ident, p.#scl_ident, ::embassy_nrf::twim::Config::default(), - #i2c_ident, + #i2c_buf_ref_ident, ); let mut #device_ident = ::rmk::input_device::iqs5xx::Iqs5xx::new( #sensor_id, diff --git a/rmk-macro/src/codegen/input_device/iqs9151.rs b/rmk-macro/src/codegen/input_device/iqs9151.rs index f5065e5d8..bbc384c46 100644 --- a/rmk-macro/src/codegen/input_device/iqs9151.rs +++ b/rmk-macro/src/codegen/input_device/iqs9151.rs @@ -34,6 +34,8 @@ pub(crate) fn expand_iqs9151_device( let device_ident = format_ident!("{}_device", sensor_name); let i2c_ident = format_ident!("{}_i2c", sensor_name); + let i2c_buf_ident = format_ident!("{}_i2c_buf", sensor_name); + let i2c_buf_ref_ident = format_ident!("{}_i2c_buf_ref", sensor_name); let rdy_ident = format_ident!("{}_rdy", sensor_name); let processor_ident = format_ident!("{}_processor", sensor_name); let processor_ident_config = format_ident!("{}_config", processor_ident); @@ -77,15 +79,15 @@ pub(crate) fn expand_iqs9151_device( let device_init = match chip.series { ChipSeries::Nrf52 => quote! { #rdy_init - static #i2c_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); - let #i2c_ident = #i2c_ident.init([0u8; 16]); + static #i2c_buf_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); + let #i2c_buf_ref_ident = #i2c_buf_ident.init([0u8; 16]); let #i2c_ident = ::embassy_nrf::twim::Twim::new( p.#instance_ident, Irqs, p.#sda_ident, p.#scl_ident, ::embassy_nrf::twim::Config::default(), - #i2c_ident, + #i2c_buf_ref_ident, ); let mut #device_ident = ::rmk::input_device::iqs9151::Iqs9151::new( #sensor_id, From e9015f5135b20e6e16b774065e1abfcdf46c81a9 Mon Sep 17 00:00:00 2001 From: aiirononeko Date: Mon, 1 Jun 2026 03:11:32 +0900 Subject: [PATCH 3/5] Tune IQS9151 nRF runtime for hardware validation --- rmk-macro/src/codegen/input_device/iqs9151.rs | 8 ++++++-- rmk/src/input_device/iqs9151.rs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rmk-macro/src/codegen/input_device/iqs9151.rs b/rmk-macro/src/codegen/input_device/iqs9151.rs index bbc384c46..e3ca2361e 100644 --- a/rmk-macro/src/codegen/input_device/iqs9151.rs +++ b/rmk-macro/src/codegen/input_device/iqs9151.rs @@ -54,7 +54,7 @@ pub(crate) fn expand_iqs9151_device( quote! { let #rdy_ident = Some(::embassy_nrf::gpio::Input::new( p.#rdy_pin_ident, - ::embassy_nrf::gpio::Pull::None, + ::embassy_nrf::gpio::Pull::Up, )); } } @@ -81,12 +81,16 @@ pub(crate) fn expand_iqs9151_device( #rdy_init static #i2c_buf_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); let #i2c_buf_ref_ident = #i2c_buf_ident.init([0u8; 16]); + let mut #i2c_ident = ::embassy_nrf::twim::Config::default(); + #i2c_ident.frequency = ::embassy_nrf::twim::Frequency::K400; + #i2c_ident.sda_pullup = true; + #i2c_ident.scl_pullup = true; let #i2c_ident = ::embassy_nrf::twim::Twim::new( p.#instance_ident, Irqs, p.#sda_ident, p.#scl_ident, - ::embassy_nrf::twim::Config::default(), + #i2c_ident, #i2c_buf_ref_ident, ); let mut #device_ident = ::rmk::input_device::iqs9151::Iqs9151::new( diff --git a/rmk/src/input_device/iqs9151.rs b/rmk/src/input_device/iqs9151.rs index 2c70e9938..cdb21d866 100644 --- a/rmk/src/input_device/iqs9151.rs +++ b/rmk/src/input_device/iqs9151.rs @@ -262,6 +262,7 @@ where }, ]); } + Timer::after_millis(1).await; } Err(e) => { error!("iqs9151 {} failure: {:?}", self.pointing_device_id, e); From 8c61ce4c79d306f420552e99e2f713b3e5d8870e Mon Sep 17 00:00:00 2001 From: aiirononeko Date: Mon, 1 Jun 2026 03:32:43 +0900 Subject: [PATCH 4/5] Avoid indefinite IQS9151 RDY waits --- rmk/src/input_device/iqs9151.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rmk/src/input_device/iqs9151.rs b/rmk/src/input_device/iqs9151.rs index cdb21d866..6dbd6f3fa 100644 --- a/rmk/src/input_device/iqs9151.rs +++ b/rmk/src/input_device/iqs9151.rs @@ -7,7 +7,7 @@ //! Gestures, virtual keys, scrolling, dynamic scaling, split custom transports, //! and device-specific configuration images are deliberately out of scope. -use embassy_time::{Duration, Instant, Timer}; +use embassy_time::{Duration, Instant, Timer, with_timeout}; use embedded_hal_async::digital::Wait; use embedded_hal_async::i2c::I2c; use rmk_macro::input_device; @@ -37,6 +37,7 @@ const SYS_CTRL_ACK_RESET: u16 = 1 << 7; const CFG_TP_TOUCH_EVENT_EN: u16 = 1 << 13; const CFG_TP_EVENT_EN: u16 = 1 << 10; const CFG_EVENT_MODE: u16 = 1 << 8; +const RDY_TIMEOUT_MS: u64 = 20; #[input_device(publish = PointingEvent)] pub struct Iqs9151 @@ -134,7 +135,12 @@ where async fn wait_ready(&mut self) -> Result<(), Error> { match self.window_detection { - WindowDetection::Rdy(ref mut rdy) => rdy.wait_for_low().await.map_err(|_| Error::Pin), + WindowDetection::Rdy(ref mut rdy) => { + match with_timeout(Duration::from_millis(RDY_TIMEOUT_MS), rdy.wait_for_low()).await { + Ok(Ok(())) | Err(_) => Ok(()), + Ok(Err(_)) => Err(Error::Pin), + } + } WindowDetection::Poll { ref mut last_poll, interval_ms, From 999b6b630942ce18a3cb35a213cc11cf7cf2992d Mon Sep 17 00:00:00 2001 From: aiirononeko Date: Wed, 3 Jun 2026 03:33:34 +0900 Subject: [PATCH 5/5] Refine IQS9151 runtime and processor codegen --- rmk-macro/src/codegen/input_device/iqs9151.rs | 43 +++++++++---------- rmk-macro/src/codegen/input_device/mod.rs | 5 +++ rmk/src/input_device/iqs9151.rs | 26 ++++++++--- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/rmk-macro/src/codegen/input_device/iqs9151.rs b/rmk-macro/src/codegen/input_device/iqs9151.rs index e3ca2361e..1dad89982 100644 --- a/rmk-macro/src/codegen/input_device/iqs9151.rs +++ b/rmk-macro/src/codegen/input_device/iqs9151.rs @@ -37,9 +37,6 @@ pub(crate) fn expand_iqs9151_device( let i2c_buf_ident = format_ident!("{}_i2c_buf", sensor_name); let i2c_buf_ref_ident = format_ident!("{}_i2c_buf_ref", sensor_name); let rdy_ident = format_ident!("{}_rdy", sensor_name); - let processor_ident = format_ident!("{}_processor", sensor_name); - let processor_ident_config = format_ident!("{}_config", processor_ident); - let instance_ident = format_ident!("{}", sensor.i2c.instance.to_uppercase()); let sda_ident = format_ident!("{}", sensor.i2c.sda); let scl_ident = format_ident!("{}", sensor.i2c.scl); @@ -54,7 +51,7 @@ pub(crate) fn expand_iqs9151_device( quote! { let #rdy_ident = Some(::embassy_nrf::gpio::Input::new( p.#rdy_pin_ident, - ::embassy_nrf::gpio::Pull::Up, + ::embassy_nrf::gpio::Pull::None, )); } } @@ -81,16 +78,12 @@ pub(crate) fn expand_iqs9151_device( #rdy_init static #i2c_buf_ident: ::static_cell::StaticCell<[u8; 16]> = ::static_cell::StaticCell::new(); let #i2c_buf_ref_ident = #i2c_buf_ident.init([0u8; 16]); - let mut #i2c_ident = ::embassy_nrf::twim::Config::default(); - #i2c_ident.frequency = ::embassy_nrf::twim::Frequency::K400; - #i2c_ident.sda_pullup = true; - #i2c_ident.scl_pullup = true; let #i2c_ident = ::embassy_nrf::twim::Twim::new( p.#instance_ident, Irqs, p.#sda_ident, p.#scl_ident, - #i2c_ident, + ::embassy_nrf::twim::Config::default(), #i2c_buf_ref_ident, ); let mut #device_ident = ::rmk::input_device::iqs9151::Iqs9151::new( @@ -122,22 +115,26 @@ pub(crate) fn expand_iqs9151_device( var_name: device_ident, }); - let processor_init = quote! { - let #processor_ident_config = ::rmk::input_device::pointing::PointingProcessorConfig { - invert_x: #proc_invert_x, - invert_y: #proc_invert_y, - swap_xy: #proc_swap_xy, + if processor_initializers.is_empty() { + let processor_ident = format_ident!("{}_processor", sensor_name); + let processor_ident_config = format_ident!("{}_config", processor_ident); + let processor_init = quote! { + let #processor_ident_config = ::rmk::input_device::pointing::PointingProcessorConfig { + invert_x: #proc_invert_x, + invert_y: #proc_invert_y, + swap_xy: #proc_swap_xy, + }; + let mut #processor_ident = ::rmk::input_device::pointing::PointingProcessor::new( + &keymap, + #processor_ident_config, + ); }; - let mut #processor_ident = ::rmk::input_device::pointing::PointingProcessor::new( - &keymap, - #processor_ident_config, - ); - }; - processor_initializers.push(Initializer { - initializer: processor_init, - var_name: processor_ident, - }); + processor_initializers.push(Initializer { + initializer: processor_init, + var_name: processor_ident, + }); + } } (device_initializers, processor_initializers) diff --git a/rmk-macro/src/codegen/input_device/mod.rs b/rmk-macro/src/codegen/input_device/mod.rs index 8fdd68744..a09ebedc3 100644 --- a/rmk-macro/src/codegen/input_device/mod.rs +++ b/rmk-macro/src/codegen/input_device/mod.rs @@ -320,6 +320,7 @@ pub(crate) fn expand_input_device_config( devices.push(quote! { #device_name }); } + let mut has_iqs9151_processor = !iqs9151_processor_initializers.is_empty(); for initializer in iqs9151_processor_initializers { initialization.extend(initializer.initializer); let processor_name = initializer.var_name; @@ -330,6 +331,9 @@ pub(crate) fn expand_input_device_config( // The devices run on peripherals, but processors need to run on central to handle the events if let BoardConfig::Split(split_config) = board { for peripheral in &split_config.peripheral { + if has_iqs9151_processor { + break; + } let peripheral_iqs9151_config = peripheral .input_device .clone() @@ -345,6 +349,7 @@ pub(crate) fn expand_input_device_config( initialization.extend(initializer.initializer); let processor_name = initializer.var_name; processors.push(quote! { #processor_name }); + has_iqs9151_processor = true; } } } diff --git a/rmk/src/input_device/iqs9151.rs b/rmk/src/input_device/iqs9151.rs index 6dbd6f3fa..f33a5d3bb 100644 --- a/rmk/src/input_device/iqs9151.rs +++ b/rmk/src/input_device/iqs9151.rs @@ -70,6 +70,7 @@ pub enum WindowDetection { enum Error { I2c { tag: &'static str, inner: I2cError }, InvalidProductNumber(u16), + NotReady, Pin, } @@ -137,7 +138,8 @@ where match self.window_detection { WindowDetection::Rdy(ref mut rdy) => { match with_timeout(Duration::from_millis(RDY_TIMEOUT_MS), rdy.wait_for_low()).await { - Ok(Ok(())) | Err(_) => Ok(()), + Ok(Ok(())) => Ok(()), + Err(_) => Err(Error::NotReady), Ok(Err(_)) => Err(Error::Pin), } } @@ -146,12 +148,17 @@ where interval_ms, } => { Timer::at(last_poll.saturating_add(Duration::from_millis(u64::from(interval_ms)))).await; - *last_poll = Instant::now(); Ok(()) } } } + fn note_communication_attempt(&mut self) { + if let WindowDetection::Poll { ref mut last_poll, .. } = self.window_detection { + *last_poll = Instant::now(); + } + } + async fn read_u16(&mut self, tag: &'static str, register: u16) -> Result> { let mut bytes = [0u8; 2]; self.read_block(tag, register, &mut bytes).await?; @@ -161,10 +168,13 @@ where async fn write_u16(&mut self, tag: &'static str, register: u16, value: u16) -> Result<(), Error> { let register = register.to_le_bytes(); let value = value.to_le_bytes(); - self.i2c + let result = self + .i2c .write(I2C_ADDR, &[register[0], register[1], value[0], value[1]]) .await - .map_err(|inner| Error::I2c { tag, inner }) + .map_err(|inner| Error::I2c { tag, inner }); + self.note_communication_attempt(); + result } async fn update_bits_u16( @@ -175,14 +185,18 @@ where value: u16, ) -> Result<(), Error> { let current = self.read_u16(tag, register).await?; + self.wait_ready().await?; self.write_u16(tag, register, (current & !mask) | (value & mask)).await } async fn read_block(&mut self, tag: &'static str, register: u16, bytes: &mut [u8]) -> Result<(), Error> { - self.i2c + let result = self + .i2c .write_read(I2C_ADDR, ®ister.to_le_bytes(), bytes) .await - .map_err(|inner| Error::I2c { tag, inner }) + .map_err(|inner| Error::I2c { tag, inner }); + self.note_communication_attempt(); + result } async fn init(&mut self) -> Result<(), Error> {