生産現場IoTへの挑戦 #02　~M5Stick Cの加速度センサで高速サンプリング~

#1.はじめに
前回の記事でM5StickCでの振動測定を試してみました。
サンプリング周期の目標を1kHzとし、目標は達成しましたが加速度センサのレジスタ回りの設定が適当だったので改めて整理します。
レジスタ設定を変更した結果、3kHz以上のサンプリングレートが実現しました。

#2.M5StickCの加速度センサ
M5StickCにはバージョン違いがあり、加速度センサはSH200QもしくはMPU6886が搭載されています。SH200Q搭載タイプはおそらく初期型のみで現在販売されているものはMPU6886ばかりだと思います。今回もMPU6886についてのまとめです。

MPU6886は3軸ジャイロと3軸加速度が1チップで測定できるセンサーです。センサーの駆動条件はレジスタに設定値を書き込むことである程度調整可能となります。
詳しい情報はこちらの[データシート](https://github.com/m5stack/M5-Schematic/blob/master/datasheet/MPU-6886-000193%2Bv1.1_GHIC.PDF.pdf)に書かれていますので興味のある方は覗いてみてください。英語ばっかりで難しそうですが、私もトライアンドエラーしながら読み込むと何となくわかってきました。レジスタとかいうものを触るのは実は今回が初めてです。。。

#3.レジスタとは
レジスタとはなんぞや、、、という方に簡単に説明しておくと、マイコンチップのデータのやり取りや設定に使うためのメモリ領域です。今回の件に限って言うと、センサーの設定を決めるためのON/OFFスイッチとして働きます。

たとえば加速度センサーのスケールをする際には、二つのスイッチの組み合わせで4通りの選択肢の中から任意の設定を行います。

| パターン | スイッチA |スイッチB |加速度センサースケール|
|:-:|:-:|:-:|:-:|
| 1 |オフ|オフ|±2G|
| 2 |オフ|オン|±4G|
| 3 |オン|オフ|±8G|
| 4 |オン|オン|±16G|
この例は実際にMPU6886でも使用する設定パターンです。設定する際にはM5StickCのプログラムの中でどのレジスタを設定するか、スイッチ番号（アドレス）を指名して、設定値を送信します。設定値を送信する際には、スイッチAとスイッチBの状態を「00」「01」「10」「11」のように表現します。

#4.MPU6886のレジスタマップ（抜粋）
MPU6886には制御可能なレジスタがたくさんありますが、今回の目的はセンサーのサンプリングレートを制御することですので、関係ありそうなレジスタのみ抜粋します。

| レジスタアドレス | 名称 | デフォルト値 | 今回の設定値 |
|:-:|:-:|:-:|:-:|
|0x19|SMPLRT_DV|5(0x05)|0x00|
|0x1A|CONFIG|1(0x01)|0x07|
|0x1B|GYRO_CONFIG|24(0x18)|0x18|
|0x1C|ACCEL_CONFIG|16(0x10)|0x10|
|0x1D|ACCEL_CONFIG2|0(0x00)|0x08|
|0x6C|PWR_MANEGEMENT_2|0(0x00)|0x07|

名称を見ると何となく雰囲気が分かるような判るような判らないような。。。
##0x19 SMPLRT_DV
0x19は重要な設定項目で、デフォルトが5になっています。このままだとせっかく測定したデータを1/(1+5)の数に間引いてしまいます。ので、0にします。

違和感があるのが、スイッチのオン・オフを1/0で表現するはずなのに、なぜ「5」という数値が入っているのか？という点です。これは、一つのレジスタには8つのスイッチが並んでいて、それぞれのオン/オフ状態を0/1の数値で示しているからです。
8つのスイッチの状態は、例えば「00000101」という数字の並びで表現できます。この0と1の数字の並びを2進数ととらえ、10進数に変換するとデフォルト値の「5」という値になります。（）内の0x00は16進数表記ですが後ほど説明します。

##0x1A CONFIG
いかにも設定が書いて有りそうなレジスタです。詳細は省きますが、データシートのP12にサンプリングレートを上げたければ①「0x1Bの下二けたを「10」「01」「11」のいずれかにする」か、②「0x1Bの氏も二けたを「00」にして0x1Aの下3桁を000もしくは111にしておけ」と書いてあります。
デフォルト設定値は10進法の「1」ですのでレジスタ全体の設定値は2進法で「00000001」となっています。今回は設定を変更して「00000111」としますので設定値は「7」となります。

##0x1B GYRO_CONFIG
今回はジャイロは使いませんが、前項で述べたとおり下二けたについて言及されています。今回は「00」とします。デフォルトでは「24」ですので全体では「00011000」です。真ん中の「11」は、ジャイロのスケールを±2000dpsにする設定です。特に変更する必要はないため、全体の設定値は「00011000」のまま、10進法の「24」です。ただし、レジストリの設定値は16進数で表記する必要があるため、設定値は「0x18」です。

##0x1C ACCEL_CONFIG
このレジスタにはサンプリングレートに影響を与える項目はありませんが、加速度のスケール設定が含まれているため取り上げました。デフォルトの「16」は「00010000」に相当します。加速度のスケールは真ん中の二けたで設定します。デフォルトでは「10」になっておりスケールは±8Gです。00,01,10,11がそれぞれ2G,4G,8G,16Gに相当します。測定したい振動に応じて設定しましょう。今回は8のままとしますので表記は16進数表記は「0x10」です。

##0x1D ACCEL_CONFIG2
加速度のサンプリングレートはこのレジスタで決まる様です。
デフォルトでは「0000000000」ですが、「00001000」でローパスフィルタをバイパスしてレートが早くなる様ですので「0x08」にします。

##0x6C PWR_MANEGEMENT_2
パワーマネジメントということで、合計6軸のセンサーの有効/無効化を設定します。今回はジャイロは使わないため、ジャイロを切れば早くなるかと思いとりあげています。デフォルトは「00000000」で、下3桁がジャイロのx,y,z軸に相当します。ジャイロ3軸をオフにするには「0x07」とします。。。。が、切っても早くはならなかったので「0x00」のままでOKです。

#5.I2C通信速度の変更
前項で、センサーのサンプリング速度は最速設定だと思われます。ですが、この設定のみではM5StickCのコアとセンサー間のI2C通信速度がボトルネックとなってしまいますので、この通信速度を早くします。デフォルトで100kbpsですが、スペック上で400kbpsまで対応しています。400kbpsまで上げるとサンプリング周期は325μsec程度まで上昇しますので、実に3kHz強の速度となりました。

ちなみにこのI2Cの通信速度は400k以上も設定可能で、500kbpsで275μsec（3,636Hz）、600kbpsで245μsec（4,081Hz）と上昇し、800kbpsあたりで220μsec（4,545Hz）の頭打ちとなります。
一方、600kbpsを超えると加速度データに二つ同じ値が連続しはじめるため、センサーのサンプリングレートがボトルネックになり始めている様です。MPU6886のデータシートにも最大4kHzとありますので期待通りということですね。

#6.ソースコード
今回の検証に利用したソースコードもアップしておきます。前回とほぼ同じですが、レジストリの読み込み関数も追加しています。

```cpp
#include <M5StickC.h>

#define SAMPLE_PERIOD 0      // サンプリング間隔(micro秒)
#define SAMPLE_SIZE 150       //

//レジスタ書き込み用関数　ヘッダファイル変更できる方は mpu6886.hの同じ関数をpublic化して使いましょう。
void 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();
}

//レジスタ読み込み用関数　ヘッダファイル変更できる方は mpu6886.hの同じ関数をpublic化して使いましょう。
void 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 setup() {
    M5.begin();
    M5.Lcd.setRotation(3);
    M5.MPU6886.Init();                        // MPU6886を初期設定する
    Wire1.setClock(500000);                   // オンボードデバイスのI2C通信速度 デフォルト100k、Fast mode 400kまでが規定値。

    unsigned char buf;
    unsigned char regdata;

    regdata = 0x00;                                         //次行で設定するレジストリ設定値
    I2C_Write_NBytes(MPU6886_ADDRESS,0x19,1,&regdata);      //SAMPLE LATE DIVIDER,デフォルトはregdata = 0x05

    regdata = 0x07;                                         //次行で設定するレジストリ設定値
    I2C_Write_NBytes(MPU6886_ADDRESS,0x1A,1,&regdata);      //CONFIG,デフォルトは 0x01

    regdata = 0x18;                                         //次行で設定するレジストリ設定値
//    I2C_Write_NBytes(MPU6886_ADDRESS,0x1B,1,&regdata);      //GYRO_CONFIG,デフォルトは 0x18

    regdata = 0x10;                                         //次行で設定するレジストリ設定値
//    I2C_Write_NBytes(MPU6886_ADDRESS,0x1C,1,&regdata);      //ACCEL_CONFIG,デフォルトは 0x10

    regdata = 0x08;                                         //次行で設定するレジストリ設定値
    I2C_Write_NBytes(MPU6886_ADDRESS,0x1D,1,&regdata);      //ACCEL_CONFIG_2,デフォルトは0x00

    regdata = 0x07;                                         //次行で設定するレジストリ設定値
//    I2C_Write_NBytes(MPU6886_ADDRESS,0x6C,1,&regdata);      //PWR_MANEGEMENT_2,デフォルトは 0x00

    I2C_Read_NBytes(MPU6886_ADDRESS,0x19,1,&buf);
    Serial.print("0x19 value = ");
    Serial.println(buf);

    I2C_Read_NBytes(MPU6886_ADDRESS,0x1A,1,&buf);
    Serial.print("0x1A value = ");
    Serial.println(buf);
    
    I2C_Read_NBytes(MPU6886_ADDRESS,0x1B,1,&buf);
    Serial.print("0x1B value = ");
    Serial.println(buf);

    I2C_Read_NBytes(MPU6886_ADDRESS,0x1C,1,&buf);
    Serial.print("0x1C value = ");
    Serial.println(buf);

    I2C_Read_NBytes(MPU6886_ADDRESS,0x1D,1,&buf);
    Serial.print("0x1D value = ");
    Serial.println(buf);

    I2C_Read_NBytes(MPU6886_ADDRESS,0x6C,1,&buf);
    Serial.print("0x6C value = ");
    Serial.println(buf);
    

}

float ax[SAMPLE_SIZE], ay[SAMPLE_SIZE], az[SAMPLE_SIZE];  // 加速度データを読み出す変数

#define X0 5        // 横軸の描画開始座標
#define MINZ 0      // 縦軸の最小値
#define MAXZ 2000   // 縦軸の最大値
long t;                     //サンプリング周期測定用
long deltaT[SAMPLE_SIZE];   //サンプリング周期記録用

void loop() {
    M5.Lcd.fillScreen(BLACK);  // 画面をクリア

    for (int i = 0; i < SAMPLE_SIZE; i++) {
        M5.MPU6886.getAccelData(&ax[i],&ay[i],&az[i]);  // MPU6886から加速度を取得
        az[i] *= 1000;  // mGに変換
        if (i > 1) {
          int y0 = map((int)(az[i - 1]), MINZ, MAXZ, M5.Lcd.height(), 0);
          int y1 = map((int)(az[i]), MINZ, MAXZ, M5.Lcd.height(), 0);
          M5.Lcd.drawLine(i - 1 + X0, y0, i + X0, y1, GREEN);
        }
        
        deltaT[i]=micros()-t;
        if (i == 0) deltaT[i] = 0;
        t = micros();
        delayMicroseconds(SAMPLE_PERIOD);
    }

    //Serial.printでのデータ確認をしたいが、通信に時間が食われてサンプルリングレートが落ちるので
    //SANPLE_SIZE個のデータを採集後に、まとめてシリアル出力してサンプリングレートとデータを確認
    //不要な場合は以下のforループをコメントアウトで測定速度アップ
/*    
    for (int i = 0; i < SAMPLE_SIZE; i++){
        Serial.print("測定周期(μsec)：");
        Serial.print(deltaT[i]);
        Serial.print(" , Z軸方向加速度(mG)：");
        Serial.print(az[i]);
        Serial.print(" , サンプル番号：");
        Serial.println(i);  
    }
*/
}

```


#7.まとめ
MPU6886のデータシートを精査した結果、カタログスペック通りのサンプリングレートを確認、実用レベルでも3kHz以上のデータ採取の見込みが立ちました。
ただし、I2Cの規定速度を超過している点と、ローパスフィルタを外してしまっている点には注意が必要です。今後採取データをFFT解析する際によく注意しておきたいと思います。