lis2dh12 (1.0.10)
Installation
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add lis2dh12@1.0.10 --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
./scripts/test.sh
cargo deny check
./scripts/check-version-sync.sh
./scripts/test-resolve-cargo-registry.sh
./scripts/test-release-branch-name.sh
./scripts/test.sh runs version sync, CI helper unit tests, then cargo test.
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.
Publish / registries
| Branch | Tag | Registry |
|---|---|---|
dev |
vX.Y.Z-rc |
forgejorc (unstable) |
staging |
— (merge gate only) | — |
main |
vX.Y.Z (+ ensures release/X.Y) |
forgejo (stable) |
Cargo.toml stays at X.Y.Z. The registry (not the version) separates stable from unstable. Bump Cargo.toml + CHANGELOG before each publish (a version can be published once per registry). On first stable publish of a minor line, CI creates release/X.Y for future hotfixes. Details: .forgejo/README.md.
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 |