ESP32 and the MPU9250: accelerometer, gyroscope and magnetometer in one module

In the lesson accelerometer and magnetometer we used two separate modules. The MPU9250 module has everything in one: an accelerometer (acceleration and tilt), a gyroscope (rotation speed) and a magnetometer (compass) – nine measured quantities in total, which is why it is called a "9-axis" sensor. Mobile phones, drones and smartwatches have sensors like this.

MPU-9250/6500 module with pin labels
MPU-9250/6500 module with pin labels

What is in the module

SensorMeasuresUnitExample use
accelerometer (3 axes)acceleration, including gravityg (1 g ≈ 9.81 m/s²)tilt, shaking, step counter
gyroscope (3 axes)rotation speed°/s (degrees per second)how far an object has turned, drone stabilization
AK8963 magnetometer (3 axes)magnetic fieldµT (microtesla)compass – direction of north
thermometerchip temperature°Cmeasurement correction (not air temperature)
Careful – "MPU9250/6500": the same board is also sold with the MPU6500 chip, which has no magnetometer. The label on the module does not tell you. The program in step 1 reads the WHO_AM_I register: 0x71 or 0x73 means MPU9250, 0x70 means MPU6500.

Wiring (I2C)

MPU9250ESP32Note
VCC3V3the module has a regulator and also runs on 3.3 V
GNDGND
SCLGPIO22I2C clock
SDAGPIO21I2C data
AD0not connected (or GND)address 0x68; on 3V3 the address is 0x69

We do not need the other pins (EDA, ECL, INT, NCS, FSYNC). You can connect the module to the same SDA and SCL wires as other I2C modules – each has its own address.

Step 1: I2C scanner and WHO_AM_I

// ESP32 – I2C scanner: does the MPU9250 answer, and which chip is it?
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);           // SDA = GPIO21, SCL = GPIO22
  delay(100);

  for (int address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      Serial.print("Found a device at 0x");
      Serial.println(address, HEX);
    }
  }

  // The WHO_AM_I register (0x75) tells which chip is really on the module
  Wire.beginTransmission(0x68);
  Wire.write(0x75);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 1);
  int id = Wire.read();
  Serial.print("WHO_AM_I = 0x");
  Serial.println(id, HEX);
  if (id == 0x71 || id == 0x73) {
    Serial.println("MPU9250/MPU9255 – it has a magnetometer too.");
  } else if (id == 0x70) {
    Serial.println("MPU6500 – accelerometer and gyroscope only (no magnetometer).");
  } else {
    Serial.println("Unknown chip – check the wiring.");
  }
}

void loop() {
}

A device is expected at 0x68. At first the magnetometer is not visible – it is inside the same package, but "behind" the main chip. We unlock it in step 3.

Step 2: accelerometer and gyroscope

As with the MPU6050, we wake the sensor by writing 0 to register 0x6B, then read pairs of bytes: acceleration from register 0x3B, temperature from 0x41 and the gyroscope from 0x43.

// ESP32 – MPU9250: accelerometer, gyroscope and temperature (no extra libraries)
#include <Wire.h>

const int MPU = 0x68;           // I2C address (AD0 to GND)

void writeRegister(int device, int reg, int value) {
  Wire.beginTransmission(device);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

// reads a signed 16-bit number: high byte first, then low byte (big-endian)
int16_t read16(int reg) {
  Wire.beginTransmission(MPU);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 2);
  int16_t high = Wire.read();
  int16_t low = Wire.read();
  return (int16_t)((high << 8) | low);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  writeRegister(MPU, 0x6B, 0x00);   // PWR_MGMT_1 = 0: wakes the sensor
  delay(100);
}

void loop() {
  // accelerometer: ±2 g, 16384 units = 1 g
  float ax = read16(0x3B) / 16384.0;
  float ay = read16(0x3D) / 16384.0;
  float az = read16(0x3F) / 16384.0;
  // chip temperature (not air!)
  float temp = read16(0x41) / 333.87 + 21.0;
  // gyroscope: ±250 °/s, 131 units = 1 °/s
  float gx = read16(0x43) / 131.0;
  float gy = read16(0x45) / 131.0;
  float gz = read16(0x47) / 131.0;

  // tilt from the accelerometer (valid while the module is still)
  float tiltForward = atan2(ay, az) * 180.0 / PI;
  float tiltSide = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;

  Serial.print("a[g]: ");
  Serial.print(ax, 2); Serial.print(" "); Serial.print(ay, 2); Serial.print(" "); Serial.print(az, 2);
  Serial.print("  w[°/s]: ");
  Serial.print(gx, 1); Serial.print(" "); Serial.print(gy, 1); Serial.print(" "); Serial.print(gz, 1);
  Serial.print("  tilt: ");
  Serial.print(tiltForward, 0); Serial.print(" / "); Serial.print(tiltSide, 0);
  Serial.print("  T: ");
  Serial.println(temp, 1);
  delay(200);
}
QuantityRaw numberDivide byResult
acceleration (±2 g)−32768 … 3276716384g
angular speed (±250 °/s)−32768 … 32767131°/s
temperature…333.87 (then + 21)°C
  • When the module lies flat and still, the accelerometer shows about Z = 1 g and the gyroscope about 0 (with a small error – the offset).
  • When you turn it, the gyroscope shows how fast it is turning, not its position. The position is found by adding up: angle = speed · time (task 3).

Step 3: magnetometer and compass

Writing 0x02 to register 0x37 turns on bypass: the main chip "lets through" the I2C connection to the AK8963 magnetometer at address 0x0C. Then we switch the magnetometer to continuous measurement.

// ESP32 – MPU9250: AK8963 magnetometer and compass
#include <Wire.h>

const int MPU = 0x68;
const int MAG = 0x0C;           // the AK8963 is "hidden" behind the MPU

void writeRegister(int device, int reg, int value) {
  Wire.beginTransmission(device);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

int readByte(int device, int reg) {
  Wire.beginTransmission(device);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(device, 1);
  return Wire.read();
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  writeRegister(MPU, 0x6B, 0x00);   // wake the MPU
  delay(100);
  writeRegister(MPU, 0x37, 0x02);   // INT_PIN_CFG: BYPASS – AK8963 visible on I2C
  delay(10);
  writeRegister(MAG, 0x0A, 0x00);   // magnetometer: off (before changing mode)
  delay(10);
  writeRegister(MAG, 0x0A, 0x16);   // 16 bits, continuous measurement at 100 Hz
  delay(10);
  Serial.print("AK8963 WIA (should be 0x48): 0x");
  Serial.println(readByte(MAG, 0x00), HEX);
}

void loop() {
  if (readByte(MAG, 0x02) & 0x01) {           // ST1: a new measurement is ready
    Wire.beginTransmission(MAG);
    Wire.write(0x03);                          // HXL – first data byte
    Wire.endTransmission(false);
    Wire.requestFrom(MAG, 7);                  // 6 bytes + ST2 (must be read)
    uint8_t b[7];
    for (int i = 0; i < 7; i++) {
      b[i] = Wire.read();
    }
    // low byte first, high byte second (little-endian) – opposite of the accelerometer
    int16_t mx = (int16_t)(b[1] << 8 | b[0]);
    int16_t my = (int16_t)(b[3] << 8 | b[2]);
    int16_t mz = (int16_t)(b[5] << 8 | b[4]);
    // b[6] is ST2 – now that it is read, the sensor can prepare a new measurement

    // 0.15 µT per unit (16-bit mode)
    float bx = mx * 0.15;
    float by = my * 0.15;
    float bz = mz * 0.15;

    // heading: module flat, chip facing up
    float direction = atan2(by, bx) * 180.0 / PI;
    if (direction < 0) {
      direction += 360;
    }

    Serial.print("B[uT]: ");
    Serial.print(bx, 1); Serial.print(" "); Serial.print(by, 1); Serial.print(" "); Serial.print(bz, 1);
    Serial.print("  heading: ");
    Serial.println(direction, 0);
  }
  delay(100);
}
  • The magnetometer byte order is reversed: low byte first, then high byte. That is why the program first reads all 7 bytes into an array and then puts them together.
  • After the data, the ST2 register must also be read – only then does the sensor prepare a new measurement.
  • The magnetometer axes are not the same as the accelerometer axes (X and Y are swapped and Z is reversed). For a compass on a table this does not matter, but for advanced calculations it must be taken into account.
  • Like any compass, this one needs calibration – the procedure is the same as in the lesson accelerometer and magnetometer: turn the module in a circle and remember the smallest and largest X and Y values.

Comparison: MPU9250, two modules and the micro:bit

MPU9250MPU6050 + QMC5883Lmicro:bit v2
number of modules12built in
accelerometeryesyesyes
gyroscopeyesyes (MPU6050)no
magnetometeryes (AK8963, 0x0C)yes (QMC5883L, 0x0D)yes
I2C addresses0x68 and 0x0C0x68 and 0x0D–
programmingregisters, bypassregistersready-made commands

Practice tasks

  1. Print "SHAKEN!" when the total acceleration sqrt(ax² + ay² + az²) goes above 2 g.
  2. Turn on the LED (GPIO2) when the module is tilted forward by more than 30°.
  3. Calculate how many degrees the module has turned around the Z axis by adding up gyroscope readings (measure the offset at rest first).
  4. Build a compass with four LEDs (N, E, S, W) – the LED of the direction closest to the module's heading lights up.
Solution: rotation angle from the gyroscope
// Task – rotation angle around the Z axis by adding up gyroscope readings
#include <Wire.h>

const int MPU = 0x68;
float angleZ = 0;                // total angle in degrees
float offsetZ = 0;             // gyroscope error at rest
unsigned long previous = 0;

int16_t read16(int reg) {
  Wire.beginTransmission(MPU);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 2);
  int16_t high = Wire.read();
  int16_t low = Wire.read();
  return (int16_t)((high << 8) | low);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission();
  delay(100);
  // calibration: 200 readings while the module is still
  for (int i = 0; i < 200; i++) {
    offsetZ += read16(0x47) / 131.0;
    delay(5);
  }
  offsetZ /= 200;
  previous = millis();
}

void loop() {
  unsigned long now = millis();
  float dt = (now - previous) / 1000.0;       // elapsed time in seconds
  previous = now;
  float speed = read16(0x47) / 131.0 - offsetZ;   // °/s
  angleZ += speed * dt;                         // angle = speed · time
  Serial.print("Angle Z: ");
  Serial.println(angleZ, 1);
  delay(10);
}

Check your knowledge

Open the quiz

Quiz: MPU9250

1. What does a gyroscope measure?




2. Which WHO_AM_I value does an MPU6500 (no magnetometer) return?




3. What must be done to see the AK8963 magnetometer at address 0x0C?




4. How many raw units are 1 g in the ±2 g range?