MPU6050: 3-Achsen Beschleunigungs- und Lagesensor

Hier geht es um den Sensorchip MPU-6050 verbaut auf dem Modul GY-521. Durch Einsatz dieses 3-Achsen-Gyroskops und 3-Achsen-Beschleunigungssensors sind 6 Freiheitsgrade (DOF = "Degrees Of Freedom") gleichzeitig erkennbar. Zusätzlich kann der Sensor noch die Umgebungstemperatur messen.

Modul GY-521
Abb.: Modul GY-521

Spezifikationen laut Hersteller

Chip: MPU6050
Sensor: Micro Electro-Mechanical Systems (MEMS) mit
3-Achsen-Beschleunigungssensor und 3-Achsen-Gyroskop.
Eingebauter Temperatur-Sensor von -40°C bis +85°C
Betriebsspannung: 3,3V / 5V (interner LDO Spannungswandler)
Stromverbrauch: ~5mA
(durch die Entfernung der Status-LED kann ein wenig Strom eingespart werden)
Schnittstelle: I²C
Adresse: 0x68 (AD0 = GND oder unverbunden) oder 0x69 (AD0 = HIGH)
ADC: Eingebauter 16bit AD-Wandler
Beschleunigungs-Messbereiche: ± 2, 4, 8 oder 16 g (=Erdbeschleunigung)
Gyroskop-Messbereiche: ± 250, 500, 1000 oder 2000°/s

Interner Aufbau des GY-521

Das Modul verfügt über einen I²C-Bus, durch den ein Mikrocontroller -wie ein Arduino- einfach angeschlossen werden kann. Möglicherweise sind 3,3V zum korrekten Betrieb des I²C-Bus nicht genug, es sollte daher -falls möglich- immer 5V als Stromversorgung verwendet werden.
Das Modul besitzt für den I²C-Bus Pullup-Widerstände, welche manchmal 10kΩ und manchmal 2,2KΩ sein können. Letzter Wert ist ziemlich niedrig, was zu Problemen führen kann, wenn noch andere Sensor-Module verwendet werden. Hier sollte dann ein zusätzlicher, externer Pullup-Widerstand Abhilfe schaffen.
Manche der GY-521-Module besitzen einen falschen (oder schlechten) Kondensator, was zu hohem Rauschen in den Messungen führen kann.

2,2KΩ-Pullup-Widerstände des Moduls GY-521
Abb.: 2,2KΩ-Pullup-Widerstände des Moduls GY-521

Der Sensor besitzt zusätzliche eine DMP-Einheit ("Digital Motion Processor"), welcher mit Firmware programmiert werden kann und es ermöglicht komplexere Berechnungen direkt auf dem Sensor-Chip durchzuführen. Anscheinend werden aber nicht genug Spezifikationen dafür von InvenSense herausgegeben, so dass durch Reverse Engineering diese Möglichkeiten doch erschlossen werden konnte.

Interner Schaltplan des Moduls GY-521
Abb.: Interner Schaltplan des Moduls GY-521

Anschlüsse des GY-521

Pin Pin-Funktion Arduino Uno
VCC Stromversorgung mit 3,3V oder 5V (interner Spannungsregler) 3,3V bzw. 5V
GND Masse/0V GND
SCL Serial clock (I²C) A5
SDA Serial data (I²C) A4
XDA Auxiliary Serial Data
(optionaler Anschluss anderer I²C-Module)
(hier nicht benutzt)
XCL Auxiliary Serial Clock
(optionaler Anschluss anderer I²C-Module)
(hier nicht benutzt)
AD0 Adress-Selektor für I²C:
LOW: 0x68 (default )
HIGH: 0x69
GND
INT Interrupt digital output (Optionaler Anschluss, um mehrere Module in Reihe zu schalten) (hier nicht benutzt)

Verwendete Bauteile

Optionale Software-Library: I2Cdevlib und MPU6050-Code

Aufbau

Aufbau auf dem Breadboard
Abb.: Aufbau auf dem Breadboard

Ex. 1: Ausgabe der Rohdaten in der seriellen Konsole

Der folgende Sketch steuert das GY-521-Modul an, liest alle verfügbaren Daten in einem festen Zeitintervall (hier: 1s) und gibt sie anschließend in der seriellen Konsole aus. Diese Rohdaten sind leicht zu lesen. Es muss nur der "sleep-mode" deaktiviert werden und nun können die Sensorwerte des Gyroskops, Beschleunigungssensors und Temperaturfühlers gelesen werden. An der Stelle Wire.requestFrom(MPU6050_ADRESS, 7*2, true) werden 14 Byte der Register ausgelesen. Dies liegt daran, dass alle 7 verfügbaren Sensor-Messwerte 2 Byte (=16 Bits) besitzen.

Sketch

#include <Wire.h>

#define MPU6050_ADRESS 0x68
int16_t accX, accY, accZ, gyrX, gyrY, gyrZ, tVal;
double temperature = 0.0;

void setup()
{
  Wire.begin();
  Wire.beginTransmission(MPU6050_ADRESS); // Begins a transmission to the I2C slave (GY-521 board)
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0); // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  Serial.begin(9600);
  Serial.flush();
}
void loop()
{
  Wire.beginTransmission(MPU6050_ADRESS);
  Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
  Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
  Wire.requestFrom(MPU6050_ADRESS, 7*2, true); // request a total of 7*2=14 registers

  // "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable
  accX = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L)
  accY = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L)
  accZ = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L)
  tVal = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
  gyrX = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L)
  gyrY = Wire.read()<<8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L)
  gyrZ = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L)

  Serial.print("aX = ");    Serial.print(toStr(accX));
  Serial.print(" | aY = "); Serial.print(toStr(accY));
  Serial.print(" | aZ = "); Serial.print(toStr(accZ));
  Serial.print(" | gX = "); Serial.print(toStr(gyrX));
  Serial.print(" | gY = "); Serial.print(toStr(gyrY));
  Serial.print(" | gZ = "); Serial.print(toStr(gyrZ));

  /**
   * The following parameters are taken from the documentation [MPU-6000/MPU-6050 Product Specification, p.14]:
   *
   * Temperature sensor is -40°C to +85°C (signed integer)
   * 340 per °C
   * Offset = -512 at 35°C
   * At 0°C: -512 - (340 * 35) = -12412
   */
  temperature = (tVal + 12412.0) / 340.0;
  Serial.print(" | T = ");  Serial.print(toStr((int16_t)temperature)); Serial.print("°C");

  Serial.println();
  delay(500);
}

/**
 * Converts int16 to string.
 * Moreover, resulting strings will have the same length
 */
char* toStr(int16_t value)
{
  char result[7];
  sprintf(result, "%6d", value);
  return result;
}

Ergebnis

Ausgabe der Rohdaten in der seriellen Konsole
Abb.: Ausgabe der Rohdaten in der seriellen Konsole

In der Arduino IDE ab Version 1.6.6 ist der sog. serielle Plotter zusätzlich zur seriellen Konsole verfügbar. Um diesen nutzen zu können, werden die Werte mit Leerzeichen (oder Tabulatoren) getrennt mit Serial.print() ausgegeben und mit einem Zeilenumbruch, z.B. Serial.println() abgeschlossen. So können die ausgegebenen Werte grafisch verfolgt werden:

Ausgabe der Rohdaten im seriellen Plotter
Abb.: Ausgabe der Rohdaten im seriellen Plotter

Ex. 3: Winkel messen mit dem MPU-6050

Der folgende Sketch berechnet nach dem Auslesen der Sensor-Daten die richtigen Winkel des Sensors und gibt diese wiederum in der seriellen Konsole aus.

Sketch

#include <Wire.h>

#define MPU6050_ADRESS 0x68

const int ACCEL_OFFSET   = 200;
const int GYRO_OFFSET    = 151;  // 151
const int GYRO_SENSITITY = 131;  // 131 is sensivity of gyro from data sheet
const float GYRO_SCALE   = 0.2; //  0.02 by default - tweak as required
const float LOOP_TIME    = 0.15; // 0.1 = 100ms

int accValue[3], accAngle[3], gyroValue[3], temperature, accCorr;
float gyroAngle[3], gyroCorr;

void setup()
{
  Wire.begin();
  Wire.beginTransmission(MPU6050_ADRESS); // Begins a transmission to the I2C slave (GY-521 board)
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0); // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  Serial.begin(9600);
  Serial.flush();
}
void loop()
{
  Wire.beginTransmission(MPU6050_ADRESS);
  Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
  Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
  Wire.requestFrom(MPU6050_ADRESS, 7*2, true); // request a total of 7*2=14 registers

  // "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable
  for(byte i=0; i<3; i++) {
    accValue[i] = Wire.read()<<8 | Wire.read(); // reading registers: ACCEL_XOUT, ACCEL_YOUT, ACCEL_ZOUT
  }
  temperature = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
  for(byte i=0; i<3; i++) {
    gyroValue[i] = Wire.read()<<8 | Wire.read(); // reading registers: GYRO_XOUT, GYRO_>OUT, GYRO_ZOUT
  }

  Serial.print("Accel: ");
  for(byte i=0; i<3; i++) {
    accCorr = accValue[i] - ACCEL_OFFSET;
    accCorr = map(accCorr, -16800, 16800, -90, 90);
    accAngle[i] = constrain(accCorr, -90, 90);
    Serial.print(accAngle[i]);
    Serial.print("\t");
  }
  Serial.print("| Gyro: \t");

  for(byte i=0; i<3; i++) {
    gyroCorr = (float)((gyroValue[i]/GYRO_SENSITITY) - GYRO_OFFSET);
    gyroAngle[i] = (gyroCorr * GYRO_SCALE) * -LOOP_TIME;
    Serial.print(gyroAngle[i]);
    Serial.print("\t");
  }

  Serial.println(" ");
  delay(LOOP_TIME * 1000);
}

Ergebnis

Ausgabe der Winkel in der seriellen Konsole
Abb.: Ausgabe der Winkel in der seriellen Konsole

Ex. 2: Grafische Ausgabe in "Processing"

Um die Funktionalität des Sensors besser darzustellen, werden die ermittelten Werte nun in Processing übertragen und dort in Echtzeit visualisiert. Die Sketches stammen von der Website "Geek Mom Projects"

Ausgabe der Daten in der seriellen Konsole
Abb.: Ausgabe der Daten in der seriellen Konsole
Video: Live-Demonstration in Processing

Library "Adafruit_MPU6050"

Wenn man die Library Adafruit_MPU6050 verwendet kann man etwas übersichtlicher auf die Werte des MPU6050 zugreifen und außerdem den Sleep-Mode des Moduls sehr einfach verwenden. Dieser kann vor allem bei batteriebetriebenen Projekte interessant werden, wo Strom gespart werden soll. Wie im folgenden Beispiel gezeigt, werden zunächst als Referenz die Werte des MPU6050 normal ausgelesen und zum seriellen Plotter geschickt. Der zweite Modus ist der Cycle-Mode, welcher in bestimmten Abständen misst und dadurch in einer etwas gröberen Auflösung der Daten resultiert. Der dritte Modus versetzt das Modul in den Sleep-Mode, d.h. es werden keine Messungen mehr vorgenommen. Allerdings können die letzten Messdaten noch aus den Registern des Moduls ausgelesen werden.

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

Adafruit_MPU6050 mpu;

void setup(void)
{
    Serial.begin(115200);
    if (!mpu.begin()) {
        Serial.println("Failed to find MPU6050 chip");
        while (true) {}
    }
}

void loop()
{
    // normal readings
    mpu.enableSleep(false);
    mpu.enableCycle(false);
    printMetrics();

    // cycle mode (ensure that sleep mode is not active)
    mpu.setCycleRate(MPU6050_CYCLE_20_HZ);
    mpu.enableSleep(false);
    mpu.enableCycle(true);
    printMetrics();

    // sleep mode (measurements halted; fetching data still enabled)
    mpu.enableCycle(false);
    mpu.enableSleep(true);
    printMetrics();
}

void printMetrics()
{
    sensors_event_t a, g, temp;

    for (byte i = 0; i < 150; i++) {
        mpu.getEvent(&a, &g, &temp);
        Serial.print(a.acceleration.x);
        Serial.print(" ");
        Serial.print(a.acceleration.y);
        Serial.print(" ");
        Serial.print(a.acceleration.z);
        Serial.println();
        delay(10);
    }
}
Ausgabe der Rohdaten im seriellen Plotter
Abb.: Ausgabe der Rohdaten im seriellen Plotter

Library "MPU6050_light"

Eine weitere nützliche Library ist MPU6050_light, mit der man z.B. die anliegenden Winkel sehr leicht auslesen kann:

#include <Wire.h>
#include <MPU6050_light.h>

MPU6050 mpu(Wire);

void setup()
{
    Serial.begin(9600);
    Serial.flush();
    Wire.begin();
    mpu.begin();
    Serial.println(F("Calculating gyro offset, do not move MPU6050"));
    delay(1000);
    mpu.calcGyroOffsets();
    Serial.println("Done.");
}

void loop()
{
    mpu.update();

    Serial.print("X: " + String(mpu.getAngleX()));
    Serial.print("\tY: " + String(mpu.getAngleY()));
    Serial.println("\tZ: " + String(mpu.getAngleZ()));

    delay(50);
}
Ausgabe der Winkeldaten in der seriellen Konsole
Abb.: Ausgabe der Winkeldaten in der seriellen Konsole
zurück