GY-521 Modul mit MPU-6050 Gyroskop/Beschleunigungssensor-Chip über I2C-Bus an Odroid Go ESP32 anschließen

Darüber, dass der Odroid Go nicht nur als mobile Spielekonsole (zudem mit Drittfirmware spieletechnisch aufrüstbare), sondern auch für eigene ESP32-basierte Programmierung taugt, habe ich ja bereits berichtet.

Heute wollen wir einen Schritt weiter gehen und unsere eigene Hardware an den Odroid-Go hängen. Dazu nutzen wir den GPIO-Port, die schwarze Buchse oben am Odroid, die von rechts nach links (bei Draufsicht) durchnummeriert 10 Header-Pins zur Verfügung stellt:



Zum Anschließen habe ich mir einen Gyroskop-Sensor MPU-6050 GY-521 ausgesucht, mit dem seinen 3 Achsen (X, Y und Z) die räumliche Positionierung wiedergibt. Ich hatte zwar schon einmal den Kompass/Beschleunigungssensor-Chip für I2C, den LSM303C vorgestellt, aber das war am Raspberry Pi. Nun kommt auch der MPU-6050 mal zu Ehren.

Die Ausgabe erfolgt dann natürlich auf dem Odroid-Go Display.

MPU-6050 Beschleunigungs / Gyroskop Sensor


Die technischen Daten des MPU-6050 sind: Modul GY-521 Chip MPU-6050 Größe: 4 x 4 x 0.9mm VDD: 2.375V-3.46V (also ideal für die 3.3V des ESP32) auf der Platine befindet sich aber auch ein Spannungs- regler, der 5V an VDD auf 3.3V herunterregeln sollte. Vlogic: 1.71V-VDD Bus: I2C Pin 8: VLogic Pin 9: AD0 Pin 23: SCL Pin 24: SDA Gyroscope Features Digital-output X-, Y-, and Z-Axis angular rate sensors (gyroscopes) with a user-programmable fullscale range of ±250, ±500, ±1000, and ±2000°/sec Integrated 16-bit ADCs enable simultaneous sampling of gyros Enhanced bias and sensitivity temperature stability reduces the need for user calibration Gyroscope operating current: 3.6mA Standby current: 5µA Factory calibrated sensitivity scale factor Accelerometer Features Digital-output triple-axis accelerometer with a programmable full scale range of ±2g, ±4g, ±8g and ±16g Integrated 16-bit ADCs enable simultaneous sampling of accelerometers while requiring no external multiplexer Accelerometer normal operating current: 500µA Low power accelerometer mode current: 10µA at 1.25Hz, 20µA at 5Hz, 60µA at 20Hz, 110µA at 40Hz Additional Features 9-Axis MotionFusion by the on-chip Digital Motion Processor (DMP) Auxiliary master I2C bus for reading data from external sensors (e.g., magnetometer) 3.9mA operating current when all 6 motion sensing axes and the DMP are enabled 1024 byte FIFO buffer reduces power consumption by allowing host processor to read the data in Digital-output temperature sensor 10,000 g shock tolerant 400kHz Fast Mode I2C for communicating with all registers
Wichtig sind von den Pins für uns momentan nur VCC und GND für die Stromversorgung und SCL und SDA für den I2C-Bus. Für andere Arduino Projekte kann ggf. AD0 interessant werden. Wird dieser Pin auf High gesetzt ist die I2C-Adresse 0x69 statt standardmäßig 0x68.

Dementsprechend müssen wir auch nur an die obersten vier Pins des Moduls einen Header löten. Am besten kleben wir dann den Gyroskop-Sensor mit Klebeknete oben links in die Ecke auf der Vorderseite des Odroid-Go und zwar so, dass die Aufschrift des Chips richtig herum zu lesen ist bzw. so, dass die X und Y-Achsen wie auf dem Modul beschriftet vor uns liegen.

Dann stecken mir den mitgelieferten 10-poligen Header-Adapter oben in den Odroid-Go und verbinden Sensor und Odroid-Go wie folgt: GY-521 Odroid-Go auf dem Foto Bemerkung GND GND (Pin #1) schwarz Masse SDA IO15 (Pin #4) gelb I2C Serial Data SCL IO4 (Pin #5) orange I2C Serial Clock VCC P3V3 (Pin #6) rot 3.3V

I2C Kommunikation auf dem Odroid-Go

Die Kommunikation mittels I2C funktioniert auf dem Odroid-Go genauso wie auf einem Arduino oder sonstigen ESP32. Eine Kleinigkeit ist allerdings zu beachten, wenn man auch die "odroid_go.h" benutzen will. Hier sind nämlich schon Beziehungen zur I2C-Library definiert, die dann bei einer Einbindung der I2C-Library mittels #include <Wire.h> mit den dort definierten Funktionen kollidieren (dann doppelt sind) und zu einem Kompilerfehler führen.

Darum muss man #include "sensors/Wire.h" zu verwenden. Inwieweit diese Odroid-Go-spezifische Wire.h kompatibel ist zur originären, dass habe ich nicht weiter untersucht. Mit dem hier vorgestellten Sensor gab es aber keine Probleme.

Welche zwei Pins für SDA (Data) und SCL (Clock) man für die I2C-Kommunikation benutzt, gibt man beim Initialisieren der Library an: #define PIN_I2C_SDA 15 // Festlegung der I2C-Pins; IO15 (Odroid-Pin #4) = SDA #define PIN_I2C_SCL 4 // IO4 (Odroid-Pin #5) = SCL ... void setup() { Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); // I2C-Libary initialisieren ... } Ich habe den I2C-Bus ja bereits für den Arduino Uno erklärt. für den Odroid-Go respektive ESP32 gelten die gleichen Funktionen. Aber hier noch einmal in Kürze:

Wichtig für jedes I2C-Modul zu wiessen ist die Adresse, über die es angesprochen werden muss. Jede Modulart hat ihre eigene Adresse, so dass mehrere I2C-Geräte am selben Bus parallel betrieben werden können. Für unser GY-521 ist dies 0x68 (hex 68 = dez 104). Es sei denn, man setzt den AD0-Pin des Moduls auf Hight, dann ist es 0x69. Dann kann man sogar zwei GY-521 Module gleichzeitig betreiben.

Hat man übrigens ein unbekanntes Modul, kann man das Tool i2cdetect für den Raspberry Pi benutzen, um diese Adresse herauszufinden. So lässt sich anhand der Adresse evtl. auch ein abgeschliffener Chip noch identifizieren.

Mit #define GYRO_ADDR 0x68 // I2C-Adresse des GY-521 Moduls ... void setup() { ... Wire.beginTransmission(GYRO_ADDR); } wird eine Kommunikation mit dem Modul initialisiert. Danach muss die weitere Befehls- und Register-Adressen aus dem Datenblatt des jeweiligen Moduls / Chips kennen, um weiterzukommen.

So ist beim MPU-6050 die Befehls-Adresse 0x6b zum Aufwecken zuständig. Und an den Register-Adressen 0x3b bis 0x48 können die Werte für den Beschleunigungssensor, Gyroskop und Temperatur ausgelesen werden (jeweils 16-bit-Werte).

Die Kommunikation geschieht dann über die Befehle Im nachfolgenden Source ist die Anwendung der Befehle gut ersichtlich und kommentiert.

Beispielprogramm

Das Bespiel besteht aus drei Teilen, zwischen denen man mit der A-Taste (ggf. länger halten) wechseln kann. Außerdem kann man die Farben zwischen augenfreundlich (weiß auf schwarz) und kamerafreundlich (schwarz auf weiß) mit Select umschalten (ggf. länger halten)

1. Anzeige der Rohwerte:

zeigt alle Rohwerte des Sensors an:
2. virtuelle Wasserwaagen:

verhält sich wie eine handelsübliche Wasserwaage (mit einer Gasblase gefülltes transparentes Röhrchen)
3. Beschleunigungskurven:

zeigt für die letzten 6 Sekunden die von Gyroskop gemessenen Beschleunigungen als Kurve an Die Orientierung der X und Y-Achse kann auf dem Modul abgelesen werden. Die Z-Achsen-Drehung entspricht der Drehung des Odroid, wenn es plan auf dem Tisch liegt.

Video-Demonstration des Beispielprogramms



Source-Code

//////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include "odroid_go.h" // Header-File für Odroid-Go-Libraries einbinden #include "sensors/Wire.h" // für I2C Kommunikation, nicht <wire.h>, Odroid hat hier seine eigene // Implementierung, wie kompatibel die ist, muss sich noch zeigen #define PIN_BLUE_LED 2 // IO-Pin der blauen LED #define PIN_I2C_SDA 15 // Festlegung der I2C-Pins; IO15 (Odroid-Pin #4) = SDA #define PIN_I2C_SCL 4 // IO4 (Odroid-Pin #5) = SCL #define GYRO_ADDR 0x68 // I2C-Adresse des GY-521 Moduls int16_t acc_x, acc_y, acc_z; // Werte Beschleunigung int16_t gyr_x, gyr_y, gyr_z; // Werte Gyroskop int16_t temp; // Wert für Temperatur void mpuWakeUp(){ Wire.beginTransmission(GYRO_ADDR); Wire.write(0x6b); Wire.write(0); // 0 in 0x6b schreiben = MPU 6050 aufwecken int ret=Wire.endTransmission(true); if (ret != 0) { GO.lcd.printf("Kommunikation mit I2C-Geraet ist fehlgeschlagen.\nErrorcode: %d\n", ret); exit; } } void waitA() { // Auf Knopfdruck A warten while (!GO.BtnA.isPressed()) { GO.update(); // Knopfzustände holen delay (10); } digitalWrite(PIN_BLUE_LED,HIGH); // auf Loslassen warten while (GO.BtnA.isPressed()) { GO.update(); delay (10); } digitalWrite(PIN_BLUE_LED,LOW); } void setup() { pinMode(PIN_BLUE_LED, OUTPUT); GO.begin(); // Go-Libary initialisieren Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); // I2C-Libary initialisieren mpuWakeUp(); } void loop() { int16_t bgc = WHITE; int16_t fgc = BLACK; int16_t tmpcol = 0; int x = 0; int y = 0; int z = 0; int br = 200; int lx, ly, lz; while (!GO.BtnA.wasPressed()) { Wire.beginTransmission(GYRO_ADDR); Wire.write(0x3b); // bei 0x3b starten die Registerwerte (0x3B = (ACCEL_XOUT_H)) Wire.endTransmission(true); Wire.requestFrom(GYRO_ADDR, 7*2, true); // 7 * 2-Byte Register // "Wire.read()<<8 | Wire.read();" = 1. read-Byte in die oberen 8 Bits der 16Bit-Variable, 2. in die unteren acc_x = Wire.read()<<8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L) acc_y = Wire.read()<<8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L) acc_z = Wire.read()<<8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L) temp = Wire.read()<<8 | Wire.read(); // 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L) gyr_x = Wire.read()<<8 | Wire.read(); // 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L) gyr_y = Wire.read()<<8 | Wire.read(); // 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L) gyr_z = Wire.read()<<8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L) // Rohwerte ausgeben //GO.lcd.clearDisplay(); GO.lcd.fillScreen(bgc); GO.lcd.setCursor(0, 5); GO.lcd.setTextWrap(1); GO.lcd.setTextFont(4); GO.lcd.setTextColor (fgc); GO.lcd.println(" Rohwerte\n"); GO.lcd.setTextFont(2); GO.lcd.printf (" Acc. X = %d (%.1f%%)\n", acc_x, acc_x*100./16384.); GO.lcd.printf (" Acc. Y = %d (%.1f%%)\n", acc_y, acc_y*100./16384.); GO.lcd.printf (" Acc. Z = %d (%.1f%%)\n\n", acc_z, acc_z*100./16384.); GO.lcd.printf (" Gyro X = %d (%.1f%%)\n", gyr_x, gyr_x*100./32768.); GO.lcd.printf (" Gyro Y = %d (%.1f%%)\n", gyr_y, gyr_y*100./32768.); GO.lcd.printf (" Gyro Z = %d (%.1f%%)\n\n", gyr_z, gyr_z*100./32768.); GO.lcd.printf (" Temp = %.2f C\n\n", temp/340.00+35.); // lt. Datenblatt S. 14: Sensitivity:340, Temp Offset 35°C (war 340.00+36.53) GO.lcd.setCursor(235, 220); GO.lcd.print ("weiter mit A"); delay(200); mpuWakeUp(); GO.update(); if (GO.BtnSelect.wasPressed()) { // Inverse Bildschirmausgabe tmpcol = bgc; bgc = fgc; fgc = tmpcol; } } waitA(); // Wasserwaage ------------------------------------------------------------------------------------------------------- while (!GO.BtnA.wasPressed()) { // Register einlesen Wire.beginTransmission(GYRO_ADDR); Wire.write(0x3b); Wire.endTransmission(true); Wire.requestFrom(GYRO_ADDR, 7*2, true); acc_x = Wire.read()<<8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L) acc_y = Wire.read()<<8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L) acc_z = Wire.read()<<8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L) temp = Wire.read()<<8 | Wire.read(); // 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L) gyr_x = Wire.read()<<8 | Wire.read(); // 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L) gyr_y = Wire.read()<<8 | Wire.read(); // 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L) gyr_z = Wire.read()<<8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L) GO.lcd.fillScreen(bgc); GO.lcd.setCursor(0, 5); GO.lcd.setTextWrap(1); GO.lcd.setTextFont(4); GO.lcd.setTextColor (fgc); GO.lcd.println(" Wasserwaage\n"); GO.lcd.setTextFont(2); GO.lcd.setCursor(235, 220); GO.lcd.print ("weiter mit A"); GO.lcd.setTextFont(4); // Mittelpunkt Pos berechnen // Bereich 120x120 px (-60...+60) x = 100 + floor(acc_x*60./16384.); y = 100 - floor(acc_y*60./16384.); // XY-Blase: Hintergrund in hellgrün GO.lcd.fillCircle(100, 100, 60, GREENYELLOW); // XY-Blase in dunkelgrün malen GO.lcd.fillCircle(x, y, 8, OLIVE); // XY-Blase: Kreise malen (sind vor der Blase) for (int i=1; i <= 6; i++) { GO.lcd.drawCircle(100, 100, i*10, DARKGREY); GO.lcd.drawCircle(100, 100, i*10, DARKGREY); } br=180; x = floor(acc_x*br/2./16384.); y = floor(acc_y*br/2./16384.*-1); z = floor(acc_z*br/2./16384.); // X-Waage: Hintergrund GO.lcd.fillRect(10,190, br+2,21, GREENYELLOW); // X-Waage: Blase GO.lcd.fillCircle(10+br/2+x, 200, 8, OLIVE); // X-Waage: Begrenzungen GO.lcd.drawRect(10,190, br+2,21, DARKGREY); // außen GO.lcd.drawRect(10+br/2-10,190, 21,21, DARKGREY); // innen GO.lcd.setCursor(10+br/2-7, 215); GO.lcd.print ("X"); // Y-Waage: Hintergrund GO.lcd.fillRect(190,10, 21,br+2, GREENYELLOW); // Y-Waage: Blase GO.lcd.fillCircle(200,10+br/2+y, 8, OLIVE); // Y-Waage: Begrenzungen GO.lcd.drawRect(190,10, 21,br+2, DARKGREY); // außen GO.lcd.drawRect(190,10+br/2-10, 21,21, DARKGREY); // innen GO.lcd.setCursor(215,10+br/2-10); GO.lcd.print ("Y"); // Z-Waage: Hintergrund GO.lcd.fillRect(240,10, 21,br+2, GREENYELLOW); // Y-Waage: Blase GO.lcd.fillCircle(250,10+br/2+z, 8, OLIVE); // Y-Waage: Begrenzungen GO.lcd.drawRect(240,10, 21,br+2, DARKGREY); // außen GO.lcd.drawRect(240,10+br/2-10, 21,21, DARKGREY); // innen GO.lcd.setCursor(265,10+br/2-10); GO.lcd.print ("Z"); delay(200); mpuWakeUp(); GO.update(); if (GO.BtnSelect.wasPressed()) { // Inverse Bildschirmausgabe tmpcol = bgc; bgc = fgc; fgc = tmpcol; } } waitA(); // rel. Beschleunigung ------------------------------------------------------------------------------------------------------- int za=0; while (!GO.BtnA.wasPressed()) { // Register einlesen Wire.beginTransmission(GYRO_ADDR); Wire.write(0x3b); Wire.endTransmission(true); Wire.requestFrom(GYRO_ADDR, 7*2, true); acc_x = Wire.read()<<8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L) acc_y = Wire.read()<<8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L) acc_z = Wire.read()<<8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L) temp = Wire.read()<<8 | Wire.read(); // 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L) gyr_x = Wire.read()<<8 | Wire.read(); // 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L) gyr_y = Wire.read()<<8 | Wire.read(); // 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L) gyr_z = Wire.read()<<8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L) if (za > 320) za=0; if (za == 0) { // neu beginnen mpuWakeUp(); GO.lcd.fillScreen(bgc); GO.lcd.setCursor(0, 5); GO.lcd.setTextWrap(1); GO.lcd.setTextFont(4); GO.lcd.setTextColor (fgc); GO.lcd.print(" rel. Beschleunigung "); GO.lcd.setTextColor (RED); GO.lcd.print(" X "); GO.lcd.setTextColor (DARKGREEN); GO.lcd.print(" Y "); GO.lcd.setTextColor (BLUE); GO.lcd.print(" Z "); GO.lcd.setTextFont(2); GO.lcd.setCursor(235, 220); GO.lcd.setTextColor (fgc); GO.lcd.print ("weiter mit A"); // Koordinatenkreuz zeichnen GO.lcd.drawLine(5,0, 5,240, DARKGREY); // | GO.lcd.drawLine(0,120, 320,120, DARKGREY); // ---- for (x=15; x <= 320; x +=10) { GO.lcd.drawLine(x,115, x,125, DARKGREY); // | | | } for (y=0; y <= 240; y +=10) { GO.lcd.drawLine(1,y, 10,y, DARKGREY); // - - - } x=0; y=0; z=0; } za ++; // kurven einzeichnen lx=x; ly=y; lz=z; x= floor(gyr_x/32768.*120); y= floor(gyr_y/32768.*120); z= floor(gyr_z/32768.*120); // X GO.lcd.drawLine (za-1,120+lx, za,120+x, RED); // Y GO.lcd.drawLine (za-1,120+ly, za,120+y, DARKGREEN); // Z GO.lcd.drawLine (za-1,120+lz, za,120+z, BLUE); delay(20); GO.update(); if (GO.BtnSelect.wasPressed()) { // Inverse Bildschirmausgabe tmpcol = bgc; bgc = fgc; fgc = tmpcol; } } waitA(); } Download der fertigen Firmware für den Odroid-Go