Hlavní navigace

Stavíme kvadrokoptéru #3: Digital Motion Processor na MPU-6050

5. 4. 2013
Doba čtení: 9 minut

Sdílet

V dnešním článku o kvadrokoptéře se podíváme na základní stavební kámen stabilizace: integrovaný obvod MPU-6050. Kromě toho, že má na sobě gyroskop a akcelerometr, obsahuje také Digital Motion Processor, který můžeme využít pro rychlé zpracování dat a získat přesné úhly, ve kterých se kvadrokoptéra nachází.

Než se pustíme k tomu zajímavému, z minulé diskuse mohlo vyplynout, že jsem dostatečně nezdůraznil, že stavba kvadrokoptéry a vývoj softwaru pro ni mohou být nebezpečné. Doufal jsem, že projevy motoru s vrtulí jsou dostatečně výrazné na to, aby do něj lidé nestrkali ruce. Pro jistotu tedy napíši, že to není dobrý nápad a že následování postupů v tomto seriálu rozhodně není bez zapojení vlastní obezřetnosti bezpečné. Autor ani Root.cz nenese zodpovědnost za to, co se vám během stavby vaší kvadrokoptéry stane, ať už se rozhodnete jít vlastní cestou nebo se inspirujete těmito řádky. Během pokusů dbejte na to, aby byla kvadrokoptéra dobře uchycena a neuletěla nějakým nečekaným směrem.

V seznamu v prvním článku můžete narazit na gyro MPU-6050. Jde o levný integrovaný obvod s podporou I2C/SPI sběrnice, tříosým gyroskopem, třiosým akcelerometrem a teploměrem. Kromě toho má na sobě ještě Digital Motion Processor™ (DMP), který umožňuje provádět výpočty s hodnotami z gyroskopu, akcelerometru a také z externě připojeného magnetometru. Integrovaný teploměr je spíše pro hrubé měření, protože více než teplotu okolí vrací teplotu čipu. Takže když máte v místnosti 18 °C, ochotně vrátí hodnotu 25 °C.

Klíčový senzor pro naši kvadrokoptéru je akcelerometr. Ten měří gravitační zrychlení, takže ho můžeme využít k měření:

  • Náklonu
  • Rychlosti
  • Otřesů

Zajímá nás především náklon, ale ať už chceme jednu, druhou nebo třetí hodnotu, ostatní dvě nám budou do získaných čísel zasahovat. Pro přesnější měření proto kombinujeme několik senzorů najednou. MPU-6050 obsahuje kromě akcelerometru ještě gyroskop. Stejně jako akcelerometr, i gyroskop umí měřit ve třech osách a se správným nastavením jsme schopni získat údaje o tom, o kolik se od posledního měření změnil úhel desky se senzorem.

MPU-6050 je zařízení mnoha možností a díky štědré komunitě, jmenovitě Jeffu Rowbergovi, nemusíme znát všechny jeho záludnosti, ani tu matematiku, která se za získáváním klíčových hodnot schovává, abychom dostali to, co naše kvadrokoptéra potřebuje.

Zapojení

Ale nebudu předbíhat. Pokud máte na stole Arduino Nano a MPU-6050 jako já, není nic jednoduššího, než vzít 4 vodiče a připojit s nimi GND gyra na GND Arduina, napájení gyra dát na 5V pin Arduina a piny SDA a SCL na gyru připojit na piny A4 a A5 na Arduinu. Lepší přehled vám dá tato tabulka:

Pin Arduina Pin MPU6050
5V VCC
GND GND
D2 INT
A4 SDA
A5 SCL

V tabulce ještě pin INT, který připojíme na D2 na Arduinu. Jedná se o externí přerušení, o kterém bude řeč níže.

Toto gyro je schopné pracovat i na 3,3 V, takže ho můžete bez problémů připojit také k Raspberry Pi, například podle návodu na astromik.org.

Získávání surových dat

S MPU-6050 lze komunikovat dvěma způsoby. Ten jednodušší je získávání surových dat. Ten komplikovanější je povídání si s integrovaným DMP, který dokáže přepočítat ledasco sám, aniž by bylo třeba s tím zatěžovat Arduino. Naštěstí pro oba způsoby existuje jedna knihovna, která je součástí balíku i2cdevlib. To je komunitní projekt, v rámci kterého různí vývojáři dávají dohromady podporu pro různá I2C zařízení. Nás zajímá hlavně naše MPU-6050. Zdrojový kód i příklady jsou dobře komentované a komunikace s gyrem pomocí této knihovny je velmi snadná. Knihovnu stačí stáhnout a nakopírovat k Arduino IDE do adresáře „libraries“.

V obou následujících ukázkách použiji mírně upravené příklady, které jsou součástí knihovny, jen je doplním o české komentáře a případně o nějaké důležité detaily. Začneme získáváním čistých dat.

# Knihovna I2Cdev využívá knihovnu Wire
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"

// Výchozí I2C adres je 0x68, ale pokud máte jinou, zde ji můžete uvést
// AD0 low = 0x68
// AD0 high = 0x69
MPU6050 accelgyro;

// získané hodnoty
int16_t ax, ay, az;
int16_t gx, gy, gz;

#define LED_PIN 13
bool blinkState = false;

void setup() {
// připojení k I2C sběrnici, I2Cdev to sama nedělá
Wire.begin();

// Inicializace UART komunikace
Serial.begin(38400);

// Inicializace gyra
Serial.println("Initializing I2C devices...");
accelgyro.initialize();

// Test komunikace s gyrem
Serial.println("Testing device connections...");
Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");

// Nastavíme diodu na výstupu D13
pinMode(LED_PIN, OUTPUT);
}

void loop() {
// Přečtení hodnot z gyra a akcelerometru
accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

// Ke stejnému účelu můžete použít tyto dvě metody
//accelgyro.getAcceleration(&ax, &ay, &az);
//accelgyro.getRotation(&gx, &gy, &gz);

// Zobrazení jednotlivých hodnot (oddělené taby)
Serial.print("a/g:\t");
Serial.print(ax); Serial.print("\t");
Serial.print(ay); Serial.print("\t");
Serial.print(az); Serial.print("\t");
Serial.print(gx); Serial.print("\t");
Serial.print(gy); Serial.print("\t");
Serial.println(gz);

// Zablikáme diodou
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState);
} 

Jak vidíte, použití s MPU-6050 je s touto knihovnou hračka. Výchozí nastavení senzorů nemusí vyhovovat pro daný účel a proto je ještě možné měnit citlivost jak gyroskopu, tak akcelerometru. Citlivost gyroskopu můžeme nastavit pomocí:

accelgyro.setFullScaleGyroRange(MPU6050_GYRO_FS_250); 

Kde možnými hodnotami jsou:

MPU6050_GYRO_FS_250   pro  ±250°/sec
MPU6050_GYRO_FS_500   pro  ±500°/sec
MPU6050_GYRO_FS_1000  pro ±1000°/sec
MPU6050_GYRO_FS_2000  pro ±2000°/sec 

Jednotka °/sec je úhlové zrychlení a čím větší číslo se před ní nachází, tím rychlejší otáčení je schopen gyroskop měřit, ale za cenu snížené přesnosti u otáček menších. U akcelerometru můžeme použít:

accelgyro.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); 

Kde možnými hodnotami jsou:

MPU6050_ACCEL_FS_2   pro  ±2g
MPU6050_ACCEL_FS_4   pro  ±4g
MPU6050_ACCEL_FS_8   pro  ±8g
MPU6050_ACCEL_FS_16  pro  ±16g 

Čím větší hodnotu nastavíme, tím menší vliv bude mít změna náklonu na měření akcelerometru.

Využití Digital Motion Processoru

To nejzajímavější, co se na MPU-6050 nachází, je Digital Motion Processor. Ten dokáže provádět výpočty s hodnotami ze senzorů, které tak nezatěžují naše pomalé Arduino. Údaje z akcelerometru a gyroskopu nelze použít přímo pro regulaci otáček motorů. I když například informace z akcelerometru mohou vytvářet zdání, že jde o náklon desky se senzorem, stačí o ni trochu zavadit a do údajů se promítne setrvačnost. Za tímhle vším se schovává složitá matematika, o kterou jsem nezavadil ani na vysoké škole, takže jsem byl vcelku rád, že výše zmíněná knihovna i2cdevlib si poradí jak s DMP, tak s výpočty, které potřebujeme pro stabilizaci. Zavoláním několika metod nám z DMP začnou padat úhly, ve kterých se senzory nachází a jediné, co musíme udělat, je, že je včas přečteme.

Z předchozího příkladu byste měli mít správně spojené Arduino s MPU-6050. Pro následující příklad musíte mít propojený pin INT gyra s pinem A2 na Arduinu. MPU-6050 přes něj říká Arduinu, že má k dispozici nové výsledky výpočtů a že si je máme vyzvednout. Samotný kód vypadá takto:

// Knihovna I2Cdev využívá knihovnu Wire
#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"

// Výchozí I2C adresa MPU-6050 je 0x68, ale pokud máte jinou, zde ji můžete uvést
// AD0 low = 0x68
// AD0 high = 0x69
MPU6050 mpu;

#define LED_PIN 13 // číslo pinu s integrovanou LED diodou

bool dmpReady = false;  // když je DPM připraveno, obsahuje true
uint8_t mpuIntStatus;   // stav externího přerušení z DPM
uint8_t devStatus;      // stav poslední operace
uint16_t packetSize;    // velikost paketu z DPM
uint16_t fifoCount;     // počet bytů ve FIFO zásobníku
uint8_t fifoBuffer[64]; // FIFO zásobník

// proměnné důležité pro výpočet
Quaternion q;           // [w, x, y, z]         kvaternion
VectorFloat gravity;    // [x, y, z]            vektor setrvačnosti
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll úhly

// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // true pokud DMP vyvolalo přerušení
void dmpDataReady() {
mpuInterrupt = true;
}

// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {
pinMode(LED_PIN, OUTPUT);

// připojíme se na I2C sběrnici
Wire.begin();

// inicializujeme UART
Serial.begin(115200);
while (!Serial); // wait for Leonardo enumeration, others continue immediately

// inicializujeme MPU-6050
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();

// ověříme připojení k MPU-6050
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

// incializujeme DMP
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();

// ujistíme se, že funguje
if (devStatus == 0) {
    // zapneme DMP
    Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);

    // externí přerušení Arduina nabindujeme na funkci dmpDataReady
    Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
    attachInterrupt(0, dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();

    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;

    // zjistíme si, jak velké pakety DMP vrací
    packetSize = mpu.dmpGetFIFOPacketSize();
} else {
    // Když dojde k chybě, tak:
    // 1 = selhala úvodní komunikace s DMP
    // 2 = selhala aktualizace nastavení DMP
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
}

digitalWrite(LED_PIN, LOW);
}

// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {
// pokud není DMP připravené, nebudeme dělat nic
if (!dmpReady) return;

// zde provádíme náš kód, cyklus ověřuje, zda nemá DMP připravena nějaká data
while (!mpuInterrupt && fifoCount < packetSize) {
    //
}

// resetujeme proměnnou informující o přerušení vyvolané z DMP a získáme INT_STATUS byte
mpuInterrupt = false;
mpuIntStatus = mpu.getIntStatus();

// získáme velikost FIFO zásobníku
fifoCount = mpu.getFIFOCount();

// zjistíme, zda nedošlo k přetečené zásobníku
// pokud k němu dojde, je třeba optimalizovat kód v cyklu výše,
// případně přerušit provádění mezi delšími výpočty, je-li to třeba
if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
    // vyčistíme zásobník
    mpu.resetFIFO();
    Serial.println(F("FIFO overflow!"));

// pokud je vše v pořádku, zpracujeme data z DMP
} else if (mpuIntStatus & 0x02) {
    // čekání na správnou délku dat
    while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

    // přečteme paket ze zásobníku
    mpu.getFIFOBytes(fifoBuffer, packetSize);

    // pokud je v zásobníku víc než jeden paket, tímto zajistíme, že bude přečten v dalším cyklu
    fifoCount -= packetSize;

    // naplnění proměnných s vypočítanými hodnotami
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);

    //Výpis YAW/PITCH/ROLL
    Serial.print("ypr\t");
    Serial.print(ypr[0] * 180/M_PI);
    Serial.print("\t");
    Serial.print(ypr[1] * 180/M_PI);
    Serial.print("\t");
    Serial.println(ypr[2] * 180/M_PI);
}
} 

Jde o ukázkový kód, který najdete v knihovně i2clibdev, ořezaný o pro nás zatím zbytečné části a také doplněný českými komentáři. Když ho zkompilujete do svého Arduina, dostanete výpis podobný tomuto:

Initializing I2C devices...
Testing device connections...
MPU6050 connection successful
Initializing DMP...
Enabling DMP...
Enabling interrupt detection (Arduino external interrupt 0)...
DMP ready! Waiting for first interrupt...
[...]
ypr -169.34 51.40   -4.06
ypr -168.96 48.65   -2.35
ypr -168.55 45.82   -0.83
ypr -168.13 42.98   0.48
ypr -167.69 40.19   1.61
ypr -167.30 37.46   2.59
ypr -166.95 34.79   3.48
ypr -166.59 32.20   4.31
ypr -166.24 29.73   5.17
[...] 

Čísla jsou oddělena tabelátorem a jsou to úhly na osách X,Y a Z desky MPU-6050. U letadel a koptér se používá označení yaw, pitch a roll. Lepší představu získáte z následujícího obrázku:

Zdroj: Wikipedia

MPU-6050 je nutné na kvadrokoptéru umístit rozumně, to znamená, aby osy senzorů kopírovaly právě yaw, pitch a roll. Je vcelku jedno, zda bude yaw osou X, Y nebo Z na MPU-6050, to snadno vyladíte během procesu, který bude následovat v dalších dílech.

Cloud23

Až budete mít vše funkční, všimněte si, že chvilku trvá, než se naměřené úhly stabilizují a je třeba několik sekund počkat. Poté, když nakloníte gyro do libovolného úhlu, tak se tento nebude měnit v závislosti ani na vibracích, ani na změnách rychlosti, kterou se pohybuje.

Shrnutí

V dnešním díle jsme z MPU-6050 dostali nějaká rozumná čísla, která nyní stačí předhodit PID regulátoru, který se postará o to, aby quadrokoptéra stála rovně ve vzduchu. Nyní je správný čas, kdy bychom měli doladit detaily na celé konstrukci a vyřešit propojení mezi Raspberry Pi a Arduinem. Další fáze bude spočívat ve vyladění PID regulátoru, resp. v jemném nastavení regulačních konstant a není pohodlné kvůli jejich změnám pokaždé nahrávat do Arduina celý program. To tedy bude téma příštího dílu.

Autor článku

Adam Štrauch je redaktorem serveru Root.cz a svobodný software nasazuje jak na desktopech tak i na routerech a serverech. Ve svém volném čase se stará o komunitní síť, ve které je již přes 100 členů.