lis2dh12 (1.0.5)

Published 2026-07-25 22:16:58 +00:00 by faicel

Installation

[registries.forgejo]
index = "sparse+" # Sparse index
# index = "" # Git

[net]
git-fetch-with-cli = true
cargo add lis2dh12@1.0.5 --registry forgejo

About this package

no_std I2C driver for the STMicroelectronics LIS2DH12 accelerometer

LIS2DH12 Accelerometer Driver

no_std Rust driver for the STMicroelectronics LIS2DH12 3-axis accelerometer over I2C.

Design

This crate uses a shared-bus friendly API:

  • the public driver type is lis2dh12::i2c::Lis2dh12
  • the instance stores sensor state
  • runtime operations receive &mut i2c at call time

This makes the driver easier to use on embedded targets where the I2C peripheral is shared with other devices.

Features

  • no_std
  • I2C driver built on embedded-hal 1.0
  • shared-bus friendly public API
  • acceleration readout
  • configurable range, data rate, and power mode
  • threshold/motion interrupts on INT1 and INT2
  • FIFO support
  • temperature sensor support
  • built-in self-test
  • click / double-click detection
  • free-fall detection
  • activity / inactivity detection

Basic usage

use embedded_hal::delay::DelayNs;
use lis2dh12::{i2c::Lis2dh12, DataRate, Lis2dh12Config, Range, SlaveAddr};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

accel.set_range(i2c, Range::G2)?;
accel.set_data_rate(i2c, DataRate::Hz_100)?;

let sample = accel.read_acceleration(i2c, delay)?;

let _ = sample;
# Ok(())
# }

Blocking and non-blocking reads

Initialization and blocking reads need a DelayNs implementation:

  • Lis2dh12::new(..., delay) waits 5 ms after CTRL_REG5.BOOT before touching registers again.
  • read_acceleration(i2c, delay) waits until STATUS_REG.ZYXDA is ready, then reads the sample.
  • read_temperature(i2c, delay) and read_temperature_raw(i2c, delay) wait until STATUS_REG_AUX.TDA is ready.

The blocking data-ready timeout is derived from the configured ODR: at least two sample periods, with a 5 ms minimum. If no fresh sample arrives in time, these methods return Lis2dh12Error::DataReadyTimeout. If the device is in DataRate::PowerDown, they return Lis2dh12Error::InvalidConfiguration without polling.

For event loops or RTOS tasks that must not block on sensor readiness, use the non-blocking API:

if accel.is_data_ready(i2c)? {
    if let Some(sample) = accel.try_read_acceleration(i2c)? {
        let _ = sample;
    }
}

if let Some(temp) = accel.try_read_temperature(i2c)? {
    let _ = temp.celsius;
}

Multi-register configuration

High-level helpers that configure several registers, such as motion detection, FIFO, click/tap, free-fall, activity, self-test setup, and timing helpers, are not atomic I2C transactions. If an I2C operation fails in the middle of one of these methods, the sensor may keep a partial configuration.

The driver orders critical helpers so the effective enable/routing write happens last where practical. For example, FIFO mode/watermark is written before FIFO_EN, and interrupt polarity/configuration is prepared before routing interrupt sources to INT1/INT2. This reduces the chance of enabling an incomplete configuration, but it is not a rollback guarantee.

After an error from a high-level multi-register helper, recover by reinitializing the device with Lis2dh12::new(..., delay) or by applying the full intended configuration sequence again before relying on the sensor state.

I2C addresses

The LIS2DH12 supports two I2C addresses depending on the SA0/SDO pin state:

  • 0x18: SlaveAddr::Low
  • 0x19: SlaveAddr::High

The application must pass the expected address to Lis2dh12::new. Do not use WHO_AM_I = 0x33 as an automatic model detector: ST reuses that value across related parts such as LIS2DH12, LIS3DH, and LIS3DE. new still checks WHO_AM_I as a sanity check after the caller-selected address responds.

Example:

use embedded_hal::delay::DelayNs;
use lis2dh12::{i2c::Lis2dh12, Lis2dh12Config, SlaveAddr};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let low = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;
let _ = low.address();
# Ok(())
# }

Interrupt / motion configuration

use lis2dh12::{
    i2c::Lis2dh12, InterruptPin, Lis2dh12Config, MotionConfig, SlaveAddr,
};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl embedded_hal::delay::DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let config = Lis2dh12Config::default().with_interrupt_active_low(false);
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, config, delay)?;

let motion = MotionConfig::vibration_default(6, 1);
accel.configure_motion_detection(i2c, InterruptPin::Int1, motion)?;

let source = accel.read_motion_interrupt(i2c, InterruptPin::Int1)?;
let _ = source.motion;
# Ok(())
# }

For lower-level interrupt setup, the public API also exposes:

  • configure_interrupt
  • set_interrupt_threshold
  • set_interrupt_duration
  • enable_interrupt_pin
  • read_interrupt_source
  • clear_interrupt_flags

Interrupt polarity is global on the LIS2DH12: CTRL_REG6.INT_POLARITY affects both INT1 and INT2. Lis2dh12Config::default() keeps the datasheet default, active-high. Use Lis2dh12Config::with_interrupt_active_low(active_low) during init, or set_interrupt_polarity(active_low) for an explicit runtime change, before routing any interrupt source. Per-source helpers only route or unroute events; they do not change polarity.

FIFO

use lis2dh12::{i2c::Lis2dh12, Acceleration, FifoConfig, FifoMode, FIFO_CAPACITY, Lis2dh12Config, SlaveAddr};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl embedded_hal::delay::DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

accel.configure_fifo(i2c, FifoConfig::new(FifoMode::Stream).with_watermark(16))?;

let mut samples = [Acceleration::new(0.0, 0.0, 0.0); FIFO_CAPACITY];
let count = accel.read_fifo(i2c, &mut samples)?;
let status = accel.fifo_status(i2c)?;

let _ = (count, status.empty);
# Ok(())
# }

Temperature sensor

use embedded_hal::delay::DelayNs;
use lis2dh12::{i2c::Lis2dh12, Lis2dh12Config, SlaveAddr};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

accel.enable_temperature_sensor(i2c)?;

if accel.temperature_status(i2c)?.data_available {
    let temp = accel.read_temperature(i2c, delay)?;
    let _ = temp.celsius;
}

if let Some(temp) = accel.try_read_temperature(i2c)? {
    let _ = temp.celsius;
}
# Ok(())
# }

Self-test

use embedded_hal::delay::DelayNs;
use lis2dh12::{i2c::Lis2dh12, Lis2dh12Config, SelfTestMode, SlaveAddr};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

let result = accel.run_self_test(i2c, delay, SelfTestMode::Test0)?;
let _ = result.passed;
# Ok(())
# }

Click / tap

use lis2dh12::{
    i2c::Lis2dh12, ClickConfig, ClickTiming, InterruptPin, Lis2dh12Config, SlaveAddr,
};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl embedded_hal::delay::DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

accel.configure_click(i2c, ClickConfig::new().with_x_single(true).with_x_double(true))?;
accel.set_click_threshold(i2c, 20, true)?;
accel.set_click_timing(
    i2c,
    ClickTiming::new()
        .with_time_limit(10)
        .with_time_latency(20)
        .with_time_window(50)
        .with_latch(true),
)?;
accel.enable_click_interrupt(i2c, InterruptPin::Int1, true)?;

let source = accel.read_click_source(i2c)?;
let _ = source.active;
# Ok(())
# }

Free-fall and activity detection

use lis2dh12::{
    i2c::Lis2dh12, ActivityConfig, FreeFallConfig, InterruptPin, Lis2dh12Config, SlaveAddr,
};

# fn demo<I2C, E>(i2c: &mut I2C, delay: &mut impl embedded_hal::delay::DelayNs) -> Result<(), lis2dh12::Lis2dh12Error<E>>
# where
#     I2C: embedded_hal::i2c::I2c<Error = E>,
#     E: core::fmt::Debug,
# {
let mut accel = Lis2dh12::new(i2c, SlaveAddr::Low, Lis2dh12Config::default(), delay)?;

accel.configure_free_fall(i2c, InterruptPin::Int1, FreeFallConfig::recommended_2g())?;
accel.enable_free_fall_interrupt(i2c, InterruptPin::Int1, true)?;

accel.configure_activity(i2c, ActivityConfig::new(16, 4))?;
accel.enable_activity_interrupt(i2c, true)?;
# Ok(())
# }

Notes

  • This crate currently focuses on the I2C path.
  • The public API is intentionally instance-based and shared-bus friendly.
  • Internally the crate uses an attached driver layer, but that is not part of the public API.

Tests

cargo test
cargo deny check
./scripts/check-version-sync.sh

cargo deny check enforces dependency advisories, allowed licenses, bans, and crate sources (see deny.toml). The same check runs in Forgejo CI.

./scripts/check-version-sync.sh fails if Cargo.toml version does not match the latest ## [x.y.z] heading in CHANGELOG.md. Forgejo CI runs the same gate via the reusable workflow rust_crate_checks.yml from central_ci. When releasing, bump both files to the same version in the same commit.

CI security note

Workflows live under .forgejo/. Branch protection on main uses Protected file patterns .gitea/**/*.yml;.forgejo/**/*.yml, so those workflows cannot be changed by a direct push. That compensates for a single RUNNER_TOKEN. See .forgejo/README.md.

Documentation

For device-level details, see the official STMicroelectronics LIS2DH12 datasheet.

Dependencies

ID Version
accelerometer ^0.12
embedded-hal ^1.0.0
embedded-hal-mock ^0.11.1
Details
Cargo
2026-07-25 22:16:58 +00:00
0
649 KiB
Assets (1)
Versions (6) View all
1.0.5 2026-07-25
1.0.3 2026-07-25
1.0.1 2026-07-24
1.0.0 2026-07-24
0.2.0 2026-07-13