🐑

PicoRubyでGPS受信モジュール (GY-NEO6MV3) を使う

に公開

PicoRubyで各種デバイスを動かしてみようシリーズ第4回。今回のチャレンジはGPS受信モジュール。

材料

材料 買い方・備考
GPS受信モジュール 電子工作ステーションで買ったGY-NEO6MV3。自分が買ったときは830円。550円以上の購入で送料無料らしいのでこれを買うときは送料なし。なお要はんだ付け。ピンが付属してたかは忘れた。

Arduinoでの動作確認

いきなりPicoRubyで動かそうとすると問題があったときのトラブルシューティングが面倒なので、まずは誰かが動作確認したものを参考にしよう。このページを参考にさせてもらったが、使ったのはAruduino Nanoの互換品。とは言ってもArudiono Unoと変わるところはなし。

配線


コードと動作確認

#include "SoftwareSerial.h"
#define GPS_TX 2
#define GPS_RX 3

SoftwareSerial gpsSerial(GPS_TX, GPS_RX);

void setup() {
  Serial.begin(115200);
  gpsSerial.begin(9600);
  delay(1000);
}

void loop() {
  if (gpsSerial.available() > 0) {
    Serial.write(gpsSerial.read());
  }
}

うまく動けばこのようなカンマ区切りの文字列がずらずら出てくる。これはNMEAフォーマットというものらしく、これをパースすれば緯度・経度の情報が取れる。結構色々な情報が入っているが、緯度・経度情報も割とそのまま含まれているので難しいことは特にない。

なおArudinoの場合はもっと使いやすくしてくれるTinyGPS++というライブラリがあるのでこれを使うと良さそう。でも今回の目的はPicoRubyで動かすことなので、そろそろ本題に移ろう。

PicoRubyでの動作確認

配線

使ったのは今回もRaspberry Pi Pico WH (2でないやつ)。

コードと動作確認

以下コードをgps.rbのような名前で保存して実行すると、緯度・経度を表示する。うまく受信できていないときは"status = warning"が表示される。受信できていればモジュールの緑のLEDが光るので、それでも状態は分かるはず。

require 'uart'

uart = UART.new(unit: :RP2040_UART0, txd_pin: 0, rxd_pin: 1, baudrate: 9600)

# "dddmm.mmmm" -> "ddd.dddd"
def convert_angle(s)
  # i.e. s = "1234.56789"
  ss = s.split(".")
  s_int = ss[0]          #=> "1234"
  s_int = ("0" * (5 - s_int.length)) + s #=> "01234"
  s_deg = s_int[0,3]     #=> "012"
  deg1 = s_deg.to_i      #=> 12
  s_min1 = s_int[3,2]    #=> "34"
  min1 = s_min1.to_i     #=> 34
  s_frac = ss[1]         #=> "56789"
  s_frac = "0." + s_frac #=> "0.56789"
  min2 = s_frac.to_f     #=> 0.56789
  min = min1 + min2      #=> 34.56789
  deg2 = min / 60
  return deg1 + deg2
end

n = 0
loop do
  if line = uart.gets
    n += 1
    line.chomp!
    #puts "line: " + line
    if line.start_with? "$GPRMC"
      print "GPRMC"
      ss = line.split(",")
      s_raw_utc = ss[1]
      time_utc = "#{s_raw_utc[0,2]}:#{s_raw_utc[2,2]}:#{s_raw_utc[4,2]}"
      s_stat = ss[2]
      if s_stat == "V"
        puts " -> #{time_utc} status=warning"
      else
        s_raw_attitude = ss[3]
        ns = ss[4] # "N"orth or "S"outh
        attitude = convert_angle(s_raw_attitude)
        s_raw_longitude = ss[5]
        longitude = convert_angle(s_raw_longitude)
        ew = ss[6] # "E"ast or "W"est
        printf " ->  #{ns} %.5f  #{ew} %.5f\n", attitude, longitude
      end
    else
      #puts "not GPRMC"
    end
  end
  if n >= 1000
    break
  end
end

実行結果

動かしたのは横浜駅東口を出たあたり。

画面に出ている"35.46509,139.62336"をGoogleマップで検索すると正しい値のようだ。

ただ、動かし始めてから取得できるまで結構時間がかかる(5分くらい?)。その場で再起動したときはすぐ使えるけど、場所が大きく変わったときはまた時間がかかる?

もう少し詳しく

通信の仕組み

Arduino版ではSoftwareSerialというのを使ったが、これは要するにUART通信のことらしい。なのでPicoRubyのuartクラスがそのまま使える。送られてくるデータは行指向で、$から始まって改行(\r\n)までが1つのセンテンス。uartクラスにはgetsがあるのでこれを使えば行の切り出しは簡単。

NMEAフォーマット

NMEAフォーマットのセンテンスにはGPRMC, GPGGA, GPGSAなど色々あり、含まれているデータが異なる。見たところ緯度・経度がGPRMCとGPGGA。GPRMCが基本的な情報をまとめたものらしいのでこっちを使う。詳しいフォーマットはこちらのページを参考にさせてもらった。

ちょっとだけ面倒なのが緯度・経度のフォーマット。Google Mapとかで使うのはddd.dddd形式(10進法)だが、GPRMCにあるのはdddmm.mmmm(度分形式)になっているところ。例えば新宿駅の経度は10進法ではE139.7004のあたりだが、GPRMCの度分形式だとE13942.024あたりになる。変換はそんなに難しくはなく、dddmm.mmmmmを、ddd + (mm.mmmm)/60すればよい。つまり13942.024なら、139 + 42.024/60 = 139 + 0.7004 = 139.7004となる。

というあたりまで書いたところで、どうもGoogleマップは度分形式も対応してたことが判明。

Discussion