ph-qmi8658 0.1.1

Async no_std driver for the QMI8658C 6-axis IMU (accelerometer + gyroscope) built on embedded-hal-async
Documentation

ph-qmi8658 Driver

Crates.io Docs.rs CI License: MIT

Async #![no_std] driver for the QMI8658C 6-axis IMU (3-axis accelerometer + 3-axis gyroscope + temperature sensor) from QST Corporation. Built on embedded-hal-async with I2C and SPI transport support.

MSRV: 1.92.0

This README covers the driver's usage flows with code examples. For module structure and data-flow diagrams, see ARCHITECTURE.md. If you change driver behavior or flow sequencing, update both documents to keep them aligned with the code.

Driver Capabilities

  • Configurable accelerometer (2/4/8/16 g) and gyroscope (16 to 2048 dps) ranges and ODRs
  • FIFO buffering (stream, FIFO, and watermark modes) with burst and manual read paths
  • Interrupt routing to INT1/INT2 pins with CTRL9 handshake support
  • Wake-on-motion detection for low-power applications
  • Sync-sample data locking for coherent multi-register reads
  • Self-test, host-delta offset calibration, and on-demand calibration
  • Integer scaling helpers (ScaleFactor ratios, no floats); optional fixed-point conversions
  • Init-sequence helper macro to reduce boilerplate

Usage Examples

All examples below assume an async context, an I2C bus, and a DelayNs implementation. Use the Common Setup snippet once, then follow the flow examples.

Common Setup

use ph_qmi8658::{Config, I2cConfig, Qmi8658Address, Qmi8658I2c};
use embedded_hal_async::delay::DelayNs;
use embedded_hal_async::i2c::I2c;

async fn setup<I2C: I2c, D: DelayNs>(
    i2c: I2C,
    delay: &mut D,
) -> Result<Qmi8658I2c<I2C>, ph_qmi8658::Error> {
    let config = Config::new();
    let i2c_config = I2cConfig::new(Qmi8658Address::Primary.addr());
    let mut imu: Qmi8658I2c<I2C> = Qmi8658I2c::with_i2c_config(i2c, None, None, config, i2c_config);
    imu.init(delay).await?;
    Ok(imu)
}

Initialization

Reset the sensor, verify the chip ID, and apply the default configuration.

// Known address
imu.init(delay).await?;

// Probe multiple addresses
let address = imu.init_with_addresses(
    delay,
    &[Qmi8658Address::Primary.addr(), Qmi8658Address::Secondary.addr()],
).await?;
let _ = address;

If init or init_with_addresses returns Error::NotReady, delay briefly and retry.

Initialization Macro

The qmi8658_init_sequence! macro combines address probing, interrupt configuration, and sensor configuration into a single call.

use ph_qmi8658::{
    Config,
    InterruptConfig,
    Qmi8658Address,
    qmi8658_init_sequence,
};

let config = Config::new();
let irq = InterruptConfig::new().with_ctrl9_handshake_statusint(true);
let address = qmi8658_init_sequence!(
    imu: imu,
    delay: delay,
    addresses: &[Qmi8658Address::Primary.addr(), Qmi8658Address::Secondary.addr()],
    interrupt: irq,
    config: config,
)?;
let _ = address;

Configuration

Set accelerometer and gyroscope ranges, output data rates, and low-pass filter modes.

use ph_qmi8658::{AccelConfig, AccelOutputDataRate, AccelRange, Config};

let accel = AccelConfig::new(AccelRange::G4, AccelOutputDataRate::Hz250);
let config = Config::new().with_accel_config(accel).without_gyro();
imu.set_config(config);
imu.apply_config().await?;

Interrupt Routing + Status

Route data-ready and motion events to physical INT pins and read back interrupt status.

use ph_qmi8658::{InterruptConfig, InterruptPin};

let irq = InterruptConfig::new()
    .with_ctrl9_handshake_statusint(true)
    .with_motion_pin(InterruptPin::Int1);
imu.apply_interrupt_config(irq).await?;

let status = imu.read_interrupt_status().await?;
let _ = status;

Raw Data Reads

Read individual sensor outputs or a full block (timestamp + temperature + accel + gyro).

let block = imu.read_raw_block().await?;
let accel = imu.read_accel_raw().await?;
let gyro = imu.read_gyro_raw().await?;
let temp = imu.read_temperature_raw().await?;
let ts = imu.read_timestamp().await?;
let _ = (block, accel, gyro, temp, ts);

Scaling Helpers (Integer, No Floats)

Convert raw counts to physical units using integer ScaleFactor ratios. Useful on targets without an FPU.

use ph_qmi8658::{AccelRange, GyroRange, accel_mg_per_lsb, gyro_mdps_per_lsb};

let accel_scale = accel_mg_per_lsb(AccelRange::G4);
let gyro_scale = gyro_mdps_per_lsb(GyroRange::Dps256);

// Example conversion for a single axis (i16 raw -> i32 milli-units).
let ax_mg = (i32::from(accel.x) * accel_scale.numerator) / accel_scale.denominator;
let gx_mdps = (i32::from(gyro.x) * gyro_scale.numerator) / gyro_scale.denominator;
let _ = (ax_mg, gx_mdps);

FIFO Burst Read + Decode

Stream sensor data through the hardware FIFO and iterate over decoded frames.

use ph_qmi8658::{FifoConfig, FifoFrameIterator, FifoMode, FifoSize};

let fifo = FifoConfig::new(FifoMode::Stream, FifoSize::Samples64, 8);
imu.apply_fifo_config(fifo).await?;
imu.reset_fifo_with_delay(delay).await?;

let mut buffer = [0u8; 192];
let readout = imu.read_fifo_burst(delay, &mut buffer).await?;
let format = imu.fifo_frame_format();
for frame in FifoFrameIterator::new(&buffer[..readout.bytes_read], format) {
    let _ = frame;
}

FIFO Manual Read Sequence

Step through the FIFO read protocol manually for fine-grained control.

let mut buffer = [0u8; 96];
imu.request_fifo_read().await?;
imu.wait_ctrl9_done(delay).await?;
imu.enable_fifo_read_mode().await?;
let timestamp = imu.read_fifo_data(&mut buffer).await?;
imu.finish_fifo_read().await?;
let _ = timestamp;

Sync Sample (Data-Lock)

Lock a coherent snapshot of all sensor registers and read them atomically.

// For I2C/I3C, disable AHB clock gating while sync sample is active.
imu.set_ahb_clock_gating_with_delay(delay, false).await?;
imu.set_sync_sample(true).await?;

let sample = imu.read_sync_sample(delay).await?;
let _ = sample;

imu.set_sync_sample(false).await?;
imu.set_ahb_clock_gating_with_delay(delay, true).await?;

Wake on Motion (WoM)

Configure low-power wake-on-motion detection to trigger on acceleration exceeding a threshold.

use ph_qmi8658::{AccelConfig, AccelOutputDataRate, AccelRange, Config, WomConfig};

let accel = AccelConfig::new(AccelRange::G4, AccelOutputDataRate::LowPowerHz21);
imu.set_config(Config::new().with_accel_config(accel).without_gyro());
imu.apply_config().await?;

let wom = WomConfig::new(50);
imu.enable_wom(delay, wom).await?;
// ... wait for motion / handle interrupt ...
imu.disable_wom(delay).await?;

Self-Test & Calibration

Run the built-in self-test, apply host-delta offsets, or trigger on-demand calibration.

let accel_report = imu.run_accel_self_test(delay).await?;
let gyro_report = imu.run_gyro_self_test(delay).await?;
let axes = imu.read_self_test_axes().await?;

imu.apply_accel_host_delta_offset_with_delay(delay, 0, 0, 0).await?;
imu.apply_gyro_host_delta_offset_with_delay(delay, 0, 0, 0).await?;
let bias = imu.copy_gyro_bias_and_read(delay, 0, 0, 0).await?;
imu.run_on_demand_calibration(delay).await?;

let _ = (accel_report, gyro_report, axes, bias);

Operating Modes

Switch between accelerometer-only, gyroscope-only, or dual-sensor modes. The driver returns the required stabilization delay.

use ph_qmi8658::OperatingMode;

let delay_ns = imu.set_mode(OperatingMode::AccelOnly).await?;
if delay_ns > 0 {
    delay.delay_ns(delay_ns).await;
}

imu.set_mode_with_delay(delay, OperatingMode::GyroOnly).await?;

Target Platforms

The driver is #![no_std] and builds for any target that supports embedded-hal-async. Tested targets include:

ESP32 (Xtensa) — requires the Espressif esp toolchain:

  • xtensa-esp32-none-elf
  • xtensa-esp32s2-none-elf
  • xtensa-esp32s3-none-elf

ESP32 (RISC-V):

  • riscv32imc-unknown-none-elf
  • riscv32imac-unknown-none-elf

ARM Cortex-M — standard Rust toolchains:

  • thumbv6m-none-eabi, thumbv7m-none-eabi, thumbv7em-none-eabi, thumbv7em-none-eabihf, thumbv8m.base-none-eabi, thumbv8m.main-none-eabi, thumbv8m.main-none-eabihf

Cargo Features

Feature Description
defmt Enable defmt::Format derives on public types for structured logging
fixed Enable fixed-point conversion helpers (I32F32) for raw-to-physical-unit math

No features are enabled by default.

Testing

The driver has unit tests covering configuration validation, data decoding, FIFO frame parsing, and interrupt/status decoding. Run them with:

cargo test -p ph-qmi8658

End-to-end hardware validation uses the apps/qa-runner app on ESP32-S3.

Release Checklist

See RELEASE_CHECKLIST.md.