皆さんこんにちは、グレキチです。
ここ日本では、桜舞い散る春の季節にすっかり様変わりして、とても過ごしやすい日々が続いています。このブログを読まれている方の中に、「桜の季節過ぎたら、遠くの街に行くのかい?」という歌詞を知っている方はいますか? 私は数年前に初めてこの曲を聴いてから、今ではすっかりお気に入り曲リストに入っていて、まさに今の季節にピッタリはまっているんですよね。ちょっと切ない10代の青春時代を思い出す感じですかね😄
そんな春の真っ只中にまた新しいモジュールを手に入れてベアメタルってみたので、今回の内容はその紹介になります。そのモジュールとは、GPS(一般呼称はGNSS)受信機です。
GPSは今や、我々の生活には欠かせないものになっていると思います。これは、今後の私の野望を達成する😆のにも必要になるモジュールだったので、このタイミングで扱ってみることにしました。
それではいってみましょう。
使用モジュールについて

今回使用したモジュールは、GY-GPSV3-NEO6Mという品番のGPS受信機です。製造元は不明ですが、安価だったので例の如く多分中華製だと思われます。
ウェブや参考資料から拾ってきた情報を元にこのモジュールの概要をまとめると、下表のようになります。
| モジュール名 | GY-GPSV3-NEO6M |
|---|---|
| 使用チップ | NEO-6M(U-blox社製) |
| GPS周波数帯 | GPS L1(1575.42MHz) |
| 受信チャンネル数 | 50 channels(最大) |
| 水平測位精度 | 2 〜 2.5 m |
| ナビゲーション更新レート | 1 Hz(最大 5 Hz) |
| ナビゲーション感度 | – 161 dBm |
| 通信プロトコル | NMEA、UBXバイナリ、RTCM |
| シリアル通信速度 | 4800 ~ 230400(Default: 9600) bps |
| インターフェース | UART、USB(但し、Winのみ) |
| アンテナサイズ | 25 × 25 mm |
| 電源電圧 | 3 〜 5 V |
| 動作電流 | 45 mA |
| 動作温度 | – 40 〜 85 ℃ |
| アンテナサイズ | 25 × 25 mm |
| モジュールサイズ | 36 × 26 mm |
表の内容で特筆すべきものについて、掻い摘んで説明します。
まずは、GPS周波数帯ですが、米国が運用するL1バンドに対応しています。民生用にはL1、L2とL5の3種類があるようです。
次に通信プロトコルですが、私は見慣れないものばかりでしたが、NMEA、UBXバイナリ、RTCMの3つが使用できるようになっているようです。
NMEAとは、アメリカ海洋電子機器工業会(National Marine Electronics Association)が制定した標準フォーマットのことを指します。元々は船舶向けに設けられた通信方式だったようで、今回のGPSや音波探査機、ソナー、風速計(風向風速計)、ジャイロコンパス、自動操舵装置(オートパイロット)などで使用されているようです。基本的には、このプロトコルを使ってGPSデータを取得します。
UBXバイナリは、u-blox社が自社のデバイス向けに定義した独自の通信プロトコルのことです。GPS受信機の基本設定の変更を行うために使用します。
RTCMは、Radio Technical Commission for Maritime Services(海上無線技術委員会)を語源とし、GPS受信機にリアルタイム差分補正データ(DGPS)を供給するために使用される、受信機への入力のみを行う単方向プロトコルのことを指しているようです。今回搭載されているNEO-6Mチップでも一応対応はしているようですが、これを使うように受信機へ指示しても数mの位置精度がほんのちょっと改善される位のようなので、もっと上位のチップ(M8シリーズなど)でない限り使用するメリットはあまり無いとのことでした。
インターフェースは基本的にはUARTを使用しますが、Windowsの場合のみ、今回のGPS受信機に使われているチップメーカーのu-blox社が提供しているu-centerというソフトウェアとそれに対応したツールを使って、USBで使用出来るようです。
それ以外の項目については、特に説明するまでもないですかね。
回路図と配線図
次に回路図と配線図です。
今回はUART通信だけなので、回路図はかなりシンプルです。使用したモジュールのFritzing用のデータをどうしようか悩みましたが、一つ前のモデルのデータを有志の方が作成されていたのを見つけたので、今回はそれを活用させてもらうことにしました。(いやー助かるぅ〜😆)
そういうわけで、作成した回路図は以下の通りです。

言わずもがな、無用なノイズを避けるため、GNDは共通MUSTです☝️
今回もUARTで受信データをPC上に表示するためにPicoProbeを使っています。それと、MCUのPicoとGPS受信機ともUART通信を行うので、今回は2系統のUARTを使えるように回路を設計しました。PicoProbe側をUART0、GPS受信機側をUART1としてます。それと念のために説明しておくと、UART通信の結線は、お互いに自分のTXピンと相手のRXピンを接続するようにします。なお、PicoProbeとMCUPicoとのUART通信線は今回1本しか結線していませんが、MCU側からPicoProbeの送信しか実際には使わないためです。(これまでのブログ記事では受信側も律儀に結線してましたが、実際には使っていないことを鑑みて今回から止めることにしました👍)
次に配線図ですが、以下の通りです。

電源については今回も、両方のPicoに各々USB経由でPCから給電しています。受信機から下に出てる白い正方形のものがアンテナを表しています。今回アンテナ線は非常に短いものになっていますが、アンテナは取り外し可能なので、実際に使用する際には必要に応じて長い線のものに付け替えることも出来ます。
プログラム内容
今回のプログラムは主に、センテンスと呼ばれるGPS受信機で受信した文字列データを、数字や文字に分割および加工して利用するための内容になります。今回のモジュールで受信したセンテンスそのものを表示してみると、以下のようになっています。
(※補足情報:今回のGPS受信機で室内で下のようにデータをうまく受信するためには、初回起動時のみ、受信機に電源供給した状態でしばらく窓際に置いておかないと、うまく衛星補足が出来ないようです。今回は、ポータブル電源に繋いで窓際に放置しておいたら、5分位で補足できた感じでした。)

デフォルトの更新レートは1Hzなので、1秒毎にこの単位のセンテンス群データが受信機から発信されてきます。1列毎に改行されて発信されてくるのでわかりやすいですが、全部で6種類8行のデータがあることがわかります。なお、センテンスとしては他にも種類があるようなのですが、今回のモジュールで受信できるのはこれだけということになります。
各センテンスの簡単な説明は、以下の通りです。
$GPGGA : 高度測定と測定状況の詳細を確認したい場合に使用
$GPGSA : 測位精度の確認に適している
$GPGSV : 捕捉衛星の詳細情報を確認可能(捕捉できた全衛星情報のため、数行に跨る)
$GPGLL : 現在位置と時間のみのシンプルなもの
$GPRMC : 唯一日付情報が含まれ、最低限のナビが可能な情報を表示
$GPVTG : 対地速度や方位を確認できる、ナビ用に使用可能なもの
この中から、今回は日付情報が取得できる$GPRMCのみを抽出して、UART通信でPC上に表示するものを作成してみました。さらにその中でも緯度と経度については、受信した文字列を単に数値化しただけでは正確な情報が得られず計算が必要になるので、今回はそれをきちんと計算して表示するものを取り急ぎ作成してみました。
また、回路図のところでも説明した通り、今回UARTを2系統使っていますが、MCUとPicoProbeとの通信速度は従来通り115200bpsとし、GPS受信機との通信速度は9600bps(デフォルトのまま)としました。理由としては、MCUがGPS受信機から受信するデータの速度がPicoProbeへ送信する速度と同等かそれより速くしてしまうと、MCU内でのデータ加工処理が追いつかずデータが上書きされたり文字化けしたりするので、それを避けるためになります。そのため、通常はGPS受信機との通信速度をPicoProbeとの通信速度より遅くしないといけないです。そうは言っても、 9600bpsは流石に遅すぎないか?と思って、半分の速度の57600bpsでも試してみたのですが、うまくいかなかったので、結局デフォルトのボーレートに落ち着きました😅
その代わりと言っては何ですが、更新レートは最大の5Hzに変更できたのでそうしました。こちらの方が用途によっては重要な設定になりますので、是非押さえておきたいところです☝️
以上を踏まえて今回は、受信機設定の変更方法と緯度、経度の計算方法についての内容に関して、簡潔に説明しておきたいと思います。
まずは、受信機設定変更に関する内容からです。
モジュール説明のところで軽く触れましたが、受信機の設定変更には、UBXバイナリのコマンドを受信機にUARTで送信して行います。UBXバイナリには、コマンドの最後部2バイトにデータ整合性確認のためのチェックサムを設定しますが、チェックサムは決まったルールで算出できるようになっているため、その算出するためのset_ubx_checksum関数を作成しました。コードは下記の通りです。
static void set_ubx_checksum(uint8_t *array, int size)
{
uint8_t ck_a = 0;
uint8_t ck_b = 0;
// インデックス2(Class)からsize-2(チェックサムの手前)まで計算
for (int i = 2; i < size - 2; i++)
{
ck_a = ck_a + array[i];
ck_b = ck_b + ck_a;
}
array[size - 2] = ck_a;
array[size - 1] = ck_b;
}入手したNEO-6マニュアルに従って作成したものですが、全コマンドの前後2バイトを除いたものを上記計算式でforループで回して、最後に最後部2バイトのチェックサムに上書きするというものです。
それと、受信機に設定情報を送信するためのchange_neo6_setting関数を作成しました。
static void change_neo6_setting(uint8_t *array, uint32_t len)
{
for (int i = 0; i < len; i++)
{
while (GET32(UART1_FR_RW) & (1 << 5));
PUT32(UART1_DR_RW, array[i]);
}
// 送信完了を待機
while (!(GET32(UART1_FR_RW) & (1 << 7)));
while (GET32(UART1_FR_RW) & (1 << 3));
}こちらの関数は、UART通信で通常使われる送信手順に倣った内容のものです。UART1専用で使うために作成しました。
これらの関数を使って、GPS受信機から発せられている6種類のセンテンスから、$GPRMCのセンテンスのみを出力するように変更するset_gps_msg_only_rmc関数を以下の通り作成しました。
static void set_gps_msg_only_rmc(void)
{
// 停止対象メッセージID: GGA(00), GLL(01), GSA(02), GSV(03), VTG(05)
uint8_t ids[] = {0x00, 0x01, 0x02, 0x03, 0x05};
for (int i = 0; i < 5; i++)
{
uint8_t cmd[] = {
0xB5, 0x62, // Header
0x06, 0x01, // CFG-MSG
0x03, 0x00, // Length
0xF0, ids[i], // NMEA Class, Message ID
0x00, // Rate: 0 (無効化)
0x00, 0x00 // Checksum
};
// 明示的に11バイトと指定
set_ubx_checksum(cmd, 11);
// 送信
change_neo6_setting(cmd, 11);
delay_ms(100);
}
delay_ms(400);
}コマンドの羅列順序などの詳細については先ほど紹介したマニュアルに記載がありますので、ここでの説明は割愛させて頂きますが、5つのセンテンスを一つずつ無効化する必要があるため、for文で5回ループを回しています。ちなみに、メッセージID:04がRMCになってます。
このように、同じ内容のものを複数回連続で実行したい場合は、今回作成したチェックサム計算関数やデータ送信関数を作成しておくと便利です。
それと今回のプログラム内容で一番苦労したポイントは、緯度、経度の小数点以下の計算のところです。例の如くpico-sdkを使用していないため、除算と剰余演算が簡単にできないため、周りくどくシフト演算や参照値定義などを多用して計算を成立させる必要がありました。そのために設けた専用関数をちょっと紹介しておきます。
以下は、対象数字を10分の1にするdiv10関数です。
/* --- 除算の代用関数 --- */
// n/10 を計算
uint32_t div10(uint32_t n)
{
// マジックナンバー0x66666667(2^34/10相当)を分割して掛ける
// (n >> 1) * 0.2に相当する計算を構成
uint32_t i;
uint32_t q = (n >> 1) + (n >> 2);
q = q + (q >> 4);
q = q + (q >> 8);
q = q + (q >> 16);
q = q >> 3;
// 最後に誤差を補正 (n - q*10 が10以上ならqをインクリメント)
uint32_t r = n - mul10(q);
return q + ((r + 6) >> 4); // 簡易補正
}マジックナンバー0x66666667とあるのは、ベアメタルでは定番のようで、10で割る処理を高速化するためのものとのことでした。何回かに分割して計算を進めているのは、PicoのCPUが32bitによるものです。ちなみに100分の1の場合は、この関数を2回実行すれば良いということが容易に想像できるかと思います。
ということで、プログラムの説明は以上です。いつも通り、全コードはこちらにアップしていますので、気になった方は確認してみて下さい。
実行結果
実行結果は、今回は画像のみになります。作業場所の位置情報になるため、個人情報保護の観点から詳細部分はモザイクをかけさせてもらっています☺️

デバッグを兼ねて、データ加工前の状態でも吐き出すようにしてます。うまく緯度と経度のデータが抽出できているのがわかると思います。今回作成したプログラムで最も苦労した除算と剰余演算を多用して計算している “min_frac”のところも、綺麗に6桁の数字で出力できていますよね!
これでもう、今回のGPS受信機の制御はマスター出来たと言っても過言ではないでしょう😆
まとめ
ということで、今回はGPS受信機の実装紹介内容でした。
文字データをUART通信で取得するだけのプログラムだったので、すごく簡単だろうなーとたかをくくっていましたが、なかなかどうして、どハマりしてしまいました😅 その主な原因は、プログラム内容の説明のところですでに触れたように、除算と剰余演算の扱いのところですね。計算上でこれらを多用する必要があったので、pico-sdkに頼らないベアメタルで取り組んでいると、色々と裏技的な(かなり機械に寄り添った😆)計算方法で対処するしか無いので、なかなかに難易度が上がるため、何度もヘロヘロになりながら進めました。この半ば時間を無駄にしているように思われる行為が、いつか報われる日が必ず来ると信じて、心折れないように今後も続けていきたいと思っています。
それでは、今回は以上です。
またの機会まで👋
〆

