Spresenseであそぼう

この記事は[リンク情報システム](https://www.lis.co.jp/)が主催するイベント「[Tech Connect! Autumn](https://qiita.com/dgkz/items/4c3a449f3168d6c787cf)」のリレー記事です。
「Tech Connect! Autumn」は engineer.hanzomon のグループメンバによってリレーされます。

#はじめに
3日目担当のmk-takahashiです。

今回は7月31日に発売されたSONYのSpresenseであそんでみようと思います。
なぜこんなテーマかというと、たまたまSpresenseの製品紹介記事を見て興味を持ったからです。
そんなふわっとした動機で始めたので内容もふわっとしてゆるいですが、見ていただけると幸いです。

#Spresenseってなに？
SpresenseはSONYが出した、IoT向けのボードコンピュータです。
特徴としては、GNSS機能、ハイレゾオーディオ機能、マルチコアプロセッサ、低消費電力があります。
開発環境としては、専用のSpresense SDK、Arduino IDEがあります。

詳細は[SONY](https://developer.sony.com/ja/develop/spresense/#overview-content)のHPに書いているので、興味がある方はそちらをご確認ください。

#なにをしてあそぼう
今回はとりあえずSpesenseの特徴のひとつである"GNSS機能"を使って簡単なGPSロガーを作ってあそぼうと思います。

#使用したもの
・Spresenseメインボード
・Spresense拡張ボード
・microSDカード 1GB
・モバイルバッテリー
・USBケーブル A-microB

モバイルバッテリーはオートパワーオフ機能のないものを使用してください。
機能のあるものだとSpresenseの電力消費が少ないので勝手にパワーオフしてしまいます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/781f06e3-3ac6-3b7b-0a79-be952da2826b.jpeg" width=50%>


#開発環境
・Windows 10 Home
・Arduino IDE（[https://www.arduino.cc/](https://www.arduino.cc/)）

Arduino IDEは上記のサイトからダウンロードすることができます。
またArduino IDEに"Spresense Arduino Library"をインストールする必要がありますが、入れ方は[Spresense Arduino IDEチュートリアル](https://developer.sony.com/ja/develop/spresense/developer-tools/get-started-using-arduino-ide/set-up-the-arduino-ide)に記載してあるので見てください。
筆者はこの手順通りでインストールできました。

#ソースコード
Arduino IDEのスケッチ例の中にあるSpresenseのスケッチ例"gnss"のコードを流用します。
このコードはSpresenseで測位した位置情報をシリアル通信でPCに送って表示するものです。
今回はGPSロガーとして持ち運びをしたいので、PCに繋げての運用はツライです。
なので以下の２点について変更を加えて、Spresense単独でログを取れるようにしたいと思います。
　1.シリアル通信で送っているデータをSDカードに保存
　2.モニターなしでGNSSの受信状況がわかるようにLEDの点灯パターンを追加

```C:saveGNSS.ino
#include <GNSS.h>
#include <SDHCI.h>

#define STRING_BUFFER_SIZE  128

#define RESTART_CYCLE       (60 * 5)

static SpGnss Gnss;
SDClass SD;
File myFile;

static void Led_isActive(void)
{
  static int state = 1;
  if (state == 1)
  {
    ledOn(PIN_LED0);
    state = 0;
  }
  else
  {
    ledOff(PIN_LED0);
    state = 1;
  }
}

static void Led_isPosfix(bool state)
{
  if (state)
  {
    ledOn(PIN_LED1);
  }
  else
  {
    ledOff(PIN_LED1);
  }
}

static void GNSS_isError(bool state)
{
  if (state)
  {
    ledOn(PIN_LED3);
  }
  else
  {
    ledOff(PIN_LED3);
  }
}

static void SD_isError(bool state)
{
  if (state)
  {
    ledOn(PIN_LED2);
  }
  else
  {
    ledOff(PIN_LED2);
  }
}

void setup() {

  int error_flag = 0;
  int sd_error_flag = 0;

  sleep(3);

  myFile = SD.open("posData.txt", FILE_WRITE);

  if (!myFile)
  {
    SD_isError(true);
    myFile.close();
    exit(0);
  }

  ledOn(PIN_LED0);
  ledOn(PIN_LED1);
  ledOn(PIN_LED2);
  ledOn(PIN_LED3);

  Gnss.setDebugMode(PrintInfo);

  int result;

  result = Gnss.begin();

  if (result != 0)
  {
    myFile.println("Gnss begin error!!");
    error_flag = 1;
  }
  else
  {
    result = Gnss.start(COLD_START);
    if (result != 0)
    {
      myFile.println("Gnss start error!!");
      error_flag = 1;
    }
    else
    {
      myFile.println("Gnss setup OK");
    }
  }

  ledOff(PIN_LED0);
  ledOff(PIN_LED1);
  ledOff(PIN_LED2);
  ledOff(PIN_LED3);

  if (error_flag == 1)
  {
    GNSS_isError(true);
    myFile.close();
    exit(0);
  }
  myFile.close();
}

static void print_pos(SpNavData *pNavData)
{
  char StringBuffer[STRING_BUFFER_SIZE];

  snprintf(StringBuffer, STRING_BUFFER_SIZE, "%04d/%02d/%02d ", pNavData->time.year, pNavData->time.month, pNavData->time.day);
  myFile.print(StringBuffer);

  snprintf(StringBuffer, STRING_BUFFER_SIZE, "%02d:%02d:%02d.%06d, ", pNavData->time.hour, pNavData->time.minute, pNavData->time.sec, pNavData->time.usec);
  myFile.print(StringBuffer);

  snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSat:%2d, ", pNavData->numSatellites);
  myFile.print(StringBuffer);

  if (pNavData->posDataExist == 0)
  {
    myFile.print("pos not fixed");
    ledOff(PIN_LED3);
  }
  else
  {
    myFile.print("Lat=");
    myFile.print(pNavData->latitude, 6);
    myFile.print(", Lon=");
    myFile.print(pNavData->longitude, 6);

    ledOn(PIN_LED0);
    ledOn(PIN_LED3);
  }

  myFile.println("");
}

static void print_condition(SpNavData *pNavData)
{
  char StringBuffer[STRING_BUFFER_SIZE];
  unsigned long cnt;

  snprintf(StringBuffer, STRING_BUFFER_SIZE, "numSatellites:%2d\n", pNavData->numSatellites);
  myFile.print(StringBuffer);

  for (cnt = 0; cnt < pNavData->numSatellites; cnt++)
  {
    const char *pType = "---";

    if ( pNavData->isSatelliteTypeGps(cnt) )
    {
      pType = "GPS";
    }
    else if ( pNavData->isSatelliteTypeGlonass(cnt))
    {
      pType = "GLN";
    }

    unsigned long Id  = pNavData->getSatelliteId(cnt);
    unsigned long Elv = pNavData->getSatelliteElevation(cnt);
    unsigned long Azm = pNavData->getSatelliteAzimuth(cnt);
    float sigLevel = pNavData->getSatelliteSignalLevel(cnt);

    snprintf(StringBuffer, STRING_BUFFER_SIZE, "[%2d] Type:%s, Id:%2d, Elv:%2d, Azm:%3d, Lv:", cnt, pType, Id, Elv, Azm );
    myFile.print(StringBuffer);
    myFile.println(sigLevel, 6);
  }
}

void loop()
{
  static int LoopCount = 0;
  static int LastPrintMin = 0;

  Led_isActive();

  myFile = SD.open("posData.txt", FILE_WRITE);

  if (!myFile)
  {
    SD_isError(true);
    myFile.close();
    exit(0);
  }

  if (Gnss.waitUpdate(-1))
  {
    SpNavData NavData;
    Gnss.getNavData(&NavData);

    bool LedSet = (NavData.posDataExist && (NavData.posFixMode != FixInvalid));
    Led_isPosfix(LedSet);

    if (NavData.time.minute != LastPrintMin)
    {
      print_condition(&NavData);
      LastPrintMin = NavData.time.minute;
    }
    print_pos(&NavData);
  }
  else
  {
    myFile.println("data not update");
  }

  LoopCount++;
  if (LoopCount >= RESTART_CYCLE)
  {
    int error_flag = 0;

    ledOff(PIN_LED0);
    ledOff(PIN_LED3);

    Led_isPosfix(false);

    if (Gnss.stop() != 0)
    {
      myFile.println("Gnss stop error!!");
      error_flag = 1;
    }
    else if (Gnss.end() != 0)
    {
      myFile.println("Gnss end error!!");
      error_flag = 1;
    }
    else
    {
      myFile.println("Gnss stop OK.");
    }

    if (Gnss.begin() != 0)
    {
      myFile.println("Gnss begin error!!");
      error_flag = 1;
    }
    else if (Gnss.start(HOT_START) != 0)
    {
      myFile.println("Gnss start error!!");
      error_flag = 1;
    }
    else
    {
      myFile.println("Gnss restart OK.");
    }

    LoopCount = 0;

    if (error_flag == 1)
    {
      GNSS_isError(true);
      myFile.close();
      exit(0);
    }
  }
  myFile.close();
}
```

#測定
実際に測定を行います。
測定区間は新菊名橋付近から横浜アリーナのセブンイレブンまでです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/c6c9366b-a51a-713a-7ad2-2b48627e7b00.png" width=50%>

実際の測定結果が以下の通りです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/72a4285e-b585-2ab7-be98-a9f22dda5f14.png" width=50%>
※測定結果のプロット表示についてはtreeさんの[tree-maps](https://www.tree-maps.com/plot/)を利用させていただきました。

ちなみに取れたログはこんな感じです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/286312/c8bbcfc1-9f84-95ce-f3d7-ed325a59aeeb.png" width=50%>



#まとめ
スケッチ例を利用することで簡単にGPSのログをとることができました。
測定結果を見ると横浜アリーナ付近に来てから大幅に位置ずれしているのでアリーナを影響をもろに受けているのがわかり面白かったです。

#ちょっと苦労した話
最初の位置を測位するまでにトライ&エラーを繰り返してました。原因はたぶん測定時の持ち方と待ち時間です。

持ち方については当初、人目について恥ずかしいのでYシャツの胸ポケットに入れていたのですが、あんまり良くなかったみたいです。
結局Spresense本体を手で持って胸の高さ辺りで測定していました。

待ち時間についてはそのまんまです。私は待ち時間が足りなかったみたいです。
最初の位置を測位するのに多少時間がかかるので動かないで気長に待ちましょう。