GPS モジュールで位置情報を取得してみる

 GPSモジュールで位置情報を使って何かやってみたかったので、下記モジュールを買って位置情報の取得を試してみました。今回は Seeeduino から GPS モジュールで取得した位置情報を読み出してみます。

www.switch-science.com

 チュートリアルが下記ページで公開されているので、この内容に沿って試してみました。

チュートリアルページ
https://learn.adafruit.com/adafruit-ultimate-gps

ピンヘッダの半田付け

 モジュールにはピンヘッダが同梱されていますが装着はされていないので、ブレッドボードで試せるように下記写真のように半田付けしました。

f:id:akanuma-hiroaki:20180603002826j:plain

シリアルポートから直接読み出す

 まずは GPS モジュールを Seeeduino の シリアル - USB コンバータに接続して直接位置情報を読み出してみます。配線は下記の回路図のようになります。 GPS モジュールの TX/RX ポートを Seeeduino の TX/RX ポートに接続していますので、 Seeeduino のチップは通さずに値を読み出す形になります。 電源電圧は 3.3V - 5V で、 Seeeduino の 5V 出力から GPS モジュールの VIN に入力しています。 Seeeduino は動作電圧も 3.3V か 5V を切り替えられるようになっていますが、今回は 5V で使用しています。

f:id:akanuma-hiroaki:20180603000356p:plain

 GPS モジュールは取得した位置情報(FIX)を毎秒シリアルポートに出力します。今回はシリアルモニタで直接確認するだけなので、 Seeeduino のファームウェア側でやることは特にないため、コードは下記のようにメソッド定義のみで Seeeduino にフラッシュします。

void setup() {    
}

void loop() {
}

 正常にフラッシュされると GPS モジュールからの値の出力が開始されます。今回は Arduino Web Editor を使い、下記画像のようにシリアルモニターで出力を確認してみました。

f:id:akanuma-hiroaki:20180603000834p:plain

 GPS の位置情報は NMEA というフォーマットで出力されます。 NMEA の中でもいくつかのデータタイプがあり、各行の先頭でその行がどのデータタイプのデータかを示しています。よく使われるのは GPRMC というデータタイプのようで、チュートリアルでも GPRMC を対象としているので、今回は GPRMC について確認します。

 また、GPS モジュールでは FIX が取得できてもできなくても値を出力します。上記画像では室内で動作させていたため FIX が取得できず、カンマばかりで出力されていますが、屋外で動作させると下記のように FIX が取得できます。(緯度・経度はサンプル値に置き換えてあります)

$GPRMC,081033.000,A,1234.5678,N,12345.6789,E,0.36,168.34,020618,,,A*65

 それぞれの値の意味は下記の通りです。

$GPRMC: データタイプ

081033.000: UTCにおけるタイムスタンプ。先頭から2桁ずつ 時、分、秒を表し、ドット以降の3桁がミリ秒を表す。(08:10:33.000 UTC)

A: ステータスコード。A は FIX が取得できていること(Active)を表し、 V は FIX が取得できていないこと(Void)を表す。

1234.5678,N: 緯度情報。先頭2〜3桁が度数を表し、ドットの前の2桁以降で分数を表す。また、 N は北緯(North)を表し、南緯(South)の場合は S で表される。(北緯 12度 34.5678分)

12345.6789,E: 経度情報。先頭2〜3桁が度数を表し、ドットの前の2桁以降で分数を表す。また、 E は東経(East)を表し、西経(West)の場合は W で表される。(東経 123度 45.6789分)

0.36: 移動速度(ノット)

168.34: 移動方向(168.34度)

020618: UTCにおける日付。フォーマットは dd/mm/yy。(2018/06/02)

A*65: チェックサム

 ちなみに Google Map では緯度、経度については N, S, W, E の代わりに +/- を使用する必要があります。N, E は +、 S, W は - で表しますので、上記の内容は +12 34.5678', +123 45.6789' とする必要があります。

Seeeduino 経由で位置情報を読み出す

 では次に Seeeduino を経由して GPS モジュールの位置情報を読み出してみたいと思います。配線図は下記の通りです。 GPS モジュールの TX/RX ピンへの接続をそれぞれ Seeduino の 3番ピン/2番ピン に変更しただけです。

f:id:akanuma-hiroaki:20180603000901p:plain

 続いてファームウェアを用意します。今回使用している GPS モジュールについては Adafruit から Arduino 用のライブラリが提供されています。ライブラリにはサンプルコードも含まれているので今回はそれをそのまま使用してみます。

github.com

 Arduino Web Editor であれば上記サイトからダウンロードしなくても、 Library Manager から Favorite に登録して使うことができます。

f:id:akanuma-hiroaki:20180603001014p:plain

 ライブラリの中の Examples フォルダにサンプルコードが入っていますので、今回はその中から echo というサンプルを使用します。

f:id:akanuma-hiroaki:20180603001108p:plain

 コンパイルと Seeeduino へのフラッシュができるように自分のスペースにサンプルコードを保存します。

f:id:akanuma-hiroaki:20180603001532p:plain

 コピーが保存されたらそのまま Seeduino にフラッシュすると、先ほどと同様に GPS モジュールから取得された位置情報がシリアルモニタに出力されるようになります。

f:id:akanuma-hiroaki:20180603001205p:plain

 ちなみにサンプルコードからコメント行を取り除いたコードは下記のようになります。今回の GPS モジュールではデータの出力頻度を 1Hz, 5Hz, 10Hz から選択できるようになっていて、下記のコードは 1Hz に設定した例になります。

#include <Adafruit_GPS.h>

#include <Adafruit_GPS.h>
#if ARDUINO >= 100
 #include <SoftwareSerial.h>
#else
#endif

#if ARDUINO >= 100
  SoftwareSerial mySerial(3, 2);
#else
  NewSoftSerial mySerial(3, 2);
#endif
Adafruit_GPS GPS(&mySerial);


#define GPSECHO  true

boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()  
{    
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PMTK_API_SET_FIX_CTL_1HZ);

  GPS.sendCommand(PGCMD_ANTENNA);

  useInterrupt(true);
  
  delay(1000);
}

SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  if (GPSECHO)
    if (c) UDR0 = c;  
}

void useInterrupt(boolean v) {
  if (v) {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}


void loop()                     // run over and over again
{
}

位置情報のパースサンプル

 位置情報の生データはそのままではみづらいので、サンプルの中からもう1つ、位置情報をパースしている例を見てみます。先ほどの echo の場合と同じような形で、今回は parsing というサンプルコードをコピーします。

f:id:akanuma-hiroaki:20180603001227p:plain

 コメントを除いたコードは下記のようになります。 GPS ライブラリを使用し、パースした位置情報をシリアルモニタに出力しています。

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(3, 2);

Adafruit_GPS GPS(&mySerial);

#define GPSECHO  true

boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()  
{
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate

  GPS.sendCommand(PGCMD_ANTENNA);

  useInterrupt(true);

  delay(1000);
  mySerial.println(PMTK_Q_RELEASE);
}

SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
#endif
}

void useInterrupt(boolean v) {
  if (v) {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

uint32_t timer = millis();
void loop()                     // run over and over again
{
  if (! usingInterrupt) {
    char c = GPS.read();
    if (GPSECHO)
      if (c) Serial.print(c);
  }
  
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }

  if (timer > millis())  timer = millis();

  if (millis() - timer > 2000) { 
    timer = millis(); // reset the timer
    
    Serial.print("\nTime: ");
    Serial.print(GPS.hour, DEC); Serial.print(':');
    Serial.print(GPS.minute, DEC); Serial.print(':');
    Serial.print(GPS.seconds, DEC); Serial.print('.');
    Serial.println(GPS.milliseconds);
    Serial.print("Date: ");
    Serial.print(GPS.day, DEC); Serial.print('/');
    Serial.print(GPS.month, DEC); Serial.print("/20");
    Serial.println(GPS.year, DEC);
    Serial.print("Fix: "); Serial.print((int)GPS.fix);
    Serial.print(" quality: "); Serial.println((int)GPS.fixquality); 
    if (GPS.fix) {
      Serial.print("Location: ");
      Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
      Serial.print(", "); 
      Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
      Serial.print("Location (in degrees, works with Google Maps): ");
      Serial.print(GPS.latitudeDegrees, 4);
      Serial.print(", "); 
      Serial.println(GPS.longitudeDegrees, 4);
      
      Serial.print("Speed (knots): "); Serial.println(GPS.speed);
      Serial.print("Angle: "); Serial.println(GPS.angle);
      Serial.print("Altitude: "); Serial.println(GPS.altitude);
      Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
    }
  }
}

 このコードを Seeeduino にフラッシュすると、位置情報を下記のように見易くフォーマットしてシリアルモニタに出力します。

Time: 8:27:57.0
Date: 3/6/2018
Fix: 1 quality: 1
Location: 1234.5678N, 12345.6789E
Location (in degrees, works with Google Maps): 12.3456, 123.4567
Speed (knots): 0.28
Angle: 9.54
Altitude: 130.50
Satellites: 5

まとめ

 サンプルコードやライブラリのおかげもあり、位置情報の取得自体はとても簡単にできてしまいました。モジュールのサイズも小さいので、 Trinket などの小型のマイコンやコイン型電池等と組み合わせればとてもコンパクトなデバイスが作れそうです。ただその場合はネットワークへの接続が課題になってくるので、リアルタイムでのトラッキングではなく、このモジュールが持っているロギング機能で位置情報データを蓄積しておいて、あとで BLE や Wi-Fi 等に接続できる環境になったらデータを取り出して利用するような用途であれば比較的簡単に作成できそうに思いました。