使ったもの
- 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インチあるので見栄えがします。
配線図
やや見づらい図になってしまいましたが、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受信までの待機画面。
そして、電波受信して速度が一度でも8km/hを超えると、下の画面のようになります。
測位方式(ニコちゃんマーク)は「標準測位方式」。「干渉測位方式」になると口が開きます。
課題
ON時やリセット時に、なぜかOLEDディスプレイの初期化が上手くいかないことがあります。画面が表示されなかったり、半分くらい縦にスクロールした感じになったり。原因は不明。サンプル通りにbegin()を呼んでいるだけなんですけどね...
おいおい探求してみます。begin()のあとに、少しdelay()を入れたら安定しないかしらん。
また、今回はほとんど文字表示だけですが、方位なんかはグラフィカルにしたいですね。一度やってみたのですが、128×64では斜めの線ですら解像度不足でカクカクになってしまうので、今回は断念。フルカラーTFTディスプレイを扱うには nano everyだとメモリが不足するだろうしね。