2011/12/15

ArduinoでGPSモジュールのNMEAメッセージを解析する

ママチャリグランプリ用に購入したGPSモジュール(GT-723F)ですが、生のデータは数字の羅列だけなので、人間が読むには厳しい状態となっています。また、いろいろな種類の行が出てくるので、必要なところだけフィルタリングする必要があります。
そこで、ArduinoでGPSモジュールの出力を解析してみました。

NMEAデータにはいろいろな行がありますが、時間と緯度・経度の情報を含んでいるGPGGA行のみ取り出して処理することにしました。
カンマ区切りなのでプログラム自体はそんなに難しくはなかったのですが、肝心の緯度・経度が現在地と大幅に違うところを指していました。

調べてみたところ、google mapに表示するためには座標を少し変換してあげなければならないようです。
下記ページを元に座標変換を行うと、見事に現在地がgoogle mapに表示されました。

GT-730F/LとNMEAデータから得られる経度緯度

次はXBee Proの通信距離の実験です。
土曜日晴れたら見通しのよい公園などで実験してみたいと思います。




/*
 GT-723から取り出したNMEAデータを解析するプログラム
 緯度・経度を取り出します。
 arduino用。
 2011-12 lhaplus 8888
 This code is in the public domain.
 */

//シリアル接続開始してバッファ初期化
void setup()
{
  Serial.begin(9600);
  ClearBuffer;
}

//バッファなど
const int arrayLength=128;  //max buffer size.
char gpsRecvArray[arrayLength];

//GPGGAフィールド定義
const byte C_UTC=1;  //UTC時間フィールド
const byte C_LATITUDE=2;//緯度
const byte C_LATITUDE_HEMISPHERE=3;//北緯・南緯フィールド
const byte C_LONGITUDE=4;
const byte C_LONGITUDE_HEMISPHERE=5;
const byte C_QUALITY=6;  //品質
const byte C_NUMOFSAT=7;//衛星の数
const byte C_PRECISION=8;//精度?
const byte C_ANTENNA_HEIGHT=9;
const byte C_GEOIDAL_HEIGHT=11;
const byte C_NORTH='N';
const byte C_SOUTH='S';
const byte C_EAST='E';
const byte C_WEST='W';
int indexLoc=0;
 
void loop() 
{
  char recvChar=0;//受信バッファ
  while(Serial.available())
  {
    recvChar=Serial.read();
    //check first $ mark.
   if((36==recvChar))
    {
      //start gps log line.
      //clear all buffer.
      ClearBuffer();
      indexLoc=0;
    }

    if(10==recvChar)
    {
      Serial.println("Receive LF");
      //check gps log buffer.
      CheckGPS();
      break;
    }
    else
    {
      //copy to buffer
      gpsRecvArray[indexLoc]=recvChar;
      indexLoc++;
      //限界超えたらバッファクリア
      if(indexLoc>=arrayLength)ClearBuffer();

    }
  }  

  delay(10);

}

//受信バッファのクリア
void ClearBuffer()
{
  memset(gpsRecvArray,0,sizeof(gpsRecvArray));
  Serial.println("Buffer cleared.");
}


//check GPS char.
void CheckGPS()
{
  /*  
   GT-723FのNMEA出力の中で、緯度・経度に関係しているのはGPGGAの行。
   フォーマット: $GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>,*<13><CR><LF> 
    意味:
       1  104549.04  UTC time in hhmmss.ss format, 000000.00 ~ 235959.99 
       2  2447.2038  Latitude(緯度)  ddmm.mmmm format 読み取り用の0が入れられる場合もあり
       3  N  Latitude hemisphere indicator, 窶朦窶・= North, 窶朶窶・= South 
       4  12100.4990  Longitude(経度) dddmm.mmmm format 読み取り用の0が入れられる場合もあり
       5  E  Longitude hemisphere indicator, 'E' = East, 'W' = West 
       6  1  Position fix quality indicator 
           0: position fix unavailable 
           1: valid position fix, SPS mode  
           2: valid position fix, differential GPS mode 
       7  06  Number of satellites in use, 00 ~ 12 
       8  01.7  Horizontal dilution of precision, 00.0 ~ 99.9 
       9  00078.8  Antenna height above/below mean sea level, -9999.9 ~ 17999.9     
       10  0016.3  Geoidal height, -999.9 ~ 9999.9 
       11  Age of DGPS data since last valid RTCM transmission in xxx format (seconds) NULL when DGPS not used 
       12  Differential reference station ID, 0000 ~ 1023 NULL when DGPS not used 
       13  5C  Checksum
       //時刻、緯度、経度を取ってくるには、1,2,4のフィールドを回収すればOK
   */

  //まずはGPGGAの行かをチェック。
  if(('G'==gpsRecvArray[4])&&('A'==gpsRecvArray[5]))
  {

      Serial.println("GPGGA DATA.Start process");

    //受信バッファを1個づつ探していく
    int charIndex=0;
    int colNumber=0;//列番号。取説にあわせて1から始める
    int colCharIdx=0;//対象列の文字配列のインデックス

    int i=0;
    //フィールドごとに区切っていく
    //とりあえず受信バッファ 20文字。
    char buff[20];
    memset(buff,0,sizeof(buff));


    double degreeLAT;
    double degreeLONG;
    char c=0;    
    for(charIndex=0;charIndex<arrayLength;charIndex++)
    {
      c=gpsRecvArray[charIndex];
      //NULL文字にあたったら終了
      if(0==c) break;

      if(44==c)//区切り文字の カンマ
      {
        //列切り替え
        //切り替える前に、今の行バッファを出力しておく

        switch(colNumber)
        {
        case C_UTC://UTC時間フィールド
          Serial.print("UTC:");
          Serial.println(buff);
          break;  
        case C_LATITUDE://緯度
          Serial.print("LAT:");
          degreeLAT=DMmtoDegree(buff,sizeof(buff));
          Serial.println(degreeLAT,8);
          break;
        case C_LATITUDE_HEMISPHERE://北緯・南緯フィールド
          Serial.print("N/S:");
          break;
        case  C_LONGITUDE:
          Serial.print("LONG:");
          degreeLONG=DMmtoDegree(buff,sizeof(buff));
          Serial.println(degreeLONG,8);
          break;
        case C_NUMOFSAT://衛星の数
          Serial.print("Num of SAT:");
          Serial.println(buff);
          break;


          default:break;
        }
        colNumber++;
        colCharIdx=0;
        memset(buff,0,sizeof(buff));
        //次の行へ切り替える。文字インデックスも初期化
      }
      else
      {
        buff[colCharIdx]=c;
        colCharIdx++;
      }
    }
  }
  else
  {
         Serial.println("NOT GPGGA DATA");
         Serial.println(gpsRecvArray);
         
  }
}


//GT-723はDMM形式というものになっている模様。
//GoogleMapに表示するにはDegree形式にしなければならないようです。
//参考:http://gps.meblog.biz/article/2761601.html
//Charの配列から、degree表示に変換する。
//2447.2038 とか
//12100.4990 の形式。
//Dの部分が3桁か2桁、
//Mの部分は2桁固定、
//mの部分は4桁固定。
double DMmtoDegree(const char* buff,const int bufflen)
{
  int d=0;
  int m=0;
  int s=0;

  double tmp=0;
  int i;
  int period;
  char atoibuff[4];
  double value=0;

  period=-1;
  for (i=0;i<bufflen;i++)
  {
    //まずはピリオドを探す。
    if('.'==buff[i])
      period=i;
  }

  if(period<0)
  {  
    return value;
  }
  else
  {
    //ピリオドの場所がわかったので、コピーしてく
    //dの部分。
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<period-2;i++)
    {
      atoibuff[i]=buff[i];   
    }
    d=atoi(atoibuff);

    //mの部分。
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<4;i++)
    {
      if(buff[period-2+i]=='.') break;
      atoibuff[i]=buff[period-2+i];
    }
    m=atoi(atoibuff);
    //Serial.println(*m);
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<4;i++)
    {
      atoibuff[i]=buff[period+1+i];
    }
    s=atoi(atoibuff);
    value=(double)d;
    value=value+(double)m/60;
   //小数点部分。  
   tmp=(double)s/(double)60;
   tmp=tmp*0.0001;
   value=value+tmp;
  } 
  return value;
}

ではまた。

0 件のコメント:

コメントを投稿