Получение данных с MPU9250

MPU9250 — 9-осевой инерциальный модуль:

  • акселерометр (3 оси, ускорения);

  • гироскоп (3 оси, угловые скорости);

  • магнитометр AK8963 (3 оси, магнитное поле).

Расположение в кубсате — шина I²C. Адрес зависит от уровня на пине AD0: 0x68 (по умолчанию) или 0x69 (при AD0 к VCC). Магнитометр AK8963 «спрятан» внутри MPU9250 на отдельном I²C bus и становится виден на основной шине по адресу 0x0C только после того, как акселерометр включит BYPASS (это делает скетч).

Тестовый скетч

examples/mpu9250_read/mpu9250_read.ino
#include <Wire.h>

const uint8_t MPU_ADDR = 0x68;   // MPU9250
const uint8_t MAG_ADDR = 0x0C;   // AK8963 inside MPU9250

uint8_t readReg8(uint8_t dev, uint8_t reg) {
  Wire.beginTransmission(dev);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return 0;
  Wire.requestFrom(dev, (uint8_t)1);
  if (!Wire.available()) return 0;
  return Wire.read();
}

bool readRegs(uint8_t dev, uint8_t reg, uint8_t* dst, uint8_t len) {
  Wire.beginTransmission(dev);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return false;
  if (Wire.requestFrom(dev, len) != len) return false;
  for (uint8_t i = 0; i < len; i++) dst[i] = Wire.read();
  return true;
}

void writeReg8(uint8_t dev, uint8_t reg, uint8_t val) {
  Wire.beginTransmission(dev);
  Wire.write(reg);
  Wire.write(val);
  Wire.endTransmission();
}

bool initMpu9250() {
  // WHO_AM_I для MPU9250 обычно 0x71 (иногда 0x73)
  uint8_t who = readReg8(MPU_ADDR, 0x75);
  if (who != 0x71 && who != 0x73) return false;

  writeReg8(MPU_ADDR, 0x6B, 0x00); // PWR_MGMT_1: wake up
  delay(50);
  writeReg8(MPU_ADDR, 0x1B, 0x00); // GYRO_CONFIG: +-250 dps
  writeReg8(MPU_ADDR, 0x1C, 0x00); // ACCEL_CONFIG: +-2g
  writeReg8(MPU_ADDR, 0x37, 0x02); // INT_PIN_CFG: bypass on (доступ к AK8963)
  return true;
}

bool initMag() {
  uint8_t who = readReg8(MAG_ADDR, 0x00); // WIA
  if (who != 0x48) return false;

  writeReg8(MAG_ADDR, 0x0A, 0x00); // power down
  delay(10);
  writeReg8(MAG_ADDR, 0x0A, 0x16); // continuous mode 2, 16-bit, 100 Hz
  delay(10);
  return true;
}

bool readAccelGyro(int16_t &ax, int16_t &ay, int16_t &az, int16_t &gx, int16_t &gy, int16_t &gz) {
  uint8_t b[14];
  if (!readRegs(MPU_ADDR, 0x3B, b, 14)) return false;

  ax = (int16_t)((b[0] << 8) | b[1]);
  ay = (int16_t)((b[2] << 8) | b[3]);
  az = (int16_t)((b[4] << 8) | b[5]);
  gx = (int16_t)((b[8] << 8) | b[9]);
  gy = (int16_t)((b[10] << 8) | b[11]);
  gz = (int16_t)((b[12] << 8) | b[13]);
  return true;
}

bool readMag(int16_t &mx, int16_t &my, int16_t &mz) {
  // ST1 bit0 = data ready
  if ((readReg8(MAG_ADDR, 0x02) & 0x01) == 0) return false;

  uint8_t b[7];
  if (!readRegs(MAG_ADDR, 0x03, b, 7)) return false;
  if (b[6] & 0x08) return false; // overflow

  // У AK8963 порядок little-endian
  mx = (int16_t)((b[1] << 8) | b[0]);
  my = (int16_t)((b[3] << 8) | b[2]);
  mz = (int16_t)((b[5] << 8) | b[4]);
  return true;
}

void setup() {
  Serial.begin(115200);
  delay(300);
  Wire.begin(); // Nano: SDA=A4, SCL=A5

  if (!initMpu9250()) {
    Serial.println("MPU9250 init failed.");
    Serial.println("Check wiring, power, GND, and address 0x68.");
    while (true) delay(1000);
  }

  if (!initMag()) {
    Serial.println("AK8963 (magnetometer) init failed.");
    Serial.println("Accel/Gyro may still work, mag may be unavailable.");
  } else {
    Serial.println("AK8963 OK.");
  }

  Serial.println("ax,ay,az,gx,gy,gz,mx,my,mz");
}

void loop() {
  int16_t ax = 0, ay = 0, az = 0, gx = 0, gy = 0, gz = 0;
  int16_t mx = 0, my = 0, mz = 0;

  bool okImu = readAccelGyro(ax, ay, az, gx, gy, gz);
  bool okMag = readMag(mx, my, mz);

  if (!okImu) {
    Serial.println("imu_read_error");
    delay(200);
    return;
  }

  Serial.print(ax); Serial.print(",");
  Serial.print(ay); Serial.print(",");
  Serial.print(az); Serial.print(",");
  Serial.print(gx); Serial.print(",");
  Serial.print(gy); Serial.print(",");
  Serial.print(gz); Serial.print(",");

  if (okMag) {
    Serial.print(mx); Serial.print(",");
    Serial.print(my); Serial.print(",");
    Serial.println(mz);
  } else {
    Serial.println("0,0,0");
  }

  delay(200);
}

Скетч: будит MPU, переключает в bypass-режим, инициализирует AK8963 в непрерывном чтении 100 Гц, и пять раз в секунду печатает 9 чисел.

Примечание

Скетч использует жёстко заданный адрес 0x68. В финальной прошивке main_full_arduino адрес определяется автоматически (0x68 или 0x69). Если этот тест выдаёт MPU9250 init failed, а i2c_scanner показывает 0x69 — модуль на этом конкретном экземпляре с AD0=HIGH. Поправьте константу MPU_ADDR в скетче с 0x68 на 0x69 и перезалейте. В реальной миссии это значения не имеет — main_full сам подберёт.

Как запустить

  1. Залейте скетч.

  2. Откройте Serial Monitor на скорости 115200.

Что должно быть видно

Старт:

AK8963 OK.
ax,ay,az,gx,gy,gz,mx,my,mz

Если магнитометр не запустился (это бывает на некоторых клонах MPU):

AK8963 (magnetometer) init failed.
Accel/Gyro may still work, mag may be unavailable.
ax,ay,az,gx,gy,gz,mx,my,mz

Дальше — пять строк в секунду:

-120,80,16380,12,-5,3,128,-340,212
-110,76,16384,10,-7,2,130,-338,210
...

Это сырые 16-битные значения. Чтобы их интерпретировать:

  • ax/ay/az: ±32768 ≈ ±2 g (диапазон по умолчанию). На неподвижной плате одна ось должна показывать ≈ 16384 (это +1 g — гравитация).

  • gx/gy/gz: ±32768 ≈ ±250 °/с. На неподвижной плате — около нуля с шумом ±20.

  • mx/my/mz: магнитное поле, ±32768 ≈ ±4912 µT. Зависит от ориентации к северу.

Ручная проверка

  • Положите кубсат ровно. az должен быть около +16384, ax/ay — около нуля. Переверните вверх ногами — az уйдёт к -16384.

  • Покачайте. Значения акселерометра должны заметно прыгать.

  • Поверните вокруг вертикальной оси. gz отклонится от нуля пропорционально скорости вращения.

  • Поднесите магнит. mx, my, mz зашкалят (значения «упрутся» в ±32767).

Если все значения по нулям и не реагируют — модуль не инициализирован, вернитесь к Проверка шины I²C.

Примечание

Финальная прошивка main_full читает только акселерометр и гироскоп — магнитометр в неё пока не интегрирован. Этот тест шире, чем то, что использует CubeSat в полёте.