ESP32 i MPU9250: akcelerometar, žiroskop i magnetometar u jednom modulu
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.

Šta je u modulu
| Senzor | Meri | Jedinica | Primer upotrebe |
|---|---|---|---|
| akcelerometar (3 ose) | ubrzanje, pa i silu Zemljine teže | g (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 |
| termometar | temperaturu čipa | °C | ispravka merenja (nije temperatura vazduha) |
0x71 ili 0x73 znači MPU9250, a 0x70 MPU6500.Povezivanje (I2C)
| MPU9250 | ESP32 | Napomena |
|---|---|---|
| VCC | 3V3 | modul ima regulator, radi i na 3,3 V |
| GND | GND | |
| SCL | GPIO22 | takt I2C veze |
| SDA | GPIO21 | podaci I2C veze |
| AD0 | ne 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čina | Sirovi broj | Delimo sa | Rezultat |
|---|---|---|---|
| ubrzanje (±2 g) | −32768 … 32767 | 16384 | g |
| ugaona brzina (±250 °/s) | −32768 … 32767 | 131 | °/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
| MPU9250 | MPU6050 + QMC5883L | micro:bit v2 | |
|---|---|---|---|
| broj modula | 1 | 2 | ugrađeno |
| akcelerometar | da | da | da |
| žiroskop | da | da (MPU6050) | ne |
| magnetometar | da (AK8963, 0x0C) | da (QMC5883L, 0x0D) | da |
| I2C adrese | 0x68 i 0x0C | 0x68 i 0x0D | – |
| programiranje | registri, bypass | registri | gotove naredbe |
Zadaci za vežbu
- Ispiši „PROTRESEN!“ kada ukupno ubrzanje
sqrt(ax² + ay² + az²)pređe 2 g. - Upali LED diodu (GPIO2) kada je modul nagnut napred više od 30°.
- Izračunaj za koliko stepeni je modul okrenut oko ose Z sabiranjem merenja žiroskopa (pre početka izmeri pomeraj u mirovanju).
- 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?
