Platform-agnostic Rust driver for the ST LIS2DE12 ultra-low-power 3-axis accelerometer. The crate targets no_std environments, generates its register API at build time from a YAML manifest, and supports both blocking and asynchronous I²C and SPI transports.
- I²C and SPI support - Use either interface with the same high-level API
- High-level blocking driver implementing
accelerometer::RawAccelerometer - Optional async driver (
Lis2de12Async) for both I²C and SPI - Auto-generated register-level API via
device-driver no_stdcompatible with optionaldefmtlogging integration
Add the crate to your Cargo.toml:
[dependencies]
lis2de12 = "0.1.2"
embedded-hal = "1.0"
# Optional features:
# lis2de12 = { version = "0.1.2", features = ["async"] }
# lis2de12 = { version = "0.1.2", features = ["defmt-03"] }use embedded_hal::i2c::I2c;
use lis2de12::{Lis2de12, SlaveAddr};
fn init_driver<I2C>(bus: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: I2c,
{
// Create driver using I²C interface
let mut lis = Lis2de12::new_i2c(bus, SlaveAddr::default())?;
let accel = lis.read_g()?;
defmt::info!("Acceleration: {:?} g", accel);
Ok(())
}use embedded_hal::spi::SpiDevice;
use lis2de12::Lis2de12;
fn init_driver_spi<SPI>(spi: SPI) -> Result<(), lis2de12::Error<SPI::Error>>
where
SPI: SpiDevice,
{
// Create driver using SPI interface
let mut lis = Lis2de12::new_spi(spi)?;
let accel = lis.read_g()?;
defmt::info!("Acceleration: {:?} g", accel);
Ok(())
}Note: The LIS2DE12 requires SPI Mode 0 or Mode 3 (CPOL=0, CPHA=0 or CPOL=1, CPHA=1). The driver uses the SpiDevice trait which handles chip select automatically.
Use Lis2de12Config to bring the device out of power-down with your preferred output data rate, operating mode, scale, axis enables, and temperature sensing:
use embedded_hal::i2c::I2c;
use lis2de12::{
AxesEnable, Fs, Lis2de12, Lis2de12Config, OperatingMode, Odr, SlaveAddr,
};
fn init_with_config<I2C>(i2c: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: I2c,
{
let config = Lis2de12Config {
odr: Odr::Hz50,
mode: OperatingMode::Normal,
scale: Fs::G4,
axes: AxesEnable { x: true, y: true, z: true },
block_data_update: true,
temperature_enable: true,
..Default::default()
};
let mut lis = Lis2de12::new_i2c_with_config(i2c, SlaveAddr::default(), config)?;
// Or for SPI: Lis2de12::new_spi_with_config(spi, config)?
defmt::info!("Sensor configured with 50 Hz output");
Ok(())
}Enable the async feature and depend on embedded-hal-async:
use embedded_hal_async::i2c::I2c;
use lis2de12::{Lis2de12Async, SlaveAddr};
async fn init_async<I2C>(i2c: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: I2c,
{
// Async I²C
let mut lis = Lis2de12Async::new_i2c(i2c, SlaveAddr::default()).await?;
// Or async SPI: Lis2de12Async::new_spi(spi).await?
let accel = lis.read_g().await?;
defmt::info!("Acceleration: {:?} g", accel);
Ok(())
}Both blocking and async drivers provide read_raw, read_mg, and read_g helpers to obtain measurements in the format that best suits your application:
use embedded_hal::i2c::I2c;
use lis2de12::{Lis2de12, SlaveAddr};
fn read_scaled<I2C>(bus: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: I2c,
{
let mut lis = Lis2de12::new_i2c(bus, SlaveAddr::default())?;
let raw = lis.read_raw()?; // Raw ADC values
let mg = lis.read_mg()?; // milli-g (integer)
let g = lis.read_g()?; // g (floating point)
defmt::info!("raw={:?}, mg={:?}, g={:?}", raw, mg, g);
Ok(())
}The async variant exposes the same API surface, returning futures for each method.
Configure the hardware FIFO to collect XYZ frames and drain them in bursts when the watermark is reached:
use embedded_hal::i2c::I2c;
use lis2de12::{
FifoConfig, FifoMode, Lis2de12, Lis2de12Config, SlaveAddr,
};
fn read_fifo<I2C>(bus: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: I2c,
{
let mut config = Lis2de12Config::default();
config.fifo = FifoConfig::enabled(FifoMode::Stream).with_watermark(8);
let mut lis = Lis2de12::new_i2c_with_config(bus, SlaveAddr::default(), config)?;
let status = lis.fifo_status()?;
if status.has_data() {
let mut frames = [[0u8; lis2de12::FIFO_FRAME_BYTES]; 4];
let read = lis.read_fifo_frames(&mut frames)?;
defmt::info!("drained {} frames", read);
}
Ok(())
}Async users get identical functionality via Lis2de12Async::read_fifo_frame, read_fifo_frames, and drain_fifo.
For advanced configuration you can interact with the generated register API directly:
use lis2de12::{Lis2de12, Odr, SlaveAddr};
fn configure_normal_mode<I2C>(i2c: I2C) -> Result<(), lis2de12::Error<I2C::Error>>
where
I2C: embedded_hal::i2c::I2c,
{
let mut lis = Lis2de12::new_i2c(i2c, SlaveAddr::default())?;
lis
.device()
.ctrl_reg_1()
.write(|reg| {
reg.set_odr(Odr::Hz100);
reg.set_lpen(false); // normal mode (low-power disabled)
reg.set_xen(true);
reg.set_yen(true);
reg.set_zen(true);
})?;
Ok(())
}| Feature | Default | Description |
|---|---|---|
async |
✗ | Enables the async driver (pulls in embedded-hal-async) |
defmt-03 |
✗ | Enables defmt logging support compatible with device-driver |
During cargo build, the build.rs script reads src/lis2de12.yaml, generates the register accessor API, and writes it into $OUT_DIR. End users do not need to run any manual code-generation steps.
# Suggested commands
cargo fmt --all
cargo clippy --all-targets --all-features
cargo test --all-features
cargo publish --dry-runCI workflows verify formatting, lints, and no_std builds.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the project by you shall be dual licensed as above (MIT OR Apache-2.0), without any additional terms or conditions.
- 📦 Crate: https://crates.io/crates/lis2de12
- 📚 Docs: https://docs.rs/lis2de12
- 🗺️ Repository: https://github.com/leftger/lis2de12