トホホな疑問(31) M5Stack、IMUの種類と取り扱いに戸惑う

Joseph Halfmoon

昨日micro:bitの加速度センサと磁気センサのボード間の相違点について調べ、結構納得。本日は、もう一つ気になっていたM5Stackのモーションセンサに再度取り組みたいと思います。以前にちょっと動かしたことはあったのですが、調べると疑問がいろいろ。どうしたら良いのだろう、トホホ。

手元にあるM5Stackは M5Stack Gray という機種で、加速度、角速度、磁気をそれぞれ3次元で測定可能(9軸)なモーションセンサMPU-9250を搭載しています。搭載しているセンサについては以前に調べて2回ほど投稿させていただいてます。

IoT何をいまさら(44) MPU9250、9軸センサモジュール
IoT何をいまさら(45) MPU9250、ノイズとLPF

ただ、上の2回のときは、センサの加速度測定部分についてのみ動かしてました。3軸動かせば残りは以下同文で簡単じゃね、などと甘くみていたつけが回って来た感じです(なおソフト開発環境はArduinoです。)

久しぶりに触ってみると、ソフトウエアの初期化のところから疑問続発です。

#include <M5Stack.h>
#include "utility/MPU9250.h"

MPU9250 IMU;

//途中略

void setup() {
  M5.begin();
  Wire.begin();
  IMU.initMPU9250();

以前はMPU9250用のヘッダファイルを上のようにインクルードした後、IMUオブジェクトを作って初期化していた筈なのですが、最近は、以下のようにラベルを定義するだけでIMUオブジェクトが出来ているみたい。

#define M5STACK_MPU9250
#include <M5Stack.h>

void setup() {
  M5.begin();
  M5.IMU.Init();

この違いを理解するためには、GitHubへ行ってソースを見てみるしかありますまい。

M5Stack Library

答えは M5Stack.h の中にアカラサマに書いてありました。もし以下の3つのマクロが定義されていれば

  • M5STACK_MPU6886
  • M5STACK_MPU9250
  • M5STACK_MPU6050

ヘッダファイル utility/MPU6886.h をインクルードして MPU6886のIMUオブジェクトを作る。なんだデバイスの設定が3通りあるけど皆同じなのね。。。異なるのは、もし、以下のマクロが定義されていれば

  • M5STACK_200Q

ヘッダファイル utility/SH200Q.h をインクルードして SH200QのIMUオブジェクトを作る、と。そしてミソなのは、何もマクロが定義されていなければM5Stack.h の中にはIMUオブジェクトを作るような定義は無くなるということです。

この記述を読ませていただくと、M5Stackのこのヘッダを共用しているデバイス群(M5Stack なんちゃら)の中で、実に4種類のモーションセンサが使われていてそれをマクロで切り替えているということのようです。マクロを定義さえすれば、ユーザープログラムからはIMUオブジェクトの操作の差は見えなくなるようなので、マクロ定義のスタイルは良い感じがします。しかし、ファイルを読んでいるうちに手放しでは喜べないということが理解されてきました。

手元にあるのはMPU9250だけんど、MPU6886のドライバで大丈夫なの?

答えはYESでもあり、NOでもあり、でした。こと加速度に関しては「M5STACK_MPU9250というマクロを定義して得たMPU6886のIMUオブジェクト」で加速度を読み取ることは実際に可能でした。しかし磁気に関しては問題ありです。InvenSense製MPU9250センサは、加速度と角速度部分の6軸はInvenSense(どうも型番的にはMPU6500。同じような番号が出てきて紛らわしいですが)ですが、磁気の3軸はAKM製のAK8963をシングルパッケージ内に取り込んで使っています。そしてMPU9250用のヘッダファイルにはAK8963にアクセスするための定義が含まれているのですが、MPU6886用のヘッダファイルにはそのような定義は見当たりません。

気になったのでこの M5Stack.h を共用しそうなデバイス達のモーションセンサの搭載状況を調べてみました。

加速度/ジャイロ 磁気
Basic
Gray MPU9250 AK8963
Fire MPU6886 BMM150
Fire SH200Q BMM150
Fire MPU6050 BMM150
Core2 MPU6886

どうもマクロを定義すれば自動でIMUオブジェクトを作ってくれるやり方は加速度、ジャイロ側共通のマナーで、磁気センサ側は切り離されているみたいです。つまり磁気センサを使いたい場合は、以前と同様、デバイス別のヘッダをインクルードして対処するべし、と理解しましたぜ。

蛇足ですが MPU9250とMPU6886のデバイス識別は WHO AM I レジスタを読めば可能でした。どちらも同じI2Cアドレス、同じWHO AM Iレジスタ番号ですので同じプログラムで取得可能。ただしMPU6886用のドライバで「とりあえず」どちらも動作してしまうようなので、わざわざ識別する必要性は小さいと思いました。

結局、以前の個別にヘッダをインクルードする方法に戻って加速度と磁気を読み取って画面に出力するプログラムを書いてみました(アイキャッチ画像に動作中写真を貼り付けたもの。全コードは末尾に。)ここでもトホホ、キャリブレーションを実施する関数、IMU.calibrateMPU9250(末尾のコードではコメントアウトしてある)を活かすと磁気のデータがとれませんでした。加速度と磁気、どちらか一方でなら問題なかったのですが。加速度をキャリブレーションしてから両方取得しようとに動かそうとしたらダメ。加速度のキャリブレーションをしなければ加速度と磁気の取得は両立するのに。何でだろ。トホホ。本日は疲れてしまったのでここまでといたしとうございまする。

トホホな疑問(30) ラズパイのPythonでトホホ連発? に戻る

トホホな疑問(32) .inoファイル内のリソース参照、どうするのがよろしいの? へ進む

加速度と磁気の読み取り(加速度のキャリブレーションうまく行かない)
#include <M5Stack.h>
#include "utility/MPU9250.h"

MPU9250 IMU;

void readAcc(float *ac) {
  ac[0] = 0.0;
  ac[1] = 0.0;
  ac[2] = 0.0;
  if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01) {
    IMU.readAccelData(IMU.accelCount);
    IMU.getAres();
    ac[0] = (float)IMU.accelCount[0] * IMU.aRes;
    ac[1] = (float)IMU.accelCount[1] * IMU.aRes;
    ac[2] = (float)IMU.accelCount[2] * IMU.aRes;
  }
}

void readMag(float *mg) {
  mg[0] = 0.0;
  mg[1] = 0.0;
  mg[2] = 0.0;
  if (IMU.readByte(AK8963_ADDRESS, AK8963_ST1) & 0x01) {
    IMU.readMagData(IMU.magCount);
    IMU.getMres();
    mg[0] = (float)IMU.magCount[0] * IMU.mRes * IMU.magCalibration[0] - IMU.magbias[0];
    mg[1] = (float)IMU.magCount[1] * IMU.mRes * IMU.magCalibration[1] - IMU.magbias[1];
    mg[2] = (float)IMU.magCount[2] * IMU.mRes * IMU.magCalibration[2] - IMU.magbias[2];
  }
}

void setup() {
  M5.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0,0);
  M5.Lcd.println("MPU9250 / AK8963");
  Wire.begin();
  IMU.initMPU9250();
  //IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);
  IMU.initAK8963(IMU.magCalibration);
}

void loop() {
  float ac[3], mg[3];
  readAcc(ac);
  readMag(mg);
  M5.Lcd.setCursor(0,32);
  M5.Lcd.printf("acc=(%5.2f, %5.2f, %5.2f)", ac[0], ac[1], ac[2]);
  M5.Lcd.setCursor(0,64);
  M5.Lcd.printf("mag=(%5.2f, %5.2f, %5.2f)", mg[0], mg[1], mg[2]);
  delay(100);
}