MPU-6050 mit Arduino und Visualisierung in Processing

Der MPU-6050 ist ein kombinierter Sensor zur Erfassung von Beschleunigungen und Drehbewegungen und wird über I2C angesteuert. Hier wird ein einfaches Beispiel (Proof-of-Concept) vorgestellt: die Sensor-Daten werden von einem Arduino ausgelesen, über die serielle Schnittstelle an einen PC übertragen und dort mit Processing visualisiert.

Die beiden Programme (Arduino und Processing) sind getestet und werden im Detail erklärt.

Grundlagen

Grundlegende Beschreibung des Sensors und dessen Schnittstellen folgt …

Mittlerweile gibt es bereits einen Nachfolgesensor MPU-9250, welcher zusätzlich noch einen Kompass integriert hat und daher für neue Projekte welche eine vollständige IMU benötigen empfohlen wird.

Programme

Hier werden zwei Programme beschrieben, die gemeinsame eine Visualisierung der vom Sensor gemessenen Daten ermöglichen.

  • Ein Programm für den Mikrocontroller, welches für die Arduino-Umgebung geschrieben wurde und die Daten des Sensors ausliest und über die serielle Schnittstelle wieder ausgibt.
  • Ein Programm für den Computer, welches für die Processing-Umgebung geschrieben wurde und die Daten an der seriellen Schnittstelle empfängt und grafisch ausgibt.

Arduino

Das folgende Programm wurde auf einem Arduino Mega 2560 entwickelt und getestet sollte aber auch auf allen anderen Arduino-Plattformen mit I2C Bus funktionieren. Für den Sensor wurde das GY-521 Breakout-Board verwendet, welches den Vorteil eines eigenen Spannungsreglers für den Sensor bietet, der dafür allerdings auch nur mit 5V Versorgungsspannung betrieben werden sollte.

Die Anschlussbelegung ist somit sehr einfach:

  • GY-521 VCC – Arduino Mega 2560 5V
  • GY-521 GND – Arduino Mega 2560 GND
  • GY-521 SCL – Arduino Mega 2560 SCL (Communication, Pin 21)
  • GY-521 SDA – Arduino Mega 2560 SDA (Communication, Pin 20)

Für die Programmierung des Arduino empfiehlt es sich die vorhandenen I2C-Bibliothek (Wire.h) zu verwenden.

Das Programm ist weitgehend mit Kommentaren versehen die für die Erklärung ausreichend sein sollten. Es besteht im Prinzip aus zwei Methoden:

  • void setup() initialisiert die serielle Schnittstelle (mit 9600baud) sowie die I2C-Schnittstelle (als Master). Anschließend wird noch der MPU-6050 initialisiert (d.h. aufgeweckt).
  • void loop() liest fortlaufend die Daten des Sensors aus (und macht sich dabei zu nutzen, dass die Messdaten in aufeinanderfolgenden Registern gespeichert werden, sodass diese auf einmal ausgelesen werden können) und gibt diese an der seriellen Schnittstelle aus.
/**
 * @file MPU-6050_Test.ino
 * @author Markus Schabel
 * @brief Einfaches Beispiel fuer die Verwendung eines MPU-6050.
 *
 * Ein einfaches Beispiel  fuer einen MPU-6050 am Arduino.  Es werden laufend
 * alle Sensordaten  ausgelesen und  ueber die serielle  Schnittstelle ueber-
 * tragen.  Das Programm wurde mit einem Arduino Mega 2560 sowie einem GY-521
 * Breakout Board fuer den MPU-6050 getestet.
 *
 * Die Anschlussbelegung am Arduino Mega 2560 ist wie folgt:
 *  GY-521 VCC -> Arduino 5V
 *  GY-521 GND -> Arduino GND
 *  GY-521 SCL -> Arduino SCL (Communication, Pin 21)
 *  GY-521 SDA -> Arduino SDA (Communication, Pin 20)
 */

// Inkludieren der Bibliothek fuer die I2C-Schnittstelle.
#include <Wire.h>

// MPU-6050 I2C Adresse
#define MPU6050_ADDR           0x68

// MPU-6050 Register Adressen (sehr unvollstaendig):
// Hier steht das High-Byte der Beschleunigung entlang der X-Achse.
#define MPU6050_ACCEL_X_OUT_H  0x3B
// Hier kann der Sensor aufgeweckt werden.
#define MPU6050_PWR_MGMT_1     0x6B

// Variablen als Zwischenspeicher fuer die aktuellen Messwerte.
int16_t AccX, AccY, AccZ, Temp, GyrX, GyrY, GyrZ;

/**
 * @brief Setup der seriellen Verbindung und des MPU-6050.
 *
 * Initialisierung der seriellen Schnittstelle (9600 baud)  sowie vom I2C-Bus
 * und dem Sensor.  Dieser muss aus dem  Schlaf-Modus aufgeweckt werden,  ehe
 * Messwerte abgerufen werden koennen.
 */
void setup() {
  // Initialisierung des I2C-Bus als Master.
  Wire.begin();
  // Initialisierung der seriellen Schnittstelle mit 9600 baud.
  Serial.begin(9600);

  /* Initialisierung des MPU-6050...
   * Eine I2C-Kommunikation  beginnt immer mit  beginTransmission(Slave-Adr.)
   * und endet  mit endTransmission.  Der Parameter bei endTransmission  gibt
   * an, wie die Kommunikation beendet wird: true -> Stop, false -> Restart
   * Um den  Sensor  aufzuwecken,  muss der Wert  0x00  ins  Power-Management
   * Register geschrieben werden.
   */
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(MPU6050_PWR_MGMT_1);
  Wire.write(0);
  Wire.endTransmission(true);
}

/**
 * @brief Fortwaehrendes auslesen und uebertragen der Sensordaten
 *
 * Die Messwerte des  MPU-6050  werden  fortlaufend ausgelesen  und ueber die
 * serielle  Schnittstelle uebertragen.  Fuer das Auslesen werden  alle Mess-
 * Register hintereinander uebertragen (diese haben fortlaufende Adressen).
 * Die Uebertragung ueber die serielle Schnittstelle folgt fuer die einzelnen
 * Sensor-Arten  (Beschleunigung, Drehrate, Temperatur)  in  eigenen  Zeilen,
 * wobei das erste Zeichen die Art des Sensors (A,G,T) angibt und dann folgen
 * die Daten durch ';' getrennt fuer die jeweiligen Achsen (X,Y,Z).
 */
void loop() {
  /*
   * Zum Auslesen eines Registers  muss dem Sensor  zuerst mitgeteilt werden,
   * welches Register  ausgelesen werden soll.  Wenn das Register geschrieben
   * werden soll, folgt nach der Adresse der entsprechende Wert. Soll es aber
   * ausgelesen werden, wird die Verbindung mit einem Restart (false) beendet
   * und die Daten werden dann vom Sensor abgefragt.
   * requestFrom benoetigt  die Adresse des  Sensors sowie  die Anzahl der zu
   * uebertragenden Bytes. Diese koennen dann mit read abgerufen werden.
   * Bei der Uebertragung wird jeweils zuerst das High- und dann das Low-Byte
   * gesendet.  Diese werden dann  zu einem  16-Bit Wert zusammengefasst.  Im
   * Anschluss  folgt eine  "zufaellige"  Skalierung,  diese  sollte  vor der
   * Verwendung entsprechend kalibriert werden.
   */
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(MPU6050_ACCEL_X_OUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 14, true);
  AccX = (Wire.read()<<8 | Wire.read())/16384.*9.81;
  AccY = (Wire.read()<<8 | Wire.read())/16384.*9.81;
  AccZ = (Wire.read()<<8 | Wire.read())/16384.*9.81;
  Temp = Wire.read()<<8 | Wire.read();
  GyrX = (Wire.read()<<8 | Wire.read())/(131.*25.);
  GyrY = (Wire.read()<<8 | Wire.read())/(131.*25.);
  GyrZ = (Wire.read()<<8 | Wire.read())/(131.*25.);

  /*
   * Uebertragung der Daten ueber die Serielle Schnittstelle.
   */
  Serial.print("A;"); Serial.print(AccX);
  Serial.print(";"); Serial.print(AccY);
  Serial.print(";"); Serial.println(AccZ);

  Serial.print("G;"); Serial.print(GyrX);
  Serial.print(";"); Serial.print(GyrY);
  Serial.print(";"); Serial.println(GyrZ);

  // Die Temperatur kann mit dieser Formel (laut Datenblatt)  in Grad Celsius
  // umgerechnet werden. Auch dies sollte kalibriert werden.
  Serial.print("T;"); Serial.println(Temp/340.0+36.53);

  // Optional: eine gewisse Zeit warten bis zum naechsten Messwert.
//  delay(333);
}

 

Processing

Das folgende Programm wurde in Processing geschrieben und liest die Daten von der seriellen Schnittstelle zeilenweise ein und stellt diese fortlaufend als Liniendiagramme dar. Auch dieses Programm ist ausführlich dokumentiert. Es besteht im wesentlichen aus den folgenden Methoden:

  • void setup() initialisiert das Fenster zur Darstellung (fixe Fenstergröße oder alternativ im Vollbild-Modus). Es werden die Daten-Arrays angelegt (deren Größe von der Fensterbreite abhängig ist, sodass für jeden Pixel ein Wert gespeichert wird) und die serielle Schnittstelle geöffnet. Je nach Umgebung muss der Index bei der seriellen Schnittstelle angepasst werden.
  • void draw() zeichnet fortlaufend das Fenster neu. Dazu wird das Fenster zuerst gelöscht und dann werden die 7 Diagramme gezeichnet. Für das zeichnen der Diagramme wird die folgende Methode verwendet:
  • void plotArray(float[] array, int middlePos) stellt die Daten eines Arrays (mit fest vorgegebener Größe) an der angegebenen Vertikalposition als Liniendiagramm dar. Die zu verwendende Farbe muss vor dem Aufruf der Methode ausgewählt werden.
  • void serialEvent(Serial port) wird automatisch ausgeführt, sobald eine Zeile vollständig von der seriellen Schnittstelle eingelesen wurde. Die Methode liest die Daten ein, interpretiert diese und speichert sie im jeweiligen Array.
/**
 * Ein einfaches Programmbeispiel zur Visualisierung von uC-Sensordaten.
 *
 * Dieses Programm  liest Daten ueber die  serielle Schnittstelle ein.  Diese
 * werden in einem Array gespeichert und als Liniendiagramm visualisiert. Bei
 * jeder empfangenen  Zeile werden die Arrays  automatisch erweitert  und die
 * Darstellung  wird laufend  (unabhaengig von Aenderungen an den Daten)  neu
 * gezeichnet.
 *
 * Das Programm erwartet  Daten auf der seriellen Schnittstelle  im folgenden
 * Format: Sensor-Typ;X-Achse;Y-Achse;Z-Achse
 * Sensor-Typ kann A (Beschleunigung), G (Drehrate) oder T (Temperatur) sein.
 *
 * @author Markus Schabel
 */

// Inkludieren der Bibliothek fuer die serielle Kommunikation.
import processing.serial.*;

// Die Anzahl  der Punkte  die in den Arrays  gespeichert werden sollen.  Der
// initiale  Wert wird  spaeter durch  die Fensterbreite ersetzt,  damit fuer
// jeden Pixel ein Messwert existiert.
int numberOfPoints = 1000;
// Die Arrays um die Sensordaten des MPU-6050 zu speichern.
float[] accX, accY, accZ;
float[] gyrX, gyrY, gyrZ;
float[] temp;
// Der Index der zuletzt gespeicherten Werte in den jeweiligen Arrays.
int currentPointAcc = 0;
int currentPointGyr = 0;
int currentPointTmp = 0;

// Ein Objekt um mit der seriellen Schnittstelle interagieren zu koennen.
Serial arduino;

/**
 * Initialisierung; Zeichenoberflaeche, Variablen, serielle Schnittstelle.
 *
 * Initialisierung  der  Zeichenoberflaeche  (ein Fenster mit 800x600 Pixeln)
 * und  der  Hintergrundfarbe  Schwarz (0).  Im Anschluss  werden die  Arrays
 * angelegt  und mit initialen Werten  (jeweils 0.0)  gefuellt.  Letztendlich
 * wird   die  serielle  Schnittstelle  geoeffnet  sowie  ein  Zwischenpuffer
 * konfiguriert  (es werden  jeweils ganze Zeilen empfangen, ehe die  Methode
 * serialEvent zu deren Verarbeitung ausgefuehrt wird).
 */
void setup() {
  // Festlegung der Fenstergroesse.
  size(800, 600);
//  fullScreen();
  // Festlegung der Hintergrundfarbe.
  background(0);

  // Die Anzahl der zu  speichernden Daten entspricht der Fensterbreite.  Die
  // einzelnen Arrays werden entsprechend dieser Groesse angelegt.
  numberOfPoints = width;
  accX = new float[numberOfPoints];
  accY = new float[numberOfPoints];
  accZ = new float[numberOfPoints];
  gyrX = new float[numberOfPoints];
  gyrY = new float[numberOfPoints];
  gyrZ = new float[numberOfPoints];
  temp = new float[numberOfPoints];

  // Initialisierung der Arrays.
  for (int i=0; i<numberOfPoints; i++) {
    accX[i] = accY[i] = accZ[i] = 0.0;
    gyrX[i] = gyrY[i] = gyrZ[i] = 0.0;
    temp[i] = 0.0;
  }

  // Ausgabe der verfuegbaren seriellen Schnittstellen.
  println(Serial.list());
  // Oeffnen der zweiten seriellen Schnittstelle  mit einer Uebertragungsrate
  // von 9600baud, die aktuelle Klasse (this) wird als callback (serialEvent)
  // verwendet.
  // Note:  [1] ist die 2. Schnittstelle -- das muss je nach System angepasst
  //        werden. Die Reihenfolge der Schnittstellen wurde dazu ausgegeben.
  arduino = new Serial(this, Serial.list()[1], 9600);
  // Erst nachdem eine ganze Zeile empfangen wurde, wird die Verarbeitung mit
  // der Methode serialEvent automatisch ausgefuehrt.
  arduino.bufferUntil('\n');
}

/**
 * Hilfsfunktion die ein Array an einer bestimmten Position zeichnet.
 *
 * Diese Funktion bekommt ein Array  (der Groesse numberOfPoints)  uebergeben
 * und  eine Position entlang der  Y-Achse des Fensters  an der die Daten als
 * Liniendiagramm gezeichnet werden sollen. Zusaetzlich wird noch die 0-Linie
 * (X-Achse) in weiss gezeichnet.
 *
 * @param array ist ein Array welches die darzustellenden Daten enthaelt.
 * @param middlePos ist die Y-Position innerhalb des Fensters an,  an der die
 *        X-Achse fuer diese Datenfolge gezeichnet werden soll.
 */
void plotArray(float[] array, int middlePos) {
  // Die Daten  werden als durchgehender  Linienzug gezeichnet,  d.h. es wird
  // eine Linie zwischen den einzelnen Punkten automatisch gezeichnet.
  beginShape();
  // Fuer alle Datenwerte ...
  for (int i=0; i<numberOfPoints; i++) {
    // ... zeichne eine Linie vom letzten Punkt zum aktuellen Punkt.
    vertex(i, middlePos-array[i]);
  }
  // Der Linienzug ist fertig.
  endShape();
  // Zeichnen einer weissen Linie entlang der X-Achse.
  stroke(color(255,255,255));
  line(0, middlePos, width, middlePos);
}

/**
 * Zeichnet das gesamte Fenster, d.h. die einzelnen Diagramme.
 *
 * Zeichnet alle Daten neu (wird automatisch fortlaufend aufgerufen).  Zuerst
 * wird das Fenster geloescht (d.h. mit Schwarz gefuellt).  Im Anschluss wird
 * die Hoehe  des Fensters ausgelesen, daraus die Groesse fuer  die einzelnen
 * Grafiken  (genau genommen der Abstand zwischen diesen)  berechnet.  Danach
 * werden die einzelnen Diagramme in unterschiedlichen Farben gezeichnet.
 */
void draw() {
  // Fenster schwarz fuellen.
  background(0);
  // Saemtliche gezeichneten Formen sollen nicht gefuellt werden.
  noFill();
  // Der Abstand zwischen den einzelnen Diagrammen ist  1/7 der Fensterhoehe,
  // damit 7 Diagramme ins Fenster passen  (unabhaengig von der Groesse).
  int offset = height/7;
  int middle = offset/2;

  // Festlegen einer Farbe.
  stroke(color(255,64,0));
  // Zeichnen des Diagramms an der entsprechenden Position.
  plotArray(accX, 1*offset-middle);

  stroke(color(255,128,0));
  plotArray(accY, 2*offset-middle);

  stroke(color(255,192,0));
  plotArray(accZ, 3*offset-middle);

  stroke(color(255,64,0));
  plotArray(gyrX, 4*offset-middle);

  stroke(color(255,128,0));
  plotArray(gyrY, 5*offset-middle);

  stroke(color(255,192,0));
  plotArray(gyrZ, 6*offset-middle);

  stroke(color(255,0,0));
  plotArray(temp, 7*offset-middle);
}

/**
 * Verarbeitet eine an der seriellen Schnittstelle empfangene Zeile.
 *
 * Wenn eine ganze Zeile empfangen wurde, wird diese Methode zur Verarbeitung
 * aufgerufen.  Dabei wird die ganze Zeile eingelesen, diese in die einzelnen
 * Komponenten aufgeteilt und in ein Array zwischengespeichert. Je nachdem um
 * welche Daten es sich handelt, werden diese dann im Array  an der aktuellen
 * Position gespeichert  und die Position wird danach erhoeht und ggf. wieder
 * auf 0 zurueckgesetzt.
 *
 * @param port die Schnittstelle an der die Daten empfangen wurden.
 */
void serialEvent(Serial port) {
  // Einlesen der empfangenen Daten von der seriellen Schnittstelle.
  String input = port.readString();
  // Trennen der empfangenen Daten in die einzelnen Werte.
  String[] values = split(input, ';');

  // Ueberpruefen der Datenquelle (G/A/T).
  switch (values[0]) {
  case "G":
    // Umwandeln der 3 Messwerte und speichern in den entsprechenden Arrays.
    gyrX[currentPointGyr] = float(values[1]);
    gyrY[currentPointGyr] = float(values[2]);
    gyrZ[currentPointGyr] = float(values[3]);
    // Erhoehen der aktuellen Position und ggf. wieder auf 0 zuruecksetzen.
    if (++currentPointGyr >= numberOfPoints)
      currentPointGyr = 0;
    break;
  case "A":
    accX[currentPointAcc] = float(values[1]);
    accY[currentPointAcc] = float(values[2]);
    accZ[currentPointAcc] = float(values[3]);
    if (++currentPointAcc >= numberOfPoints)
      currentPointAcc = 0;
    break;
  case "T":
    temp[currentPointTmp] = float(values[1]);
    if (++currentPointTmp >= numberOfPoints)
      currentPointTmp = 0;
    break;
  }
}

 

Resultat

Das Ergebnis sieht dann wie folgt aus. Die ersten drei Diagramme stellen dabei die Beschleunigung an der X-, Y- und Z-Achse dar, die nächsten drei Diagramme stellen die Rotationsgeschwindigkeiten entlang der X-, Y- und Z-Achse dar und das letzte Diagramm stellt die Umgebungstemperatur dar:
MPU-6050 Processing Demo

Quellenverzeichnis

Markus Schabel

4 thoughts on “MPU-6050 mit Arduino und Visualisierung in Processing

  1. Teru

    Hallo
    Ich bekomme in Stillstand folgende Werte heraus:
    A;-9;-1;-2
    G;0;0;0
    T;25.28

    Für A erwarte ich eigentlich null. soll ich eine Kalibrierung vornehmen?
    mfg
    Teru

    Reply
    1. Markus Schabel Post author

      Hallo!

      Nein, das passt schon so ungefähr (wenn es genauer sein soll, muss eine Kalibrierung vorgenommen werden). Der Wert “-9” entspricht der Erdbeschleunigung (9.81m/s^2) und ist nur im “freien Fall” 0.

      Mit freundlichen Grüßen,
      Markus Schabel

      Reply
  2. Jean-Claude DEPREZ

    Hallo Herr Schabel,
    Ich habe ein MPU (GY-521), Arduino MEGA 2560 und ein TFT HUGA HX8357C.
    Mein project war das Model auf den TFT screen zu haben.
    Ich habe schon alles probiert, hilfe gefragt und nichts geht.Ich habe ihre programm auf Arduino aufgeladed und habe error: variable or field “serialEvent” declared void. Ich verstehe es nicht.
    Frage: wie wurden Sie das program schreiben dass ich es auf den TFT j-habe.
    Ich bin relativ neu auf Arduino.
    Danke fur ihre hilfe

    Reply
    1. Markus Schabel Post author

      Hallo!

      Das “serialEvent” ist teil des Processing-Codes (der am PC läuft), nicht im Arduino.
      Ich hab bisher noch kein TFT mit dem Arduino betrieben.

      Mit freundlichen Grüßen

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *