BMX055でMadgwickフィルタを使う

9軸センサとは

9軸センサは加速度・角速度・地磁気を計測するセンサです。
それぞれ独立したセンサも存在しています。

  • 加速度センサ
  • ジャイロセンサ
  • 地磁気センサ

これらのセンサを3つ合わせて9軸センサと呼ばれています。

各々のセンサでXYZ3軸分の数値が出力されます。
つまり、9軸センサでは3×3で9つの数値が出力されます。

ちなみに英語で9軸は「9DoF」(9 degree of freedom)といいます。

主な機能としてはセンサ搭載物の姿勢推定などができます。
続いて、それぞれのセンサについて説明していきます。

加速度センサ

加速度センサは加速度を読み取るセンサになります。
このセンサは加速度を検出できるため、センサ上の座標系で重力加速度のXYZ軸それぞれの成分が分かります。

よって、このセンサでは重力加速度の方向が測定できるということです。

POINT!
センサのプログラムがうまくできていれば、センサを地面に置いたときに、地面に対して垂直な方向の軸の値は9.8付近になると思います。これは高校物理でも出てくる重力加速度の値です。

重力以外にもセンサ搭載物の動きに対して加速度を得ることができます。
しかし、このとき重力加速度の方向が一時的にブレてしまうことになります。

つまり、この加速度センサだけだと静止した物体の姿勢推定しかできません。

ジャイロセンサ

ジャイロセンサは物体の角速度を読み取れます。

先ほど紹介した加速度センサとあわせて6軸センサとして利用されています。
この6軸センサのことをIMU*1といいます。(9軸はAHRS*2といいます)

理論上は6軸IMUで姿勢推定が可能になります。
しかし、センサにはドリフトオフセットという誤差が生じます。
また、ヨー方向の姿勢推定はどこかを基準とした相対姿勢しか分かりません。

いかにこの誤差を取り除くか、ヨー方向の姿勢を推定するかが問題です。
その際に9軸のほうが6軸よりも有利になるわけです。

地磁気センサ

地磁気だけでなく磁気があるものを計測できるセンサです。
地磁気という常に一定方向に生じているベクトルを計測できるのでIMUに補うことでより精度のいい値をとれます。

弱点としては近くに磁力を持つものがあると正確な値が取れません。

座標系


ここで座標系について捕捉の説明をします。 XYZ軸のそれぞれの方向に沿って右ねじの向きで Roll(ロール)Pitch(ピッチ)Yaw(ヨー)になります。

f:id:misosoup4258:20211217144326p:plain
座標系の説明
角速度やフィルタを扱う時に使います。

オフセットとドリフト


オフセット

オフセットとは零点誤差とも言われます。 例えばジャイロセンサの計測対象である物体が全く回転していない場合を考えます。 この場合、計測値は(0,0,0)をとるのが理想ですが、実際は(1,1,0)など少し誤差がでることがあります。 回転してない、つまり零点での誤差がオフセットと言われます。

ドリフト

ドリフトとは温度変化などによって変化する誤差のことです。 センサによってその特徴が異なるので考慮するのが難しい誤差になります。

オフセットやドリフトはセンサの種類やセンサの使用環境で異なってきます。 センサのデータシートに許容誤差が記載されている場合が多いです。

9軸センサの特徴についてここまで紹介してきました。 ここからは9軸センサの使い方を紹介していきます。

先に紹介した通り、オフセットとドリフトによる誤差を考慮しなければ使い物になりません。

そのため9軸センサの誤差を取り除く必要があります。 そこで、フィルタというものを使います。

フィルタ


9軸センサに使われる主なフィルタは私が知っている限りでは以下の通りです。

  • カルマンフィルター
  • 相補フィルター
  • Madgwickフィルター(相補フィルターの1種)

この3つが主に使われています。
この中でも高速処理が可能な「Madgwickフィルター」を紹介します。

Madgwickフィルター

このフィルターは高速処理が可能なためマイコンでも精度が良い補正ができます。 クォータニオンという演算子を用いて計算しているようです。 原理に関しては割愛させていただきます。

ArduinoIDEにてMadgwickライブラリがあるのでそちらの使い方を紹介します。
まずは、ライブラリの入れ方です。

ライブラリ導入

ArduinoIDEのツールからライブラリを管理を選択しましょう。

f:id:misosoup4258:20211229173734p:plain
ライブラリを管理を選択

すると、このような画面が現れると思います。

f:id:misosoup4258:20211229173739p:plain
ライブラリ検索画面

この画面の右上の検索窓に「madgwick」と入力してください。

おそらく一番上に「Madgwickライブラリ」があるのでインストールしましょう。 これでライブラリのインスト―ルは終了です。

続いて、このライブラリの使い方を紹介していきます。

madgwickフィルタの使い方

今回紹介するプログラムでは処理速度の向上と簡単のため、加速度とジャイロの値を用いた6軸で書いています。(9軸での使い方も載せています)私が前に製作した倒立振子のプログラムを使って説明します。

9軸センサにBMX055を使ったプログラムです。プログラムから一部抜粋して説明します。最後にまとめてプログラムを掲載しています。一部のプログラムは秋月電子のサイトから引用しています。 まずはライブラリの読み込みです。

#include<MadgwickAHRS.h>
#include<Wire.h>

次に、フィルタを「MadgwickFilter」の文字列で呼び出すようにインスタンス化します。

Madgwick MadgwickFilter;

そして、setup関数内でフィルタの初期設定としてサンプリング周波数を設定します。

void setup()
{
  MadgwickFilter.begin(10);
  delay(300);
}

今回は、サンプリング周波数が10[Hz]になっています。 これは1秒間に10回、センサから得た計測値をフィルタに入れるということです。 この値は高いほど、誤差が小さくなる傾向にありますが大差はない気がします。 逆に、高いほど処理が重くなるため処理速度が遅くなります。(arduino nano everyで動作確認) 処理速度を求める場合は高く設定しないようにしましょう。

void loop()
{

  //BMX055 ジャイロの読み取り
  BMX055_Gyro();

  MadgwickFilter.updateIMU(xGyro,  yGyro,  zGyro,  xAccl,  yAccl,  zAccl);
  I = 0;
  float kp = 1, ki = 5, kd = 5, dt = 0.01 , prePITCH;
  prePITCH = PITCH;
  PITCH = MadgwickFilter.getPitch();
  S = PITCH;
  D = (PITCH - prePITCH );

  I += kp * yGyro + ki * S + kd * D ; //PID制御

こちらは、loop関数内のプログラムです。

  MadgwickFilter.updateIMU(xGyro,  yGyro,  zGyro,  xAccl,  yAccl,  zAccl);

これはセンサで得られた値をフィルタにかける部分です。 Gyroは「ジャイロ」、Acclは「加速度」センサから得られた生の値です。 どちらも誤差が含まれています。

  PITCH = MadgwickFilter.getPitch();

上記のプログラムで精度のいいPITCH角を得ることができます。ROLL角やYAW角を得たい場合は「get~」のところをそれぞれRollかYawにすればいいです。

なお、9軸センサーで9つの計測値を使う場合(AHRS)は生のデータをフィルタにかける部分が「update」だけになり「IMU」はいりません。以下に載せておきます。

   MadgwickFilter.update(xGyro,  yGyro,  zGyro,  xAccl,  yAccl,  zAccl, xMag, yMag, zMag);

ここまで、9軸センサの使い方の紹介になりました。 終始、分かりにくいところが多いとは思いますが読んでいただきありがとうございました。

参考


以下倒立振子プログラム(うまく動かなかったので参考までに)

#include<MadgwickAHRS.h>
#include<Wire.h>
// BMX055 加速度センサのI2Cアドレス
#define Addr_Accl 0x19  // (JP1,JP2,JP3 = Openの時)
// BMX055 ジャイロセンサのI2Cアドレス
#define Addr_Gyro 0x69  // (JP1,JP2,JP3 = Openの時)
// BMX055 磁気センサのI2Cアドレス
#define Addr_Mag 0x13   // (JP1,JP2,JP3 = Openの時)

const uint8_t speeds = 75;
Madgwick MadgwickFilter;

// センサーの値を保存するグローバル関数
float xAccl = 0.00;
float yAccl = 0.00;
float zAccl = 0.00;
float xGyro = 0.00;
float yGyro = 0.00;
float zGyro = 0.00;
float xMag  = 0;
float yMag  = 0;
float zMag  = 0;
float PITCH;
float S; //積分値
float D; //微分値
float I; //制御値
float output = I + speeds;

void setup()
{
  // Wire(Arduino-I2C)の初期化
  Wire.begin();
  // デバック用シリアル通信は9600bps
  Serial.begin(9600);
  //BMX055 初期化
  BMX055_Init();
  MadgwickFilter.begin(100);
  delay(300);
}

void loop()
{

  //BMX055 ジャイロの読み取り
  BMX055_Gyro();

  MadgwickFilter.updateIMU(xGyro,  yGyro,  zGyro,  xAccl,  yAccl,  zAccl);
  I = 0;
  float kp = 1, ki = 5, kd = 5, dt = 0.01 , prePITCH;
  prePITCH = PITCH;
  PITCH = MadgwickFilter.getPitch();
  S = PITCH;
  D = (PITCH - prePITCH );

  I += kp * yGyro + ki * S + kd * D ; //PID制御

  Serial.print(I);
  Serial.print(" ");
  Serial.println(yGyro);

  if (abs(yGyro) > 140) {
    while (1) {
      analogWrite(5, 0);
      analogWrite(6, 0);
      analogWrite(9, 0);
      analogWrite(10, 0);
    }
  } else if (yGyro > 0.5) {
    analogWrite(5, output );
    analogWrite(6, 0);
    analogWrite(9, output);
    analogWrite(10, 0);
  } else if (yGyro < 0.5) {
    analogWrite(6, output );
    analogWrite(5, 0);
    analogWrite(10, output);
    analogWrite(9, 0);
  } else {
    analogWrite(6, 100 );
    analogWrite(5, 100);
    analogWrite(10, 100);
    analogWrite(9, 100);
  }
  delay(10);
}

//=====================================================================================//
void BMX055_Init()
{
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x0F);  // Select Range register
  Wire.write(0x04);  // Full scale = +/- 125 degree/s
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x10);  // Select Bandwidth register
  Wire.write(0x07);  // ODR = 100 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x11);  // Select LPM1 register
  Wire.write(0x00);  // Normal mode, Sleep duration = 2ms
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x83);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x01);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4C);  // Select Mag register
  Wire.write(0x00);  // Normal Mode, ODR = 10 Hz
  Wire.endTransmission();
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4E);  // Select Mag register
  Wire.write(0x84);  // X, Y, Z-Axis enabled
  Wire.endTransmission();
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x51);  // Select Mag register
  Wire.write(0x04);  // No. of Repetitions for X-Y Axis = 9
  Wire.endTransmission();
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x52);  // Select Mag register
  Wire.write(0x16);  // No. of Repetitions for Z-Axis = 15
  Wire.endTransmission();
}
//=====================================================================================//
void BMX055_Accl()
{
  int data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Accl);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Accl, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}
//=====================================================================================//
void BMX055_Gyro()
{
  int data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Gyro);
    Wire.write((2 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Gyro, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xGyro lsb, xGyro msb, yGyro lsb, yGyro msb, zGyro lsb, zGyro msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xGyro = (data[1] * 256) + data[0];
  if (xGyro > 32767)  xGyro -= 65536;
  yGyro = (data[3] * 256) + data[2];
  if (yGyro > 32767)  yGyro -= 65536;
  zGyro = (data[5] * 256) + data[4];
  if (zGyro > 32767)  zGyro -= 65536;

  xGyro = xGyro * 0.0038; //  Full scale = +/- 125 degree/s
  yGyro = yGyro * 0.0038; //  Full scale = +/- 125 degree/s
  zGyro = zGyro * 0.0038; //  Full scale = +/- 125 degree/s
}
//=====================================================================================//
void BMX055_Mag()
{
  int data[8];
  for (int i = 0; i < 8; i++)
  {
    Wire.beginTransmission(Addr_Mag);
    Wire.write((0x42 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Mag, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xMag lsb, xMag msb, yMag lsb, yMag msb, zMag lsb, zMag msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xMag = ((data[1] << 8) | (data[0] >> 3));
  if (xMag > 4095)  xMag -= 8192;
  yMag = ((data[3] << 8) | (data[2] >> 3));
  if (yMag > 4095)  yMag -= 8192;
  zMag = ((data[5] << 8) | (data[4] >> 3));
  if (zMag > 16383)  zMag -= 32768;
}

*1:アイエムユー:慣性計測装置「Inertial Measurement Unit」

*2:エーハース:姿勢方位基準装置「Attitude Heading Reference System」