ESP32 i MPU9250: akcelerometar, žiroskop i magnetometar u jednom modulu

Lekcija 24 od 28 · Arduino i ESP32

U lekciji akcelerometar i magnetometar koristili smo dva odvojena modula. Modul MPU9250 ima sve u jednom: akcelerometar (ubrzanje i nagib), žiroskop (brzina okretanja) i magnetometar (kompas) – ukupno devet merenih veličina, zato se zove „9-osni“ senzor. Takve senzore imaju mobilni telefoni, dronovi i pametni satovi.

Modul MPU-9250/6500 sa oznakama pinova
Modul MPU-9250/6500 sa oznakama pinova

Šta je u modulu

SenzorMeriJedinicaPrimer upotrebe
akcelerometar (3 ose)ubrzanje, pa i silu Zemljine težeg (1 g ≈ 9,81 m/s²)nagib, protresanje, brojač koraka
žiroskop (3 ose)brzinu okretanja°/s (stepeni u sekundi)koliko se predmet okrenuo, stabilizacija drona
magnetometar AK8963 (3 ose)magnetno poljeµT (mikrotesla)kompas – smer severa
termometartemperaturu čipa°Cispravka merenja (nije temperatura vazduha)
Pažnja – „MPU9250/6500“: na istoj pločici se prodaje i čip MPU6500, koji nema magnetometar. Natpis na modulu to ne otkriva. Program u koraku 1 čita registar WHO_AM_I: 0x71 ili 0x73 znači MPU9250, a 0x70 MPU6500.

Povezivanje (I2C)

MPU9250ESP32Napomena
VCC3V3modul ima regulator, radi i na 3,3 V
GNDGND
SCLGPIO22takt I2C veze
SDAGPIO21podaci I2C veze
AD0ne vezuje se (ili GND)adresa 0x68; na 3V3 adresa je 0x69

Ostali pinovi (EDA, ECL, INT, NCS, FSYNC) nam ne trebaju. Modul možeš da vežeš na iste žice SDA i SCL kao i druge I2C module – svaki ima svoju adresu.

Korak 1: I2C skener i WHO_AM_I

// ESP32 – I2C skener: proverava da li se MPU9250 javlja i šta je stvarno na ploči
#include <Wire.h>

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

  for (int adresa = 1; adresa < 127; adresa++) {
    Wire.beginTransmission(adresa);
    if (Wire.endTransmission() == 0) {
      Serial.print("Pronadjen uredjaj na 0x");
      Serial.println(adresa, HEX);
    }
  }

  // Registar WHO_AM_I (0x75) kaže koji čip je zaista na modulu
  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 – ima i magnetometar.");
  } else if (id == 0x70) {
    Serial.println("MPU6500 – samo akcelerometar i ziroskop (nema magnetometar).");
  } else {
    Serial.println("Nepoznat cip – proveri vezu.");
  }
}

void loop() {
}

Očekuje se uređaj na 0x68. Magnetometar se na početku ne vidi – on je unutar istog kućišta, ali „iza“ glavnog čipa. U koraku 3 ćemo ga otključati.

Korak 2: akcelerometar i žiroskop

Kao kod MPU6050, senzor budimo upisom 0 u registar 0x6B, a zatim čitamo parove bajtova: ubrzanje od registra 0x3B, temperaturu od 0x41 i žiroskop od 0x43.

// ESP32 – MPU9250: akcelerometar, žiroskop i temperatura (bez dodatnih biblioteka)
#include <Wire.h>

const int MPU = 0x68;           // I2C adresa (AD0 na GND)

void pisiRegistar(int uredjaj, int registar, int vrednost) {
  Wire.beginTransmission(uredjaj);
  Wire.write(registar);
  Wire.write(vrednost);
  Wire.endTransmission();
}

// čita 16-bitni broj sa znakom: prvo viši, pa niži bajt (big-endian)
int16_t citaj16(int registar) {
  Wire.beginTransmission(MPU);
  Wire.write(registar);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 2);
  int16_t visi = Wire.read();
  int16_t nizi = Wire.read();
  return (int16_t)((visi << 8) | nizi);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  pisiRegistar(MPU, 0x6B, 0x00);   // PWR_MGMT_1 = 0: budi senzor
  delay(100);
}

void loop() {
  // akcelerometar: ±2 g, 16384 jedinica = 1 g
  float ax = citaj16(0x3B) / 16384.0;
  float ay = citaj16(0x3D) / 16384.0;
  float az = citaj16(0x3F) / 16384.0;
  // temperatura čipa (ne vazduha!)
  float temp = citaj16(0x41) / 333.87 + 21.0;
  // žiroskop: ±250 °/s, 131 jedinica = 1 °/s
  float gx = citaj16(0x43) / 131.0;
  float gy = citaj16(0x45) / 131.0;
  float gz = citaj16(0x47) / 131.0;

  // nagib iz akcelerometra (važi dok modul miruje)
  float nagibNapred = atan2(ay, az) * 180.0 / PI;
  float nagibStrana = 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("  nagib: ");
  Serial.print(nagibNapred, 0); Serial.print(" / "); Serial.print(nagibStrana, 0);
  Serial.print("  T: ");
  Serial.println(temp, 1);
  delay(200);
}
VeličinaSirovi brojDelimo saRezultat
ubrzanje (±2 g)−32768 … 3276716384g
ugaona brzina (±250 °/s)−32768 … 32767131°/s
temperatura…333,87 (pa + 21)°C
  • Kada modul miruje vodoravno, akcelerometar pokazuje oko Z = 1 g, a žiroskop oko 0 (uz malu grešku – pomeraj).
  • Kada ga okrećeš, žiroskop pokazuje koliko brzo se okreće, a ne u kom je položaju. Položaj se dobija sabiranjem: ugao = brzina · vreme (zadatak 3).

Korak 3: magnetometar i kompas

Upisom vrednosti 0x02 u registar 0x37 uključujemo bypass: glavni čip „propušta“ I2C vezu do magnetometra AK8963 na adresi 0x0C. Zatim magnetometar prebacujemo u neprekidno merenje.

// ESP32 – MPU9250: magnetometar AK8963 i kompas
#include <Wire.h>

const int MPU = 0x68;
const int MAG = 0x0C;           // AK8963 je „sakriven“ iza MPU-a

void pisiRegistar(int uredjaj, int registar, int vrednost) {
  Wire.beginTransmission(uredjaj);
  Wire.write(registar);
  Wire.write(vrednost);
  Wire.endTransmission();
}

int citajBajt(int uredjaj, int registar) {
  Wire.beginTransmission(uredjaj);
  Wire.write(registar);
  Wire.endTransmission(false);
  Wire.requestFrom(uredjaj, 1);
  return Wire.read();
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  pisiRegistar(MPU, 0x6B, 0x00);   // budi MPU
  delay(100);
  pisiRegistar(MPU, 0x37, 0x02);   // INT_PIN_CFG: BYPASS – AK8963 postaje vidljiv na I2C
  delay(10);
  pisiRegistar(MAG, 0x0A, 0x00);   // magnetometar: isključen (pre promene režima)
  delay(10);
  pisiRegistar(MAG, 0x0A, 0x16);   // 16 bita, neprekidno merenje 100 Hz
  delay(10);
  Serial.print("AK8963 WIA (treba 0x48): 0x");
  Serial.println(citajBajt(MAG, 0x00), HEX);
}

void loop() {
  if (citajBajt(MAG, 0x02) & 0x01) {           // ST1: novo merenje je spremno
    Wire.beginTransmission(MAG);
    Wire.write(0x03);                          // HXL – prvi bajt podataka
    Wire.endTransmission(false);
    Wire.requestFrom(MAG, 7);                  // 6 bajtova + ST2 (mora da se pročita)
    uint8_t b[7];
    for (int i = 0; i < 7; i++) {
      b[i] = Wire.read();
    }
    // niži bajt je prvi, viši drugi (little-endian) – obrnuto od akcelerometra
    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] je ST2 – pročitali smo ga, pa senzor može da pripremi novo merenje

    // 0,15 µT po jedinici (16-bitni režim)
    float bx = mx * 0.15;
    float by = my * 0.15;
    float bz = mz * 0.15;

    // smer: modul vodoravno, čipom nagore
    float smer = atan2(by, bx) * 180.0 / PI;
    if (smer < 0) {
      smer += 360;
    }

    Serial.print("B[uT]: ");
    Serial.print(bx, 1); Serial.print(" "); Serial.print(by, 1); Serial.print(" "); Serial.print(bz, 1);
    Serial.print("  smer: ");
    Serial.println(smer, 0);
  }
  delay(100);
}
  • Kod magnetometra je redosled bajtova obrnut: prvo niži, pa viši bajt. Zato program prvo pročita svih 7 bajtova u niz, pa ih sklapa.
  • Posle podataka mora da se pročita i registar ST2 – tek tada senzor priprema novo merenje.
  • Ose magnetometra nisu iste kao ose akcelerometra (X i Y su zamenjene, a Z je obrnuta). Za kompas na stolu to ne smeta, ali za napredna izračunavanja mora se uzeti u obzir.
  • Kao i svaki kompas, i ovaj treba kalibrisati – postupak je isti kao u lekciji akcelerometar i magnetometar: okreći modul u krug i pamti najmanje i najveće vrednosti X i Y.

Poređenje: MPU9250, dva modula i micro:bit

MPU9250MPU6050 + QMC5883Lmicro:bit v2
broj modula12ugrađeno
akcelerometardadada
žiroskopdada (MPU6050)ne
magnetometarda (AK8963, 0x0C)da (QMC5883L, 0x0D)da
I2C adrese0x68 i 0x0C0x68 i 0x0D–
programiranjeregistri, bypassregistrigotove naredbe

Zadaci za vežbu

  1. Ispiši „PROTRESEN!“ kada ukupno ubrzanje sqrt(ax² + ay² + az²) pređe 2 g.
  2. Upali LED diodu (GPIO2) kada je modul nagnut napred više od 30°.
  3. Izračunaj za koliko stepeni je modul okrenut oko ose Z sabiranjem merenja žiroskopa (pre početka izmeri pomeraj u mirovanju).
  4. Napravi kompas sa četiri LED diode (S, I, J, Z) – pali se dioda strane sveta koja je najbliža smeru modula.
Rešenje: ugao okretanja iz žiroskopa
// Zadatak – ugao okretanja oko ose Z sabiranjem merenja žiroskopa
#include <Wire.h>

const int MPU = 0x68;
float ugaoZ = 0;                // ukupan ugao u stepenima
float pomerajZ = 0;             // greška žiroskopa u mirovanju
unsigned long prethodno = 0;

int16_t citaj16(int registar) {
  Wire.beginTransmission(MPU);
  Wire.write(registar);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU, 2);
  int16_t visi = Wire.read();
  int16_t nizi = Wire.read();
  return (int16_t)((visi << 8) | nizi);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission();
  delay(100);
  // kalibracija: 200 merenja dok modul miruje
  for (int i = 0; i < 200; i++) {
    pomerajZ += citaj16(0x47) / 131.0;
    delay(5);
  }
  pomerajZ /= 200;
  prethodno = millis();
}

void loop() {
  unsigned long sada = millis();
  float dt = (sada - prethodno) / 1000.0;       // proteklo vreme u sekundama
  prethodno = sada;
  float brzina = citaj16(0x47) / 131.0 - pomerajZ;   // °/s
  ugaoZ += brzina * dt;                         // ugao = brzina · vreme
  Serial.print("Ugao Z: ");
  Serial.println(ugaoZ, 1);
  delay(10);
}

Proveri znanje

Otvori kviz

Kviz: MPU9250

1. Šta meri žiroskop?




2. Koju vrednost WHO_AM_I vraća MPU6500 (bez magnetometra)?




3. Šta treba uraditi da bi se video magnetometar AK8963 na adresi 0x0C?




4. Koliko jedinica sirovog broja je 1 g u opsegu ±2 g?