MPU6050とArduinoの使い方:加速度センサー、ジャイロスコープ、傾斜角
MPU6050とArduino:配線、I2Cアドレス、生の値、キャリブレーション、atan2と相補フィルタによる傾斜角、Adafruitライブラリ、ESP32とPicoのコードを解説します。
MPU6050は、3軸加速度センサーと3軸ジャイロスコープを1つの小さなチップにまとめたもので、Arduinoのプロジェクトでもっとも広く使われているモーションセンサーです。ボードがどれだけ傾いているか、どの向きにどれだけの速さで回転しているか、揺すられたり落ちたりしたかを測れます。倒立振子ロボット、ドローン、カメラのスタビライザー(ジンバル)、歩数計、体の動きで操作するゲームは、このセンサーで作られています。MPU6050はI2Cを使い、たった4本の線でArduinoにつながります。
この記事では、MPU6050のしくみ、軸と生の値、I2Cの配線、ライブラリを使う読み取りと使わない読み取り、キャリブレーション、atan2による傾斜角の計算、そして2つのセンサーの長所を組み合わせる相補フィルタをステップごとに学びます。そのあと、ESP32で転倒検知アラームを作り、Raspberry Pi Pico用のMicroPythonのコードを書きます。

MPU6050とは?そのしくみ
InvenSense(現在はTDK)が製造するMPU6050は、慣性計測装置であるIMUです。内部にはシリコンで作られた微小な機械構造(MEMS)があります。加速度センサーは、ばねで支えられた小さなおもりが加速度でどれだけ動くかを測ります。静止していても重力を感じるため、机の上に平らに置いたセンサーはZ軸に約1 g(9.81 m/s²)を示し、これを使って地面に対する傾きを求められます。ジャイロスコープは角速度、つまりセンサーが1秒間に何度回転しているかを、振動する構造にはたらくコリオリの力から測ります。測定値はチップ内部の16ビットADCで数値に変換され、I2Cで読み出されます。チップには温度センサーと、モーションの計算を肩代わりできるデジタルモーションプロセッサ(DMP)も入っています。
| 項目 | 値 |
|---|---|
| センサー | 3軸加速度センサー+3軸ジャイロスコープ+温度 |
| 加速度センサーの測定範囲 | ±2、±4、±8、±16 g |
| ジャイロスコープの測定範囲 | ±250、±500、±1000、±2000 °/s |
| 分解能 | 16ビット(各軸-32768〜32767) |
| インターフェース | I2C、最大400 kHz |
| I2Cアドレス | 0x68(AD0をGNDに)または0x69(AD0を3.3 Vに) |
| チップの電圧 | 2.375〜3.46 V(GY-521基板は3.3〜5 Vを受け付ける) |
| 電流 | 約3.9 mA(スリープ時5 µA) |
| その他の機能 | DMP、1024バイトのFIFO、モーション割り込み、外部センサー用の補助I2C |
軸、生の値、感度
基板の矢印はX軸とY軸を示し、Z軸は基板に垂直で上向きです。センサーは各軸について符号付き16ビットの数値を返します。この生の数値を物理単位に直すには、選んだ測定範囲の感度で割ります。たとえば初期設定の±2 gの範囲では、生の値16384が1 gを意味します。机に置いたセンサーからはaz ≈ 16384、ax、ay ≈ 0が読めます。ジャイロスコープの±250 °/sの範囲では、生の値131が毎秒1度の回転に相当します。範囲を広げると激しい動きも測れますが、小さな変化は粗くしか表せないため、プロジェクトに足りるいちばん狭い範囲を選びましょう。
| 設定値 | 加速度センサーの範囲 | LSB/g | ジャイロスコープの範囲 | LSB/(°/s) |
|---|---|---|---|---|
| 0 | ±2 g | 16384 | ±250 °/s | 131 |
| 1 | ±4 g | 8192 | ±500 °/s | 65.5 |
| 2 | ±8 g | 4096 | ±1000 °/s | 32.8 |
| 3 | ±16 g | 2048 | ±2000 °/s | 16.4 |
チップは温度も測ります:°C = 生の値 / 340 + 36.53。これは室温ではなくチップ自体の温度ですが、ジャイロスコープのゼロ点は温度で変化するため、高度な補正に役立ちます。
MPU6050の配線:Arduino Uno、ESP32、Pico
| MPU6050のピン | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC | 5V | 3V3 | 3V3(OUT) |
| GND | GND | GND | GND |
| SCL | A5 | GPIO 22 | GP5 |
| SDA | A4 | GPIO 21 | GP4 |
| AD0 | 未接続(アドレス0x68) | 未接続(アドレス0x68) | 未接続(アドレス0x68) |
| INT | D2(任意) | 任意のGPIO(任意) | 任意のGPピン(任意) |
| XDA、XCL | 接続しない | 接続しない | 接続しない |
GY-521基板には3.3 Vレギュレーターがあるため、VCCをUnoの5Vピンにつなげます。AD0ピンは基板上で4.7 kΩの抵抗を通してGNDに引き下げられているので、未接続のときアドレスは0x68です。同じバスに2つのMPU6050をつなぎたい場合は、片方のAD0ピンを3.3 Vにつないでアドレスを0x69に変えます。INTピンは新しいデータの準備ができたときや動きを検出したときに知らせるもので、簡単なプロジェクトではつながなくてかまいません。配線したら、I2Cスキャナーのスケッチでセンサーが見えるか確認するのがよい習慣です。方法はI2C LCDディスプレイで紹介しています。
ライブラリですぐに始める:Adafruit MPU6050
- Arduino IDEでライブラリマネージャを開き、Adafruit MPU6050を検索します。
- インストール中に依存ライブラリについて聞かれたら全てをインストールを選び、Adafruit Unified SensorとAdafruit BusIOも一緒にインストールします。
- 表のとおりにセンサーを配線し、次のスケッチを書き込んで、シリアルモニタを115200 baudで開きます。
- 「ツール」メニューからシリアルプロッタを開くと、6軸すべてをリアルタイムのグラフで見られます。ボードを傾けると加速度の曲線が、回すとジャイロスコープの曲線が変わります。
1#include <Wire.h>
2#include <Adafruit_MPU6050.h>
3#include <Adafruit_Sensor.h>
4
5Adafruit_MPU6050 mpu;
6
7void setup() {
8 Serial.begin(115200);
9 if (!mpu.begin()) { // varsayılan adres 0x68 / default address 0x68
10 Serial.println("MPU6050 bulunamadi / not found");
11 while (true) delay(10);
12 }
13 mpu.setAccelerometerRange(MPU6050_RANGE_4_G); // ±4 g
14 mpu.setGyroRange(MPU6050_RANGE_500_DEG); // ±500 °/s
15 mpu.setFilterBandwidth(MPU6050_BAND_21_HZ); // titreşimi süz / filter out vibration
16}
17
18void loop() {
19 sensors_event_t a, g, t;
20 mpu.getEvent(&a, &g, &t);
21 Serial.print("ax:"); Serial.print(a.acceleration.x); // m/s²
22 Serial.print(" ay:"); Serial.print(a.acceleration.y);
23 Serial.print(" az:"); Serial.print(a.acceleration.z);
24 Serial.print(" gx:"); Serial.print(g.gyro.x); // rad/s
25 Serial.print(" gy:"); Serial.print(g.gyro.y);
26 Serial.print(" gz:"); Serial.print(g.gyro.z);
27 Serial.print(" T:"); Serial.println(t.temperature); // °C
28 delay(100);
29}Adafruitライブラリは結果を物理単位で直接返します。加速度はm/s²、角速度はrad/s、温度は°Cです。ジャイロスコープの値を毎秒の度数に直すには、57.3を掛けるだけです。setFilterBandwidthで選ぶ内部のローパスフィルタは振動ノイズを減らします。手の動きなら21 Hzから始めるのがよく、短い衝撃をとらえたいなら94 Hzか260 Hzを選びます。起動時にライブラリはチップの識別レジスタ(WHO_AM_I)を読み、0x68でなければbegin()はfalseを返します。
ライブラリなしの読み取り、キャリブレーション、傾斜角
ライブラリは便利ですが、レジスタを直接読むと、センサーの中で何が起きているかがよくわかります。MPU6050はスリープモードで起動するため、まずレジスタ0x6B(PWR_MGMT_1)に0を書き込んで起こす必要があります。次にアドレス0x3Bから14バイトを読みます。加速度が6バイト、温度が2バイト、ジャイロスコープが6バイトです。各値は2バイトで、上位バイトが先に来ます。コードで使う、知っておきたい主なレジスタは次のとおりです。
| レジスタ | アドレス | 用途 |
|---|---|---|
| SMPLRT_DIV | 0x19 | サンプリングレートの分周 |
| CONFIG | 0x1A | 内部ローパスフィルタ(DLPF) |
| GYRO_CONFIG | 0x1B | ジャイロスコープの範囲(±250〜±2000 °/s) |
| ACCEL_CONFIG | 0x1C | 加速度センサーの範囲(±2〜±16 g) |
| ACCEL_XOUT_H | 0x3B | 測定値が始まるアドレス(14バイト) |
| PWR_MGMT_1 | 0x6B | スリープとクロック源。0を書き込むと起きる |
| WHO_AM_I | 0x75 | 識別子。純正のMPU6050では0x68を返す |
静止したセンサーでは、加速度センサーが感じる力は重力だけです。重力が各軸にどう分かれているかを見ることで、2つの角度がわかります。X軸まわりのロール角はroll = atan2(ay, az)、Y軸まわりのピッチ角はpitch = atan2(−ax, √(ay² + az²))です。通常のアークタンジェントと違い、atan2は4つの象限すべてで正しい符号を返し、azが0に近づいても0で割ることがありません。たとえばay = 8192、az = 14189と読めたら、roll = atan2(8192, 14189) ≈ 30°です。一方、Z軸まわりの回転(ヨー)は加速度センサーには見えません。重力の各軸への分かれ方が変わらないからです。
どちらのセンサーにも弱点があります。加速度センサーは長い目で見れば正確ですが、重力に加えて動きそのものの加速度も測るため、動きや振動があるときはとてもノイズが多くなります。ジャイロスコープは短時間ではなめらかですが、角度を求めるには角速度を時間で足し合わせる(angle += rate × dt)ため、わずかなオフセットでも積み重なり、角度が少しずつずれていきます。相補フィルタは2つを組み合わせます:角度 = 0.98 ×(角度 + ジャイロ × dt)+ 0.02 × 加速度センサーの角度。短期的にはジャイロスコープを、長期的には加速度センサーを信頼するしくみです。100 Hz(dt = 0.01 s)では、この係数で時定数は約0.5秒になります:τ = 0.98 × 0.01 / 0.02 = 0.49 s。
キャリブレーションはフィルタと同じくらい重要です。まったく動いていないジャイロスコープでも、数°/sの一定のオフセットを示すことがあります。補正しないと、計算した角度は数秒で数十度もずれます。次のスケッチは、起動時にセンサーが静止している間に500回の読み取りを平均し、以後のすべての読み取りからその値を引きます。キャリブレーション中はボードを平らな場所に置いて触らないでください。そのあと、シリアルプロッタでroll、pitch、yawの曲線を見てみましょう。
1#include <Wire.h>
2
3const int MPU_ADDR = 0x68; // AD0 GND'de ya da boşta / AD0 to GND or floating
4const float GYRO_LSB = 131.0; // ±250 °/s aralığında 1 °/s / 1 °/s at the ±250 °/s range
5const float ALPHA = 0.98; // tamamlayıcı filtre katsayısı / complementary filter weight
6
7int16_t ax, ay, az, tempRaw, gx, gy, gz;
8float gxOff = 0, gyOff = 0, gzOff = 0; // jiroskop sapmaları / gyro offsets
9float roll = 0, pitch = 0, yaw = 0;
10unsigned long lastMicros;
11
12void writeReg(uint8_t reg, uint8_t val) {
13 Wire.beginTransmission(MPU_ADDR);
14 Wire.write(reg);
15 Wire.write(val);
16 Wire.endTransmission();
17}
18
19int16_t read16() {
20 uint8_t hi = Wire.read(); // önce yüksek bayt / high byte first
21 uint8_t lo = Wire.read();
22 return (int16_t)(((uint16_t)hi << 8) | lo);
23}
24
25void readMPU() {
26 Wire.beginTransmission(MPU_ADDR);
27 Wire.write(0x3B); // ACCEL_XOUT_H
28 Wire.endTransmission(false); // hattı bırakma, tekrar başlat / repeated start
29 Wire.requestFrom(MPU_ADDR, 14); // ivme 6 + sıcaklık 2 + jiroskop 6 bayt / accel 6 + temp 2 + gyro 6 bytes
30 ax = read16(); ay = read16(); az = read16();
31 tempRaw = read16();
32 gx = read16(); gy = read16(); gz = read16();
33}
34
35void calibrateGyro() {
36 const int N = 500;
37 long sx = 0, sy = 0, sz = 0;
38 for (int i = 0; i < N; i++) {
39 readMPU();
40 sx += gx; sy += gy; sz += gz;
41 delay(3);
42 }
43 gxOff = sx / (float)N;
44 gyOff = sy / (float)N;
45 gzOff = sz / (float)N;
46}
47
48void setup() {
49 Serial.begin(115200);
50 Wire.begin();
51 Wire.setClock(400000); // 400 kHz hızlı mod / 400 kHz fast mode
52 writeReg(0x6B, 0x00); // PWR_MGMT_1: uykudan uyandır / wake up
53 writeReg(0x1A, 0x03); // CONFIG: alçak geçiren filtre ~44 Hz / low-pass ~44 Hz
54 writeReg(0x1B, 0x00); // GYRO_CONFIG: ±250 °/s
55 writeReg(0x1C, 0x00); // ACCEL_CONFIG: ±2 g
56 delay(100);
57 Serial.println("Kalibrasyon: sensoru oynatma / calibrating: keep still");
58 calibrateGyro();
59 readMPU();
60 roll = atan2(ay, az) * RAD_TO_DEG; // başlangıç açıları / initial angles
61 pitch = atan2(-ax, sqrt((float)ay * ay + (float)az * az)) * RAD_TO_DEG;
62 lastMicros = micros();
63}
64
65void loop() {
66 readMPU();
67 unsigned long now = micros();
68 float dt = (now - lastMicros) / 1000000.0; // saniye / seconds
69 lastMicros = now;
70
71 float accRoll = atan2(ay, az) * RAD_TO_DEG; // ivmeölçerden açı / angle from accelerometer
72 float accPitch = atan2(-ax, sqrt((float)ay * ay + (float)az * az)) * RAD_TO_DEG;
73 float gxRate = (gx - gxOff) / GYRO_LSB; // °/s
74 float gyRate = (gy - gyOff) / GYRO_LSB;
75 float gzRate = (gz - gzOff) / GYRO_LSB;
76
77 roll = ALPHA * (roll + gxRate * dt) + (1 - ALPHA) * accRoll; // tamamlayıcı filtre / complementary filter
78 pitch = ALPHA * (pitch + gyRate * dt) + (1 - ALPHA) * accPitch;
79 yaw += gzRate * dt; // yalnız jiroskop: zamanla kayar / gyro only: drifts over time
80
81 Serial.print("roll:"); Serial.print(roll, 1);
82 Serial.print(" pitch:"); Serial.print(pitch, 1);
83 Serial.print(" yaw:"); Serial.println(yaw, 1);
84 delay(10); // yaklaşık 100 Hz / about 100 Hz
85}| コードの行 | 働き |
|---|---|
| writeReg(0x6B, 0x00) | センサーをスリープモードから起こします |
| writeReg(0x1A, 0x03) | 内部フィルタを約44 Hzで有効にします |
| Wire.requestFrom(MPU_ADDR, 14) | 加速度、温度、ジャイロスコープのデータを一度に読みます |
| calibrateGyro() | 500回の読み取りを平均してジャイロスコープのオフセットを求めます |
| atan2(ay, az) * RAD_TO_DEG | 加速度センサーからロール角を度単位で計算します |
| ALPHA * (...) + (1 - ALPHA) * ... | 相補フィルタ:ジャイロスコープと加速度センサーを組み合わせます |
| yaw += gzRate * dt | ジャイロスコープだけでヨーを計算し、時間とともにずれていく様子を観察できます |
ESP32でMPU6050を使う:転倒検知アラーム
ESP32の配線はUnoと同じですが、I2CのピンはGPIO 21(SDA)とGPIO 22(SCL)で、モジュールは3V3から給電します。ここまでのスケッチはすべてESP32でそのまま動きます。必要ならWire.begin(21, 22)でピンを明示的に指定できます。次の例は転倒検知器です。自由落下している物体では、センサーの本体と内部のおもりが一緒に落ちるため、加速度センサーはほぼ0 gを示し、そのあとの衝撃で数gのピークが生じます。スケッチは、100 ms以上の自由落下のあと0.5秒以内に2.5 gを超える衝撃を検出すると、GPIO 25のアクティブブザーを1秒間鳴らします。100 msの自由落下は、約5 cmの落下に相当します(h = ½ × g × t²)。
1#include <Wire.h>
2#include <Adafruit_MPU6050.h>
3#include <Adafruit_Sensor.h>
4
5Adafruit_MPU6050 mpu;
6const int BUZZER_PIN = 25; // aktif buzzer / active buzzer
7const float FREE_FALL_G = 0.4; // serbest düşüş eşiği / free-fall threshold
8const float IMPACT_G = 2.5; // darbe eşiği / impact threshold
9unsigned long fallStart = 0, fallEnd = 0;
10
11void setup() {
12 Serial.begin(115200);
13 pinMode(BUZZER_PIN, OUTPUT);
14 Wire.begin(21, 22); // SDA, SCL
15 if (!mpu.begin(0x68, &Wire)) {
16 Serial.println("MPU6050 bulunamadi / not found");
17 while (true) delay(10);
18 }
19 mpu.setAccelerometerRange(MPU6050_RANGE_8_G); // darbeler için geniş aralık / wide range for impacts
20 mpu.setFilterBandwidth(MPU6050_BAND_94_HZ); // kısa darbeleri kaçırma / keep short peaks
21}
22
23void loop() {
24 sensors_event_t a, g, t;
25 mpu.getEvent(&a, &g, &t);
26 float x = a.acceleration.x, y = a.acceleration.y, z = a.acceleration.z;
27 float total = sqrt(x * x + y * y + z * z) / SENSORS_GRAVITY_STANDARD; // toplam ivme, g / total acceleration in g
28 unsigned long now = millis();
29
30 if (total < FREE_FALL_G) {
31 if (fallStart == 0) fallStart = now; // düşüş başladı / fall started
32 } else if (fallStart != 0) {
33 if (now - fallStart > 100) fallEnd = now; // en az 100 ms süren düşüş / fall of at least 100 ms
34 fallStart = 0;
35 }
36
37 if (fallEnd != 0 && now - fallEnd < 500 && total > IMPACT_G) { // düşüşten sonra 0,5 s içinde darbe / impact within 0.5 s
38 Serial.println("DUSME ALGILANDI / FALL DETECTED");
39 digitalWrite(BUZZER_PIN, HIGH);
40 delay(1000);
41 digitalWrite(BUZZER_PIN, LOW);
42 fallEnd = 0;
43 }
44 if (fallEnd != 0 && now - fallEnd >= 500) fallEnd = 0; // darbe gelmedi, sıfırla / no impact, reset
45 delay(5);
46}これをもとに、ESP32のWi-Fiを加えれば、たとえば高齢の家族が転倒したときにスマートフォンへTelegramで通知したり、角度をWebページでリアルタイムに表示したりできます。しきい値は自分でテストして調整してください。机から落ちる箱と手首に着けたセンサーでは、ピークの出方が違います。ブザーの選び方と配線はブザーとは?を参照してください。
Raspberry Pi PicoでMPU6050を使う(MicroPython)
Picoでは追加のライブラリは不要で、MicroPythonに組み込まれたI2Cクラスでレジスタを直接読めます。センサーはI2C0バスにつなぎ、SDAをGP4に、SCLをGP5にします。14バイトを7つの符号付き16ビットの数値に変換するにはstruct.unpackを使います。「>」はビッグエンディアン(上位バイトが先)、「h」は符号付き16ビットを意味します。コードはまず見つかったI2Cアドレスを表示します。リストに0x68がなければ配線を確認してください。簡単にするためキャリブレーションは入れていませんが、必要ならArduinoのスケッチの平均化の方法をそのまま使えます。
1from machine import Pin, I2C
2import math, struct, time
3
4MPU = 0x68
5i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
6print("Bulunan adresler / found addresses:", [hex(a) for a in i2c.scan()])
7
8i2c.writeto_mem(MPU, 0x6B, b"\x00") # uykudan uyandır / wake up
9i2c.writeto_mem(MPU, 0x1A, b"\x03") # alçak geçiren filtre ~44 Hz / low-pass ~44 Hz
10i2c.writeto_mem(MPU, 0x1B, b"\x00") # jiroskop ±250 °/s / gyro ±250 °/s
11i2c.writeto_mem(MPU, 0x1C, b"\x00") # ivmeölçer ±2 g / accelerometer ±2 g
12roll = pitch = 0.0
13last = time.ticks_us()
14
15while True:
16 data = i2c.readfrom_mem(MPU, 0x3B, 14)
17 ax, ay, az, t, gx, gy, gz = struct.unpack(">hhhhhhh", data) # büyük endian, işaretli / big-endian, signed
18 now = time.ticks_us()
19 dt = time.ticks_diff(now, last) / 1000000
20 last = now
21 acc_roll = math.degrees(math.atan2(ay, az))
22 acc_pitch = math.degrees(math.atan2(-ax, math.sqrt(ay * ay + az * az)))
23 roll = 0.98 * (roll + gx / 131 * dt) + 0.02 * acc_roll # tamamlayıcı filtre / complementary filter
24 pitch = 0.98 * (pitch + gy / 131 * dt) + 0.02 * acc_pitch
25 temp = t / 340 + 36.53 # °C
26 print("roll {:.1f} pitch {:.1f} T {:.1f}".format(roll, pitch, temp))
27 time.sleep_ms(10)MPU6050のプロジェクトアイデア
- デジタル水準器:ロール角とピッチ角を、動く気泡と一緒にOLEDディスプレイに表示します。
- 動きで操作するサーボ:手に持ったセンサーを傾けると、サーボモーターが同じ角度に回ります。ロボットアームやカメラヘッドに最適です。
- 傾きで遊ぶゲーム:ジョイスティックの代わりにボードを傾けて、ポンや迷路ゲームを遊びます。
- 倒立振子ロボット:ピッチ角をPID制御で処理し、L298Nで駆動するDCモーターでロボットを立たせたままにします。
- 歩数計:合成加速度の規則的なピークを数えて歩数を測ります。
- 振動ロガー:機械や洗濯機の揺れを測り、しきい値を超えたら警告を出します。
よくある間違いと対処法
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| begin()がfalseを返す(「MPU6050 not found」) | 配線ミス、アドレスの間違い、互換チップ | I2Cスキャナーでアドレスを調べる。WHO_AM_Iが0x68でなければ(たとえば0x70)、ライブラリなしのコードを使う |
| すべての値が0 | センサーがスリープモードのまま | レジスタ0x6Bに0を書き込む |
| 値が-1になる、または止まる | 接触不良、長いI2Cバス | 配線を短くし、ピンをはんだ付けする |
| 静止しているのに角度がずれていく | ジャイロスコープのオフセットを補正していない | 起動時に静止した状態でキャリブレーションする |
| 動かすと角度が跳ねる | 加速度センサーだけを使っている | 相補フィルタを加える |
| ヨーがずれ続ける | MPU6050にはコンパスがない | QMC5883Lなどの地磁気センサーを加えるか、9軸センサーを使う |
| ピッチが±90°に近づくとロールがおかしくなる | オイラー角の特異点 | その領域を避けるか、DMPのクォータニオン出力を使う |
安価ながら、MPU6050は動きを測る世界への入り口になります。まず生の値に慣れ、次に角度を計算し、最後にフィルタでなめらかにしましょう。ほかのI2Cセンサーとその用途はArduinoセンサーガイドで確認できます。BlueGraysアプリには、MPU6050と組み合わせられるサーボ制御、I2Cディスプレイ、ジョイスティック、ロボットのプロジェクトが、ステップごとの回路図とコード付きでそろっています。
よくある質問
MPU6050は何に使いますか?
MPU6050は、物体の傾き、回転の速さ、加速度を測ります。倒立振子ロボット、ドローンやジンバル、歩数計、転倒や衝撃の検知、体の動きで操作するゲームに使われています。
MPU6050のアドレスは0x68ですか、0x69ですか?
AD0ピンが未接続またはGNDにつながっているときは0x68、3.3 Vにつながっているときは0x69です。GY-521基板ではAD0が抵抗を通してGNDに引き下げられているため、初期アドレスは0x68です。確実を期すなら、I2Cスキャナーのスケッチを実行してください。
MPU6050は5 Vで動きますか?
MPU6050のチップ自体は最大3.46 Vで動作しますが、GY-521基板のレギュレーターのおかげで、VCCピンには5 Vを供給できます。I2Cの信号線は3.3 Vレベルのままで、Arduino Unoは問題なく読み取れます。レギュレーターのないむき出しの基板には5 Vをかけないでください。
MPU6050で角度はどう計算しますか?
センサーが静止しているときは、加速度センサーからroll = atan2(ay, az)とpitch = atan2(−ax, √(ay² + az²))で角度を求めます。動いている間も正確な結果を得るには、ジャイロスコープの角速度を時間で積分し、相補フィルタやカルマンフィルタで加速度センサーの角度と組み合わせます。
MPU6050でヨーは測れますか?
短時間なら測れます。Z軸の角速度を積分すればヨーを計算できます。ただし、MPU6050には地磁気センサーがないため、ヨーは時間とともにずれ、絶対的な方位(北)はわかりません。安定した方位にはQMC5883Lのようなコンパスセンサーか9軸IMUが必要です。
MPU6050とMPU9250の違いは?
MPU6050は、加速度センサーとジャイロスコープを備えた6軸センサーです。MPU9250は3軸地磁気センサーを加えた9軸センサーで、ずれのないヨーを測れ、SPIにも対応しています。MPU9250は生産終了しているため、新しいプロジェクトではICM-20948などの後継品が選ばれています。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
サーボモーターの使い方
小型サーボモーターのアームが、一方の端、中央、もう一方の端の3つの位置へ順番に回り、それぞれの位置で2秒間止まります。Arduinoが保持する角度をモーターに指示すると、モーターはその位置まで動いて止まります。ロボットアームや自動開閉するふたが動く仕組みを学べます。

ジョイスティックでサーボモーターを制御
防犯カメラの向きを変えるように、ジョイスティックで2つのモーターを操作します。スティックを左右に倒すと下側のサーボモーターがゆっくり回り、上下に倒すと上側のサーボモーターが上下に傾きます。手を離すと、どちらもその位置で止まります。カメラやレーザーの台座など、2方向に動く装置の基礎を作ります。

ESP32サーボモーター(ポテンショメーター制御)
このプロジェクトでは、ポテンショメーターでSG90サーボモーターを制御します。ポテンショメーターから読み取ったアナログ値を0~180度の範囲に変換し、サーボの角度を調整します。ESP32Servoライブラリは、ESP32の出力に対応した適切なGPIOでサーボ信号を生成します。このプロジェクトではGPIO 18を使います。ロボットアームやカメラ用ジンバル、自動ドアの機構に使われる基本的な制御方法です。

パドルボールゲーム(SSD1306)
ボール1個とパドル2本で遊ぶ、ビデオゲームの原点の1つである卓球ゲームを、小さなOLEDディスプレイで楽しめます。2つのボタンで右のパドルを上下に動かします。左のパドルはコンピューターがボールを追いながら操作し、ボールを打つたびにブザーが短く鳴ります。先に9点を取ると勝ちです。その後、新しいゲームが自動で始まります。

I2CでLCDディスプレイを使う
Arduinoにディスプレイを接続し、最初のメッセージを表示します。2行のLCDディスプレイは数本の配線だけで接続できます。まず上の行にあいさつを表示し、次に下の行にもう1つのメッセージを表示します。その後、表示を消去して最初から繰り返します。温度や時刻、スコアを表示するプロジェクトは、すべてこのステップから始まります。

ESP32 I2C LCDディスプレイ(16x2)
このプロジェクトでは、16x2文字のLCDをI2CでESP32に接続して文字を表示します。I2Cならデータ用の2本のピン(SDAとSCL)だけで制御でき、従来のパラレル接続のように6本以上のピンを使わずに済みます。コードでは1行目に固定メッセージ、2行目に毎秒増えるカウンターを表示します。また、起動時にI2Cスキャナーを実行し、ディスプレイのアドレス(通常は0x27または0x3F)をシリアルモニタに表示します。

障害物回避ロボット
自分で進む道を見つける、賢い車を作ってみませんか?障害物回避ロボットは、前面の超音波センサーで周囲を常に調べます。障害物を見つけると停止し、サーボモーターで左右を確認して、最も障害物の少ない方向を選んで進みます。自動運転の基礎を学ぶのにぴったりの入門プロジェクトです。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

