SoftwareSerialを使う ~ Arduino Uno R4でGPS

以前、VK2828U7G5 GPSモジュールの紹介記事を公開しました。

今回は、より実用的にプログラムと構成を作りました。車でGPS速度を日常的に確認することを目的としたパッケージになっています。

1.使用するハードウェア

前回の記事と重複しますが、ハードウェアを紹介します。接続に関しては前回記事をご覧ください。

Arduino UNO R4 Minima

created by Rinker
Arduino (アルドゥイーノ)
¥3,300 (2025/10/13 18:00:50時点 Amazon調べ-詳細)

Arduino UNO R4 Minimaは、Raspberry Pi並みのGPIOが確保されており、I/Oを多く必要とする工作に適しています。また、R4になってから演算能力が向上しており、後でも述べますがESP32 Picoなどよりも処理性能を必要とする場合に適しているようです。(自分の体感で述べています。正確ではないかもしれません)

GPS

価格が安くてGPS衛星の補足能力が高いことで、追加で購入しました。コストパフォーマンスが高いGPSユニットです。

0.96インチOLED (I2C接続)

I2Cで4線で操作できることから、私が一番良く使う表示器です。また、ライブラリが充実していて安定していることも、多く使う理由です。

タクトスイッチ

created by Rinker
KKHMF
¥597 (2025/10/13 23:38:31時点 Amazon調べ-詳細)

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だからこそです。良い使い方ではないかもしれませんが、活用していきたいと考えています。