行き当たりばったり電子工作

昔の論理IC電子工作少年が、四苦八苦しながら現在の電子工作に挑戦する話。

ArduinoとGPS(2)~ GPS高度方位計を作る

はじめに

前回はGPSからの情報取得まで確認したので、今度はちゃんと欲しい情報をディスプレイに表示させます。

今回は欲しい情報として、速度、方位、高度、衛星数、測位方式をピックアップしました。

使ったもの

  • Arduino nano every
  • TXS0108E 双方向ロジックレベル変換モジュール
  • 秋月電子 GNSS(GPS,GLONASS,QZSS) 1PPS出力 みちびき2機対応 アンテナ外付タイプ
  • 秋月電子 GPS/GLONASS アクティブアンテナ LNA内蔵
  • 秋月電子 SMA-J⇔IPEX(IPX/U.FL)変換ケーブル(120mm)
  • OLED 液晶ディスプレイ 白色 128x64 2.42インチ SPI接続(ドライバIC:SSD1309)

前回から追加したのは液晶ディスプレイのみ。単色ながら視認性のよい OLED液晶にしました。解像度は低いですが、2.42インチあるので見栄えがします。

配線図

ArduinoでGPS2 配線図

やや見づらい図になってしまいましたが、OLEDディスプレイも3.3Vで動くので、レベルシフターで変換して、Arduino nano と通信します。

プログラム

今回は、GPSからのデータを取得するのにTinyGPS+ライブラリを、OLEDディスプレイに表示するのにU8g2ライブラリを利用します。どちらも Arduino IDEのライブラリマネージャで検索してインストール。

TinyGPS+ライブラリは、GPSから取得したデータから直接速度を取り出せる便利な関数も用意されていますが、ある程度通信内容を見ながら考えたかったので、レコードから任意のデータを取り出せる機能を利用しています。

とりあえず書き下ろしたのが、下のプログラム。loop()に詰め込み過ぎ...

#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include "U8g2lib.h"
#include <SPI.h>

// Softwear Serial Pin
static const int RXPin = 2, TXPin = 3;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

// GPS baud
static const uint32_t GPSBaud = 9600;

// The TinyGPSPlus object
TinyGPSPlus gps;

// Tiny GPS Custom object
TinyGPSCustom gps_alt (gps, "GPGGA", 9); // 高度
TinyGPSCustom gps_sat (gps, "GPGGA", 7); // 衛星数
TinyGPSCustom gps_qlt (gps, "GPGGA", 6); // 測位方式
TinyGPSCustom gps_spd (gps, "GNRMC", 7); // 速度(ノット)
TinyGPSCustom gps_dir (gps, "GNRMC", 8); // 方位

// 方位の定義。16番目は WAIT
static const char *c_dir[17] = {
  "  N   ",
  "N-N-E ",
  " N-E  ",
  "E-N-E ",
  "  E   ",
  "E-S-E ",
  " S-E  ",
  "S-S-E ",
  "  S   ",
  "S-S-W ",
  " S-W  ",
  "W-S-W ",
  "  W   ",
  "W-N-W ",
  " N-W  ",
  "N-N-W ",
  "WAIT  "};

// 前回方位保管用
float f_olddir=999;

// OLE object
U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  

void setup() {
  Serial.begin(9600);

  // LCD初期化
  u8g2.begin();
  u8g2.setPowerSave(0);
  u8g2.setFlipMode(0);

  // ソフトウェアシリアル初期化
  ss.begin(GPSBaud);
}

void loop() {
  // 変数宣言
  float f_alt;
  float f_spd;
  float f_dir;
  int   i_dir;
  int   i_qlt;
  char buf[10];

  // 更新があったときのみ表示を変更する
  if (gps_alt.isUpdated() || gps_sat.isUpdated() || gps_qlt.isUpdated() || gps_spd.isUpdated() || gps_dir.isUpdated()) {
    // 標高を数値に変換  
    f_alt=atof(gps_alt.value());

    // 速度を数値に変更してkm/h換算
    f_spd=atof(gps_spd.value()) * 1.852;
  
    // 速度が 8km/h 以上で方位を更新する
    if (f_spd >= 8.0) {
      // 方位を元に配列引数を算出
      f_dir=atof(gps_dir.value());
    }
    else {
      f_dir=f_olddir;
    }

    // 方位を元に方位文字配列引数を算出
    if (f_dir < 360) {
      float f_tmp=f_dir + 11.25;
      if (f_tmp >= 360.0) {
        f_tmp -= 360.0;
      }
      i_dir=(int)(f_tmp/22.5);
    }
    else {
      i_dir = 16;
    }

    // 方位を保存
    f_olddir = f_dir;

    // LCDに表示
    u8g2.clearBuffer();

    // 速度表示
    u8g2.setFont(u8g2_font_6x13_mr);
    u8g2.setCursor(3,15);
    dtostrf(f_spd, 4, 0, buf);
    u8g2.print(F("Spd : ")); u8g2.print(buf); u8g2.print(" km/h");

    // 方位表示
    u8g2.setFont(u8g2_font_6x13_mr);
    u8g2.setCursor(3,30);
    if (f_dir > 360) {
      u8g2.print(F("Dir : WAIT"));
    }
    else {
      dtostrf(f_dir, 4, 0, buf);
      u8g2.print(F("Dir : ")); u8g2.print(buf); u8g2.print(" deg  ");
      u8g2.setCursor(95,30);
      u8g2.print(c_dir[i_dir]);
    }
    
    // 高度表示
    u8g2.setFont(u8g2_font_6x13_mr);
    u8g2.setCursor(3,45);
    dtostrf(f_alt, 4, 0, buf);
    u8g2.print(F("Alt : ")); u8g2.print(buf); u8g2.print(" m  ");

    // 衛星数表示
    u8g2.setFont(u8g2_font_5x8_mr);
    u8g2.setCursor(3,60);
    sprintf(buf, "Satelite : %02s", gps_sat.value());
    u8g2.print(buf);

    // 測位方式表示
    i_qlt = atoi(gps_qlt.value());
    u8g2.setFont(u8g2_font_emoticons21_tr);
    u8g2.drawGlyph(95,60,0x24-i_qlt*2);
    
    // 画面出力
    u8g2.sendBuffer();
  }

  // delay付きのGPS受信
  smartDelay(1000);
}

// delay付きのGPS受信
static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}

18行目から23行目で、GPSからのデータのどこを取り出すかを指定しています。今回はGPGGAセンテンスから9番目の高度、7番目の衛星数、6番目の測位方式を取得、GNRMCセンテンスから速度と方位を取得しています。速度は単位がノットなので、78行目でkm/h に変換しています。

25行目から43行目は方位の定義です。17番目にWAITを入れていますが、実際には使っていません。

方位は速度0のときにフラフラしてしまうので、8km/h 以上の時に更新するようにしました。そのために、1回前の方位を保存するためのグローバル変数を用意しています。ちなみになぜ8km/hかというと、交通法規上徐行と見なされる速度だからです、はい。

89行目から99行目で、方位角度を元に、方位文字配列の引数を計算しています。16方位なので、22.5度間隔ですね。

測位方式は、u8g2_font_emoticons21_tr にスマイルマークがあったので、これを採用。141行目で、コードを計算して表示しています。コードはふたつおきで使えたので計算が楽になりました。

GPGGAセンテンスとGNRMCセンテンスについては、こちらを参照させていただきました。感謝です。
www.hiramine.com

実行結果

まずは電源を入れてからGPS受信までの待機画面。

GPS待機中

そして、電波受信して速度が一度でも8km/hを超えると、下の画面のようになります。

GPS受信中

測位方式(ニコちゃんマーク)は「標準測位方式」。「干渉測位方式」になると口が開きます。

課題

ON時やリセット時に、なぜかOLEDディスプレイの初期化が上手くいかないことがあります。画面が表示されなかったり、半分くらい縦にスクロールした感じになったり。原因は不明。サンプル通りにbegin()を呼んでいるだけなんですけどね...
おいおい探求してみます。begin()のあとに、少しdelay()を入れたら安定しないかしらん。

また、今回はほとんど文字表示だけですが、方位なんかはグラフィカルにしたいですね。一度やってみたのですが、128×64では斜めの線ですら解像度不足でカクカクになってしまうので、今回は断念。フルカラーTFTディスプレイを扱うには nano everyだとメモリが不足するだろうしね。