M5Stackをつかってライトセーバーを作る

#概要
M5Stackの加速度センサで「振る」動作を検知するコントローラーを作った時のノウハウを共有します。

#完成品
ライトセーバー的なものを作りたかった。M5Stackをチャンバラの柄に固定し「振る」動作に合わせてM5Stackから音を鳴らします。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ライトセーバー作ってみた。M5stackを鍔の近くに括り付けて加速度検知して音鳴らすだけ。LEDも付けてみたい。 <a href="https://t.co/CKfUmpilKp">pic.twitter.com/CKfUmpilKp</a></p>&mdash; unagi (@UnagiHuman) <a href="https://twitter.com/UnagiHuman/status/1097826662316572673?ref_src=twsrc%5Etfw">2019年2月19日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#動作環境
Windows10
M5Stack Gray
[チャンバラキングネオ](https://www.amazon.co.jp/dp/B01BQ1UJ70/ref=asc_df_B01BQ1UJ702588098/?tag=jpgo-22&creative=9315&creativeASIN=B01BQ1UJ70&linkCode=df0&hvadid=266606158728&hvpos=1o1&hvnetw=g&hvrand=1333908286913248248&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009310&hvtargid=pla-457808356471)
Arduino Editor

#M5Stackの設定
WindowsでM5stackをarduinoとして利用するための設定をします。以下の記事を参考にしつつ設定しました。 
http://tutuurara.com/m5stack-arduino/

#加速度の取り方
スケッチ例->M5Stack->Modules->MPU9250->MPU9250BasicAHRSを参考にします。
これの処理をもとにして必要な処理を追加していきます。

#重力成分の削除
![m5stack重力加速度.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/041ea589-2d61-bb46-ba1a-7b966e22330f.png)
さて、加速度の測定値には重力成分が必ず含まれてしまいます。下の図はM5Stackを水平な場所に置いた状態の加速度のxyz軸のグラフですが重力成分だけオフセットが加わっている事が分かると思います。「振る」というアクションをとるには加速度に閾値を設定してそれを超えたら判定するような方法が考えられますが、それには重力成分が邪魔になります。
重力成分のように、一定の方向に一定の加速度があるようなオフセット成分は0Hzの周波数帯に現れるので、ハイパスフィルタで低周波成分をカットすることで重力の影響を消す事が出来ます。

Arduinoで使えるローパス、ハイパス、バンドパスフィルタのライブラリを利用します。
https://github.com/JonHub/Filters

ハイパスフィルタで0hz近辺をカットすれば良いのでカットオフ周波数を2hzぐらいに設定します。

setup()時にfilterを定義

```cpp:
  float filterFrequency = 2;
  filterOneHighpassX = FilterOnePole( HIGHPASS, filterFrequency,0 );
  filterOneHighpassY = FilterOnePole( HIGHPASS, filterFrequency,0 );
  filterOneHighpassZ = FilterOnePole( HIGHPASS, filterFrequency,0 );
```

loop()時に生データをフィルタ

```cpp:
  filterOneHighpassX.input(IMU.ax);
  float ax = filterOneHighpassX.output();
  filterOneHighpassY.input(IMU.ay);
  float ay = filterOneHighpassY.output();
  filterOneHighpassZ.input(IMU.az);
  float az = filterOneHighpassZ.output();
```

結果重力成分がフィルタリングされて水平においたときにxyz軸の加速度が0になる事が分かります。これで「振る」で発生する加速度のみを得る事が出来ます。

![accfilter.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/94cec23f-7bfc-1a69-6cc9-553ddf3ab7a4.png)

#「振る」検知アルゴリズム
はじめ、「振る」を検知するときに単純に「加速度の絶対値が閾値を超えたとき」にしてしまうと2~3回連続で「振る」判定をしてしまってました。原因を調べる為にM5Stackの加速度をモニターして分かったのですが、加速度はひとつの山だけでなく明らかに振った方向とは逆方向にも加速度が検知されてます。その為に単純に閾値のみで判定してしまうと連続で振った判定をしてしまうのです。

![filterdAcc.PNG](https://qiita-image-store.s3.amazonaws.com/0/116884/d29d2c07-a898-1a5b-099a-e18bebc8af04.png)

これを利用して加速度の閾値を検知して「振る」アクション開始を開始し、加速度の絶対値が０付近になったら（ある閾値を下回ったら）「振る」を終了するようにします。

* 「振る」検知アルゴリズム
    * xyz軸のいずれかの加速度の絶対値が閾値を超えると「振る」アクションが開始される
    * xyz軸のいずれかの加速度が閾値を下回ったら「振る」アクションが終了する
    * 終了を検知するまで次のアクションを検知しない。

```cpp

float detectBeginValue = 1.2;
float detectEndValue = 0.7;
boolean IsDetectPower = true;

loop()
{
       float ax = HighPassFilter(IMU.ax);
       float ay = HighPassFilter(IMU.ay);
       float az = HighPassFilter(IMU.az);
     

       if(!IsDetectPower) boolean detectThrethold = DetectThrethold(ax, ay, ax, detectBeginValue);
       if (detectThrethold && !IsDetectPower)
       {
        IsDetectPower = true;
        ///「振るを検知」した後の処理を挿入する
       }

       if(IsDetectPower){
        if(!DetectThrethold(ax, ay, ax, detectEndValue))
        {
          IsDetectPower = false;
        }
       }

}

boolean DetectThrethold(float ax, float ay, float az, float detectPowerValue)
{
   float powerX2 = ax * ax;
   float powerY2 = ay * ay;
   float powerZ2 = az * az;
   float powerValue2 = detectPowerValue * detectPowerValue;

   if(powerX2 > powerY2 && powerX2 > powerZ2)
   {
      if(powerX2 > powerValue2)
      {
         return true;
      }
   }
   else if (powerY2 > powerX2 && powerY2 > powerZ2)
   {
      if (powerY2 > powerValue2)
      {
         return true;
      }

  }
  else if (powerZ2 > powerY2 && powerZ2 > powerX2)
  {
     if (powerZ2 > powerValue2)
     {
        return true;
     }
  }

  return false;
}
```

# 音を鳴らす
方法は２種類あって、SDカード経由か内蔵のフラッシュメモリ経由があります。手持ちにSDカードが無かったのでフラッシュメモリ経由で再生する方法を試しました。手順としてはPCからM5stackのフラッシュメモリにmp3ファイルを転送してからM5Stackで再生します。

PCからM5Stackのフラッシュメモリにファイルを転送する方法は以下の記事を参考にしました。
https://www.mgo-tec.com/blog-entry-spiffs-uploader-plugin-arduino-esp32.html

mp3再生用のソースが以下になります。

```cpp
   AudioGeneratorMP3 *mp3;
   AudioFileSourceSPIFFS *file;
   AudioOutputI2S *out;
   AudioFileSourceID3 *id3;

   file = new AudioFileSourceSPIFFS("/filename.mp3");
   id3 = new AudioFileSourceID3(file);
   out = new AudioOutputI2S(0, 1); // Output to builtInDAC
   out->SetOutputModeMono(true);
   mp3 = new AudioGeneratorMP3();
   mp3->begin(id3, out);
   while(mp3->isRunning()){
     if (!mp3->loop()) mp3->stop();
   }
```

# まとめ
M5Stackの加速度センサを使ってライトセーバーっぽいのを作った。加速度センサ値から「振る」アクションを検知する方法はM5stackに限らず色々使えそう。