Arduinoで実験 (GPSモジュール)

2013/04/15

秋月電子で販売しているGPSモジュール GT-723F を制御してみます。

スペックはRS-232Cで出力ありますが、TTLレベルの出力もサポートいるのでレベルコンバーター無しで Arduino とアクセスできます。


◆ GT-723F

Ground
Power (3.3V ~ 6.0V DC)
Serial Data In (RS-232 level)
Serial Data Out (RS-232 level)
Serial Data In (TTL level)  → Arduino D11
Serial Data Out (TTL level)  → Arduino D10

付属のコネクタを加工し、Arduino と配線します。 PCでモニタするのでGPSモジュールはソフトウェアシリアルで接続します。 本稿では10番ピンと11番ピンを使用します。

早速通信をしてみたいと思います。 スケッチをアップロード後、Arduino IDE のシリアルモニタを起動します。

//
// 105-01 GPS実験スケッチ
//

#define pinGpsRx 10 //GPS Rx (Data Out) のピン番号
#define pinGpsTx 11 //GPS Tx (Data In) のピン番号

#define SerialBaudrate 9600 //シリアル通信のボーレート
#define GpsBaudrate 9600 //GPSのボーレート

#include <SoftwareSerial.h>
SoftwareSerial sGps(pinGpsRx, pinGpsTx);  // ソフトウェアシリアルの設定

void setup() {
  Serial.begin(SerialBaudrate);  // シリアル通信の初期化
  Serial.println("Software Serial Test Start!");  // 開始メッセージの出力

  sGps.begin(GpsBaudrate);  // ソフトウェアシリアルの初期化
}

void loop() {
  if (sGps.available()) {  
    Serial.write(sGps.read());
  }
}

屋内や測位条件の悪い場所ではなかなか測位ができないと思います。 測位を開始するとモジュールのLEDが点滅するのでわかります。

久しぶりに GPSモジュールを使ってみた所、1時間しても測位を開始しませんでした。 もうひとつモジュールを持っているのでそちらで試してみたらこちらは5分程度で測位を開始しました。
なかなか測位を開始しないモジュールの方は、初めてつかったときに電源の配線を間違えて煙が出だしたことがあるので部品が怪しくなっているのかもしれません。 なんとか最初のモジュールも次の日には測位を開始し初めました。

測位を開始しなくても GPS モジュールはデータを吐き出しますが緯度、経度のデータが0なので見るとわかると思います。


◆ GPSモジュールからのデータ

複数の衛星を拾って測位出来ると意味のある緯度、経度のデータとなります。 以下がそのサンプルです。 緯度、経度は伏せています。

$GPRMC,071250.336,A,****.****,N,*****.****,E,000.0,337.4,150413,,,A*6C
$GPGGA,071250.336,****.****,N,*****.****,E,1,05,1.4,20.5,M,31.7,M,,0000*6D
$GPGSA,A,3,31,32,25,14,16,,,,,,,,2.7,1.4,2.3*35
$GPGSV,3,1,11,27,64,257,30,14,61,147,20,31,57,338,37,30,45,253,30*74
$GPGSV,3,2,11,29,44,089,10,25,29,044,26,32,25,287,33,16,20,235,18*78
$GPGSV,3,3,11,22,08,190,09,20,08,308,,06,00,198,*47
$GPVTG,337.4,T,,M,000.0,N,000.0,K,A*0E

次の表は$で始まる各コマンドの内容です。 以下の他にもコマンドはありますが割愛します。

$GPRMC $GPRMC,m1,c1,m2,c2,m3,c3,f1,f2,d1,d2.c4,c5*cc
m1 測位時刻(UTC) hhmmss.ss
c1 ステータス A:有効 V:無効
m2 緯度 ddmm.mmmmmm
c2 ステータス N:北緯 S:南緯
m3 経度 ddmm.mmmmmm
c3 ステータス W:西経 E:東経
f1 対地速度(ノット)
f2 進行方向 真北に対しての度
d1 日付 ddmmyy yy:20yy(西暦)
f3 地磁気の偏角 度
c4 偏角の方向 W:西 E:東
*cc チェックサム
$GPGGA $PGGA,m1,m2,c1,m3,c2,d1,d2,f1,f2,M,f3,M,f4,d3*cc
m1 測位時刻(UTC) hhmmss.ss
m2 緯度 ddmm.mmmmmm
c1 ステータス N:北緯 S:南緯
m3 経度 ddmm.mmmmmm
c2 ステータス W:西経 E:東経
d1 測位の種類 1:自立測位 2:差分補正
d2 測位に使用した人工衛星の個数
f1 位置の精度(水平方向) HDOPn.nn
f2 標高(基準楕円体からの標高) n.nn
M 高さの単位 M:メートル
f3 ジオイドからの距離(ジオイド高)n.nn
M ジオイド高の単位 M:メートル
f4 ディファレンシャル補正値
d3 RTCMの基地局ID
*cc チェックサム
$GPGSA $GPGSA,m1,m2,d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,d11,d11,pd,hd,vd*cc
m1 モード M:2D/3Dマニュアル A:2D/3D自動
m2 モード 1:無効 2:2D 3:3D
d1~d11 位置補正情報
pd PDOP
hd HDOP
vd VDOP
*cc チェックサム
$GPGSV $GPGSV,n1,n2,n3,d1-1,d1-2,d1-3,d1-4,d2-1,d2-2,d2-3,d2-4
               ,d3-1,d3-2,d3-3,d3-4,d4-1,d4-2,d4-3,d4-4*cc
n1 メッセージの個数
n2 メッセージ番号
n3 取得できた人工衛星の個数
d1~4-1 人工衛星のID番号(PRN)
d1~4-2 人工衛星の仰角
d1~4-3 人工衛星の方位角
d1~4-4 受信レベル(SN比 db)
*cc チェックサム
$GPVTG $GPVTG,f1,c1,f2,c2,f3,c3,f4,c4,c5*cc
f1 真北に対する進行方向(度)
c1 T
f2 磁北に対する進行方向(度)
c2 M
f3 対地速度(ノット)
c3 N
f4 対地速度(km/h)
c4 K
c5 モード A:単独測位 D:ディファレンシャル測位 N:無効
*cc チェックサム

◆ 目的データ行の抽出

GPSモジュールから取得したデータのうち、$GPGGA 以外は必要無いのでデータ取得時に仕分けします。

モジュールからのシリアル通信の各行末には改行コードが付いているので 0x0A で判定し、行データとします。

行データの先頭が $GPGGA で始まるかどうかを判定し、必要なデータかそうでないかを判定します。 判定には strncmp 関数を使用するため string.h をインクルードします。

$GPGGA で始まるデータならばPC宛にデータ送信し、シリアルモニタで確認します。

//
// 105-02 $GPGGA データ抽出実験スケッチ
//

#include <string.h> // strncmp に必要

#define pinGpsRx 10
#define pinGpsTx 11

#define SerialBaudrate 9600
#define GpsBaudrate 9600

#include <SoftwareSerial.h>
SoftwareSerial sGps(pinGpsRx, pinGpsTx);

void setup() {
  Serial.begin(SerialBaudrate);
  Serial.println("Software Serial Test Start!");

  sGps.begin(GpsBaudrate);
}

void loop() {
  char buf[256]; // 受信用バッファを255文字分確保
  char c; // 受信用文字
  int count = 0; // 受信用カウンタ

  do {
    if (sGps.available()) {
      buf[count] = sGps.read();
      count++;
    }
    if (count > 250) break; // バッファが溢れそうなら強制的に一行終了。
  } while(buf[count - 1] != 0x0A);
  buf[count] = '\0';

  if (0 == strncmp("$GPGGA", buf, 6)) { // 先頭6文字を比較
    Serial.print(buf); // データ内に改行コードが含まれているので print
  }
}

上記スケッチを実行すると $GPGGA の行のみが出力されるはずです。


◆ 目的データの選別

前段で $GPGGA のデータ行を文字列変数に格納したので、さらに必要なデータのみ個別の変数に格納してゆきます。

文字列の選別には strtok 関数を使用します。

以下のスケッチではとりあえず UTC時刻、緯度、経度を選別します。

//
// 105-03 $GPGGA 個別データ選別実験スケッチ
//

#include <string.h> // strncmp に必要

#define pinGpsRx 10
#define pinGpsTx 11

#define SerialBaudrate 9600
#define GpsBaudrate 9600

#include <SoftwareSerial.h>
SoftwareSerial sGps(pinGpsRx, pinGpsTx);

void setup() {
  Serial.begin(SerialBaudrate);
  Serial.println("Software Serial Test Start!");

  sGps.begin(GpsBaudrate);
}

void loop() {
  char buf[256];
  char c;
  int count = 0;
  char *gpsTime, *gpsLat, *gpsLong, *gpsTemp; // 個別データ用の文字列用ポインタ

  do {
    if (sGps.available()) {
      buf[count] = sGps.read();
      count++;
    }
    if (count > 250) break;
  } while(buf[count - 1] != 0x0A);
  buf[count] = '\0';

  if (0 == strncmp("$GPGGA", buf, 6)) {
    strtok(buf, ",");
    gpsTime = strtok(NULL, ","); // UTC時刻の抽出
    gpsLat = strtok(NULL, ",");  // 緯度の抽出
    strtok(NULL, ",");
    gpsLong = strtok(NULL, ","); // 経度の抽出

    Serial.print("Time = ");
    Serial.print(gpsTime);
    Serial.print(" : ");
    Serial.print("Latitude = ");
    Serial.print(gpsLat);
    Serial.print(" : ");
    Serial.print("Longitude = ");
    Serial.println(gpsLong);
  }
}

上記を参考に人工衛星の個数や標高を選別しデータとして確保します。

取得した緯度、経度のデータは小数点以下の値を持っています。 Arduino では double 型が 4byte のため 8byte の double 型で計算した時との精度の誤差が出ます。 あくまでも文字列としてデータを保存してゆく分は問題ありませんが、 Arduino での座標計算では精度が出ません。 ロガーとして利用し、座標計算はパソコンに任せる方が良いかもしれません。