« STM32 Primer2 V1.2 | トップページ | mbed Get »

NMEA-0183メッセージの解析

GPSモジュールが出力するNMEA-0183メッセージと呼ばれる出力文字列の解析プログラムの実験を行いました。最終的にはSTM32 Primer2に組み込んで使用するのですが、今回はPC上(VC++ 2008)で動作確認を行いました。

NMEA-0183メッセージとは

GPSモジュールが衛星を補足すると、以下のような文字列をシリアルインターフェースから吐き出します(自宅で収集したデーターのため、緯度・経度を示す部分は、xxxx.xxxx, yyyy.yyyyに書き換えています)。

$GPGGA,230400.600,xxxx.xxxx,N,yyyy.yyyy,E,1,4,39.03,-170.6,M,39.4,M,,*4D
$GPGLL,xxxx.xxxx,N,yyyy.yyyy,E,230400.600,A,A*56
$GPGSA,A,3,06,03,31,19,,,,,,,,,46.41,39.03,25.10*35
$GPGSV,3,1,10,16,64,350,,06,56,223,29,31,56,129,26,03,42,229,30*74
$GPGSV,3,2,10,23,34,287,,21,31,086,,19,17,218,26,13,14,320,*7A
$GPGSV,3,3,10,29,08,043,,25,04,317,*7A
$GPRMC,230400.600,A,xxxx.xxxx,N,yyyy.yyyy,E,0.05,169.01,131109,,,A*60
$GPVTG,169.01,T,,M,0.05,N,0.08,K,A*3F

各パラメーターの意味は、このリンクを参照ください。概略を示すと;

  • GPGGAメッセージから、衛星の補足状態、経度・緯度や補足している衛星の数が分かります
  • GPGSVメッセージから、個々の衛星の位置(仰角・方位)、受信信号レベルが分かります
  • 経度・緯度情報は複数のメッセージに挿入されており、GPGLL, GPRMCメッセージでも取得できます

NMEA-0183メッセージの解析

経度・緯度を得るためには、GPGGAの「GPSのクオリティ(Position Fix Indicator)」が1 or 2になった後で、経度・緯度情報を取り出す必要があります。NMEA-0183メッセージ(以下NMEAメッセージ)は、カンマ区切り形式の単純な構造であるため、解析はそれほど難しくないと思うのですが、チェックサムの計算とか色々考慮すべき点もあります。また、経度・緯度情報だけ抜き取るのなら簡単ですが、汎用性を持たせて任意のデーターの取り出しまで求めるとプログラムは結構複雑そうです。

そこで、例によって人様の成果を活用させてもらうべく、フリーのnmeaパーサーを探しました。グーグル様で検索してみるとC言語で使用できるライブラリとしては、[dmh2000] – NMEAP, NMEA Libraryが見つかりました。NMEAPはGPGGA/GPRMCのみを解析しているのに対して、NMEA LibraryはすべてのNMEAメッセージを解析できます。GPGSVメッセージも解析して衛星からの信号強度も表示したいため、NMEA Libraryを使用することにします。

NMEA Libraryの使い方

パッケージに含まれるexampleをベースに以下のサンプルプログラムを書いてみました(ほとんどexampleそのままです、、)。サンプルプログラムはテキスト形式で保存したNMEAメッセージのログファイルを読み込んで、位置情報と信号強度を表示します。

#include "nmea/nmea.h"

#include <string.h>
#include <stdio.h>

#ifdef NMEA_WIN
#   include <io.h>
#endif

void trace(const char *str, int str_size)  // debug用のcallback
{
    printf("Trace: ");
    write(1, str, str_size);
}
void error(const char *str, int str_size)
{
    printf("Error: ");
    write(1, str, str_size);
}

int main(int argc, char *argv[])
{
    nmeaINFO info;             // 取得情報を保持するための構造体
    nmeaPARSER parser;         // パーサーの内部管理情報
    FILE *file;
    char buff[256];            // 受信データーのバッファ
    int size, it = 0, i;

    if (argc < 2)
    {
        printf("need log filename");
        return -1;
    }

    file = fopen(argv[1], "rb");

    if(!file)
    {
        printf("File open error");
        return -1;
    }

    nmea_property()->trace_func = &trace;  // trace callbackの登録
    nmea_property()->error_func = &error;

    nmea_zero_INFO(&info);      // nmeaINFO構造体の初期値設定
    nmea_parser_init(&parser);  // パーサーオブジェクトの初期化

    while(!feof(file))
    {
        size = (int)fread(&buff[0], 1, 128, file);   // NMEAメッセージの取得
        nmea_parse(&parser, buff, size, &info);  // パーサーの起動

        printf(                     // 経度・緯度情報の表示
            "%03d, Lat: %f, Lon: %f, Sig: %d, Fix: %d\n",
            it++, info.lat, info.lon, info.sig, info.fix );
        for (i = 0; i < NMEA_MAXSAT; i++)
        {                           // 受信信号強度の表示
            if (info.satinfo.sat[i].in_use)
                printf(
                    "  sat_id:%02d, sig:%02d\n", 
                    info.satinfo.sat[i].id , info.satinfo.sat[i].sig);
        }
    }

    fseek(file, 0, SEEK_SET);
    nmea_parser_destroy(&parser);  // パーサーオブジェクトの廃棄(メモリーの開放など)
    fclose(file);
    return 0;
}

10, 15行目は、デバック用のcallback関数で、43, 44行目で登録しています。ソースを眺めてみたのですが、どのタイミングで呼び出しているかは分からず。

51, 52行目で、128文字単位にファイルからテキストを読み出してパーサーに渡しています。すなわち、メッセージの切れ目(改行)を意識する必要がないということになります。実際にGPSモジュールをつないで動かした場合、割り込みで文字を受信して一定の文字数がたまったらパーサーを起動するようにすればよく、処理が簡略化できそうです。

解析した情報は、nmeaINFO構造体に格納されます。構造体の定義は以下となっています。

typedef struct _nmeaINFO
{
    int     smask;      /**< Mask specifying types of packages from which data have been obtained */

    nmeaTIME utc;       /**< UTC of position */

    int     sig;        /**< GPS quality indicator (0 = Invalid; 1 = Fix; 2 = Differential, 3 = Sensitive) */
    int     fix;        /**< Operating mode, used for navigation (1 = Fix not available; 2 = 2D; 3 = 3D) */

    double  PDOP;       /**< Position Dilution Of Precision */
    double  HDOP;       /**< Horizontal Dilution Of Precision */
    double  VDOP;       /**< Vertical Dilution Of Precision */

    double  lat;        /**< Latitude in NDEG - +/-[degree][min].[sec/60] */
    double  lon;        /**< Longitude in NDEG - +/-[degree][min].[sec/60] */
    double  elv;        /**< Antenna altitude above/below mean sea level (geoid) in meters */
    double  speed;      /**< Speed over the ground in kilometers/hour */
    double  direction;  /**< Track angle in degrees True */
    double  declination; /**< Magnetic variation degrees (Easterly var. subtracts from true course) */

    nmeaSATINFO satinfo; /**< Satellites information */

} nmeaINFO;


typedef struct _nmeaSATINFO
{
    int     inuse;      /**< Number of satellites in use (not those in view) */
    int     inview;     /**< Total number of satellites in view */
    nmeaSATELLITE sat[NMEA_MAXSAT]; /**< Satellites information */

} nmeaSATINFO;


typedef struct _nmeaSATELLITE
{
    int     id;         /**< Satellite PRN number */
    int     in_use;     /**< Used in position fix */
    int     elv;        /**< Elevation in degrees, 90 maximum */
    int     azimuth;    /**< Azimuth, degrees from true north, 000 to 359 */
    int     sig;        /**< Signal, 00-99 dB */

} nmeaSATELLITE; 

構造体が階層化されており、ちょっとややこしいですが;

  • 経度緯度は、info.lat, info.lonで取得できます(infoはnmeaINFOのインスタンス)
  • 信号強度は階層をたぐって、info.satinfo.sat[i].sigで取得できます

サンプルを動かした結果を以下に示します(経度・緯度はxxxx.xxxx, yyyy.yyyyで、、)。
パーサーには128文字単位でデーターを渡していますが、パーサー内部でメッセージ単位に処理を行う作りになっています。そのため、最初のGPGSAメッセージが読み込まれた時点で(最初のTrace表示)000番目の解析結果が表示され、続けてGPGSAとGPGSVが読み込まれた時点で001番目の解析結果が表示されています。

Trace: $GPGGA,112645.000,xxxx.xxxxx,N,yyyy.yyyy,E,2,05,1.7,465.4,M,39.4,M,3.8,0000*7C
000, Lat: xxxx.xxxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 1
Trace: $GPGSA,A,3,12,24,21,09,18,,,,,,,,2.0,1.7,1.0*31
Trace: $GPGSV,3,1,12,09,80,345,22,27,71,022,17,32,68,311,,18,62,305,16*7E
001, Lat: xxxx.xxxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPGSV,3,2,12,15,42,071,,12,29,161,39,21,28,241,31,26,26,292,*7E
Trace: $GPGSV,3,3,12,22,26,314,17,24,15,198,37,30,09,186,21,05,06,142,*7A
002, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPRMC,112645.000,A,xxxx.xxxx,N,yyyy.yyyy,E,0.00,,170110,,,D*7B
Trace: $GPVTG,,T,,M,0.00,N,0.0,K,D*16
Trace: $GPGGA,112646.000,xxxx.xxxx,N,yyyy.yyyy,E,2,05,1.7,465.4,M,39.4,M,4.8,0000*78
003, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
Trace: $GPGSA,A,3,12,24,21,09,18,,,,,,,,2.0,1.7,1.0*31
Trace: $GPGSV,3,1,12,09,80,347,22,27,70,022,16,32,69,311,,18,62,305,16*7D
004, Lat: xxxx.xxxx, Lon: yyyy.yyyy, Sig: 2, Fix: 3
  sat_id:09, sig:22
  sat_id:18, sig:16
  sat_id:12, sig:39
  sat_id:21, sig:31
  sat_id:24, sig:37
Trace: $GPGSV,3,2,12,15,42,072,,12,29,160,39,21,28,240,31,26,26,293,*7C
Trace: $GPGSV,3,3,12,22,26,314,17,24,14,197,37,30,09,186,21,05,06,142,*74
005, Lat: xxxxx.xxxxx, Lon: yyyyy.yyyyy, Sig: 2, Fix: 3
  sat_id:09, sig:22
  sat_id:18, sig:16
  sat_id:12, sig:39
  sat_id:21, sig:31
  sat_id:24, sig:37

余談

パーサーが動くようになったので、Primer2につないでCircleOS配下でNMEA Libraryを動かそうと作業を始めたのですが、ここで問題発生。Primer2のVBATとGを、GPSモジュールのGとVCCに接続していまい(電源をショートさせてしまい)、GPSモジュールがあえなく昇天してしまいました。SparkFunから直接買ったモジュールで、送料込みで$90以上かかったのに涙です・・・ 電源周りの接続ミスで部品を壊したのは2度目。まったく注意散漫です。

ここまできたので、同じモジュールを再購入して出直しを図ろうかと。
しかし、Primer2を触り始めてから、本体を壊して2個目を買ったりGPSモジュールを2個壊したりとどうも縁起が悪いので、mbedに寄り道してみようかしら、、と思っています。

« STM32 Primer2 V1.2 | トップページ | mbed Get »

STM32-ARM」カテゴリの記事

コメント

この記事へのコメントは終了しました。

2018年10月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ