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


目的

M5Stack GrayGPSモジュールを取り付けて、センサー値(GPS座標、加速度センサー、ジャイロセンサー)の値をLCDに出力したので、コード内容を共有します。Arduino IDEを使用する前提です。捕捉した衛星の数、HDOP、日時、GPS座標、加速度・ジャイロセンサー値、を画像の様に出力します。

コード

リポジトリから入手可能。

M5Stack GrayとGPSモジュールを準備すれば、上記リポジトリのコードで上記画像のLCD出表示力が得られます。

ハードウェア下準備

  • GPSモジュールをそのまま使うと内部アンテナを使う設定になっているため、衛星を捉える(FIXする)まで時間がかかります。外部アンテナにするとFIXが早くなるので、変更することをおすすめします。
  • 付属するアンテナを付けるだけでは切り替わらず、画像のようにGPSモジュール内の灰色の線を外して黒い線を接続する必要があります。
  • 灰色の線の電極は動いてショートを発生させる可能性があるので、テープなどで絶縁したほうがよいです。
  • 外部アンテナの先端は窓際あたりに設置すると効果的。屋内でも1分くらいでFIXしました。

ライブラリ下準備

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("");
  }
}

ここから下は内部関数
// 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);
}

終わりに

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