ArduinoのライブラリでGPSモジュールで時刻を取得する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

GPSモジュールは現在地の情報や時刻情報などを衛星からの電波を受信してユーザーに通知するモジュールです。Arduino環境ではGPSモジュールのためのライブラリをインストールすることで時刻データや位置情報を簡単に取得できます。

GPSモジュールのライブラリである「TinyGPSPlus」を使ってGPSモジュールから取得した時刻をシリアルモニタに表示して動作確認しています。GPSモジュールは秋月電子の「GPS受信機キット:AE-GYSFDMAXB」を使っています。ライブラリを使用せずNMEAパケットの仕様に合わせて電文を生成して時刻を表示する方法を下記記事にまとめています。

ArduinoとGPSモジュールで時刻を取得してLCDに表示する

Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

GPSモジュール用ライブラリを使用する

ArduinoのIDEの初期ではGPSモジュールに関するライブラリがありません。(あるのかもしれませんが分かりませんでした。)GPSモジュール用のライブラリは複数ありますが今回はTinyGPSPlusライブラリをインストールして使用します。

TinyGPSPlusのインストール

Arduino-ライブラリマネージャでTinyGPSPlusを追加
Arduino-ライブラリマネージャでTinyGPSPlusを追加

Arduino IDEのツール内のライブラリを管理を選択するとライブラリマネージャ画面が表示されます。検索をフィルタに「tinygps」(大文字小文字はどちらでもよい)を入力すると表示される「TinyGPSPlus」が候補として表示されます。インストールボタンを押すとライブラリが追加されます。

ライブラリの準備と初期化

#include <TinyGPSPlus.h>
#include <time.h>
#include <SoftwareSerial.h>

#define PIN_DI1 13

TinyGPSPlus gps; //gpsの情報を格納する変数
SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
  Serial.begin(115200); //シリアルモニタ用
  mySerial.begin(9600); //GPSモジュール用
  pinMode( PIN_DI1, INPUT_PULLUP ); //1pps用のDI
}

ライブラリを使用するためTinyGPSPlus.hをインクルードします。Timer.hは標準関数で時間管理を行う際に使用するためインクルードしています。

Arduino UNOではシリアル通信専用のポートがシリアルモニタを兼ねているためGPSから取得したデータをモニター上に表示するとGPSモジュールに対して通信してしまいます。そのためソフトウェアシリアル(DO・DIポートでシリアルを模擬)を使用してGPSモジュールからデータを取得します。

GPSモジュールのボーレートのデフォルトに合わせてソフトウェアシリアルのボーレートを9600bpsにしています。GPSモジュールが衛星を補足UTC時刻が確定すると1ppsからパルスが出力されます。1ppsのパルス出力をDIで確認するために13ピンをプルアップ付きのDIピンにしています。

広告

GPSの情報を獲得

void loop() {
  while (mySerial.available()){
    if( gps.encode(mySerial.read())){ //受信した文字を解釈
      GpsTimeRead();
    }
  }
}

encode()関数では受信した文字を解釈していきGPS関係の一連の文字列が取得できるとtrueを返します。GpsTimRead()関数内でGPSモジュールから取得した情報を表示します。

void  GpsTimeRead(void){
  tm t;
  time_t tim;
  tm* ltim;

  if( gps.time.isValid() && gpsok == true){
    t.tm_year = gps.date.year() - 1900;
    t.tm_mon = gps.date.month() - 1; //0からカウントするので-1

    t.tm_hour = gps.time.hour() + 9; //日本:世界標準時から9時間ずれ

    tim = mktime(&t); //UNIX時間からの経過
    ltim = localtime(&tim); //ローカル時間に置き換え

    year = ltim->tm_year + 1900;
    month = ltim->tm_mon + 1;
    Serial.print(year); //年を表示

    if (gps.location.isValid() && gpsok == true){
      gpsok = false;
      Serial.print(gps.location.lat(), 6); //経度
      Serial.print(",");
      Serial.println(gps.location.lng(), 6); //緯度
  }
}

time.isValid()で時刻情報の取得を確認します。例では日付データから先に取得してtmの型の変数tに格納しています。dateではyear(年)・month(月)・day(日)を取得します。timeではhour(時)・minite(分)・second(秒)を取得します。

GPSモジュールから取得できる時間は世界標準時なので日本時間に直すと+9時間する必要があります。+9時間によりあり得ない時間になることを防ぐためmktime()関数を使用します。

mktime()はUNIX時間(1970年1月1日0時0分0秒)からの経過時間を時刻の桁上がりなどを考慮しての補正を行いtime_tの型で戻り値として格納できる関数です。

localtime()はtime_tの型のUNIX時間をtmの型(年・月・日・時・分・秒に分けてtmの型に変換する関数です。

location.isValid()で位置情報の取得を確認します。location.lat()で経度、location.lng()で緯度が取得できます。

時刻の繰り上がりによる計算の煩雑さを回避するためmktime()を使用してUNIX時間からの経過時間を補正しlocaltime()で日時分秒などを分割して表示すると楽に時刻を管理できます。

広告

動作確認

GPSモジュール動作確認の回路図
GPSモジュール動作確認の回路図

GPSモジュールからの受信データをシリアルモニタに表示するための回路図です。GPSモジュールが測位を完了して時刻や位置情報が確定すると1ppsのパルス出力が1秒毎に出力します。このタイミングでシリアルモニタに表示時刻を表示しています。

GPSモジュールから時刻情報を取得した結果
GPSモジュールから時刻情報を取得した結果

動作確認する際は位置情報の表示をコメントアウトしているためシリアルモニタに表示していませんが、位置情報をGoogle Map上で座標を入力すると最大で100m程度の誤差が出ることもありましたが位置情報が取得できていました。

PR:あなたのキャリアのお供に「生涯学習のユーキャン」

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include <MsTimer2.h>
#include <time.h>

#define PIN_DI1 13
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define GPS_WAIT 20

// application use
SoftwareSerial mySerial(10, 11); // RX, TX
TinyGPSPlus gps;

uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
uint8_t week;

int16_t cnt10ms;
int16_t timgpswait = TIME_OFF;
bool gpsok;

void  GpsTimeRead(void);
void TimerCnt(void);
void mainTimer(void);

void setup() {

  Serial.begin(115200);
  mySerial.begin(9600);

  pinMode( PIN_DI1, INPUT_PULLUP ); //1pps用のDI

   MsTimer2::set(1,TimerCnt); //1msごとに関数へ遷移
   MsTimer2::start();
   timgpswait = GPS_WAIT;
}

void loop() {

  mainTimer();

  while (mySerial.available())
  {
    if( gps.encode(mySerial.read())){
      GpsTimeRead();
    }
  }

  if( digitalRead(PIN_DI1) == 0 && timgpswait == TIME_OFF){
    timgpswait = GPS_WAIT;
    gpsok = true;
  }

  if(timgpswait == TIME_UP){
    timgpswait = TIME_OFF;
  }
}
/* callback function add */
void TimerCnt(void){
    ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(void){

  if( cnt10ms >= BASE_CNT ){ //10msごとにここに遷移する
    cnt10ms -=BASE_CNT;

    if( timgpswait > TIME_UP ){
        timgpswait--;
    }
  }
}

/*GPSモジュールの情報を表示*/
void  GpsTimeRead(void){
  tm t;
  time_t tim;
  tm* ltim;

  if( gps.time.isValid() && gpsok == true){
    t.tm_year = gps.date.year() - 1900;
    t.tm_mon = gps.date.month() - 1; //0からカウントするので-1
    t.tm_mday = gps.date.day();
    t.tm_hour = gps.time.hour() + 9; //日本:世界標準時から9時間ずれ
    t.tm_min = gps.time.minute();
    t.tm_sec = gps.time.second();

    tim = mktime(&t); //UNIX時間からの経過
    ltim = localtime(&tim); //ローカル時間に置き換え(経過時間をtm構造体に置き換え)

    year = ltim->tm_year + 1900;
    month = ltim->tm_mon + 1;
    day = ltim->tm_mday;
    hour = ltim->tm_hour;
    min = ltim->tm_min;
    sec = ltim->tm_sec;
    week = ltim->tm_wday;

    Serial.print(year);
    Serial.print("/");

    if( month > 9 ){
      Serial.print(month);
    }
    else{
      Serial.print("0"); Serial.print(month);
    }
    Serial.print("/");

    if( day > 9 ){
      Serial.print(day);
    }
    else{
      Serial.print("0"); Serial.print(day);
    }
    Serial.print("/");

    if( hour > 9 ){
      Serial.print(hour);
    }
    else{
      Serial.print("0"); Serial.print(hour);
    }
    Serial.print(":");

    if( min > 9 ){
      Serial.print(min);
    }
    else{
      Serial.print("0"); Serial.print(min);
    }
    Serial.print(":");

    if( sec > 9 ){
      Serial.print(sec);
    }
    else{
      Serial.print("0"); Serial.print(sec);
    }
    Serial.print("-");

    switch(week){
      case 0:
        Serial.println("SUN");
        break;
      case 1:
        Serial.println("MON");
        break;
      case 2:
        Serial.println("TUE");
        break;
      case 3:
        Serial.println("WED");
        break;
      case 4:
        Serial.println("THU");
        break;
      case 5:
        Serial.println("FRI");
        break;
      case 6:
        Serial.println("SAT");
        break;
    }
  }

  if (gps.location.isValid() && gpsok == true){
    gpsok = false;
    Serial.print(gps.location.lat(), 6);
    Serial.print(",");
    Serial.println(gps.location.lng(), 6);
  }
}

TinyGPSPlusライブラリを使用せずNMEAパケットの仕様に合わせて電文を生成して時刻を表示する方法を下記記事にまとめています。

ArduinoとGPSモジュールで時刻を取得してLCDに表示する

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

PR: ゼロからはじめるPython入門講座の申込 テックジム

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました