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
- Wire.beginTransmission(ADDR): Kommunikation mit Gerät an Adresse ADDR beginnen
- Wire.write([Befehls-/Register-Adresse]): Funktion aufrufen bzw. Register-Adresse zum Auslesen angeben
- Wire.write([Parameter bei Befehl]): ggf. Werte an eine aufgerufene Funktion übergeben
- Wire.endTransmission(true): Kommunikations-Befehl / Anforderung abschließen
- Wire.requestFrom(GYRO_ADDR, Byteanzahl, true): Byteanzahl Bytes anfordern
- Wire.read(): 1 Byte lesen
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:
- Accelerometer X: Absolute Beschleunigung auf der X-Achse
- Accelerometer Y: Absolute Beschleunigung auf der Y-Achse
- Accelerometer Z: Absolute Beschleunigung auf der Z-Achse
- Gyroskop X: Relative Beschleunigung auf der X-Achse
- Gyroskop Y: Relative Beschleunigung auf der X-Achse
- Gyroskop Z: Relative Beschleunigung auf der Z-Achse
- Temperatur (lt. Datenblatt von -40 bis +85 °C)
2. virtuelle Wasserwaagen:
verhält sich wie eine handelsübliche Wasserwaage (mit einer Gasblase gefülltes transparentes Röhrchen)
- kombinierte X/Y-Wasserwaage (rund): planeben aufgelegt sollte die Blase genau im Zentrum liegen
- X- und Y- Wasserwaage (Balken): zur separaten Messung der X und Y-Abweichung zum Beispiel auf dem Tisch. Planeben aufgelegt sollte die Blase genau im Zentrum liegen
- Z- Wasserwaage (Balken): zur separaten Messung der Z-Abweichung zum Beispiel an der Wand. Im rechten Winkel zur Schwerkraft angelegt sollte die Blase genau im Zentrum liegen
3. Beschleunigungskurven:
zeigt für die letzten 6 Sekunden die von Gyroskop gemessenen Beschleunigungen als Kurve an
- Rot: Beschleunigungskurve zur Drehung um die X-Achse
- Grün: Beschleunigungskurve zur Drehung um die Y-Achse
- Blau: Beschleunigungskurve zur Drehung um die Z-Achse
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