lis2dh12 (1.0.5)
Installation
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add lis2dh12@1.0.5 --registry forgejoAbout this package
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 i2cat 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-hal1.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 afterCTRL_REG5.BOOTbefore touching registers again.read_acceleration(i2c, delay)waits untilSTATUS_REG.ZYXDAis ready, then reads the sample.read_temperature(i2c, delay)andread_temperature_raw(i2c, delay)wait untilSTATUS_REG_AUX.TDAis 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::Low0x19: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_interruptset_interrupt_thresholdset_interrupt_durationenable_interrupt_pinread_interrupt_sourceclear_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 |