# `BB.Sensor.BMI323`
[🔗](https://github.com/beam-bots/bb_sensor_bmi323/blob/main/lib/bb/sensor/bmi323.ex#L5)

A BB sensor that publishes `BB.Message.Sensor.Imu` messages from a Bosch
BMI323 6-DoF inertial measurement unit (3-axis accelerometer + 3-axis
gyroscope) over I2C.

The BMI323 has no magnetometer, so this sensor cannot determine
orientation on its own — every published `Imu` carries
`BB.Math.Quaternion.identity/0` for the `orientation` field. **You
almost always want to pair this sensor with an orientation estimator**
such as the ones in
[`bb_estimator_ahrs`](https://hex.pm/packages/bb_estimator_ahrs)
(Madgwick, Mahony, Complementary). See
[Pairing with an AHRS estimator](#module-pairing-with-an-ahrs-estimator).

## Modes

Pick based on output data rate:

- `:polling` — periodically calls `BMI323.read_imu/1` at
  `publish_rate`. Low-overhead, low-jitter at modest rates. Use when
  ODR ≤ 200 Hz. Above ~200 Hz the BEAM scheduler starts to lose
  samples between polls.
- `:interrupt` — runs `BMI323.Sampler` which buffers samples in the
  chip's on-chip 2 KB FIFO and fires the host's INT1 GPIO when a
  configurable watermark is reached. Reliable up to the chip's
  6.4 kHz ODR. Requires INT1 wired to a host GPIO.

In interrupt mode, samples arrive in bursts of `watermark_frames` at
once. With ODR 800 Hz and watermark 8, you'll see batches every 10 ms.
Downstream consumers should be designed to handle a burst (an estimator
like the AHRS filters integrates each sample with its own dt, so it
copes naturally).

## Coordinate frame

Axes are the chip's own +X / +Y / +Z (see the BMI323 datasheet
§3.2 for the silkscreen orientation). The BB topology entity this
sensor attaches to *is* its coordinate frame — orient the IMU on the
link as appropriate and apply a static transform in your downstream
consumer if the chip mounting axes don't match the link axes.

## Example DSL Usage

    topology do
      link :base do
        sensor :imu, {BB.Sensor.BMI323,
          bus: "i2c-1",
          address: 0x68,
          mode: :polling,
          accelerometer_range: 8,
          accelerometer_odr: 200,
          gyroscope_range: 2000,
          gyroscope_odr: 200,
          publish_rate: ~u(100 hertz)
        }
      end
    end

Or in interrupt mode, with INT1 wired to GPIO 17:

    sensor :imu, {BB.Sensor.BMI323,
      bus: "i2c-1",
      address: 0x68,
      mode: :interrupt,
      int1_pin: 17,
      accelerometer_range: 8,
      accelerometer_odr: 800,
      gyroscope_range: 2000,
      gyroscope_odr: 800,
      watermark_frames: 8
    }

## Pairing with an AHRS estimator

The BMI323 produces raw acceleration + angular-velocity samples; turning
those into an orientation needs sensor fusion. Attach an estimator from
`bb_estimator_ahrs`:

    sensor :imu, {BB.Sensor.BMI323, bus: "i2c-1", ...} do
      estimator :orientation, {BB.Estimator.Ahrs.Madgwick, beta: 0.1}
    end

The estimator subscribes to this sensor's `Imu` messages, replaces the
identity quaternion with a fused orientation, and republishes. See
`BB.Estimator.Ahrs.Madgwick`, `BB.Estimator.Ahrs.Mahony`, and
`BB.Estimator.Ahrs.Complementary` for the algorithm choices.

## Options

- `bus` — I2C bus name (e.g. `"i2c-1"`) — required.
- `address` — I2C address (`0x68` or `0x69`, default `0x68`).
- `mode` — `:polling` or `:interrupt` (default `:polling`).
- `accelerometer_range` — `2 | 4 | 8 | 16` g (default `8`).
- `accelerometer_odr` — output data rate in Hz (default `200`).
- `accelerometer_mode` — `:disabled | :low_power | :normal |
  :high_performance` (default `:normal`). Setting either axis to
  `:disabled` powers it down — the IMU will publish constant /
  invalid values for that axis until the mode is changed back.
- `gyroscope_range` — `125 | 250 | 500 | 1000 | 2000` °/s (default
  `2000`).
- `gyroscope_odr` — output data rate in Hz (default `200`).
- `gyroscope_mode` — as for accelerometer (default `:normal`).
- `publish_rate` — polling rate (default `~u(100 hertz)`). Ignored in
  interrupt mode.
- `int1_pin` — GPIO pin number wired to BMI323's INT1. Required when
  `mode: :interrupt`.
- `watermark_frames` — FIFO frames per interrupt (default `8`).
  Interrupt mode only.

## Published Messages

`BB.Message.Sensor.Imu` published to `[:sensor | path]` where `path` is
the sensor's position in the topology. Fields:

- `angular_velocity` — gyroscope reading as `BB.Math.Vec3` in rad/s.
- `linear_acceleration` — accelerometer reading as `BB.Math.Vec3` in
  m/s².
- `orientation` — `BB.Math.Quaternion.identity/0` (see above).

## Runtime parameter changes

Options handled live (no restart):

- `publish_rate` (polling mode) — interval is recomputed.
- `accelerometer_range` / `accelerometer_odr` / `accelerometer_mode`
  — `BMI323.configure_accelerometer/2` is re-issued.
- `gyroscope_range` / `gyroscope_odr` / `gyroscope_mode` —
  `BMI323.configure_gyroscope/2` is re-issued.

Options that trigger `{:stop, :reconfigure}` (supervisor restarts the
sensor with new params):

- `mode`, `bus`, `address`, `int1_pin`, `watermark_frames`.

## Error handling

A single failed read or reconfiguration is logged at warning level and
does not crash the process — the polling loop or interrupt handler
continues. Persistent failures will manifest as silence on the topic
rather than a crash.

## Troubleshooting

- `{:stop, {:chip_id_mismatch, got: id, expected: 0x43}}` — the device
  at the configured I2C address is not a BMI323. Check `address`
  (`0x68` when SDO is tied to GND, `0x69` when tied to VDDIO), the
  physical bus, and that the chip is actually powered.
- `{:stop, :no_such_bus}` from `Wafer.Driver.Circuits.I2C.acquire/1`
  — the `bus` string doesn't match any `/dev/i2c-*` device. On Linux
  list available buses with `i2cdetect -l`.
- `{:stop, :int1_pin_required_for_interrupt_mode}` — `mode:
  :interrupt` was set without an `int1_pin`.
- GPIO acquire failure in interrupt mode — the pin may already be
  exported, owned by another process, or not exist on this board.
- Constant / silently-wrong samples after changing `*_mode` — check
  you didn't leave an axis on `:disabled`; that powers it down.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
