dangomushi115のアイコン画像
dangomushi115 2022年03月08日作成 (2022年03月08日更新)
セットアップや使用方法 セットアップや使用方法 閲覧数 4558
dangomushi115 2022年03月08日作成 (2022年03月08日更新) セットアップや使用方法 セットアップや使用方法 閲覧数 4558

M5 ATOM LiteとIMU Unitで加速度を取得する

M5 ATOM LiteとIMU Unitで加速度を取得する

概要

M5 Stackシリーズは、M5 Stack単体でも様々なことができるうえ、拡張モジュール(Module、Base、Unit、Hatなどなど)が多数用意されており、大変便利です。

しかしながら、Arduino IDEで開発をしようとした場合(※)、M5Stack本体だけで実現できるシステムについては情報が大量にある一方で、拡張モジュールの使用方法に関する情報は意外に少なく感じています。特に、M5Stack CoreやM5StickCなどのオーソドックスな本体に付属しているセンサーと同じセンサーを搭載したUnitを、M5 Atom Liteのようなややマイナーな本体で使用している例はほとんど見当たりません。
※:処理速度等を気にしない用途なら、UI Flow(≒MicroPython)を使っておけばいい気がします。個人的には、UIFlowではマイクロ秒のスリープなどができない(?)ため仕方なくArduino IDEを使ったりしています。

そこで本記事では、M5StackのIMU Unitを、M5 Atom Liteで使用するにあたりつまづいたポイントと解決した手順を示すことで、他のマイナーな組み合わせを試そうとしている人の参考になればと思い執筆することにしました。

背景

M5Stackシリーズの本体には、本体にIMU(慣性計測装置)を備え、単体で加速度などを測定できるものがあります。
例)M5 Stack Gray、M5 StickC、M5 Atom Matrix

筆者の手元にもこれらの本体はありますが、たまたまM5 Atom LiteIMU Unitが余っていたため、この組み合わせで加速度を測定することにしました。
なお、特段の事情がない限り(※)、M5 Atom Matrixを使用した方が、286円安くサンプルコードもあるためおススメです。
※:M5 Atom Liteはこういうのを買うと一緒についてきたりするので、気づいたら余っていたりします。

つまづいたポイントと解決までにトライしたこと

M5 AtomでIMUを使用するサンプルプログラムに「MPU6886」があります。このプログラムは、おそらくM5 Atom Matrix(など?)向けのプログラムで、内部に搭載されているIMU(MPU6886)を使用するものとなっています。
下記にサンプルプログラムの「加速度を取得する」のに関連する部分を抜粋します。

MPU6886の一部を抜粋

#include "M5Atom.h" //<中略> bool IMU6886Flag = false; //<中略> void setup() { M5.begin(true, false, true); if (M5.IMU.Init() != 0) IMU6886Flag = false; else IMU6886Flag = true; } void loop() { if (IMU6886Flag == true) { //<中略> M5.IMU.getAccelData(&accX, &accY, &accZ); //<中略> } delay(500); M5.update(); }

M5 Atom LiteにIMU Unitを接続して、上記のサンプルプログラムを書き込むと、加速度等は取得できませんでした。

こういうときどこが怪しそうか考えるには、まず容疑者(怪しそうな処理)を挙げてみます。この場合は
「M5.begin(true, false, true)」
「M5.IMU.Init()」
「M5.IMU.getAccelData(&accX, &accY, &accZ)」
あたりが該当するでしょうか。

次に、容疑者がどういう悪さをしているかを何となく想像します。

  1. M5.begin(true, false, true)に問題があるとめんどくさいなぁ・・・
  2. M5.IMU.Init()の処理に問題があるせいでIMU6886FlagがTrueになっていない?
  3. M5.IMU.getAccelData(&accX, &accY, &accZ)の処理に問題があって値が取得できていない?

ここで、とりあえずこれら3つに共通する「M5.~」がどんな処理になっているか調べてみます。
→先述のサンプルプログラムでは「M5Atom.h」のみをincludeしているので、まずは「C:\Users\★ユーザー名★\Documents\Arduino\libraries\M5Atom\src」みたいな場所にある「M5Atom.h」、「M5Atom.cpp」を見てみます。

すると、以下のことが分かります。

  1. M5.begin(true, false, true)は、シリアル通信とディスプレイの表示をEnable、I2C通信をDisableにしているらしい
  2. 「M5.IMU.~」の処理はここには書かれていない
    →「M5Atom.h」中でincludeしている"utility/MPU6886.h"が怪しそうだ?

そこで、"utility/MPU6886.h"関連を探してみてみると、「MPU6886.cpp」の中にM5.IMU.Init()やM5.IMU.getAccelData(&accX, &accY, &accZ)に関係しそうな処理があります。さらによく見ると、M5.IMU.Init()の処理は以下のようになっているようです。

M5.IMU.Init()の処理

int MPU6886::Init(void){ unsigned char tempdata[1]; unsigned char regdata; Wire1.begin(25,21); //<以下略>

「 Wire1.begin(25,21);」の「25」、「21」という数字には何か意味がありそうです。ここで「そういえばM5Atom MatrixにIMU Unitを接続したら内蔵のIMUとUnitとして接続したIMUとを区別するための設定ってどこでやるんだろう?」といった疑問を抱けると、問題は解決します。すなわち、「ここで指定されている「25」、「21」は内蔵IMUの接続されているピン番号なのではないか?」「このピン番号を修正すれば、IMU Unitから値が取得できるのではないか?」

解決策

前置きが長くなりましたが、以下のようにすればIMU Unitから値を取得できます。「ファイル→新規ファイル」で「IMU_main.ino」を作成し、「スケッチ→ファイルを追加」から「IMU_Unit.h」及び「IMU_Unit.cpp」を追加してください。なお、「IMU_Unit.h」及び「IMU_Unit.cpp」は「MPU6886.h」の「 Wire1.begin(25,21);」の「25」、「21」を「26」、「32」に修正したほか、加速度を取得するのに関わる処理のみを抜粋したもの(※)です。
※:プログラムを作成してからこの記事を書くまでに半年以上経過しているため、もしかしたら他にも何か考えて編集しているかもしれません。

IMU_main.ino

#include "IMU_Unit.h" float accX = 0, accY = 0, accZ = 0; IMU_Unit unit1; bool IMU6886Flag = false; void setup() { M5.begin(true, false, true); Serial.printf("Start!\r\n"); if (unit1.Init() != 0) IMU6886Flag = false; else IMU6886Flag = true; Serial.printf("%d\r\n",IMU6886Flag); } void loop() { if (IMU6886Flag == true) { unit1.getAccelData(&accX, &accY, &accZ); Serial.printf("%.2f,%.2f,%.2f mg\r\n", accX * 1000, accY * 1000, accZ * 1000); } delay(500); M5.update(); }

IMU_Unit.h

#include "M5Atom.h" class IMU_Unit { public: enum Ascale { AFS_2G = 0, AFS_4G, AFS_8G, AFS_16G }; enum Gscale { GFS_250DPS = 0, GFS_500DPS, GFS_1000DPS, GFS_2000DPS }; Ascale Acscale = AFS_8G; public: IMU_Unit(); int Init(void); void getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az); void getAccelData(float* ax, float* ay, float* az); void SetAccelFsr(Ascale scale); public: float aRes; private: float _last_theta = 0; float _last_phi = 0; float _alpha = 0.5; private: void I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer); void I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer); void getAres(); };

IMU_Unit.cpp

#include "IMU_Unit.h" IMU_Unit::IMU_Unit(){ } void IMU_Unit::I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer){ Wire1.beginTransmission(driver_Addr); Wire1.write(start_Addr); Wire1.endTransmission(false); uint8_t i = 0; Wire1.requestFrom(driver_Addr,number_Bytes); //! Put read results in the Rx buffer while (Wire1.available()) { read_Buffer[i++] = Wire1.read(); } } void IMU_Unit::I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer){ Wire1.beginTransmission(driver_Addr); Wire1.write(start_Addr); Wire1.write(*write_Buffer); Wire1.endTransmission(); } int IMU_Unit::Init(void){ unsigned char tempdata[1]; unsigned char regdata; Wire1.begin(26,32); I2C_Read_NBytes(MPU6886_ADDRESS, MPU6886_WHOAMI, 1, tempdata); Serial.printf("%02X\r\n",tempdata[0]); if(tempdata[0] != 0x19) return -1; delay(1); regdata = 0x00; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata); delay(10); regdata = (0x01<<7); I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata); delay(10); regdata = (0x01<<0); I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata); delay(10); regdata = 0x10; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG, 1, &regdata); delay(1); regdata = 0x18; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_GYRO_CONFIG, 1, &regdata); delay(1); regdata = 0x01; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_CONFIG, 1, &regdata); delay(1); regdata = 0x05; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_SMPLRT_DIV, 1,&regdata); delay(1); regdata = 0x00; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, &regdata); delay(1); regdata = 0x00; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG2, 1, &regdata); delay(1); regdata = 0x00; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_USER_CTRL, 1, &regdata); delay(1); regdata = 0x00; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_FIFO_EN, 1, &regdata); delay(1); regdata = 0x22; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_PIN_CFG, 1, &regdata); delay(1); regdata = 0x01; I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, &regdata); delay(100); //getGres(); getAres(); return 0; } void IMU_Unit::getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az){ uint8_t buf[6]; I2C_Read_NBytes(MPU6886_ADDRESS,MPU6886_ACCEL_XOUT_H,6,buf); *ax=((int16_t)buf[0]<<8)|buf[1]; *ay=((int16_t)buf[2]<<8)|buf[3]; *az=((int16_t)buf[4]<<8)|buf[5]; } void IMU_Unit::SetAccelFsr(Ascale scale) { unsigned char regdata; regdata = (scale<<3); I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG, 1, &regdata); delay(10); Acscale = scale; getAres(); } void IMU_Unit::getAccelData(float* ax, float* ay, float* az){ int16_t accX = 0; int16_t accY = 0; int16_t accZ = 0; getAccelAdc(&accX,&accY,&accZ); *ax = (float)accX * aRes; *ay = (float)accY * aRes; *az = (float)accZ * aRes; } void IMU_Unit::getAres(){ switch (Acscale) { // Possible accelerometer scales (and their register bit settings) are: // 2 Gs (00), 4 Gs (01), 8 Gs (10), and 16 Gs (11). // Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value: case AFS_2G: aRes = 2.0/32768.0; break; case AFS_4G: aRes = 4.0/32768.0; break; case AFS_8G: aRes = 8.0/32768.0; break; case AFS_16G: aRes = 16.0/32768.0; break; } }
dangomushi115のアイコン画像
主に生き物の観察・飼育環境の測定に関する工作をします。 ↑よく見たら、あんまり生き物関係の工作の記事を載せていなかった・・・
ログインしてコメントを投稿する