2025年8月12日火曜日

MPU‑6050で6軸センサ値を読み取り、OLED SSD1306にリアルタイム表示!

 今回は、MPU‑6050(加速度センサ+ジャイロセンサを内蔵した6軸モーションセンサ)を使い、デバイスの動きや傾きを計測して、そのデータをOLEDディスプレイ(SSD1306)にリアルタイム表示します。

MPU‑6050とは?

MPU‑6050は、3軸方向の加速度を測定する加速度センサと、3軸の角速度を測定するジャイロセンサを1つの小型チップに集約した、6軸モーションセンサです。

  • 加速度センサは、デバイスの直線的な動きや傾きを検出します。例えば、スマートフォンの画面が回転する仕組みや、歩数計の動きを感知する役割を担います。

  • ジャイロセンサは、回転の速さや角度の変化を測定します。これにより、デバイスがどの方向にどれだけ回転したかを正確に把握できます。

この2つのセンサを組み合わせることで、デバイスの姿勢や動きを多角的に把握することが可能です。例えば、ロボットやドローンの制御、自作ゲームコントローラの動き検知、モーションキャプチャーなど、幅広い応用が期待できます。

また、MPU‑6050はI²C通信を用いて簡単にマイコンと接続でき、小型で省電力、比較的安価なため、多くの電子工作や組み込みシステムで人気のあるセンサです。

ライブラリのインストール(Thonny)

  1. Pico W を PC に接続し、Thonny を開きます。

  2. メニュー [ツール] → [パッケージを管理..] をクリック。

OLED 用ライブラリ

  • 検索窓に ssd1306 と入力 → micropython-ssd1306 を選び Install
    (詳しい手順や日本語解説は、前回の記事を参考にしてください)

配線図


3.ソースコード

# main.py ── Raspberry Pi Pico W + MPU‑6050  (OLED 表示 + キャリブレーション & フィルタ版)
#   ・起動時に静置キャリブレーション(オフセット補正)
#   ・IIR ローパスフィルタで揺れを低減
#   ・DLPF 10 Hz・±250 °/s に設定
#   ・ジャイロ角速度を OLED にリアルタイム表示
#
# 20250703  sakura lain 用

from machine import Pin, I2C
import time, struct, ssd1306

# ───────────────────────────────
# 定数・設定
# ───────────────────────────────
I2C_ID   = 0                 # I2C0 (SDA=GP16, SCL=GP17)
I2C_FREQ = 400_000           # 400 kHz
MPU_ADDR = 0x68              # AD0=LOW → 0x68

GYRO_SENS     = 131.0        # ±250°/s → 131 LSB/°/s
ALPHA         = 0.2          # IIR フィルタ係数 (0<α≤1)
CAL_SAMPLES   = 200          # キャリブレーション試行回数
CAL_DELAY_MS  = 5            # キャリブレーション間隔

# ───────────────────────────────
# インスタンス生成
# ───────────────────────────────
i2c  = I2C(I2C_ID, sda=Pin(16), scl=Pin(17), freq=I2C_FREQ)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# ───────────────────────────────
# MPU‑6050 操作関数
# ───────────────────────────────
def mpu_write(reg: int, data: int) -> None:
    i2c.writeto_mem(MPU_ADDR, reg, bytes([data]))

def mpu_read(reg: int, length: int) -> bytes:
    return i2c.readfrom_mem(MPU_ADDR, reg, length)

def mpu_init() -> None:
    """
    DLPF: 10 Hz / ±250 °/s / ±8 g で初期化
    """
    mpu_write(0x6B, 0x00)   # PWR_MGMT_1: スリープ解除
    mpu_write(0x1B, 0x00)   # GYRO_CONFIG: ±250 °/s
    mpu_write(0x1C, 0x10)   # ACCEL_CONFIG: ±8 g
    mpu_write(0x1A, 0x05)   # CONFIG: DLPF 10 Hz
    time.sleep_ms(100)

def read_raw():
    """0x3B から 14 byte 読み出し → tuple(raw_ax, raw_ay, raw_az, raw_tmp, raw_gx, raw_gy, raw_gz)"""
    return struct.unpack(">hhhhhhh", mpu_read(0x3B, 14))

# ───────────────────────────────
# キャリブレーション(静置でオフセット取得)
# ───────────────────────────────
def calibrate_gyro():
    sum_gx = sum_gy = sum_gz = 0
    for _ in range(CAL_SAMPLES):
        _, _, _, _, gx, gy, gz = read_raw()
        sum_gx += gx
        sum_gy += gy
        sum_gz += gz
        time.sleep_ms(CAL_DELAY_MS)
    return (sum_gx // CAL_SAMPLES,
            sum_gy // CAL_SAMPLES,
            sum_gz // CAL_SAMPLES)

# ───────────────────────────────
# OLED 画面レイアウト
# ───────────────────────────────
def oled_header():
    oled.fill(0)
    oled.text("MPU-6050  Gyro", 0, 0)
    oled.text("X:        dps",  0, 16)
    oled.text("Y:        dps",  0, 32)
    oled.text("Z:        dps",  0, 48)
    oled.show()

def oled_update(x, y, z, old):
    """
    値が変わった軸のみ部分書き換え (x,y,z: float, old: dict)
    """
    def _draw(val, ypos, key):
        if val != old[key]:
            oled.fill_rect(24, ypos, 48, 8, 0)
            oled.text("{:+6.2f}".format(val), 24, ypos)
            old[key] = val
    _draw(x, 16, 'x')
    _draw(y, 32, 'y')
    _draw(z, 48, 'z')
    oled.show()

# ───────────────────────────────
# メイン
# ───────────────────────────────
def main():
    print("Initializing MPU‑6050 …")
    mpu_init()
    print("Calibrating gyro … (keep sensor still)")
    off_gx, off_gy, off_gz = calibrate_gyro()
    print("Offset =", off_gx, off_gy, off_gz)

    oled_header()

    # IIR フィルタ初期値
    filt_x = filt_y = filt_z = 0.0
    prev = {'x': None, 'y': None, 'z': None}

    while True:
        # ── 生データ取得 & オフセット補正 ───────────────────────
        _, _, _, _, raw_gx, raw_gy, raw_gz = read_raw()
        gx = (raw_gx - off_gx) / GYRO_SENS   # °/s
        gy = (raw_gy - off_gy) / GYRO_SENS
        gz = (raw_gz - off_gz) / GYRO_SENS

        # ── IIR ローパスフィルタ ───────────────────────────
        filt_x += ALPHA * (gx - filt_x)
        filt_y += ALPHA * (gy - filt_y)
        filt_z += ALPHA * (gz - filt_z)

        # ── OLED 更新 ────────────────────────────────────
        oled_update(filt_x, filt_y, filt_z, prev)

        # ── デバッグ出力 (USB‑Serial) ─────────────────────
        print("gyr_fx:{:+.2f},gyr_fy:{:+.2f},gyr_fz:{:+.2f}"
              .format(filt_x, filt_y, filt_z))

        time.sleep_ms(50)  # 20 Hz 表示

# スクリプト直実行時のみ
if __name__ == "__main__":
    main()


処理の流れ

  1. ⚙️ I2CとOLEDの初期化

    • I2Cバスを設定(SDA=GP16、SCL=GP17)

    • SSD1306 OLEDディスプレイの初期化

  2. 🔧 MPU-6050の初期設定

    • スリープ解除

    • ジャイロの感度設定(±250°/秒)

    • 加速度の感度設定(±8g)

    • DLPF(デジタルローパスフィルタ)を10Hzに設定

  3. 🧮 静置状態でジャイロのキャリブレーション(オフセット補正)

    • 一定回数(200回)ジャイロ値を読み取り、平均値をオフセットとして保存

    • これにより動作開始時の誤差を低減

  4. 🖥️ OLED画面のレイアウト表示

    • 画面にタイトルと軸ラベル(X, Y, Z)を表示

  5. 🔄 メインループ(約20Hzで繰り返し)

    • MPU-6050から生データ(加速度・ジャイロ)を取得

    • ジャイロデータからキャリブレーションオフセットを引いて補正

    • IIR(単純指数移動平均)ローパスフィルタで揺れを低減

    • フィルタ後の角速度データをOLEDにリアルタイム表示(変化のあった部分のみ更新)

    • USBシリアルに値を出力(デバッグ用)

  6. ⏳ 一定時間(50ms)スリープしてループ継続

    • 約20Hzの更新速度を実現


0 件のコメント:

コメントを投稿

Google Spread Sheetの利用

  以前に ESP32 で作っていたものを、Raspberry Pi Pico W + MicroPythonで再現してみました。 1.Googleスプレッドシートの設定 1.Google Drive → 右クリック → Google スプレッドシート 2.作成して共有をクリック