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.
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 |
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.
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.
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) |
Optionale Software-Library: I2Cdevlib und MPU6050-Code
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.
#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;
}
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:
Der folgende Sketch berechnet nach dem Auslesen der Sensor-Daten die richtigen Winkel des Sensors und gibt diese wiederum in der seriellen Konsole aus.
#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);
}
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"
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);
}
}
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);
}
zurück