# IMU

The accelerometer and gyroscope in a Kode Dot: a QMI8658A on the shared I2C bus, what each one measures, and the code to read them.

## What it is

An IMU, an inertial measurement unit, is an accelerometer and a gyroscope in
one package. Together they tell the Kode Dot how it is moving.

- The **accelerometer** measures acceleration on three axes, gravity
  included. At rest it reads 1 g straight down, so tilting the Kode Dot
  moves that 1 g from one axis to another: it is how you know which way is
  down. Shake it and the reading spikes; that is how a step counter counts.
- The **gyroscope** measures rotation rate on three axes, in degrees per
  second. It says nothing about where you are pointing, only how fast that
  is changing. Integrate it over time and you get an angle; leave it long
  enough and the angle drifts.

The part is a **QMI8658A** from QST: both sensors on one die, 16-bit
outputs, a small motion co-processor for things like pedometer and tap
detection, and a thermometer for the die itself.

## How it is connected

```mermaid
flowchart LR
  p4["ESP32-P4"] -- "I2C, 0x6A" --> imu["QMI8658A"]
```

*One part on the shared I2C bus, at its own address*

The QMI8658A sits on the P4's shared I2C bus at `0x6A`, brought up by
`kode_bsp_init` and reached through `kode_bsp_i2c()`. Whether its two
interrupt pins reach the P4 is **TBD**; no Kode example uses them, and the
examples poll instead.

## Specifications

| Thing | Value |
| --- | --- |
| Address | `0x6A`. Register `0x00` reads `0x05`, the part's identity |
| Accelerometer | ±2, ±4, ±8 or ±16 g, bits 6:4 of `CTRL2` (`0x03`). At ±2 g one count is 2000/32768 mg |
| Gyroscope | ±16 to ±1024 dps in seven steps, bits 6:4 of `CTRL3` (`0x04`). At ±256 dps one count is 256000/32768 mdps |
| Rate | Bits 3:0 of `CTRL2` and `CTRL3`: `3` = 1000 Hz, `4` = 500, `5` = 250, `6` = 125, `7` = 62.5, `8` = 31.25 |
| On and off | `CTRL7` (`0x08`): bit 0 accelerometer, bit 1 gyroscope. Both off out of reset |
| Output | `0x35`–`0x40`: ax ay az gx gy gz, `int16_t`, low byte first. Set bit 6 of `CTRL1` (`0x02`) and one read returns all twelve bytes |
| Temperature | `0x33`–`0x34`, the die's own |
| Kode driver | None yet: `kode_imu` in the `kode` component targets the prototype's part and returns `ESP_ERR_NOT_FOUND` here. **TBD**: a QMI8658A driver |
| Validated | **TBD**: not yet run on a V1 board. Register map from QST's datasheet, cross-checked against a public driver |

## Gotchas

> **NOTE**
>
> **Gravity is in the accelerometer's numbers.** A Kode Dot lying still is not
> reading zero: it reads 1 g on whichever axis points down. To measure your
> own motion, subtract gravity first, which means knowing which way is down,
> which is the tilt the accelerometer just gave you. That circle is why an IMU
> is used with a filter and not read raw.

> **NOTE**
>
> **The gyroscope drifts.** Integrating a rate to get an angle also integrates
> its small constant error, so an angle from the gyroscope alone walks away
> over a minute or two. The accelerometer's down and the compass's north pull
> it back: see `device/motion/overview`.

## Apps that use it

On the marketplace, apps that put this part to work. Install one from
`start/first-app` and see it before you write a line.

- https://apps.kode.diy/app/cb2636c7-8273-4df2-9fb6-be5a007dc49d
- https://apps.kode.diy/app/04c1ec90-c7c1-48fb-ac53-c8822aef31ad
- https://apps.kode.diy/app/b9031bab-a75a-4f86-8640-ea9fd5711526
- https://apps.kode.diy/app/db6f7781-9d9c-4240-87cb-8f4f9bf1e796
- https://apps.kode.diy/app/6e483c49-acaf-479c-b80e-f58faaf68f98
- https://apps.kode.diy/app/437ec99a-0046-4dbe-8391-7be4e799ddc0
- https://apps.kode.diy/app/de67c852-4d33-47fa-8ea6-9fd10d26fcd2
- https://apps.kode.diy/app/7b60e571-9826-405f-8af5-3185899dfc74

## Example code

Start the accelerometer and the gyroscope, then log a sample ten times a
second. Set the Kode Dot flat and one axis reads about 1000 mg with the other
two near zero; tilt it and the 1000 mg walks from one axis to another.

```c
/*
 * imu.c - 2026-09-17 - from Kode with love
 *
 * Reads the Kode Dot's QMI8658A over the shared I2C bus. Brings the board
 * up, wakes the accelerometer at ±2 g and the gyroscope at ±256 dps, both at
 * 125 Hz, and logs acceleration in mg and rotation in mdps ten times a
 * second. Flat on a table, the Z axis reads about 1000 mg.
 */
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "kode.h"

#define IMU_ADDR   0x6A
#define REG_CTRL1  0x02   /* interface: bit 6 = address auto-increment */
#define REG_CTRL2  0x03   /* accelerometer: range in bits 6:4, rate in 3:0 */
#define REG_CTRL3  0x04   /* gyroscope: same layout */
#define REG_CTRL7  0x08   /* bit 0 = accel on, bit 1 = gyro on */
#define REG_AX_L   0x35   /* twelve bytes: ax ay az gx gy gz, low byte first */

static i2c_master_dev_handle_t s_imu;

/* One register write: address byte, then the value. */
static void imu_write(uint8_t reg, uint8_t value)
{
    uint8_t frame[2] = { reg, value };
    ESP_ERROR_CHECK(i2c_master_transmit(s_imu, frame, 2, 100));
}

/* Attach the IMU to the bus kode_bsp owns, configure it, then log forever. */
void app_main(void)
{
    ESP_ERROR_CHECK(kode_bsp_init());
    i2c_device_config_t cfg = { .device_address = IMU_ADDR, .scl_speed_hz = KODE_I2C_FREQ_HZ };
    ESP_ERROR_CHECK(i2c_master_bus_add_device(kode_bsp_i2c(), &cfg, &s_imu));

    imu_write(REG_CTRL1, 0x40);   /* auto-increment, so one read returns all six axes */
    imu_write(REG_CTRL2, 0x06);   /* ±2 g, 125 Hz */
    imu_write(REG_CTRL3, 0x46);   /* ±256 dps, 125 Hz */
    imu_write(REG_CTRL7, 0x03);   /* accelerometer and gyroscope on */

    while (1) {
        uint8_t reg = REG_AX_L, raw[12];
        ESP_ERROR_CHECK(i2c_master_transmit_receive(s_imu, &reg, 1, raw, 12, 100));
        int16_t v[6];
        for (int i = 0; i < 6; i++) v[i] = (int16_t)(raw[2 * i] | raw[2 * i + 1] << 8);
        ESP_LOGI("imu", "acc %5ld %5ld %5ld mg   gyro %6ld %6ld %6ld mdps",
                 v[0] * 2000L / 32768, v[1] * 2000L / 32768, v[2] * 2000L / 32768,
                 v[3] * 256000L / 32768, v[4] * 256000L / 32768, v[5] * 256000L / 32768);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}
```

Compiled on the date in the header against the `kode` component with
ESP-IDF v6.1. **TBD**: a run on a V1 board.

## In the SDK

- [`kdrv_motion`](https://github.com/kodediy/kode-sdk/tree/main/components/kdrv/kdrv_motion): the motion drivers in `kode-sdk`. **TBD**: a QMI8658A driver; today the folder holds the prototype's `lsm6dsv`.
