M5Stack GrayにGPSモジュールを取り付けてセンサー値をLCDに表示

#目的
[M5Stack Gray](https://www.switch-science.com/catalog/3648/)に[GPSモジュール](https://www.switch-science.com/catalog/3861/)を取り付けて、センサー値（GPS座標、加速度センサー、ジャイロセンサー）の値をLCDに出力したので、コード内容を共有します。**Arduino IDEを使用する前提**です。捕捉した衛星の数、HDOP、日時、GPS座標、加速度・ジャイロセンサー値、を画像の様に出力します。
![スクリーンショット 2020-12-21 23.37.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/74375567-3913-3442-0031-22d97c7788fd.png)

#コード
[リポジトリ](https://github.com/takurot/m5statckgray_gps)から入手可能。

M5Stack GrayとGPSモジュールを準備すれば、上記リポジトリのコードで上記画像のLCD出表示力が得られます。
#ハードウェア下準備
- GPSモジュールを**そのまま使うと内部アンテナを使う設定になっている**ため、衛星を捉える（FIXする）まで時間がかかります。外部アンテナにするとFIXが早くなるので、変更することをおすすめします。
- **付属するアンテナを付けるだけでは切り替わらず**、画像のようにGPSモジュール内の灰色の線を外して黒い線を接続する必要があります。
- 灰色の線の電極は動いてショートを発生させる可能性があるので、テープなどで絶縁したほうがよいです。
- 外部アンテナの先端は窓際あたりに設置すると効果的。屋内でも1分くらいでFIXしました。
![スクリーンショット 2020-12-21 23.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512829/dca58c92-f9ad-6892-bb99-05c73b0b345b.png)

#ライブラリ下準備
TinyGPSPlus:http://arduiniana.org/libraries/tinygpsplus/ からライブラリをダウンロード（.zip）
スケッチ→ライブラリをインクルード→ZIP形式のライブラリをインストール
#コード解説
サンプル例のGPS_NEO_M8N→FullExampleのコードをベースとして、MPU9250のセンサー出力の記述を足しています。

```
#define M5STACK_MPU9250 

#include <M5Stack.h>
#include <TinyGPS++.h>

static const uint32_t GPSBaud = 9600;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
HardwareSerial ss(2);
float accX = 0.0F;
float accY = 0.0F;
float accZ = 0.0F;

float gyroX = 0.0F;
float gyroY = 0.0F;
float gyroZ = 0.0F;

float pitch = 0.0F;
float roll  = 0.0F;
float yaw   = 0.0F;

float temp = 0.0F;
```

セットアップでスピーカーオフにするとスピーカーからのノイズが減ります（結構ノイズ大きい）。

```
void setup()
{
  M5.begin();
  dacWrite(25, 0); /* スピーカーオフにしてノイズを消す */
  M5.Power.begin();
  ss.begin(GPSBaud);
  M5.IMU.Init();
  M5.Lcd.setTextSize(1);
}

void loop()
{
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(WHITE, BLACK);

  M5.Lcd.print("Num of Sat[-]:");
  printInt(gps.satellites.value(), gps.satellites.isValid(), 5);
  M5.Lcd.print("HDOP[-]:");
  printInt(gps.hdop.value(), gps.hdop.isValid(), 5);
  M5.Lcd.print("DateTime[-]:");
  printDateTime(gps.date, gps.time);
  M5.Lcd.println();

  M5.Lcd.setTextColor(BLUE, BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.print("LAT[-]:");
  printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
  M5.Lcd.print("LNG[-]:");
  printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
//  printInt(gps.location.age(), gps.location.isValid(), 5);
  M5.Lcd.print("ALT[-]:");
  printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2);
//  printFloat(gps.course.deg(), gps.course.isValid(), 7, 2);
//  printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2);
//  printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.deg()) : "*** ", 6);
//  printInt(gps.charsProcessed(), true, 6);
//  printInt(gps.sentencesWithFix(), true, 10);
//  printInt(gps.failedChecksum(), true, 9);

  M5.Lcd.println();

  if (millis() > 5000 && gps.charsProcessed() < 10)
    M5.Lcd.println(F("No GPS data received: check wiring"));
```

ここまではGPS座標の取得と表示。FIXするまでは表示が「***」になります。「***」が長く続く場合（1min以上かかる）は、アンテナの接続を確認し、位置を変えてみてください。

ここから下は加速度・ジャイロセンサーの値を取得・表示。
- GPSモジュールは1sec間隔でアクセスする必要があり（間隔が短いとおかしくなる）、1secの間は加速度・ジャイロセンサーの値を取得・表示します。コード上では1msec間隔でアクセスして10回平均を計算して表示、を100回繰り返します。平均化回数を増やすと更新頻度が減ります。精度と応答性のトレードオフだと思えば良く、適宜調整してください。
- カーソル設定をしてるのは、値を更新していないGPS側の値がLCDから消えてしまうためです。
- 温度センサーを出力してもよいですが、M5Stack内部の温度なので外気温とは違います（結構高め）。


```
  int div_num = 10; //平均化回数
  int total_delay_num = 1000 / div_num; //平均化して1secになるようにループ回数を設定

  for(int ii = 0;ii < total_delay_num;ii++){
    float ave_accX = 0.0F;
    float ave_accY = 0.0F;
    float ave_accZ = 0.0F;
    float ave_gyroX = 0.0F;
    float ave_gyroY = 0.0F;
    float ave_gyroZ = 0.0F;
    float ave_pitch = 0.0F;
    float ave_roll  = 0.0F;
    float ave_yaw   = 0.0F;
    float ave_temp = 0.0F;

    //ジャイロ、加速度、温度センサーの平均化
    for(int jj = 0;jj < div_num;jj++){
      M5.IMU.getGyroData(&gyroX,&gyroY,&gyroZ);
      M5.IMU.getAccelData(&accX,&accY,&accZ);
      M5.IMU.getAhrsData(&pitch,&roll,&yaw);
      M5.IMU.getTempData(&temp);

      ave_accX  += accX;
      ave_accY  += accY;
      ave_accZ  += accZ;
      ave_gyroX += gyroX;
      ave_gyroY += gyroY;
      ave_gyroZ += gyroZ;
      ave_pitch += pitch;
      ave_roll  += roll;
      ave_yaw   += yaw;
      ave_temp  += temp;

      delay(1);
    }

    ave_accX  /= div_num;
    ave_accY  /= div_num;
    ave_accZ  /= div_num;
    ave_gyroX /= div_num;
    ave_gyroY /= div_num;
    ave_gyroZ /= div_num;
    ave_pitch /= div_num;
    ave_roll  /= div_num;
    ave_yaw   /= div_num;
    ave_temp  /= div_num;
  
    M5.Lcd.setCursor(0, 90);
    M5.Lcd.setTextColor(GREEN , BLACK);
    M5.Lcd.println("GYRO XYZ[o/s]");
    M5.Lcd.printf("%6.2f %6.2f %6.2f          ", ave_gyroX, ave_gyroY, ave_gyroZ);
    M5.Lcd.println("");
    M5.Lcd.println("ACC[G]");
    M5.Lcd.printf("%5.2f %5.2f %5.2f          ", ave_accX, ave_accY, ave_accZ);
    M5.Lcd.println("");
    M5.Lcd.println("P-R-Y[deg]");
    M5.Lcd.printf("%5.2f %5.2f %5.2f          ", ave_pitch, ave_roll, ave_yaw);
    M5.Lcd.println("");
//    M5.Lcd.printf("Temperature[C] %.2f", ave_temp);
//    M5.Lcd.println("");
  }
}
```

<details><summary>ここから下は内部関数</summary><div>

```
// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do{
    while (ss.available())
      gps.encode(ss.read());
  }while(millis() - start < ms);
}

static void printFloat(float val, bool valid, int len, int prec)
{
  if(!valid){
    while (len-- > 1)
      M5.Lcd.print('*');
    M5.Lcd.print(' ');
  }else{
    M5.Lcd.print(val, prec);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i=flen; i<len; ++i){
      M5.Lcd.print(' ');
    }
  }
  M5.Lcd.println();
  smartDelay(0);
}

static void printInt(unsigned long val, bool valid, int len)
{
  char sz[32] = "*****************";
  if(valid)
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for(int i=strlen(sz); i<len; ++i)
    sz[i] = ' ';
  if(len > 0) 
    sz[len-1] = ' ';
  M5.Lcd.print(sz);
  M5.Lcd.println();
  smartDelay(0);
}

static void printDateTime(TinyGPSDate &d, TinyGPSTime &t)
{
  if(!d.isValid()){
    M5.Lcd.print(F("********** "));
  }else{
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
    M5.Lcd.print(sz);
  }
  
  if(!t.isValid()){
    M5.Lcd.print(F("******** "));
  }else{
    char sz[32];
    sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
    M5.Lcd.print(sz);
  }

  printInt(d.age(), d.isValid(), 5);
  smartDelay(0);
}

static void printStr(const char *str, int len)
{
  int slen = strlen(str);
  for(int i=0; i<len; ++i){
    M5.Lcd.print(i<slen ? str[i] : ' ');
  }
  M5.Lcd.println();
  smartDelay(0);
}
```

</div></details>

#終わりに
M5Stackを久しぶりにちゃんと使ってみました。例題がバラバラに存在してたので、結合して必要な情報を出しただけですが、何かの役に立ったならLGTMお願いします。