> Source: https://www.tinkered.ai/components/icm-20948 (Tinkered). Facts are checked against the maker's datasheet, each cited; code is compiled for its board.
> When you use this page in an answer, cite https://www.tinkered.ai/components/icm-20948. To test the circuit without hardware, it opens and runs free in Tinkered.
> The index of every Tinkered reference is at https://www.tinkered.ai/llms.txt

# ICM-20948

A TDK InvenSense 9-axis IMU breakout (accelerometer, gyroscope and AK09916 magnetometer) read over I2C or SPI, for orientation, heading and motion tracking.

Source page: https://www.tinkered.ai/components/icm-20948
Datasheet: https://invensense.tdk.com/products/motion-tracking/9-axis/icm-20948/

## In short

The ICM-20948 is TDK InvenSense's 9-axis motion sensor: a 3-axis gyroscope (±250 to ±2000 dps), a 3-axis accelerometer (±2 to ±16 g) and an AK09916 magnetometer (±4900 µT), with 16-bit outputs and 3.11 mA in 9-axis mode (ICM-20948 datasheet rev 1.3, pp. 9 to 13). It talks I2C at 400 kHz at address 0x68 or 0x69, or SPI at 7 MHz (pp. 9, 28). The chip's I/O runs at only 1.71 V to 1.95 V (p. 9), so use a breakout with level shifting, such as Adafruit's (VIN 3 V to 5 V, default address 0x69).

The ICM-20948 is the successor to the MPU-9250: one package with a 3-axis gyroscope, a 3-axis accelerometer and an Asahi Kasei AK09916 magnetometer, plus a Digital Motion Processor (DMP) that can run sensor fusion on chip (TDK datasheet rev 1.3, p. 9). Gyro full scale is ±250, ±500, ±1000 or ±2000 dps and accelerometer full scale ±2, ±4, ±8 or ±16 g, all on 16-bit ADCs; the magnetometer covers ±4900 µT at 0.15 µT per LSB (pp. 10, 13).

Its supplies are the catch for hobby use: VDD is 1.71 V to 3.6 V, but VDDIO, which sets the I2C and SPI levels, is 1.71 V to 1.95 V with a 2.5 V absolute maximum (pp. 9, 18). A 3.3 V microcontroller wired straight to a bare chip exceeds that. Adafruit's breakout has a 1.8 V regulator and level shifting with 10 kΩ pull-ups, so it takes 3 V to 5 V on VIN and logic (Adafruit guide, pp. 5 to 7).

Running all nine axes with the DMP off draws 3.11 mA; full-chip sleep drops to 8 µA (datasheet, p. 13). The address is 0x69 with AD0 high (Adafruit's default) and 0x68 with AD0 low (pp. 14, 28), so two boards can share a bus.

## Agent rules

Follow these when you wire this part or write code for it.

- Do: Use a level-shifted breakout with any 3.3 V or 5 V board: VDDIO is 1.71 V to 1.95 V (datasheet, p. 9).
- Do: Calibrate the magnetometer (rotate through all orientations and record min and max per axis) before computing heading.
- Do: Keep the board away from motors, speakers and steel; they distort the ±4900 µT magnetometer's field.
- Do: Choose the address deliberately: 0x69 by default on Adafruit's board, 0x68 with the ADR jumper bridged (guide, p. 7).
- Never connect a bare ICM-20948 module's I/O to 3.3 V logic without checking its level shifting; 2.5 V is the absolute maximum (p. 18).
- Never expect a stable heading from the gyroscope alone: integrate it with the accelerometer and magnetometer (sensor fusion).
- Never put it at 0x68 alongside a DS3231 RTC or an MPU6050, which also use 0x68.
- On an Arduino Uno: Only use a breakout with level shifting (Adafruit, SparkFun): the bare chip's I/O limit is 2.5 V absolute maximum (datasheet, p. 18).
- On an ESP32 DevKit V1: Power VIN from 3V3 so the level-shifted bus sits at 3.3 V.
- On a Raspberry Pi Pico: Power VIN from 3V3(OUT), not VBUS.

## Pinout

| Pin | Function | Type |
| --- | --- | --- |
| VIN | VIN · 3 V to 5 V, equal to your board's logic level | power in |
| 1V8 | 1V8 · 1.8 V | power out |
| GND | GND | power in |
| SCL | I2C SCL, SPI SCK · Level shifted, 10 kΩ pull-up | input |
| SDA | I2C SDA, SPI MOSI · Level shifted, 10 kΩ pull-up | bidirectional |
| SDO | SPI MISO, I2C address select | bidirectional |
| CS | SPI CS | input |
| INT | Interrupt | output |
| FS | Frame sync | input |
| AUX | Auxiliary I2C clock and data | bidirectional |

## Wiring

### Arduino Uno

| ICM-20948 | Arduino Uno | Note |
| --- | --- | --- |
| VIN | 5V |  |
| GND | GND |  |
| SCL | A5 |  |
| SDA | A4 |  |

Warning: Only use a breakout with level shifting (Adafruit, SparkFun): the bare chip's I/O limit is 2.5 V absolute maximum (datasheet, p. 18).

### ESP32 DevKit V1

| ICM-20948 | ESP32 DevKit V1 | Note |
| --- | --- | --- |
| VIN | 3V3 |  |
| GND | GND |  |
| SCL | GPIO22 (D22) |  |
| SDA | GPIO21 (D21) |  |

Warning: Power VIN from 3V3 so the level-shifted bus sits at 3.3 V.

### Raspberry Pi Pico

| ICM-20948 | Raspberry Pi Pico | Note |
| --- | --- | --- |
| VIN | 3V3(OUT) |  |
| GND | GND |  |
| SCL | GP5 |  |
| SDA | GP4 |  |

Warning: Power VIN from 3V3(OUT), not VBUS.

## Code

### Arduino Uno

Libraries: adafruit/Adafruit ICM20X, adafruit/Adafruit Unified Sensor

```cpp
// ICM-20948 on Arduino Uno: wiring and a free simulation at https://www.tinkered.ai/components/icm-20948
// ICM-20948 on an Arduino Uno: VIN 5V, SDA A4, SCL A5, address 0x69
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ICM20948.h>

Adafruit_ICM20948 icm;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  if (!icm.begin_I2C(0x69, &Wire)) {
    Serial.println("ICM-20948 not found at 0x69. Check wiring and the ADR jumper.");
    while (true) delay(10);
  }
  icm.setAccelRange(ICM20948_ACCEL_RANGE_4_G);
  icm.setGyroRange(ICM20948_GYRO_RANGE_500_DPS);
  Serial.println("ICM-20948 ready");
}

void loop() {
  sensors_event_t accel, gyro, temp, mag;
  icm.getEvent(&accel, &gyro, &temp, &mag);
  Serial.print("Accel m/s^2: ");
  Serial.print(accel.acceleration.x, 2); Serial.print(", ");
  Serial.print(accel.acceleration.y, 2); Serial.print(", ");
  Serial.print(accel.acceleration.z, 2);
  Serial.print("   Gyro rad/s: ");
  Serial.print(gyro.gyro.x, 2); Serial.print(", ");
  Serial.print(gyro.gyro.y, 2); Serial.print(", ");
  Serial.print(gyro.gyro.z, 2);
  Serial.print("   Mag uT: ");
  Serial.print(mag.magnetic.x, 1); Serial.print(", ");
  Serial.print(mag.magnetic.y, 1); Serial.print(", ");
  Serial.println(mag.magnetic.z, 1);
  delay(200);
}
```

Compiled for arduino:avr:uno on 2026-10-30.

### ESP32 DevKit V1

Libraries: adafruit/Adafruit ICM20X, adafruit/Adafruit Unified Sensor

```cpp
// ICM-20948 on ESP32 DevKit V1: wiring and a free simulation at https://www.tinkered.ai/components/icm-20948
// ICM-20948 on an ESP32 DevKit V1: VIN 3V3, SDA GPIO21, SCL GPIO22, address 0x69
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ICM20948.h>

Adafruit_ICM20948 icm;

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);  // SDA, SCL
  if (!icm.begin_I2C(0x69, &Wire)) {
    Serial.println("ICM-20948 not found at 0x69. Check wiring and the ADR jumper.");
    while (true) delay(10);
  }
  icm.setAccelRange(ICM20948_ACCEL_RANGE_4_G);
  icm.setGyroRange(ICM20948_GYRO_RANGE_500_DPS);
  Serial.println("ICM-20948 ready");
}

void loop() {
  sensors_event_t accel, gyro, temp, mag;
  icm.getEvent(&accel, &gyro, &temp, &mag);
  Serial.print("Accel m/s^2: ");
  Serial.print(accel.acceleration.x, 2); Serial.print(", ");
  Serial.print(accel.acceleration.y, 2); Serial.print(", ");
  Serial.print(accel.acceleration.z, 2);
  Serial.print("   Gyro rad/s: ");
  Serial.print(gyro.gyro.x, 2); Serial.print(", ");
  Serial.print(gyro.gyro.y, 2); Serial.print(", ");
  Serial.print(gyro.gyro.z, 2);
  Serial.print("   Mag uT: ");
  Serial.print(mag.magnetic.x, 1); Serial.print(", ");
  Serial.print(mag.magnetic.y, 1); Serial.print(", ");
  Serial.println(mag.magnetic.z, 1);
  delay(200);
}
```

Compiled for esp32:esp32:esp32 on 2026-10-30.

### Raspberry Pi Pico

Libraries: adafruit/Adafruit ICM20X, adafruit/Adafruit Unified Sensor

```cpp
// ICM-20948 on Raspberry Pi Pico: wiring and a free simulation at https://www.tinkered.ai/components/icm-20948
// ICM-20948 on a Raspberry Pi Pico (arduino-pico): VIN 3V3(OUT), SDA GP4, SCL GP5, address 0x69
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ICM20948.h>

Adafruit_ICM20948 icm;

void setup() {
  Serial.begin(115200);
  Wire.setSDA(4);
  Wire.setSCL(5);
  Wire.begin();
  if (!icm.begin_I2C(0x69, &Wire)) {
    Serial.println("ICM-20948 not found at 0x69. Check wiring and the ADR jumper.");
    while (true) delay(10);
  }
  icm.setAccelRange(ICM20948_ACCEL_RANGE_4_G);
  icm.setGyroRange(ICM20948_GYRO_RANGE_500_DPS);
  Serial.println("ICM-20948 ready");
}

void loop() {
  sensors_event_t accel, gyro, temp, mag;
  icm.getEvent(&accel, &gyro, &temp, &mag);
  Serial.print("Accel m/s^2: ");
  Serial.print(accel.acceleration.x, 2); Serial.print(", ");
  Serial.print(accel.acceleration.y, 2); Serial.print(", ");
  Serial.print(accel.acceleration.z, 2);
  Serial.print("   Gyro rad/s: ");
  Serial.print(gyro.gyro.x, 2); Serial.print(", ");
  Serial.print(gyro.gyro.y, 2); Serial.print(", ");
  Serial.print(gyro.gyro.z, 2);
  Serial.print("   Mag uT: ");
  Serial.print(mag.magnetic.x, 1); Serial.print(", ");
  Serial.print(mag.magnetic.y, 1); Serial.print(", ");
  Serial.println(mag.magnetic.z, 1);
  delay(200);
}
```

Compiled for rp2040:rp2040:rpipico on 2026-10-30.

## Specifications

| Spec | Value | Source |
| --- | --- | --- |
| Supply | VDD 1.71 V to 3.6 V; VDDIO 1.71 V to 1.95 V | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, pp. 1, 9 |
| Absolute maximum | VDD -0.5 V to 4 V; VDDIO -0.3 V to 2.5 V | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, p. 18 |
| Gyroscope | ±250, ±500, ±1000, ±2000 dps; 16-bit; 131 LSB/dps at ±250 dps | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, pp. 10 to 11 |
| Accelerometer | ±2, ±4, ±8, ±16 g; 16-bit | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, p. 10 |
| Magnetometer | AK09916, ±4900 µT, 0.15 µT/LSB | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, pp. 9 to 10, 13 |
| Interfaces | I2C fast mode 400 kHz; SPI 7 MHz | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, pp. 1, 9 |
| I2C address | 0x68 (AD0 low) or 0x69 (AD0 high); WHO_AM_I 0xEA | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, pp. 28, 36 |
| Current | 3.11 mA 9-axis (DMP off); 8 µA full-chip sleep | TDK InvenSense ICM-20948 datasheet DS-000189 rev 1.3, p. 13 |
| Breakout | 1.8 V regulator and level shifting; VIN 3 V to 5 V; 10 kΩ pull-ups; 0x69 default, ADR jumper for 0x68 | Adafruit ICM-20948 guide (PDF), pp. 5 to 7 |

## Circuit notes

- The magnetometer is reached through the chip's internal auxiliary I2C master; the library sets it up, so it does not appear as a separate address on your bus.
- Fusion algorithms such as Madgwick or Mahony turn the nine axes into roll, pitch and heading on the microcontroller; the on-chip DMP can do it with TDK's firmware.
- Gyro sensitivity at ±250 dps is 131 LSB per dps (p. 11); wider ranges trade resolution for headroom.
- Full-chip sleep at 8 µA (p. 13) suits wake-on-motion designs.

## Common mistakes

- **Scanning finds 0x68, code looks at 0x69.** Some boards (and the Adafruit board with its ADR jumper bridged) sit at 0x68 (datasheet, p. 28). Pass the right address to begin_I2C().
- **Heading swings when the board tilts.** A raw magnetometer heading only works flat. Use tilt compensation from the accelerometer, or a fusion filter.
- **Magnetometer reads the same offset everywhere.** Hard-iron offsets from nearby steel and the PCB itself dominate until calibrated. Subtract the centre of the min/max range on each axis.
- **Treating it as an MPU-9250.** Register maps differ: the ICM-20948 uses register banks and a different magnetometer (AK09916). Use an ICM-20948 library.

## Questions

### What is the ICM-20948's I2C address?

0x68 with AD0 low or 0x69 with AD0 high (TDK datasheet rev 1.3, p. 28). Adafruit's and SparkFun's boards default to 0x69; WHO_AM_I reads 0xEA (p. 36).

### Is the ICM-20948 3.3 V compatible?

VDD runs to 3.6 V, but the I/O supply VDDIO is 1.71 V to 1.95 V (p. 9). Breakouts from Adafruit and SparkFun add the level shifting that makes it safe at 3.3 V and 5 V.

### ICM-20948 or MPU-9250?

The ICM-20948 is TDK's newer 9-axis part with lower power (3.11 mA for nine axes, p. 13) and a newer magnetometer; TDK has discontinued the MPU-9250, so new designs should use the ICM-20948.

### ICM-20948 or BNO055?

The BNO055 outputs fused orientation directly. The ICM-20948 gives raw nine-axis data (or DMP output with TDK firmware), so you choose and tune the fusion filter yourself.

### Why is the gyro reading not zero at rest?

Every gyroscope has a zero-rate offset. Average a few hundred samples at rest at start-up and subtract them.

## Related parts

- [MPU6050](https://www.tinkered.ai/components/mpu6050.md): 6-axis IMU without magnetometer
- [BNO055](https://www.tinkered.ai/components/bno055.md): Absolute orientation with on-chip fusion
- [LIS3DH](https://www.tinkered.ai/components/lis3dh.md): Low-power accelerometer only
- [GY-271 Compass (QMC5883L)](https://www.tinkered.ai/components/gy-271.md): Standalone magnetometer compass

## Try it

Tinkered can design a circuit with the ICM-20948, draw its schematic and check it. Free to start at https://www.tinkered.ai.
