🪐

【連載⑥】宇宙を目指す小さなIMU試作の記録:温度ドリフト補正の実践 - IMU出力を正確にするためにできること

に公開

1. はじめに

これまでの連載では、ArduinoとMEMSセンサーを使って加速度・ジャイロ・温度のデータを取得し、Pythonで可視化するところまでを実践してきました。


温度ドリフトは、MEMSセンサーにとって避けがたい物理的な特性です。
シリコン構造そのものが温度によって微妙に変化するため、内部のキャリブレーションだけでは完全に補正しきれません

そこで本記事では、取得した温度とセンサー出力の関係をもとに、ドリフトを補正するための方法論を実践的に解説します。


今回の目的

  • センサ出力(例:Accel_Z)と温度の関係を 数学モデル(近似式)として表現
  • 得られた式をもとに、Arduino側でリアルタイム補正
  • 補正後と補正前を比較して、安定性の向上を視覚的に評価

温度ドリフト補正は、**センサーの信頼性を高めるための“本質的な一歩”**です。
PoCから実装へと進化するこのステップを、実際のコードとグラフを交えて丁寧に進めていきましょう。

2. ドリフトの傾向を「見える化」する

まず最初に、温度によってセンサ出力がどう変化しているかを視覚的に確認します。

対象とするのは、MPU6050の**Z軸加速度(Accel_Z)**です。これは重力加速度の影響で静止時でも常に約1gを示す軸であり、ドリフトの有無が特にわかりやすく現れます。


温度 vs Accel_Z の散布図を描く

Arduinoから出力したCSVファイルに含まれるデータをPythonで読み込み、
X軸に「温度(Temp_C)」、Y軸に「加速度Z軸(Accel_Z[g])」を取って散布図(scatter plot)を描画します。

import pandas as pd
import matplotlib.pyplot as plt

# 日本語フォント指定(macOS想定)
plt.rcParams["font.family"] = "Hiragino Sans"  # または "AppleGothic", "IPAexGothic"

# CSV読み込み
df = pd.read_csv("imu_log.csv")

# 散布図
plt.figure(figsize=(8, 5))
plt.scatter(df["Temp_C"], df["Accel_Z[g]"], alpha=0.7, color='teal')
plt.title("温度とZ軸加速度の関係")
plt.xlabel("Temperature [°C]")
plt.ylabel("Accel_Z [g]")
plt.grid(True)
plt.tight_layout()
plt.show()

3. 補正式の導出(Python)

前のセクションで散布図を描いたことで、「温度が上がると加速度センサの値がわずかに上がっていく」といった**傾向(ドリフト)**が視覚的に見えました。

ここでは、その傾向を数式として定式化します。
Pythonの numpy ライブラリの polyfit() 関数を使えば、温度(x軸)とセンサ出力(y軸)から回帰直線を導出できます。


使用する近似モデル(1次式)

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# データ読み込み
df = pd.read_csv("imu_log.csv")

# X: 温度, Y: 加速度Z軸
x = df["Temp_C"]
y = df["Accel_Z[g]"]

# 1次回帰(直線フィット)
a, b = np.polyfit(x, y, 1)

print(f"補正式: Accel_Z[g] ≒ {a:.5f} × Temp_C + {b:.5f}")


# 散布図 + 回帰直線
plt.scatter(x, y, alpha=0.7, label="実測値")
plt.plot(x, a * x + b, color='red', label="近似直線")

plt.title("温度とZ軸加速度の関係(線形近似)")
plt.xlabel("Temperature [°C]")
plt.ylabel("Accel_Z [g]")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

4. Arduino側に補正式を実装

前章で得られた補正式(例:Accel_Z[g] ≒ 0.00291 × Temp_C + 0.92935)を使って、
センサ出力から“温度によるズレ”を差し引く補正処理を、Arduinoのコード内に組み込みます。


Arduinoコード内の実装例

まずは、az がスケーリング後の Z軸加速度[g]単位の値であると仮定した場合、補正処理は以下のように記述できます:

#include <Wire.h>

#define MPU_ADDR    0x68
#define TMP102_ADDR 0x48

const float ACCEL_SCALE = 16384.0; // ±2g
const float GYRO_SCALE  = 131.0;   // ±250°/s

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // MPU6050 スリープ解除
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B); // PWR_MGMT_1
  Wire.write(0x00); // スリープ解除
  Wire.endTransmission();

  delay(100);

  // CSVヘッダー出力(補正後Z軸も追加)
  Serial.println("Temp_C,Accel_X[g],Accel_Y[g],Accel_Z[g],Corr_AZ[g],Gyro_X[dps],Gyro_Y[dps],Gyro_Z[dps]");
}

void loop() {
  float tempC = readTMP102();

  int16_t ax_raw, ay_raw, az_raw, gx_raw, gy_raw, gz_raw;
  readMPU6050(ax_raw, ay_raw, az_raw, gx_raw, gy_raw, gz_raw);

  float ax = ax_raw / ACCEL_SCALE;
  float ay = ay_raw / ACCEL_SCALE;
  float az = az_raw / ACCEL_SCALE;
  float gx = gx_raw / GYRO_SCALE;
  float gy = gy_raw / GYRO_SCALE;
  float gz = gz_raw / GYRO_SCALE;

  // ✅ 補正式によるZ軸加速度の補正(ドリフト分を引く)
 float corrected_az = az - (0.00291 * tempC + 0.92935) + 1.0;


  // ✅ 補正値も含めてCSV出力
  Serial.print(tempC, 2); Serial.print(",");
  Serial.print(ax, 2); Serial.print(",");
  Serial.print(ay, 2); Serial.print(",");
  Serial.print(az, 2); Serial.print(",");
  Serial.print(corrected_az, 2); Serial.print(","); // 補正後Z軸
  Serial.print(gx, 2); Serial.print(",");
  Serial.print(gy, 2); Serial.print(",");
  Serial.println(gz, 2);

  delay(500);
}

5. 3軸全ての補正や、ジャイロへの応用

ここまでの例では、Z軸の加速度(Accel_Z)だけを対象として補正を行ってきました。
しかし、温度によるドリフトは他の軸やセンサにも確実に影響を与えています。


加速度:X軸・Y軸も補正可能

MPU6050では、加速度センサはX/Y/Zの3軸すべてに搭載されています。
そのため、同様にして Accel_X や Accel_Y に対しても温度との相関を調べ、補正式を導出することができます

Accel_X = aₓ × Temp + bₓ  
Accel_Y = aᵧ × Temp + bᵧ

6. 補正後の可視化(before/after比較)

ここまでで、温度によるセンサドリフトを数式化し、Arduino側で補正処理を実装する方法を解説してきました。
では実際に、**補正前と補正後でセンサ出力がどのように変化したのか?**を、Pythonでグラフ化して確認してみましょう。


CSVログに補正値も出力しておく

以下のように補正後の値(例:corrected_az)を追加でCSV出力しておきます:

monitor_re2.py

import serial
import matplotlib.pyplot as plt
import pandas as pd
import time
from collections import deque

# シリアル接続と初期化
ser = serial.Serial('/dev/tty.usbmodem11301', 9600, timeout=1)
time.sleep(2)

# データ格納と可視化準備
max_len = 50
temp_data = deque([0.0]*max_len, maxlen=max_len)
accel_z_data = deque([0.0]*max_len, maxlen=max_len)
log_rows = []

# グラフ初期化(ツインY軸)
plt.ion()
fig, ax1 = plt.subplots()

# 左Y軸(Accel_Z)
line1, = ax1.plot(range(max_len), list(accel_z_data), label="Accel_Z (g)", color='orange')
ax1.set_ylabel("Accel_Z (g)")
ax1.set_ylim(-2.5, 2.5)
ax1.set_xlabel("Sample Index")
ax1.grid(True)

# 右Y軸(Temp_C)
ax2 = ax1.twinx()
line2, = ax2.plot(range(max_len), list(temp_data), label="Temp_C (°C)", color='blue', linestyle='--')
ax2.set_ylabel("Temperature (°C)")
ax2.set_ylim(20, 100)  # 例:TMP102は常温で25〜35℃の範囲を想定

# 凡例
fig.legend(loc="upper right")
ax1.set_title("Real-time IMU Data")

# メインループ
try:
    while True:
        line = ser.readline().decode('utf-8').strip()
        if not line or line.startswith("Temp_C"):
            continue

        parts = line.split(',')
        if len(parts) == 7:
            temp = float(parts[0])
            accel_z = float(parts[3])

            temp_data.append(temp)
            accel_z_data.append(accel_z)

            line1.set_ydata(accel_z_data)
            line1.set_xdata(range(len(accel_z_data)))
            line2.set_ydata(temp_data)
            line2.set_xdata(range(len(temp_data)))

            ax1.relim()
            ax1.autoscale_view()
            ax2.relim()
            ax2.autoscale_view()

            plt.pause(0.05)

            # ログに保存
            log_rows.append(parts)

except KeyboardInterrupt:
    print("リアルタイム表示を終了しました。CSVに保存します...")
    df = pd.DataFrame(log_rows, columns=[
        "Temp_C", "Accel_X[g]", "Accel_Y[g]", "Accel_Z[g]",
        "Corr_AZ[g]", "Gyro_X[dps]", "Gyro_Y[dps]", "Gyro_Z[dps]"
    ])
    df.to_csv("imu_log_re2.csv", index=False)
    print("imu_log_re2.csv に保存しました。")
    ser.close()

補正前後の比較グラフ

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

# 日本語フォントを明示的に指定(Mac環境例: ヒラギノ)
plt.rcParams['font.family'] = 'Hiragino Sans'

# CSVデータ読み込み
df = pd.read_csv("imu_log_re2.csv")

# 必要な列の存在を確認
required_cols = ["Accel_Z[g]", "Corr_AZ[g]"]
if not all(col in df.columns for col in required_cols):
    raise ValueError("CSVに 'Accel_Z[g]' または 'Corr_AZ[g]' の列が含まれていません。")

# 補正前後の折れ線グラフを描画
plt.figure(figsize=(10, 5))
plt.plot(df["Accel_Z[g]"], label="補正前 Accel_Z[g]", alpha=0.6, linestyle='--')
plt.plot(df["Corr_AZ[g]"], label="補正後 Corr_AZ[g]", alpha=0.8, color='blue')

plt.title("Z軸加速度の補正前後の比較")
plt.xlabel("サンプル番号")
plt.ylabel("加速度 [g]")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

うーん、少し補正が強すぎたかも。

7. まとめと今後の応用

本連載では、MEMSセンサーにありがちな「温度ドリフト」を対象に、以下のステップで補正手法を実践してきました:

  1. Arduinoから取得した温度と加速度のログを分析
  2. Pythonで相関を可視化し、近似式(補正式)を導出
  3. Arduinoでリアルタイム補正処理を実装
  4. 補正の効果を可視化して定量的に確認

補正式は万能ではない ― だからこそ“自分で検証”が重要

今回の補正式は「一例」にすぎません。実際には次のような要因によって補正の結果は変わります:

  • センサー個体ごとのばらつき(工業製品ゆえの誤差)
  • 実験時の温度範囲(20〜40°C か、−10〜+80°Cかで傾向も変化)
  • 加速度の向き(設置の角度)
  • 電源電圧やI²C通信の安定性

したがって、「汎用的な補正式を使う」よりも、「自分の環境で測って、自分の補正式を得る」ことが最も重要です。


補正は「安定性」という価値に直結する

本質的に、IMUセンサーにおける補正とは測定精度そのものではなく、信頼性と再現性を上げる行為です。
たとえ±0.05gの誤差でも、それが「温度のせい」と分かっていて補正できれば、
センサーはもはやブラックボックスではなく「使いこなせる道具」に変わります。

Discussion