Skip to content

6軸センサー(MPU-6050)の使い方

MPU-6050は、3軸の加速度センサーと3軸のジャイロセンサーを1つのチップに搭載した、高性能な6軸慣性計測ユニット(IMU)です。物体の傾きや動き、回転を検出できます。

通信方式:I2C (Inter-Integrated Circuit)

MPU-6050との通信には、I2Cという2本の線(SDA:データ, SCL:クロック)だけで多数のデバイスと通信できる、非常に便利なシリアル通信方式を使います。各デバイスは固有のアドレスを持っており、マイコン(マスター)はアドレスを指定して特定のデバイス(スレーブ)と通信します。MPU-6050のデフォルトアドレスは0x68です。

回路図と接続

MPU-6050モジュールを、Raspberry Pi PicoのI2C0ポートに接続します。

(ここに circuits.drawio で作成したMPU-6050の回路図を挿入)

MPU-6050 Raspberry Pi Pico 機能
VCC 3.3V (OUT) 電源
GND GND グラウンド
SCL GP5 (I2C0 SCL) クロック
SDA GP4 (I2C0 SDA) データ

補足: 私たちが使うMPU-6050モジュールには、I2Cに必要なプルアップ抵抗が既に取り付けられています。そのため、追加の抵抗は不要です。

横道:I2C vs SPI マイコン用の通信規格には、I2Cと並んで**SPI (Serial Peripheral Interface)**もよく使われます。どちらもマスター・スレーブ型の同期式シリアル通信ですが、大きな違いがあります。 | 特徴 | I2C | SPI | | :----------- | :-------------------------------- | :---------------------------------------- | | **信号線** | 2本 (SDA, SCL) | 4本 (MISO, MOSI, SCK, CS) | | **接続形態** | バス型(全デバイスを並列接続) | スター型(CS線でデバイスを選択) | | **通信速度** | やや遅い (標準100kbps, 高速400kbps) | 非常に高速 (数Mbps〜数十Mbps) | | **通信方式** | 半二重 | 全二重(送受信を同時に行える) | | **利点** | 配線が少なく、デバイス追加が容易 | 高速、構造がシンプルで確実 | | **欠点** | 速度が遅め、アドレス競合の可能性 | 配線が多い、デバイス数だけCS線が必要 | **使い分け**: SDカードや高速なディスプレイのように大量のデータを高速に送受信したい場合はSPI、センサーのように比較的低速で、多数のデバイスを少ない配線でつなぎたい場合はI2Cが適しています。

ライブラリとプログラム

高機能なセンサーは、内部の多数のレジスタを読み書きして制御しますが、これを手動で行うのは大変です。そのため、通常はライブラリを使います。platformio.iniに以下を追記してください。

lib_deps =
    adafruit/Adafruit MPU6050
    adafruit/Adafruit Unified Sensor
    adafruit/Adafruit BusIO

サンプルコード

まずは、Adafruitのライブラリに付属するサンプルコードを動かしてみましょう。

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

Adafruit_MPU6050 mpu;

float angle = 0.0;
float accel_angle = 0.0;
float gyro_rate = 0.0;
unsigned long last_time;

void setup(void) {
    Serial.begin(115200);
    if (!mpu.begin()) {
        Serial.println("Failed to find MPU6050 chip");
        while (1) { delay(10); }
    }
    Serial.println("MPU6050 Found!");
    last_time = micros();
}

void loop() {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);

    // 加速度から角度を計算 (atan2を使用)
    accel_angle = atan2(a.acceleration.y, a.acceleration.z) * 180 / PI;

    // ジャイロの角速度
    gyro_rate = g.gyro.x;

    // 経過時間を計算
    unsigned long current_time = micros();
    float dt = (current_time - last_time) / 1000000.0;
    last_time = current_time;

    // 相補フィルター
    angle = 0.98 * (angle + gyro_rate * dt) + 0.02 * accel_angle;

    Serial.println(angle);
    delay(10);
}

I2Cトラブルシューティング:動かないときは?

Failed to find MPU6050 chip」と表示されて動かない!これはI2C通信で誰もが通る道です。原因の多くは以下の通りです。 - 配線が間違っている(SDAとSCLが逆、VCC/GNDが抜けている等) - MPU-6050のI2Cアドレスが0x68ではない(モジュールによっては0x69の場合がある) - センサーの故障

こういう時、まず最初にやるべきことは「I2Cバススキャナ」を動かして、マイコンがI2Cデバイスを認識できているかを確認することです。 現在のmain.inoを、一時的に以下のI2Cスキャナのコードに完全に書き換えてみましょう。

// I2C Scanner
#include <Wire.h>

void setup() {
    Wire.begin();
    Serial.begin(115200);
    # 6軸センサー(MPU-6050)の使い方

MPU-6050は、3軸の加速度センサーと3軸のジャイロセンサーを1つのチップに搭載した、高性能な6軸慣性計測ユニット(IMU)です。物体の傾きや動き、回転を検出できます。

## 通信方式:I2C (Inter-Integrated Circuit)

MPU-6050との通信には、**I2C**という2本の線(SDA:データ, SCL:クロック)だけで多数のデバイスと通信できる、非常に便利なシリアル通信方式を使います。各デバイスは固有の**アドレス**を持っており、マイコン(マスター)はアドレスを指定して特定のデバイス(スレーブ)と通信します。MPU-6050のデフォルトアドレスは`0x68`です。

### 回路図と接続

MPU-6050モジュールを、Raspberry Pi PicoのI2Cポートに接続します。Picoは複数のI2Cポートを持っていますが、ここでは`Wire1` (GP15, GP14) を使用します。

*(ここに `circuits.drawio` で作成したMPU-6050の回路図を挿入)*

| MPU-6050 | Raspberry Pi Pico | 機能       |
| :       | :---------------- | :--------- |
| VCC      | 3.3V (OUT)        | 電源       |
| GND      | GND               | グラウンド |
| SCL      | GP15 (I2C1 SCL)   | クロック   |
| SDA      | GP14 (I2C1 SDA)   | データ     |

**補足**: 私たちが使うMPU-6050モジュールには、I2Cに必要なプルアップ抵抗が既に取り付けられています。そのため、追加の抵抗は不要です。

<details>
<summary><b>横道:</b>I2C vs SPI</summary>

マイコン用の通信規格には、I2Cと並んで**SPI (Serial Peripheral Interface)**もよく使われます。どちらもマスター・スレーブ型の同期式シリアル通信ですが、大きな違いがあります。

| 特徴         | I2C                               | SPI                                       |
| :----------- | :-------------------------------- | :---------------------------------------- |
| **信号線**   | 2本 (SDA, SCL)                    | 4本 (MISO, MOSI, SCK, CS)                 |
| **接続形態** | バス型(全デバイスを並列接続)    | スター型(CS線でデバイスを選択)          |
| **通信速度** | やや遅い (標準100kbps, 高速400kbps) | 非常に高速 (数Mbps〜数十Mbps)             |
| **通信方式** | 半二重                            | 全二重(送受信を同時に行える)            |
| **利点**     | 配線が少なく、デバイス追加が容易  | 高速、構造がシンプルで確実                |
| **欠点**     | 速度が遅め、アドレス競合の可能性  | 配線が多い、デバイス数だけCS線が必要      |

**使い分け**: SDカードや高速なディスプレイのように大量のデータを高速に送受信したい場合はSPI、センサーのように比較的低速で、多数のデバイスを少ない配線でつなぎたい場合はI2Cが適しています。

</details>

## ライブラリとプログラム

高機能なセンサーは、内部の多数の**レジスタ**を読み書きして制御しますが、これを手動で行うのは大変です。そのため、通常は**ライブラリ**を使います。`platformio.ini`に以下を追記してください。

```ini
lib_deps =
    tockn/MPU6050_tockn

サンプルコード

以下のコードは、MPU-6050から角度データを読み取り、シリアルモニタに出力し続けます。

#include <Arduino.h>
#include <Wire.h>
#include <MPU6050_tockn.h>
#define SDA_PIN 14
#define SCL_PIN 15
MPU6050 mpu6050(Wire1);

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing I2C...");
  pinMode(SDA_PIN, INPUT_PULLUP);
  pinMode(SCL_PIN, INPUT_PULLUP);
  Wire1.setSDA(SDA_PIN);
  Wire1.setSCL(SCL_PIN);
  Wire1.begin();
  mpu6050.begin();
  Serial.println("MPU6050 initialized");
  mpu6050.calcGyroOffsets(true); // ジャイロのオフセットをキャリブレーション
}

void loop() {
  mpu6050.update(); // センサー値を更新

  Serial.print("angleX: ");
  Serial.print(mpu6050.getAngleX());
  Serial.print("    angleY: ");
  Serial.print(mpu6050.getAngleY());
  Serial.print("    angleZ: ");
  Serial.println(mpu6050.getAngleZ());

  delay(10);
}

I2Cトラブルシューティング:動かないときは?

MPU6050 initialized」の表示の後、値が表示されない、またはbegin()で止まってしまう!これはI2C通信で誰もが通る道です。原因の多くは以下の通りです。 - 配線が間違っている(SDAとSCLが逆、VCC/GNDが抜けている等) - MPU-6050のI2Cアドレスが0x68ではない(モジュールによっては0x69の場合がある) - センサーの故障

こういう時、まず最初にやるべきことは「I2Cバススキャナ」を動かして、マイコンがI2Cデバイスを認識できているかを確認することです。 現在のmain.cppを、一時的に以下のI2Cスキャナのコードに完全に書き換えてみましょう。

// I2C Scanner
#include <Wire.h>

void setup() {
    Wire1.setSDA(14); // 使用するSDAピン
    Wire1.setSCL(15); // 使用するSCLピン
    Wire1.begin();
    Serial.begin(115200);
    Serial.println("
I2C Scanner on Wire1");
}

void loop() {
    byte error, address;
    int nDevices;
    Serial.println("Scanning...");
    nDevices = 0;
    for(address = 1; address < 127; address++ ) {
        Wire1.beginTransmission(address);
        error = Wire1.endTransmission();
        if (error == 0) {
            Serial.print("I2C device found at address 0x");
            if (address<16) {
                Serial.print("0");
            }
            Serial.println(address, HEX);
            nDevices++;
        }
        else if (error==4) {
            Serial.print("Unknown error at address 0x");
            if (address<16) {
                Serial.print("0");
            }
            Serial.println(address, HEX);
        }
    }
    if (nDevices == 0) {
        Serial.println("No I2C devices found
");
    }
    else {
        Serial.println("done
");
    }
    delay(5000);
}

このコードを実行すると、0x01から0x7Fまでの全てのアドレスに応答があるかを確認し、見つかったデバイスのアドレスをシリアルモニタに表示します。 - I2C device found at address 0x68 と表示されれば、配線は正しく、センサーも生きています。ライブラリ側の問題の可能性があります。 - No I2C devices found と表示されたら、配線を徹底的に見直しましょう。それでもダメなら、センサーの故障を疑います。

横道:データシートを読んでみよう ライブラリは便利ですが、ブラックボックスです。中身を理解するために、[MPU-6050のデータシート](https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf)や[レジスタマップ](https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf)を見てみましょう。 - **I2Cアドレス**: データシートのセクション9.2を見ると、AD0ピンがLOWならアドレスは`0x68`、HIGHなら`0x69`だと書かれています。私達のモジュールはAD0がGNDに接続されているので`0x68`です。 - **WHO_AM_I レジスタ**: レジスタマップの最後(p.45)にある`WHO_AM_I`レジスタ(アドレス`0x75`)は、デバイスIDを返す特別なレジスタです。この値を読むと、MPU-6050の場合は`0x68`が返ってきます。`mpu.begin()`の中でライブラリは、このレジスタを読んでI2C通信が正しくできているかを確認しています。

センサーフュージョン:ライブラリが裏側でやっていること

加速度センサーとジャイロセンサー、それぞれ単体では正確な傾斜角度を継続して知ることはできません。

  • 加速度センサー: 静止しているとき、重力加速度を検出することで傾きが計算できます。しかし、少しでも動き(並進加速度)が加わると、重力との区別がつかなくなり、ノイズだらけになります。
  • ジャイロセンサー: 角速度(1秒間に何度回転したか)を積分することで、角度の変化を知ることができます。しかし、わずかな誤差が時間と共に蓄積していき、だんだん角度がズレていく(ドリフト)という宿命的な欠点があります。

MPU6050_tocknライブラリは、この2つのセンサーの長所を組み合わせるセンサーフュージョン(具体的には相補フィルターに近い処理)を内部で行っています。 - mpu6050.calcGyroOffsets(true): 起動時にジャイロの静止状態の誤差(オフセット)を測定し、ドリフトを軽減します。 - mpu6050.update(): 加速度センサーとジャイロセンサーの両方の値を読み取り、それらを統合して、より正確で安定した傾斜角度を計算します。

私たちはupdate()を呼び出すだけで、この複雑な計算の恩恵を受けることができます。

究極の道:DMP (Digital Motion Processor) 実はMPU-6050の内部には、**DMP**という専用のプロセッサが内蔵されています。これを使うと、センサー側で自動的にセンサーフュージョン(しかも高度なカルマンフィルター)の計算を行い、マイコンは完成品の角度データ(**クォータニオン**という形式)を受け取るだけで済みます。マイコンの計算負荷を大幅に下げられるため、非常に強力な機能です。`MPU6050_tockn`ライブラリはDMPを直接サポートしていませんが、他のライブラリ(例えば `jrowberg/i2cdevlib`)を使えば利用可能です。

}

void loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address<16) { Serial.print("0"); } Serial.println(address, HEX); nDevices++; } else if (error==4) { Serial.print("Unknown error at address 0x"); if (address<16) { Serial.print("0"); } Serial.println(address, HEX); } } if (nDevices == 0) { Serial.println("No I2C devices found\n"); } else { Serial.println("done\n"); } delay(5000); }

このコードを実行すると、`0x01`から`0x7F`までの全てのアドレスに応答があるかを確認し、見つかったデバイスのアドレスをシリアルモニタに表示します。
- **`I2C device found at address 0x68`** と表示されれば、配線は正しく、センサーも生きています。ライブラリ側の問題の可能性があります。
- **`No I2C devices found`** と表示されたら、配線を徹底的に見直しましょう。それでもダメなら、センサーの故障を疑います。

<details>
<summary><b>横道:</b>データシートを読んでみよう</summary>
ライブラリは便利ですが、ブラックボックスです。中身を理解するために、[MPU-6050のデータシート](https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf)や[レジスタマップ](https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf)を見てみましょう。

- **I2Cアドレス**: データシートのセクション9.2を見ると、AD0ピンがLOWならアドレスは`0x68`、HIGHなら`0x69`だと書かれています。私達のモジュールはAD0がGNDに接続されているので`0x68`です。
- **WHO_AM_I レジスタ**: レジスタマップの最後(p.45)にある`WHO_AM_I`レジスタ(アドレス`0x75`)は、デバイスIDを返す特別なレジスタです。この値を読むと、MPU-6050の場合は`0x68`が返ってきます。`mpu.begin()`の中でライブラリは、このレジスタを読んでI2C通信が正しくできているかを確認しています。

</details>

## センサーフュージョン入門:加速度とジャイロから角度を求める

加速度センサーとジャイロセンサー、それぞれ単体では正確な傾斜角度を継続して知ることはできません。

-   **加速度センサー**: 静止しているとき、重力加速度を検出することで傾きが計算できます。しかし、少しでも動き(並進加速度)が加わると、重力との区別がつかなくなり、ノイズだらけになります。
-   **ジャイロセンサー**: 角速度(1秒間に何度回転したか)を積分することで、角度の変化を知ることができます。しかし、わずかな誤差が時間と共に蓄積していき、だんだん角度がズレていく(**ドリフト**)という宿命的な欠点があります。

そこで、**短期的には正確なジャイロ**を使い、**長期的には(平均すれば)正確な加速度センサー**でそのズレを補正し続ける、というアイデアが生まれます。これを**センサーフュージョン**と呼び、その最もシンプルな実装が**相補フィルター(Complementary Filter)**です。

I2Cスキャナで無事`0x68`が見つかったら、元のサンプルコードに戻し、今度は相補フィルターを実装してみましょう。

```diff
--- a/src/mpu6050/main.ino
+++ b/src/mpu6050/main.ino
@@ -6,6 +6,11 @@

 Adafruit_MPU6050 mpu;

+float angle = 0.0;
+float accel_angle = 0.0;
+float gyro_rate = 0.0;
+unsigned long last_time;
+
 void setup(void) {
     Serial.begin(115200);
     if (!mpu.begin()) {
@@ -13,20 +18,25 @@
         while (1) { delay(10); }
     }
     Serial.println("MPU6050 Found!");
+    last_time = micros();
 }

 void loop() {
     sensors_event_t a, g, temp;
     mpu.getEvent(&a, &g, &temp);

-    Serial.print("Accel X: "); Serial.print(a.acceleration.x);
-    Serial.print(" Y: "); Serial.print(a.acceleration.y);
-    Serial.print(" Z: "); Serial.println(a.acceleration.z);
+
+    // 加速度から角度を計算 (atan2を使用)
+    accel_angle = atan2(a.acceleration.y, a.acceleration.z) * 180 / PI;
+    
+    // ジャイロの角速度
+    gyro_rate = g.gyro.x;
+
+    // 経過時間を計算
+    unsigned long current_time = micros();
+    float dt = (current_time - last_time) / 1000000.0;
+    last_time = current_time;
+
+    // 相補フィルター
+    angle = 0.98 * (angle + gyro_rate * dt) + 0.02 * accel_angle;
+
+    Serial.println(angle);
+    delay(10);
 }

元のサンプルコードから、加速度とジャイロの値を個別に表示する部分を削除し、相補フィルターの計算を追加しました。atan2関数を使って加速度ベクトルから角度を計算し、ジャイロの角速度を時間で積分したものと組み合わせることで、より安定した角度データが得られます。シリアルプロッタで値を見ると、センサーを傾けたときにスムーズな角度変化が観測できるはずです。

究極の道:DMP (Digital Motion Processor) 実はMPU-6050の内部には、**DMP**という専用のプロセッサが内蔵されています。これを使うと、センサー側で自動的にセンサーフュージョン(しかも高度なカルマンフィルター)の計算を行い、マイコンは完成品の角度データ(**クォータニオン**という形式)を受け取るだけで済みます。マイコンの計算負荷を大幅に下げられるため、非常に強力な機能です。Adafruitのライブラリでは直接サポートされていませんが、他のライブラリ(例えば `jrowberg/i2cdevlib`)を使えば利用可能です。