SoftwareSerialを使う ~ Arduino Uno R4でGPS

以前、VK2828U7G5 GPSモジュールの紹介記事を公開しました。
今回は、より実用的にプログラムと構成を作りました。車でGPS速度を日常的に確認することを目的としたパッケージになっています。
1.使用するハードウェア

前回の記事と重複しますが、ハードウェアを紹介します。接続に関しては前回記事をご覧ください。
Arduino UNO R4 Minima
Arduino UNO R4 Minimaは、Raspberry Pi並みのGPIOが確保されており、I/Oを多く必要とする工作に適しています。また、R4になってから演算能力が向上しており、後でも述べますがESP32 Picoなどよりも処理性能を必要とする場合に適しているようです。(自分の体感で述べています。正確ではないかもしれません)
GPS
価格が安くてGPS衛星の補足能力が高いことで、追加で購入しました。コストパフォーマンスが高いGPSユニットです。
0.96インチOLED (I2C接続)
I2Cで4線で操作できることから、私が一番良く使う表示器です。また、ライブラリが充実していて安定していることも、多く使う理由です。
タクトスイッチ
OLEDの画面が小さいので、GPS各値を画面を切り替えて表示する仕様にしました。Arduino UNO R4のGPIOは11番を使用しました。ボタンはプルアップで動作するようにして、プルアップ抵抗は510Ωを使用しています。
2.プログラム
前回のプログラムは必要最低限の処理だったので、今回は実用的な部分を書き足しています。
#include <SoftwareSerial.h>
#include <TinyGPSPlus.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <Fonts/FreeSans18pt7b.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define MODE_PIN 11
// ディスプレイの初期化
Adafruit_SSD1306 tft(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// スプライト表示
GFXcanvas16 canvas(SCREEN_WIDTH, SCREEN_HEIGHT) ;
SoftwareSerial GpsSerial(2, 3); // 受信, 送信
TinyGPSPlus mygps;
int g_speed = 0 ;
int g_altitude = 0 ;
int g_direction = 0 ;
double g_latitude = 0 ;
double g_longtitude = 0 ;
int g_year = 0 ;
int g_month = 0 ;
int g_day = 0 ;
int g_hour = 0 ;
int g_minute = 0 ;
int g_second = 0 ;
int g_satellites = 0 ; // 衛星数
int g_mode = 0 ; // 0 : 速度 1:方角 2:高度 3:時刻 4:緯度・経度など
void setup() {
char buff[256];
// シリアル
Serial.begin(9600);
GpsSerial.begin(9600);
// I2C
if(!tft.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
// モード切替
pinMode(MODE_PIN, INPUT_PULLUP);
// OLED
tft.display();
delay(2000);
tft.clearDisplay();
tft.setRotation(2); // 画面の向き
canvas.fillScreen(BLACK);
canvas.setTextColor(WHITE);
canvas.setTextSize(2);
canvas.setCursor(0, 0);
sprintf(buff, "Setup Complete");
canvas.print(buff);
tft.drawRGBBitmap(0, 0, canvas.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
tft.display();
Serial.println(F("Setup Complete"));
}
void loop() {
bool isUpdate = false ; // GPSのアップデートがあった
char buff[256] ;
// モード切替
int value;
value = digitalRead(MODE_PIN);
if (value == LOW) {
g_mode ++ ;
if(g_mode == 5) {
g_mode = 0 ;
}
sprintf(buff, "Btn Pushed=%d", g_mode) ;
Serial.println(buff);
delay(1000) ;
}
if (GpsSerial.available()) {
if(mygps.encode(GpsSerial.read())) {
if (mygps.location.isUpdated()) {
g_latitude = mygps.location.lat() ;
g_longtitude = mygps.location.lng() ;
// Serial.print("lng: ");
// Serial.println(g_longtitude, 20);
isUpdate = true ;
}
if (mygps.speed.isUpdated()) {
g_speed = (int)(mygps.speed.kmph() + 0.5);
// Serial.print("speed: ");
// Serial.println(g_speed);
isUpdate = true ;
}
if (mygps.course.isUpdated()) {
g_direction = (int)(mygps.course.deg() + 0.5) ;
// Serial.print("direction: ");
// Serial.println(g_direction);
isUpdate = true ;
}
if (mygps.altitude.isUpdated()) {
g_altitude = (int)(mygps.altitude.meters() + 0.5) ;
// Serial.print("altitude: ");
// Serial.println(g_altitude);
isUpdate = true ;
}
if (mygps.time.isUpdated()) {
// 年月日を取得
g_year = mygps.date.year();
g_month = mygps.date.month();
g_day = mygps.date.day();
// 時分秒を取得
g_hour = mygps.time.hour();
g_minute = mygps.time.minute();
g_second = mygps.time.second();
// Serial.print("year: ");
// Serial.println(g_year);
isUpdate = true ;
}
if (mygps.satellites.isUpdated()) {
g_satellites = mygps.satellites.value();
// Serial.print("satellites: ");
// Serial.println(g_satellites);
isUpdate = true ;
}
// 描画関数を呼ぶ
if (isUpdate) {
switch(g_mode) {
case 0 :
SpeedDisplay() ;
break ;
// ここに、各処理を追加してください
default :
break ;
}
}
}
}
}
void SpeedDisplay() {
char buff[16];
canvas.fillScreen(BLACK);
// 速度表示
canvas.setTextSize(5);
canvas.setCursor(0, 0);
sprintf(buff, "%3d", g_speed);
canvas.print(buff);
canvas.setTextSize(3);
canvas.setCursor(85, 40);
canvas.print("km/h");
tft.drawRGBBitmap(0, 0, canvas.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
tft.display();
//delay(100) ; 入れると処理が間に合わなくなる
}
速度表示の関数しか作っていないので、それぞれ作成してみてください。
3.Raspberry Pi Picoでも試したが
実は、Raspberry Pi Picoでも同じくやってみたのですが、SoftwareSerialとTinyGPSそれぞれ不安定で、保留中です。プログラムはほとんど同一のものを動作させたのですが、安定しません。理由ははっきり分かりませんが、処理能力に関係するタイミングにより、うまく動作しないのかもしれません。
その後、Raspberry Pi Zeroの方でGPSロガーを作成したのですが、動作はさすがに完璧ですが、OSで動作するRaspberry Piは、使い勝手が良くありません。車での運用では、アクセサリーソケットを使うと電源がエンジンの動作でON/OFFしてしまいますし、バッテリーで動作させると車内温度が50度以上になった場合、発火が心配です。
そのため、今回のシステムは、車のアクセサリーソケットから電源を取り、電源が入るたびにプログラムが実行される形になっています。これも、ストレージを必要としないArduino UNO R4 Minimaだからこそです。良い使い方ではないかもしれませんが、活用していきたいと考えています。