GPS

GPS GYSFDMAXB を mruby/c から利用する. GPS GYSFDMAXB はシリアル通信の 1 つである UART でマイコンと通信を行うものであり,ここでは mrubyc for ESP32 ライブラリ に含まれる UART 用クラスを利用するものとする.

注意: GPS の電波は屋内では受信しづらい.マイコンを窓側に置くこと.

UART

UART は非同期のシリアル通信である. 非同期のためにクロック用のラインは存在しない.

  • 2 本の信号線で構成される (全二重通信)
    • TX: データ出力
    • RX: データ入力
  • クロック同期式と比べて伝送速度は低速 (最大 20 kbps)
  • UART にはクロック信号がないため, 通信をする前に送受信するデバイス間で通信速度 (ボーレート) を決める必要がある.

GPS の出力: NMEAフォーマット

GPSモジュールからは NMEA フォーマットの情報が送信される. NMEAフォーマットの情報はセンテンスの集まりであり,1 つのセンテンスは「$」で始まり, csv と同様にカンマ区切りでデータが並ぶ.

$GPGGA,030917.000,3529.8275,N,13301.5109,E,1,6,3.66,41.3,M,28.6,M,,*67
$GPGLL,3529.8275,N,13301.5109,E,030917.000,A,A*5D
$GPGSA,A,3,19,01,14,28,17,21,,,,,,,3.77,3.66,0.93*0B
$GPGSV,4,1,15,01,73,016,28,22,53,107,,14,51,310,34,21,50,041,24*71
$GPGSV,4,2,15,03,42,148,,28,35,316,29,08,30,084,,17,29,297,34*71
$GPGSV,4,3,15,30,26,246,,07,15,208,,19,06,284,27,27,01,096,*7A
$GPGSV,4,4,15,193,,,,194,,,,195,,,*47
$GPRMC,030917.000,A,3529.8275,N,13301.5109,E,0.15,22.47,160621,,,A*5F
$GPVTG,22.47,T,,M,0.15,N,0.29,K,A*01
$GPZDA,030917.000,16,06,2021,,*5A

各センテンスの意味は「NMEAフォーマット」で Google 検索すると多くの記事が引っかかるが, 例えば <URL:https://www.hiramine.com/physicalcomputing/general/gps_nmeaformat.html> などが読み易い.

RMC

GPS から日時・緯度経度を取得する場合には,センテンス $GPRMC に着目すれば良い.

センテンス例:
$GPRMC,085120.307,A,3541.1493,N,13945.3994,E,000.0,240.3,181211,,,A*6A
  • 第 1 引数: 世界標準時 (UTC) での時刻. フォーマットは hhmmss.ss
    • 例: 085120.307 → 08:51:20.307 UTC
  • 第 2 引数: ステータス
    • V = 警告, A = 有効
  • 第 3 引数: 緯度. フォーマットは dddmm.mmmm
    • 例: 3541.1493 → 緯度:35度41.1493分
  • 第 4 引数: 北緯か南緯か
    • N = 北緯, S = 南緯
  • 第 5 引数: 経度. フォーマットは dddmm.mmmm
    • 例: 13945.3994 → 経度;139度45.3994分
  • 第 6 引数: 東経か西経か
    • E = 東経、W = 西経
  • 第 7 引数: 地表面における移動の速度 000.0 -- 999.9 [knot].
  • 第 8 引数: 地表における移動の真方位。000.0~359.9度
  • 第 9 引数: 世界標準時 (UTC) での日付
    • 例: 181211 → 2011年12月18日(UTC)
  • 第 10 引数: 磁北と真北の間の角度の差。000.0~359.9度
  • 第 11 引数: 磁北と真北の間の角度の差の方向。E = 東、W = 西
  • 第 12 引数: モード
    • N = データなし, A = Autonomous(自律方式), D = Differential(干渉測位方式), E = Estimated(推定)
  • 第 13 引数: チェックサム値

プロジェクトの準備

$ cd ~/esp

$ git clone https://github.com/gfd-dennou-club/iotex-esp32-mrubyc.git mrubyc-13-gps

$ cd mrubyc-13-gps

なお,make menuconfig で I2C, LCD (I2C), UART にチェックを入れること

$ make menuconfig 

  [*] USR ESP32 I2C
  [*]     PERIPHERAL: LCD Display AQM0802A

  [*] USR ESP32 UART

プログラム例と実行 (1)

mrblib/loops/master.rb を以下のように編集して実行してみよ. この例では, GPS のデフォルトの出力を確認する.

1  # coding: utf-8
2 
3  # GPS初期化 txPin = 17, rxPin = 16 のため uart_num = 2 とする
4  gps = UART.new(2, 9600)
5 
6  # 出力をデフォルトに戻す
7  gps.write("$PMTK314,-1*04\r\n")
8 
9  # 入力データをclear_tx_bufferで消去する
10 puts "> gps.clear_tx_buffer"
11 gps.clear_tx_buffer
12 
13 # 入力データが来るのを待つ
14 sleep 2
15 
16 # nonblock で到着している分のデータを取得する (デフォルトでは出力もされる)
17 lines = gps.read_nonblock(4096)
18 
19 # 以下、到着したデータを 1 行ずつ読み込んで表示
20 while true
21   puts  gps.gets()
22   sleep 1
23 end

コンパイルと実行. デフォルトでは大量のセンテンスが表示されることがわかる.

$ make flash monitor

  ...(前略)......
  UART: driver was successfully installed
  > gps.clear_tx_buffer
  )?38
  $GPZDA,032458.000,19,06,2021,,*51
  $GPGGA,032459.000,3529.8110,N,13301.4898,E,1,6,4.45,40.2,M,28.6,M,,*64
  $GPGLL,3529.8110,N,13301.4898,E,032459.000,A,A*58
  $GPGSA,A,3,28,21,01,14,17,19,,,,,,,4.54,4.45,0.93*0B
  $GPGSV,4,1,15,14,62,296,28,01,60,029,14,22,54,084,,03,53,135,*78
  $GPGSV,4,2,15,28,45,312,30,21,39,046,20,17,38,307,31,08,22,095,*75
  $GPGSV,4,3,15,30,19,235,,19,14,293,21,07,05,201,,06,03,240,*71
  $GPGSV,4,4,15,193,,,,194,,,,195,,,*47
  $GPRMC,032459.000,A,3529.8110,N,13301.4898,E,0.18,217.89,190621,,,A*6E
  $GPVTG,217.89,T,,M,0.18,N,0.33,K,A*31
  $GPZDA,032459.000,19,06,2021,,*50
  $GPGGA,032500.000,3529.8107,N,13301.4903,E,1,6,4.45,40.3,M,28.6,M,,*6D
  $GPGLL,3529.8107,N,13301.4903,E,032500.000,A,A*50
  $GPGSA,A,3,28,21,01,14,17,19,,,,,,,4.54,4.45,0.93*0B
  $GPGSV,4,1,15,14,62,296,28,01,60,029,13,22,54,084,,03,53,135,*7F
  $GPGSV,4,2,15,28,45,312,30,21,39,046,20,17,38,307,31,08,22,095,*75
  $GPGSV,4,3,15,30,19,235,,19,14,293,21,07,05,201,,06,03,240,*71
  .....(以下, 略).....

プログラム例と実行 (2)

上記の mrblib/loops/master.rb の 7 行目を修正し,RMC のセンテンスのみ表示する. GPS に送信するコマンド (UART.write の引数) の詳細は, GPS のデータシート を参照すること.

1  # coding: utf-8
2 
3  # GPS初期化 txPin = 17, rxPin = 16 のため uart_num = 2 とする
4  gps = UART.new(2, 9600)
5 
6  # 出力を RMS のみに
7  gps.write("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n")
8 
9 # 入力データをclear_tx_bufferで消去する
10 puts "> gps.clear_tx_buffer"
11 gps.clear_tx_buffer
12 
13 # 入力データが来るのを待つ
14 sleep 2
15 
16 # nonblock で到着している分のデータを取得する (デフォルトでは出力もされる)
17 lines = gps.read_nonblock(4096)
18 
19 # 以下、到着したデータを 1 行ずつ読み込んで表示
20 while true
21   puts  gps.gets()
22   sleep 1
23 end

コンパイルと実行. RMS のセンテンスのみ出力されるようになったことが確認できる. タイミングの問題で欠損が生じる (取り出したデータサイズがゼロ) の場合も見られる.

$ make flash monitor

  ...(前略)......
  start UART (mruby/c class)
  start AQM0802A (mruby/c class)
  start RC8035SA (mruby/c class)
  UART: driver was successfully installed
  > gps.clear_tx_buffer
  ???b*?1,6,3.71,48.0,M,28.6,M,,*6B
  $GPGLL,3529.8205,N,13301.5026,E,033250.000,A,A*5D
  $GPGSA,A,3,28,01,14,17,21,19,,,,,,,3.83,3.71,0.94*01
  $GPGSV,4,1,15,14,64,290,24,01,57,032,18,03,56,129,,22,53,078,13*78
  $GPGSV,4,2,15,28,48,309,31,17,40,310,25,21,37,048,19,08,20,098,*7A
  $GPGSV,4,3,15,19,17,295,30,30,16,232,,06,05,242,,07,03,199,*7C
  $GPGSV,4,4,15,193,,,,194,,,,195,,,*47
  $GPRMC,033250.000,A,3529.8205,N,13301.5026,E,0.92,196.34,190621,,,A*65
  $GPVTG,196.34,T,,M,0.92,N,1.71,K,A*38
  $GPZDA,033250.000,19,06,2021,,*5E
  $PMTK001,314,3*36
  $GPRMC,033251.000,A,3529.8199,N,13301.5026,E,1.17,198.50,190621,,,A*62
                                                                           欠損
  $GPRMC,033252.000,A,3529.8198,N,13301.5029,E,0.60,189.15,190621,,,A*6F
  $GPRMC,033253.000,A,3529.8198,N,13301.5027,E,0.59,189.15,190621,,,A*6A
  $GPRMC,033254.000,A,3529.8201,N,13301.5026,E,0.13,189.15,190621,,,A*61
  $GPRMC,033255.000,A,3529.8202,N,13301.5023,E,0.38,189.15,190621,,,A*6F
  $GPRMC,033256.000,A,3529.8204,N,13301.5021,E,0.11,189.15,190621,,,A*63
  $GPRMC,033257.000,A,3529.8204,N,13301.5021,E,0.30,189.15,190621,,,A*61
  $GPRMC,033258.000,A,3529.8204,N,13301.5020,E,0.35,189.15,190621,,,A*6A
  $GPRMC,033259.000,A,3529.8204,N,13301.5019,E,0.51,189.15,190621,,,A*63
  $GPRMC,033300.000,A,3529.8204,N,13301.5017,E,0.65,222.88,190621,,,A*61
  .....(以下, 略)..... 

プログラム例と実行 (3)

上記の mrblib/loops/master.rb の 22 行目を修正し,10 秒間隔で UART.get してみる.

1  # coding: utf-8
2 
3  # GPS初期化 txPin = 17, rxPin = 16 のため uart_num = 2 とする
4  gps = UART.new(2, 9600)
5 
6  # 出力を RMS のみに
7  gps.write("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n")
8 
9 # 入力データをclear_tx_bufferで消去する
10 puts "> gps.clear_tx_buffer"
11 gps.clear_tx_buffer
12 
13 # 入力データが来るのを待つ
14 sleep 2
15 
16 # nonblock で到着している分のデータを取得する (デフォルトでは出力もされる)
17 lines = gps.read_nonblock(4096)
18 
19 # 以下、到着したデータを 1 行ずつ読み込んで表示
20 while true
21   puts  gps.gets()
22   sleep 10
23 end

コンパイルと実行. 10 秒間隔で出力するようプログラミングしたつもりだが, 第 2 引数の時刻部分を見ると 033932, 033933, 033934, 033935, 033936, ... というように 1 秒間隔で表示される. これは GPS から受け取ったセンテンスはバッファに溜まっており,UART.get はバッファの最初の 1 行を読み出しているためである.

$ make flash monitor

  ...(前略)......
  UART: driver was successfully installed
  > gps.clear_tx_buffer
  $PMTK001,314,3*36
  $GPRMC,033932.000,A,3529.8222,N,13301.5116,E,0.14,162.49,190621,,,A*62
  $GPRMC,033933.000,A,3529.8221,N,13301.5116,E,0.32,162.49,190621,,,A*64
  $GPRMC,033934.000,A,3529.8220,N,13301.5117,E,0.62,165.37,190621,,,A*68
  $GPRMC,033935.000,A,3529.8210,N,13301.5114,E,0.63,166.90,190621,,,A*66
  $GPRMC,033936.000,A,3529.8201,N,13301.5112,E,0.83,188.62,190621,,,A*60
  .....(以下, 略)..... 

プログラム例と実行 (4)

mrblib/loops/master.rb を編集し,バッファに溜まったセンテンスの最後の行を取り出して 表示する. 具体的には,18 行目で UART.read_nonblock でバッファに入っているデータを全部取り出し, $ 区切りで配列化し,配列の最後の要素だけを pop メソッドで取り出すようにしている.

1  # coding: utf-8
2 
3  # GPS初期化 txPin = 17, rxPin = 16 のため uart_num = 2 とする
4  gps = UART.new(2, 9600)
5 
6  # 出力を RMC に限定する. 
7  #gps.write("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n")
8 
9  while true
10 
11   # バッファをクリア
12   gps.clear_tx_buffer
13   
14   # 2 秒間バッファに溜める
15   sleep 2
16   
17   # データ取得・表示
18   line = gps.read_nonblock(4096).split('$').pop
19   puts "*** #{line} ***"
20 
21   # 待ち
22   sleep 8
23 end

コンパイルと実行. UART.read_nonblock を用いると読み込んだデータが標準出力に表示され, さらに 19 行目の puts コマンドの出力が表示される. 今度は先の例と異なり,10 秒間隔でデータが出力されていることが確認できる. なお, 先の例で示したように,GPS は 1 秒単位でデータを出力するが, タイミングの問題でマイコン側でデータの欠損が生じることがある. そのため,上記プログラムでは GPS からのデータの受け取りに 2 秒間費やしている.

$ make flash monitor

  ...(前略)......
  start RC8035SA (mruby/c class)
  UART: driver was successfully installed
  $GPRMC,040618.000,A,3529.8208,N,13301.5070,E,0.70,217.33,190621,,,A*66
  $GPRMC,040619.000,A,3529.8205,N,13301.5070,E,0.62,217.33,190621,,,A*69
   ***GPRMC,040619.000,A,3529.8205,N,13301.5070,E,0.62,217.33,190621,,,A*69
  $GPRMC,040628.000,A,3529.8207,N,13301.5068,E,0.38,217.33,190621,,,A*6F
  $GPRMC,040629.000,A,3529.8206,N,13301.5069,E,0.44,217.33,190621,,,A*65
   ***GPRMC,040629.000,A,3529.8206,N,13301.5069,E,0.44,217.33,190621,,,A*65
  0638.000,A,3529.8192,N,13301.5068,E,0.07,186.15,190621,,,A*62                データ欠損
  $GPRMC,040639.000,A,3529.8192,N,13301.5068,E,0.28,186.15,190621,,,A*6E
   ***GPRMC,040639.000,A,3529.8192,N,13301.5068,E,0.28,186.15,190621,,,A*6E
  ,3529.8191,N,13301.5072,E,0.17,186.20,190621,,,A*6A                          データ欠損
  $GPRMC,040649.000,A,3529.8191,N,13301.5073,E,0.52,186.20,190621,,,A*6B
   ***GPRMC,040649.000,A,3529.8191,N,13301.5073,E,0.52,186.20,190621,,,A*6B
  090,E,0.27,66.62,190621,,,A*52                                               データ欠損
  $GPRMC,040659.000,A,3529.8205,N,13301.5091,E,0.11,66.62,190621,,,A*56
   ***GPRMC,040659.000,A,3529.8205,N,13301.5091,E,0.11,66.62,190621,,,A*56
  190621,,,A*62                                                                データ欠損
  $GPRMC,040709.000,A,3529.8206,N,13301.5109,E,0.39,107.08,190621,,,A*61
  ***GPRMC,040709.000,A,3529.8206,N,13301.5109,E,0.39,107.08,190621,,,A*61
  .....(以下, 略)..... 

プログラム例と実行 (4)

mrblib/loops/master.rb を編集し,日時と緯度経度を LCD への出力できるようにする.

1  # coding: utf-8
2 
3  #I2C 初期化
4  i2c = I2C.new(22, 21)
5 
6  # LCD 初期化
7  lcd = AQM0802A.new(i2c)
8  lcd.setup
9 
10 # GPS初期化 txPin = 17, rxPin = 16 のため uart_num = 2 とする
11 gps = UART.new(2, 9600)
12 
13 
14 # 出力の調整
15 #gps.write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n") # RMC と GGA
16 gps.write("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n") # RMC のみ
17 #gps.write("$PMTK314,-1*04\r\n")                                    # デフォルト
18 
19 
20 while true
21 
22   sleep 3
23 
24   # バッファをクリア
25   gps.clear_tx_buffer
26 
27   # 2 秒間バッファに溜める
28   sleep 2
29 
30   # データ取得・表示
31   lines = gps.read_nonblock(4096).split('$').pop
32   data = lines.split(',')  #カンマ区切りで配列化
33 
34   if data[2] == "A" && data.size == 13    #ステータスが "A" であること,データ数が揃っていることを確認. 
35     # data[9] : 日付, data[1] : 時刻
36     time1 = "#{data[9][4]}#{data[9][5]}-#{data[9][2]}#{data[9][3]}-#{data[9][0]}#{data[9][1]}"
37     time2 = "#{data[1][0]}#{data[1][1]}:#{data[1][2]}#{data[1][3]}:#{data[1][4]}#{data[1][5]}"
38     lat = "#{data[3].to_f / 100.0}#{data[4]}"
39     lng = "#{data[5].to_f / 100.0}#{data[6]}"
40     
41     puts "time (UTC): #{time1}T#{time2}"
42     puts "Latitude:   #{lat}"
43     puts "Longitude:  #{lng}"
44     
45     lcd.clear
46     lcd.cursor(0, 0)
47     lcd.write_string(time1)
48     lcd.cursor(0, 1)
49     lcd.write_string(time2)
50     sleep 5
51     lcd.cursor(0, 0)
52     lcd.write_string(lat)
53     lcd.cursor(0, 1)
54     lcd.write_string(lng)    
55   
56   else
57     lcd.clear
58     lcd.cursor(0, 0)
59     lcd.write_string("Please")
60     lcd.cursor(1, 1)
61     lcd.write_string("Wait...")
62   end
63 
64 end

コンパイルと実行. 10 秒ごとに標準出力に日時と緯度経度が表示され, さらに LCD にも同じものが表示されることが確認できる.

$ make flash monitor

  ...(前略)......
  start I2C (mruby/c class)
  start UART (mruby/c class)
  start AQM0802A (mruby/c class)
  UART: driver was successfully installed
  $GPRMC,042027.000,A,3529.8215,N,13301.5004,E,0.45,36.80,190621,,,A*5E
  $GPRMC,042028.000,A,3529.8215,N,13301.5003,E,0.29,36.80,190621,,,A*5C
  time (UTC): 21-06-19T04:20:28
  Latitude:   35.2982N
  Longitude:  133.015E
  $GPRMC,042037.000,A,3529.8223,N,13301.4984,E,0.77,357.23,190621,,,A*66
  $GPRMC,042038.000,A,3529.8227,N,13301.4974,E,0.76,1.14,190621,,,A*67
  time (UTC): 21-06-19T04:20:38
  Latitude:   35.2982N
  Longitude:  133.015E
  A*6D
  0.79,342.58,190621,,,A*62                                                データ欠損
  $GPRMC,042049.000,A,3529.8255,N,13301.4943,E,0.83,347.63,190621,,,A*6B
  time (UTC): 21-06-19T04:20:49
  Latitude:   35.2983N
  Longitude:  133.015E
  $GPRMC,042058.000,A,3529.8255,N,13301.4940,E,0.62,294.92,190621,,,A*66
  $GPRMC,042059.000,A,3529.8249,N,13301.4935,E,0.59,294.92,190621,,,A*60
  time (UTC): 21-06-19T04:20:59
  Latitude:   35.2982N
  Longitude:  133.015E
  .....(以下, 略).....