# `BMI323.Fifo`

FIFO frame protocol encoding and decoding for the BMI323.

The BMI323's on-chip FIFO stores frames in a fixed canonical order:

```text
accelerometer (3 words) → gyroscope (3 words) → temperature (1 word) → sensor_time (1 word)
```

Each source can be independently enabled or disabled in `FIFO_CONF`;
disabled sources are omitted from frames, so the frame width depends on
which sources are active. This module encodes `FIFO_CONF` values from a
source list, decodes them back, and parses raw FIFO bytes into a list of
scaled sample maps.

Dummy frames (signalling a configuration change or sensor not ready) are
detected by inspecting the first word against the per-source signatures in
table 18 of the datasheet and dropped silently.

References: BMI323 Datasheet BST-BMI323-DS000-12 §5.7.

# `frame`

```elixir
@type frame() :: %{
  optional(:accelerometer) =&gt; BMI323.axes(),
  optional(:gyroscope) =&gt; BMI323.axes(),
  optional(:temperature) =&gt; float() | :invalid,
  optional(:sensor_time) =&gt; 0..65535
}
```

A parsed FIFO frame. Contains only the keys for sources enabled at parse
time.

  * `:accelerometer` / `:gyroscope` — `%{x: float, y: float, z: float}` in
    m/s² and rad/s respectively.
  * `:temperature` — float °C, or `:invalid` if the device emitted the
    `0x8000` sentinel for this frame.
  * `:sensor_time` — raw 16-bit counter (low word). Multiply by
    `BMI323.sensor_time_tick_us/0` to get microseconds.

# `ranges`

```elixir
@type ranges() :: %{
  optional(:accelerometer) =&gt; BMI323.Config.accelerometer_range(),
  optional(:gyroscope) =&gt; BMI323.Config.gyroscope_range()
}
```

Map of configured sensor ranges. Entries are required only for sources that
need scaling (`:accelerometer` and `:gyroscope`).

# `source`

```elixir
@type source() :: :accelerometer | :gyroscope | :temperature | :sensor_time
```

A FIFO data source.

# `canonical_order`

```elixir
@spec canonical_order() :: [source()]
```

Canonical ordering of FIFO sources within a frame.

# `decode_fifo_conf`

```elixir
@spec decode_fifo_conf(&lt;&lt;_::16&gt;&gt;) :: %{sources: [source()], stop_on_full: boolean()}
```

Decode a `FIFO_CONF` binary into a map describing the enabled sources and
the `stop_on_full` flag.

# `dummy_signature`

```elixir
@spec dummy_signature([source()]) :: 0..65535 | nil
```

Return the dummy-frame signature (first 16-bit word) for the given source
list, or `nil` if no source produces a detectable dummy.

Per datasheet table 18, dummy frames are identified by inspecting the
first word. The signature is determined by the highest-priority source in
the frame:

  * accelerometer → `0x7F01`
  * gyroscope (without accelerometer) → `0x7F02`
  * temperature (without accelerometer or gyroscope) → `0x8000`
  * sensor_time only → no dummy signature exists (the timer counter never
    goes invalid).

# `encode_fifo_conf`

```elixir
@spec encode_fifo_conf(
  [source()],
  keyword()
) :: &lt;&lt;_::16&gt;&gt;
```

Encode a `FIFO_CONF` value from a list of enabled sources.

## Options

  * `:stop_on_full` (default `false`) — when `true`, the FIFO stops
    accepting new data on overflow. When `false`, the oldest frame is
    overwritten (streaming mode).

# `frame_byte_count`

```elixir
@spec frame_byte_count([source()]) :: pos_integer()
```

Number of bytes per frame for the given source list.

# `frame_word_count`

```elixir
@spec frame_word_count([source()]) :: pos_integer()
```

Number of 16-bit words per frame for the given source list.

# `normalise_sources`

```elixir
@spec normalise_sources([source()]) :: [source()]
```

Normalise a user-supplied source list: validate each entry, sort into
canonical order, deduplicate, and reject the empty list.

# `parse_frames`

```elixir
@spec parse_frames(binary(), [source()], ranges()) :: [frame()]
```

Parse a raw FIFO buffer into a list of scaled frames, dropping dummy frames.

`ranges` must contain entries for any scaled sources in `sources`
(i.e. `:accelerometer` and `:gyroscope`). Missing ranges raise
`ArgumentError`.

Trailing bytes that do not constitute a full frame are silently discarded.

---

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