LIS3DH (lis3dh v0.1.0)

Copy Markdown

Driver for the STMicroelectronics LIS3DH 3-axis MEMS accelerometer.

Communicates over I²C (or any other Wafer transport that implements the Wafer.I2C protocol).

Protocol notes

The LIS3DH uses a standard byte-oriented I²C register protocol. Multi-byte reads and writes only auto-increment the register address when bit 7 of the sub-address is set; this driver sets that bit unconditionally on every transaction, which is harmless for single-byte access and required for bursts.

I²C address

The 7-bit address is 0b0011000x where x is the value of the SA0 pin (also called SDO):

  • SA0 = GND0x18 (default).
  • SA0 = VDD0x19.

Example

{:ok, i2c} = Wafer.Driver.Circuits.I2C.acquire(bus_name: "i2c-1", address: 0x18)
{:ok, acc} = LIS3DH.acquire(conn: i2c)

Summary

Functions

Wrap an existing Wafer connection in a LIS3DH struct.

Configure the accelerometer's operating mode, ODR, range, axis enables, and block-data-update setting. Caches the chosen :operating_mode and :range on the struct so subsequent reads can scale samples without re-reading the config registers.

Configure sleep-to-wake / return-to-sleep by writing ACT_THS and ACT_DUR.

Configure click / double-click / tap detection by writing CLICK_CFG, CLICK_THS, TIME_LIMIT, TIME_LATENCY, and TIME_WINDOW.

Configure a free-fall detector on the given interrupt pin.

Configure the on-chip high-pass filter via CTRL_REG2.

Configure an inertial interrupt (1 or 2) by writing INT*_CFG, INT*_THS, and INT*_DURATION atomically.

Configure a motion (wake-up) detector on the given interrupt pin.

Configure 6D or 4D orientation detection on the given interrupt pin.

The default 7-bit I²C address (0x18, SA0 pin tied to GND). The alternate address 0x19 is selected by tying SA0 to VDD.

Populate the cached :operating_mode and :range by reading CTRL_REG1 and CTRL_REG4. Useful after acquire/1 when the device has already been configured by some other process.

Disable activity detection by writing 0 to ACT_THS.

Clear TEMP_CFG_REG.ADC_EN, disabling all three auxiliary ADC channels.

Mask out the given routing bits in CTRL_REG3 (INT1 routing).

Mask out the given routing bits in CTRL_REG6 (INT2 routing).

Clear TEMP_CFG_REG.TEMP_EN (leaving ADC_EN alone).

Enable the on-chip auxiliary ADC by setting TEMP_CFG_REG.ADC_EN. The ADC samples at the configured CTRL_REG1.ODR. Requires :block_data_update (CTRL_REG4.BDU) to be :hold for consistent reads — configure_accelerometer/2 defaults to that already.

OR-in the given routing bits in CTRL_REG3 (INT1 routing). Leaves the other bits untouched, so it composes cleanly with LIS3DH.Sampler which also writes the FIFO bits in this register.

OR-in the given routing bits in CTRL_REG6 (INT2 routing). Preserves the INT_POLARITY bit and any others not in events.

Enable the embedded temperature sensor by setting both TEMP_CFG_REG.ADC_EN and TEMP_CFG_REG.TEMP_EN. The temperature reading is routed to channel 3 of the auxiliary ADC; read it via read_temperature/1.

The expected WHO_AM_I value (0x33) returned by an unmodified LIS3DH.

Set CTRL_REG1.ODR to 0000 (power-down mode), preserving the other fields.

Set CTRL_REG1.ODR to a non-zero rate without changing the other fields, bringing the sensor out of power-down. Equivalent to a write to CTRL_REG1 with the chosen ODR while preserving the LPen and axis enable bits.

Read the accelerometer x/y/z sample and return scaled values in m/s².

Read auxiliary ADC channel 1, 2, or 3 and return the absolute voltage in millivolts.

Read the CLICK_SRC register and decode it. Reading clears the latched flags if LIR_Click was set during configure.

Read the INT*_SRC register. Reading clears the latched flags if latching is enabled (LIR_INTx in CTRL_REG5).

Read the REFERENCE register. With :normal_with_reset HPF mode (the default after power-up), this read also resets the high-pass filter's internal state.

Read the embedded temperature sensor on auxiliary ADC channel 3 and return the delta temperature in °C, relative to the 25 °C factory calibration point (i.e. add 25.0 for the absolute reading).

Refresh the internal trim registers from non-volatile memory by setting CTRL_REG5.BOOT. Blocks for 5 ms to give the device time to finish the boot sequence before returning.

Toggle 4D detection for the given pin via CTRL_REG5.D4D_INT1 / D4D_INT2. 4D restricts 6D detection to the X/Y plane (Z position ignored). Has no effect unless INT*_CFG.6D is also set.

Toggle interrupt latching for the given pin via CTRL_REG5.LIR_INT1 / LIR_INT2. When latched, the interrupt pin stays asserted until the corresponding INT*_SRC register is read.

Set the active level for both INT pins via CTRL_REG6.INT_POLARITY.

Set the CTRL_REG4.ST self-test field while preserving the other bits.

Read the device's WHO_AM_I register.

Write the REFERENCE register (used as the HPF reference in :reference mode).

Types

acquire_option()

@type acquire_option() ::
  {:conn, Wafer.Conn.t()} | {:verify_who_am_i, boolean()} | {:reboot, boolean()}

axes()

@type axes() :: %{x: float(), y: float(), z: float()}

t()

@type t() :: %LIS3DH{
  conn: Wafer.Conn.t(),
  operating_mode: LIS3DH.Config.operating_mode() | nil,
  range: LIS3DH.Config.range() | nil
}

who_am_i()

@type who_am_i() :: byte()

Functions

acquire(opts)

@spec acquire([acquire_option()]) :: {:ok, t()} | {:error, term()}

Wrap an existing Wafer connection in a LIS3DH struct.

Options

  • :conn (required) — a Wafer connection that implements the Wafer.I2C protocol, e.g. Wafer.Driver.Circuits.I2C or Wafer.Driver.Fake.
  • :verify_who_am_i (default true) — when true, read WHO_AM_I and return {:error, {:who_am_i_mismatch, got: byte, expected: 0x33}} if the device does not identify as a LIS3DH.
  • :reboot (default false) — when true, set CTRL_REG5.BOOT to refresh the internal trim registers from non-volatile memory and block for 5 ms before returning. Useful after power glitches or when you suspect the trim values have been corrupted.

configure_accelerometer(acc, opts)

@spec configure_accelerometer(
  t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Configure the accelerometer's operating mode, ODR, range, axis enables, and block-data-update setting. Caches the chosen :operating_mode and :range on the struct so subsequent reads can scale samples without re-reading the config registers.

See LIS3DH.Config.encode_ctrl_reg_1/1 and LIS3DH.Config.encode_ctrl_reg_4/1 for the supported options. :mode and :odr are required.

Writes CTRL_REG4 first (range / HR / BDU), then CTRL_REG1 (ODR / LPen / axes), so the device is fully reconfigured before sampling resumes.

configure_activity(acc, opts)

@spec configure_activity(
  t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Configure sleep-to-wake / return-to-sleep by writing ACT_THS and ACT_DUR.

When acceleration falls below :threshold_mg for the configured :duration, the device automatically switches to low-power mode at 10 Hz ODR regardless of the original CTRL_REG1 / CTRL_REG4 settings. When acceleration rises above the threshold, the device restores the original configuration.

Options

  • :threshold_mg — threshold in milli-g (required). Uses the same LSB table as INT*_THS. Pass 0 to disable activity detection.
  • :duration0..255 (required). One LSB corresponds to (8 × duration + 1) / ODR seconds per datasheet §8.36.

Requires the accelerometer range to be cached on the struct.

configure_click(acc, opts)

@spec configure_click(
  t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Configure click / double-click / tap detection by writing CLICK_CFG, CLICK_THS, TIME_LIMIT, TIME_LATENCY, and TIME_WINDOW.

Options

  • :events — list of LIS3DH.Click.click_event/0 to enable (required; pass [] to disable all).
  • :threshold_mg — threshold in milli-g (required). Same LSB table as INT*_THS.
  • :latched — when true, the click interrupt stays high until CLICK_SRC is read (default false).
  • :time_limit0..127 count of 1/ODR periods, the max click pulse width (required).
  • :time_latency0..255 count of 1/ODR periods, the dead time after a click (required).
  • :time_window0..255 count of 1/ODR periods, the search window for the second click of a double-click (default 0).

Requires the accelerometer range to be cached on the struct.

configure_free_fall(acc, pin, opts \\ [])

@spec configure_free_fall(t(), LIS3DH.Interrupts.pin(), keyword()) ::
  {:ok, t()} | {:error, term()}

Configure a free-fall detector on the given interrupt pin.

Free-fall is signalled when the magnitude of acceleration on all three axes falls below a threshold for a configurable duration (i.e. the device is in true free fall, ~0 g on every axis).

Options

  • :threshold_mg — threshold in milli-g (default 350, the AN3308 recommended value). Lower thresholds trigger more easily.
  • :duration0..127 count of 1/ODR periods (default 5).

configure_high_pass_filter(acc, opts \\ [])

@spec configure_high_pass_filter(
  t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Configure the on-chip high-pass filter via CTRL_REG2.

See LIS3DH.Config.encode_ctrl_reg_2/1 for the supported options.

configure_inertial_interrupt(acc, pin, opts)

@spec configure_inertial_interrupt(t(), LIS3DH.Interrupts.pin(), keyword()) ::
  {:ok, t()} | {:error, term()}

Configure an inertial interrupt (1 or 2) by writing INT*_CFG, INT*_THS, and INT*_DURATION atomically.

Options

  • :modeLIS3DH.Interrupts.aoi_mode/0 (default :or).
  • :axes — list of LIS3DH.Interrupts.axis_event/0 to enable.
  • :threshold_mg — non-negative integer threshold in milli-g. The LSB size depends on the cached :range; this function reads the cached value and rounds the threshold to fit.
  • :duration0..127 count of 1/ODR periods the condition must hold before the interrupt fires (default 0).

Requires the accelerometer range to be cached on the struct.

configure_motion(acc, pin, opts)

@spec configure_motion(t(), LIS3DH.Interrupts.pin(), keyword()) ::
  {:ok, t()} | {:error, term()}

Configure a motion (wake-up) detector on the given interrupt pin.

Motion is signalled when any enabled axis exceeds the threshold for the configured duration.

Options

  • :threshold_mg — threshold in milli-g (no default, must be specified).
  • :duration0..127 count of 1/ODR periods (default 0).
  • :axes — list of LIS3DH.Interrupts.axis_event/0 (default [:x_high, :y_high, :z_high]).

configure_orientation(acc, pin, opts)

@spec configure_orientation(t(), LIS3DH.Interrupts.pin(), keyword()) ::
  {:ok, t()} | {:error, term()}

Configure 6D or 4D orientation detection on the given interrupt pin.

Options

  • :mode:movement (interrupt fires on transitions between known zones) or :position (interrupt stays asserted while inside a known zone). Default :position.
  • :detection:six_d (default, all six face-down/face-up directions) or :four_d (X/Y plane only, Z ignored — for portrait/landscape).
  • :axes — list of LIS3DH.Interrupts.axis_event/0 to enable (default all six).
  • :threshold_mg — threshold in milli-g (no default; the zone half-width is typically chosen so two zones don't overlap).
  • :duration0..127 count of 1/ODR periods (default 0).

Writes the configured INT*_CFG, INT*_THS, INT*_DURATION and also toggles CTRL_REG5.D4D_INT* to match the :detection choice.

default_i2c_address()

@spec default_i2c_address() :: 24

The default 7-bit I²C address (0x18, SA0 pin tied to GND). The alternate address 0x19 is selected by tying SA0 to VDD.

detect_configuration(acc)

@spec detect_configuration(t()) :: {:ok, t()} | {:error, term()}

Populate the cached :operating_mode and :range by reading CTRL_REG1 and CTRL_REG4. Useful after acquire/1 when the device has already been configured by some other process.

disable_activity(acc)

@spec disable_activity(t()) :: {:ok, t()} | {:error, term()}

Disable activity detection by writing 0 to ACT_THS.

disable_auxiliary_adc(acc)

@spec disable_auxiliary_adc(t()) :: {:ok, t()} | {:error, term()}

Clear TEMP_CFG_REG.ADC_EN, disabling all three auxiliary ADC channels.

disable_int1_routing(acc, events)

@spec disable_int1_routing(t(), [int1_event]) :: {:ok, t()} | {:error, term()}
when int1_event:
       :click
       | :ia1
       | :ia2
       | :zyxda
       | :adc_drdy_321
       | :fifo_watermark
       | :fifo_overrun

Mask out the given routing bits in CTRL_REG3 (INT1 routing).

disable_int2_routing(acc, events)

@spec disable_int2_routing(t(), [int2_event]) :: {:ok, t()} | {:error, term()}
when int2_event: :click | :ia1 | :ia2 | :boot | :activity

Mask out the given routing bits in CTRL_REG6 (INT2 routing).

disable_temperature_sensor(acc)

@spec disable_temperature_sensor(t()) :: {:ok, t()} | {:error, term()}

Clear TEMP_CFG_REG.TEMP_EN (leaving ADC_EN alone).

enable_auxiliary_adc(acc)

@spec enable_auxiliary_adc(t()) :: {:ok, t()} | {:error, term()}

Enable the on-chip auxiliary ADC by setting TEMP_CFG_REG.ADC_EN. The ADC samples at the configured CTRL_REG1.ODR. Requires :block_data_update (CTRL_REG4.BDU) to be :hold for consistent reads — configure_accelerometer/2 defaults to that already.

enable_int1_routing(acc, events)

@spec enable_int1_routing(t(), [int1_event]) :: {:ok, t()} | {:error, term()}
when int1_event:
       :click
       | :ia1
       | :ia2
       | :zyxda
       | :adc_drdy_321
       | :fifo_watermark
       | :fifo_overrun

OR-in the given routing bits in CTRL_REG3 (INT1 routing). Leaves the other bits untouched, so it composes cleanly with LIS3DH.Sampler which also writes the FIFO bits in this register.

Valid events: :click, :ia1, :ia2, :zyxda, :adc_drdy_321, :fifo_watermark, :fifo_overrun.

enable_int2_routing(acc, events)

@spec enable_int2_routing(t(), [int2_event]) :: {:ok, t()} | {:error, term()}
when int2_event: :click | :ia1 | :ia2 | :boot | :activity

OR-in the given routing bits in CTRL_REG6 (INT2 routing). Preserves the INT_POLARITY bit and any others not in events.

Valid events: :click, :ia1, :ia2, :boot, :activity.

enable_temperature_sensor(acc)

@spec enable_temperature_sensor(t()) :: {:ok, t()} | {:error, term()}

Enable the embedded temperature sensor by setting both TEMP_CFG_REG.ADC_EN and TEMP_CFG_REG.TEMP_EN. The temperature reading is routed to channel 3 of the auxiliary ADC; read it via read_temperature/1.

expected_who_am_i()

@spec expected_who_am_i() :: 51

The expected WHO_AM_I value (0x33) returned by an unmodified LIS3DH.

power_off(acc)

@spec power_off(t()) :: {:ok, t()} | {:error, term()}

Set CTRL_REG1.ODR to 0000 (power-down mode), preserving the other fields.

power_on(acc, odr)

@spec power_on(t(), LIS3DH.Config.odr()) :: {:ok, t()} | {:error, term()}

Set CTRL_REG1.ODR to a non-zero rate without changing the other fields, bringing the sensor out of power-down. Equivalent to a write to CTRL_REG1 with the chosen ODR while preserving the LPen and axis enable bits.

read_accelerometer(acc)

@spec read_accelerometer(t()) :: {:ok, axes()} | {:error, term()}

Read the accelerometer x/y/z sample and return scaled values in m/s².

Requires :operating_mode and :range to be cached on the struct — call configure_accelerometer/2 or detect_configuration/1 first.

read_auxiliary_adc(acc, channel)

@spec read_auxiliary_adc(t(), 1 | 2 | 3) :: {:ok, float()} | {:error, term()}

Read auxiliary ADC channel 1, 2, or 3 and return the absolute voltage in millivolts.

The chip's ADC input range is centred on 1200 mV with a ±400 mV span, so the returned value is in 800..1600 mV. ADC resolution depends on the operating mode (10-bit in normal / high-resolution, 8-bit in low-power), so this function requires :operating_mode to be cached on the struct.

read_click_source(acc)

@spec read_click_source(t()) :: {:ok, LIS3DH.Click.source_flags()} | {:error, term()}

Read the CLICK_SRC register and decode it. Reading clears the latched flags if LIR_Click was set during configure.

read_interrupt_source(acc, pin)

@spec read_interrupt_source(t(), LIS3DH.Interrupts.pin()) ::
  {:ok, LIS3DH.Interrupts.source_flags()} | {:error, term()}

Read the INT*_SRC register. Reading clears the latched flags if latching is enabled (LIR_INTx in CTRL_REG5).

read_reference(acc)

@spec read_reference(t()) :: {:ok, integer()} | {:error, term()}

Read the REFERENCE register. With :normal_with_reset HPF mode (the default after power-up), this read also resets the high-pass filter's internal state.

read_temperature(acc)

@spec read_temperature(t()) :: {:ok, float()} | {:error, term()}

Read the embedded temperature sensor on auxiliary ADC channel 3 and return the delta temperature in °C, relative to the 25 °C factory calibration point (i.e. add 25.0 for the absolute reading).

Only the OUT_ADC3_H byte carries temperature data — sensitivity is 1 LSB/°C and resolution is 8-bit regardless of operating mode (datasheet §3.2). The full 16-bit word is still read so BDU=:hold unlatches cleanly.

Requires the temperature sensor to be enabled via enable_temperature_sensor/1.

reboot(acc)

@spec reboot(t()) :: {:ok, t()} | {:error, term()}

Refresh the internal trim registers from non-volatile memory by setting CTRL_REG5.BOOT. Blocks for 5 ms to give the device time to finish the boot sequence before returning.

set_4d_detection(acc, pin, enabled?)

@spec set_4d_detection(t(), LIS3DH.Interrupts.pin(), boolean()) ::
  {:ok, t()} | {:error, term()}

Toggle 4D detection for the given pin via CTRL_REG5.D4D_INT1 / D4D_INT2. 4D restricts 6D detection to the X/Y plane (Z position ignored). Has no effect unless INT*_CFG.6D is also set.

set_interrupt_latching(acc, pin, latched?)

@spec set_interrupt_latching(t(), LIS3DH.Interrupts.pin(), boolean()) ::
  {:ok, t()} | {:error, term()}

Toggle interrupt latching for the given pin via CTRL_REG5.LIR_INT1 / LIR_INT2. When latched, the interrupt pin stays asserted until the corresponding INT*_SRC register is read.

set_interrupt_polarity(acc, polarity)

@spec set_interrupt_polarity(t(), :active_high | :active_low) ::
  {:ok, t()} | {:error, term()}

Set the active level for both INT pins via CTRL_REG6.INT_POLARITY.

polarity is :active_high (default after reset) or :active_low.

set_self_test(acc, mode)

@spec set_self_test(t(), LIS3DH.Config.self_test_mode()) ::
  {:ok, t()} | {:error, term()}

Set the CTRL_REG4.ST self-test field while preserving the other bits.

The recommended self-test procedure (per ST application note AN3308) is:

  1. Power up the device and configure_accelerometer/2 for normal mode, ±2g, 50 Hz, BDU=:hold.
  2. Wait for stable output (≥ a few ODR periods) and average several baseline samples.
  3. Call set_self_test(acc, :self_test_0) and wait for the documented turn-on time (90 ms typical).
  4. Average several test samples; the per-axis delta vs. the baseline must fall within the limits in datasheet table 4.
  5. Restore with set_self_test(acc, :off).
  6. Optionally repeat with :self_test_1 for the alternate direction.

This helper just toggles the ST field; the user owns the timing, averaging, and pass/fail check.

who_am_i(acc)

@spec who_am_i(t()) :: {:ok, who_am_i()} | {:error, term()}

Read the device's WHO_AM_I register.

write_reference(acc, value)

@spec write_reference(t(), integer()) :: {:ok, t()} | {:error, term()}

Write the REFERENCE register (used as the HPF reference in :reference mode).