NMEAフォーマットについて
GPSレシーバをマイコンに接続できるようになったところで、GPS等でよく使われるデータの書式であるNMEAフォーマットについてまとめておきます。
概要
NMEA(正確にはNMEA 0183)は、もとは船舶用電子機器間の通信のための規格です。GPSだけではなく、ソナーやジャイロコンパスなどでも使用されています。この規格は米国海洋電子機器協会(National Marine Electronics Association、本来NMEAとはこの団体の略称)により定義・管理されています。
NMEA 0183は、シリアル通信(電気的にはEIA-422)により伝達されるASCIIコードの書式です。1つのトーカ(送信者)から複数のリスナ(受信者)へ同時に情報を伝達できるような仕様になっています。
基本的な書式
使用文字
NMEAフォーマットでは、ASCIIコード0x20~0x7Eの印刷可能文字および改行を表す0x0D+0x0Aが使用されます。
書式の中で特殊な意味を持つ文字(予約文字)は以下の通りです。これ以外の文字はデータ(フィールド)の記述に使用されます。
文字 | 16進コード | 意味 |
---|---|---|
LF | 0x0A | ラインフィード(改行) |
CR | 0x0D | キャリッジリターン(復帰) |
! | 0x21 | カプセル化センテンスの開始区切り |
$ | 0x24 | 開始区切り(行頭) |
* | 0x2A | チェックサム区切り(メッセージのこの文字以降がチェックサム) |
, | 0x2C | フィールド区切り |
\ | 0x5C | TAGブロック区切り |
^ | 0x5E | 16進コード区切り |
~ | 0x7E | (予約済み) |
メッセージの構造
データは、メッセージ(またはセンテンスとも言います)という単位で送信されます。1つのメッセージは、改行文字まで含めて最大で82文字(バイト)です。
各メッセージは『$』または『!』で始まり、『CR+LF』(16進数では0x0D 0x0A、CやJava風に表すと\r\n)で終わります。
メッセージには複数のデータフィールドが含まれます。データフィールド間は『,(カンマ)』によって区切られています。ExcelなどでもおなじみのCSVに似ていますね。
メッセージに『*』が含まれていた場合、その後にはチェックサムが16進数文字列で記述されています。メッセージにチェックサムが含まれる場合、それは最後のデータフィールドの後に1回だけ記述されます。
では、実際のメッセージの例を見てみましょう。
行頭の『$』または『!』に続く①の部分の英文字2文字は、トーカIDといい、データを送信しているデバイスの種類を表します。この例の『GP』はGPSレシーバを表します。以下に主なトーカIDを示します。
トーカID | 意味 |
---|---|
GP | GPS アメリカが運用する人工衛星による測位システムです。 |
GL | GLONASS(=Global Navigation Satellite System) ロシアが運用する人工衛星による測位システムです。 |
GA | Galileo EUが運用する人工衛星による測位システムです。GPSより後発なだけに精度が高いと言われていますが、利用が有料です。 |
GQ | QZSS(=Quasi-Zenith Satellite System、準天頂衛星システム) 日本が運用する衛星測位システムです。民間でもcm単位の高精度位置情報を利用できますが、日本を中心としたアジア~オセアニアの限られた地域でしか使用できません。 |
トーカIDの後、②の部分の英文字3文字は、メッセージの種類を表しています。この例の『GGA』は、時刻と緯度経度を伝えるメッセージであることを表します。詳細は次節で解説します。
③の部分がデータフィールドです。『,』に区切られた各部分がそれぞれ何らかのデータを表しています。詳しくは次節で解説します。
『*』以降行末までの④の部分がチェックサムです。チェックサムは、0を初期値とした『$』と『*』の間のすべての文字のASCIIコードの排他的論理和(当然1バイト値になります)を2桁の16進数文字列で表しています。つまり、上の例だと、
0 xor (『G』の文字コード) xor (『P』の文字コード) xor (『G』の文字コード) xor … xor (『0』の文字コード)
= 0x6D
ということです。
例えばJavaで以下のような簡単なプログラムを作れば確認できます。
String text="GPGGA,013627.000,3633.8029,N,13639.7692,E,2,9,0.97,52.7,M,35.0,M,0000,0000";
int ch = 0;
for(int i=0; i<text.length(); i++) {
ch = ch ^ text.charAt(i);
}
System.out.println("CS:"+Integer.toHexString(ch));
メッセージごとの書式
本節では、よく使われるメッセージについて、種類ごとにフィールドのフォーマットを解説します。
GPSレシーバから位置・時刻を取得するプログラムを作るなら、GGAかRMCを読み取れるようにしておけばよいでしょう。
なお、各フィールドの値がない場合は空文字列として出力されることが許されています。また数値データの桁数が少ない場合、上位桁を0埋めするかどうか不定なようです。よって、解析のプログラムを作る場合は、一見桁数が固定のように見えても固定長フォーマットとしてではなく区切り文字『,』を見てパースするようにプログラミングする必要があります。
NMEAフォーマットで出力するGPSレシーバの出力例:
$GPGGA,013627.000,3633.8029,N,13639.7692,E,2,9,0.97,52.7,M,35.0,M,0000,0000*6D
$GPGSA,A,3,28,05,13,20,15,21,24,18,12,,,,1.65,0.97,1.34*0C
$GPGSV,3,1,11,20,71,141,34,24,69,211,40,15,68,010,38,42,46,166,34*71
$GPGSV,3,2,11,18,41,312,34,21,38,270,34,13,36,059,32,05,21,131,30*75
$GPGSV,3,3,11,28,14,046,18,12,09,163,29,10,08,313,16*47
$GPRMC,013627.000,A,3633.8029,N,13639.7692,E,0.04,144.21,050316,,,D*6F
GGA
位置(緯度・経度)および時刻を含むメッセージです。
例:
番号 | 内容 |
---|---|
1 | 時刻をUTC(協定世界時)hhmmss.sssの書式で表す |
2 | 緯度をddmm.mmmmの書式で表す |
3 | Nなら2番のデータが北緯、Sなら南緯 |
4 | 経度をdddmm.mmmmの書式で表す |
5 | Eなら4番のデータが東経、Wなら西経 |
6 | 測位の品質を表す。 0:測位不能 1:標準測位サービスモード 2:干渉測位方式モード 4:RTK fis 5:RTK float |
7 | 測位に使用した衛星数 |
8 | 水平精度低下率 |
9 | アンテナ海抜高度 |
10 | アンテナ海抜高度の単位 |
11 | ジオイド高さ(ジオイド楕円面の海抜高度) |
12 | ジオイド高さの単位 |
13 | 最後に補正データを受診してからの経過時間 |
14 | 補正情報を受け取った基準局ID |
さらに補足が必要そうな項目について解説します。
時刻(第1フィールド)
UTC(協定世界時)による時刻です。時差があるので、JST(日本標準時)はUTCに9時間加えた値です。
上記の例では『013627.000』で、これはUTCで『1時36分27.000秒』、日本時間では9時間加えて『10時36分27.000秒』を表します。
緯度(第2~第3フィールド)
緯度はちょっと特殊な表し方をしています。第2フィールドの書式は ddmm.mmmm となっていますが、これはひと続きの小数ではなくddの部分とmm.mmmmの部分で切り分けて考え、ddの部分(整数部の3桁目~)が『度』、mm.mmmmの部分(整数部下2桁+小数部)が『分』を表します。
上の例では、緯度(第2フィールド)は『3633.8029』となっているので、『36度33.8029分』となります。
・『度単位の小数』で表す場合、角度の『1分』は『1度』の1/60なので、\(36+33.8029\times\frac{1}{60}=36.56338166666667\) となります。また第3フィールドが『N』なので『北緯36.56338166666667度』です。
・『度・分・秒』で表す場合、mm.mmmmの小数部に60を掛ければ『秒』が求まります。\(0.8029\times60=48.174\) より、『北緯36度33分48.174秒』となります。
経度(第4~第5フィールド)
経度(第4~第5フィールド)についても緯度と同様です。第4フィールドの書式はdddmm.mmmmと『度』が3桁になる可能性があることだけが違います(というか日本付近では3桁です)。上の例では『13639.7692』なので、これは『136度39.7692分』です。
・度単位の小数で表す場合、\(136+39.7692\times\frac{1}{60}=136.66282\) なので、第5フィールドが『E』であることと合わせて『東経136.66282度』です。
・『度・分・秒』で表す場合、\(0.7692\times60=46.152\) より、『東経136度39分46.152秒』となります。
ちなみに、北緯36.56338166666667度、東経136.66282度は、兼六園(石川県金沢市)の虹橋・徽軫灯籠ちかく、兼六園内でも一番人気の撮影スポットの座標です。
実は以前旅行したときのGPSデータから引っぱってきたので(笑
アンテナ海抜高度(第9~第10フィールド)
アンテナ海抜高度とは、要するにレシーバの海抜高度です。この例では第9フィールドが52.7、そして単位を表す第10フィールドのMはそのままm(メートル)の意味です。よって52.7mとなります。
Google Earthによるとこのあたりの標高は52mらしいので、かなりいい精度で測定できています。
ジオイド(第11~第12フィールド)
ジオイドという言葉を調べると『地球の平均海水面に一致する等ジオポテンシャル面』などと説明されているのですが、判りにくいですね(汗)。要するに、『地球にもっともよく似た回転楕円体』のことです。
GSA
測位に使用した衛星に関する情報を含んだメッセージです。
例:
番号 | 内容 |
---|---|
1 | 2D/3D即位切り替えモード。A:自動切り替え、M:手動切り替え |
2 | 即位ステータス。1:No FIX、2:2D FIX、3:3D FIX |
3~14 | 即位に使用した衛星番号。3~14の12個分のフィールドがある。 衛星が12個に満たない場合はフィールドは空文字列となる。 衛星が13個以上ある場合は続けてGPGSAメッセージが送信される。 |
15 | 3D位置精度劣化指標 |
16 | 水平位置精度劣化指標 |
17 | 垂直位置精度劣化指標 |
GSV
測位に使用した衛星の位置情報を含んだメッセージです。
例:
番号 | 内容 |
---|---|
1 | GSVメッセージの行数 |
2 | このGSVメッセージが全行数のうち何行目か |
3 | 確認可能な衛星数 |
4 | 1番目の衛星の番号 |
5 | 1番目の衛星の仰角(0~90度) |
6 | 1番目の衛星の方位角(0~359度) |
7 | 1番目の衛星の信号強度(0~99dB) |
8 | 2番目の衛星の番号 |
9 | 2番目の衛星の仰角(0~90度) |
10 | 2番目の衛星の方位角(0~359度) |
11 | 2番目の衛星の信号強度(0~99dB) |
12 | 3番目の衛星の番号 |
13 | 3番目の衛星の仰角(0~90度) |
14 | 3番目の衛星の方位角(0~359度) |
15 | 3番目の衛星の信号強度(0~99dB) |
16 | 4番目の衛星の番号 |
17 | 4番目の衛星の仰角(0~90度) |
18 | 4番目の衛星の方位角(0~359度) |
19 | 4番目の衛星の信号強度(0~99dB) |
第1~第3フィールド
GSVメッセージは、1メッセージにつき4個までの衛星の情報を記述できます。5個以上の衛星を補足している場合は、GSVメッセージが複数行になります。補足している衛星の総数は第3フィールド、GSVメッセージの行数は第1フィールド、このメッセージがそのうち何番目かが第2フィールドに格納されています。
衛星の番号、仰角、方位角、信号強度
第4フィールド~第19フィールドには、衛星の番号・仰角・方位角・信号強度が4個分くりかえして格納されています。
仰角は度単位で0~90の数値、方位角は度単位で0~359の数値、信号強度はdB表記で0~99の数値です。
RMC
位置(緯度、経度)、時刻、日付情報を格納したメッセージです。
例:
番号 | 内容 |
---|---|
1 | 時刻をUTC(協定世界時)hhmmss.sssの書式で表す |
2 | 測位状況ステータス。A:有効、V:渓谷 |
3 | 時刻をUTC(協定世界時)hhmmss.sssの書式で表す |
4 | Nなら2番のデータが北緯、Sなら南緯 |
5 | 経度をdddmm.mmmmの書式で表す |
6 | Eなら4番のデータが東経、Wなら西経 |
7 | 移動速度 |
8 | 移動方向 |
9 | UTC(協定世界時)の日付をddmmyyの書式で表す |
10 | 磁気偏角 |
11 | 磁気偏角の方向。E:東、W:西 |
12 | 測位モードステータス。N:No FIX、A:自立方式、D:干渉測位方式 |
時刻(第1フィールド)、緯度(第3~第4フィールド)、経度(第5~第6フィールド)
これらのフィールドはGGAメッセージと同じ書式です。
移動速度(第7フィールド)
単位はknot、0~999.0の数値です。上記の例では0.04knot=0.074km/h=0.02m/s、立ち止まっていますね。
移動方向(第8フィールド)
単位は度、0~359.9の数値です。
日付(第9フィールド)
UTC(協定世界時)による日付です。ddmmyy(dd:日、mm:月、yy:西暦年の下二桁)の書式です。上記の例の『050316』は、2016年3月5日を意味します。
GPSの素の信号では、日付は『年・月・日』ではなく『起点となる日時からの週数+秒数』で表されています。『週数』は10ビットの整数値として扱われているので、1024週(20年弱)ごとに0に戻ってしまいます。これをロールオーバーといい、そのたびに日付の起点(計算方法)が変わってしまいます。
最初の起点は1980年1月6日午前0時(UTC)でしたが、現在までに1999年8月21~22日と2019年4月7~8日の2回ロールオーバーが発生しています。つまり2024年現在、日付は2019年4月8日からの週数と秒数として計算しなければいけないのですが、2018年以前に発売されたデバイスだと1999年8月22日を起点として計算してしまい、日付を間違って出力する可能性があります。
実は今回のサンプルデータを取得するのに使用したGPSレシーバは2019年のロールオーバーに対応していなかったため、現在は引退しています…。
まとめ
- GPSの出力データとしてよく利用されるNMEAフォーマットは、CSVに似た改行・カンマ区切りのテキスト形式である
- 緯度・経度の他、時刻や衛星に関する情報も記述できる
コメント