こんにちは、ENGかぴです。
GPSモジュールは現在地の情報や時刻情報などを衛星からの電波を受信してユーザーに通知するモジュールです。GPSモジュールのデータはシリアル通信を使って得られますが、1ppsと組み合わせることで測位後のデータと判断してデータを得られます。
秋月電子の「GPS受信機キット:AE-GYSFDMAXB」を使ってGPSモジュールから日付と時刻情報を取得してLCDに表示する方法を説明しています。LCDはQAPASS1602(スターターキットに付属)を使用しています。
GPSモジュール用のライブラリを追加して時刻情報を取得して動作確認したことを下記記事にまとめています。
ArduinoのライブラリでGPSモジュールで時刻を取得する
Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリーを使用して動作確認したことをまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
GPSモジュールの使い方
GPSモジュールはArduinoのシリアル通信とGPSモジュールを配線するだけでデータを取得することができます。デフォルト設定では不要なデータも含まれてしまうため必要なデータだけが受信できるようにGPSモジュールの設定を行います。
GPSモジュールは秋月電子の「GPS受信機キット:AE-GYSFDMAXB」を使っているため下記リンクのデータシート(技術資料)を一部抜粋して説明します。
GPSモジュールの電文(NMEAパケット)
GPSモジュールはメーカー問わず基本的にNMEAパケットフォーマットに従ってデータを送信するようになっています。NEMAパケットフォーマットは以下の通りです。

NMEAパケットは最後のCR・LFを除いて文字コード(アスキーコード)となります。各パラメータをまとめます。
パケット | 内容 |
---|---|
Preamble | 1バイトの文字’$’ |
Talker ID | 4バイトの文字”PMTK”(メーカーによって異なる) |
Packet Type | 3バイトの文字”000″から”999″(電文の番号) |
Data Field | 任意の文字列で、Packet Typeによってデータ長が異なる。 設定項目が複数ある場合は”,”で区切る。 |
* | 1バイトの文字でData Fieldの終わり判定に使用する。 |
CHK1、CHK2 | Preambleと*までの間のチェックサム(EOR)値を2バイト文字で表現する。 |
CR、LF | 2バイトのバイナリデータCR:0x0D、LF:0x0Aとなる。 |
GYSFDMAXBの仕様書ではCHK1、CHK2の説明にPreambleと*までのチェックサム値と記載されているため各バイトを加算して1バイトデータを文字列としてチェックサムとして計算してしまいそうですが、EORで計算する必要があります。
チェックサムが一致しない場合はGPSモジュール側で無効なパケットとして処理されてしまいます。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
出力制御のフォーマット
GPSモジュールには各種データを出力する項目を選択する314パケットがあります。今回使用しているAE-GYSFDMAXBにおいてはDate Fieldは全部で19の設定項目がありますが、実装されている項目は以下の通りです。
種別 | 内容 | 設定番号 |
---|---|---|
GPGLL | 位置情報(緯度、経度)を出力する。 | 0 |
GPRMC | 位置情報と時刻(UTC時刻)、速度と方位を同時に出力する。 | 1 |
GPVTG | 方位と速度を出力する。 | 2 |
GPGGA | 位置情報(緯度、経度)、GPS測位状態、測位衛星数を出力する。 | 3 |
GPGSA | GPS衛星の使用衛星番号、各種DOP、動作モードを出力する。 | 4 |
GPGSV | GPS衛星の生成情報を出力する。 | 5 |
GPZDA | 時刻(UTC時刻)と日付を出力する。 | 17 |
GPSモジュールから日付と時刻を含む情報を取得したいので「314 PMTK_API_SET_NMEA_OUTPUT」の設定からGPZDA interval以外を出力しないように設定します。314フォーマットにおいて対象の番号の周期設定を変更することでインターバルのタイミングを任意に設定できます。
char buf314[]= "PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0";
//正式なフォーマットは$,*checksum,CR,LFを追加する
例のように設定番号の17番目以外のインターバルを0にすることでGPSモジュールからの情報を制限することができます。GPZDAにおいては1にしているため1秒周期でGPZDAが出力されます。他の項目についても出力したい場合は0~5までの任意のタイミングに設定します。
初期化処理で314パケットが受け付けられなかった場合は受信データにGPZDA以外のデータが来た場合に再度314パケットを送出するようにして受け付けるまで繰り返すようにしています。
リスタートには大きくホットスタートとコールドスタートがありますが、今回使用しているGPSモジュールにおいては4つの項目に分類されています。
種別 | 説明 |
---|---|
HOT START | 利用可能なデータを保持したままのリスタートする。 |
WARM START | リスタート後エフェメリス(衛星の測位情報)が使用できない。 |
COLD START | リスタート後、時間・位置・日付・エフェメリスのデータが使用できない。 |
FULL COLD START | COLD STARTよりもさらにシステム/ユーザーのコンフィグレーションデータをクリアする。 |
電源ON以降(モジュールは電源ON後で最大1500ms準備時間が必要)にGPSモジュールが取得したデータを無駄にせずにリスタートができるホットスタートを使用することで初期化を行い、314フォーマットによる出力制御設定を行います。
時間データの受け取りタイミング
GPSモジュールは衛星を測位する前からシリアル通信でデータを通知します。データが確定していない場合のデータで時刻や測位情報として採用してしまうと不確定な情報が表示されることになります。
GPSモジュールは測位が完了し正確なデータとして確定できた時1ppsピンからパルスを出力して通知する機能を持っています。1ppsピンの状態を監視してパルスが生成されていれば正常な情報であると判断してGPSモジュールの情報として処理するようにします。
void loop(){
GpsMain();
if( digitalRead(PIN_DI1) == 0 ){ //LOWアクティブなので0で判定
gpsok = true; //GPSのデータが確定
timgpswait = GPS_WAIT;
}
if( timgpswait == TIME_UP ){ //1ppsが規定時間途絶えた
timgpswait = TIME_OFF;
gpsok = false; //GPSのデータが未確定とする
}
}
/*** RTCメイン処理 ***/
void GpsMain(){
if( gpsok ){ //GPSのデータが確定していれば以下の処理
//LCDに時刻を表示する
}
}
PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
GPSモジュールからの受信データの受け入れ
GPSモジュールから受信するデータもNMEAパケットに従っているためNMEAパケットの’$’から’*’までのデータを確認することでパケットタイプによるデータの管理ができます。
/* RX function add */
void RxDataChk(){
//受信データ数の算出
if( rxgps.buf[rp] == '$' ){ //ヘッダーの確認
flg = false;
for(uint16_t i=0; i< rxsz; i++ ){
if( rxgps.buf[rp] == '*' ){ //'*'までのデータ数を確認
flg = true;
sz = i+1;
allsz = sz + 4; //チェックサムと制御文字を含めた全体を受信するため+4
}
if(++rp >= RING_SZ ){
rp = 0;
}
}
if( rxsz >= allsz && flg){
//受信データを一時的に保管
sum = sumbcc(&RxData[1], sz-2); //チェックサムの計算
if( RxData[sz] == sum_h && RxData[sz+1] == sum_l ){
ControlSet(); //チェックサムで受け入れた後の処理
}
}else{
if(++rxgps.rp >= RING_SZ ){
rxgps.rp = 0;
}
}
}
データ長はGPSモジュールから出力される種別に応じて異なるため、データ長の確認を行ってから必要なデータ数のみ一時的に受け入れチェックサム(EOR)の計算を行い、異常がなければデータを受け入れる処理を行います。
void ControlSet(){
if( RxData[0] =='$' && RxData[1] =='G'
&& RxData[2] =='P' && RxData[3] =='Z'
&& RxData[4] =='D' && RxData[5] =='A' ){
//LCD表示用のデータを生成するなど必要な処理を追加
}
受け入れたデータのパケットタイプから必要なパケットのデータであるかの確認を行い対象の処理を行います。上記の例では「$GPZDA」のパケットであることを判断してLCD用のデータを生成しています。
位置情報を示すパケットに対してデータを作成する場合は「$GPGLL」のパケットであることを確認して処理を追加します。今回は公開していませんが、GPGLLフォーマットで位置情報を取得して座標をMAP上で入力すると少し誤差がありました(約100m)が位置情報が取れていました。
PR:RUNTEQ(ランテック )- マイベスト3年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール
GPSの情報の時間を補正する
void GpsTimeRead(void){
tm t;
time_t tim;
tm* ltim;
year = strtol((char*)&date[0],NULL,10); //文字を10進数に置き換え
mon = strtol((char*)&date[5],NULL,10);
hour = strtol((char*)&date[11],NULL,10);
t.tm_year = year - 1900;
t.tm_mon = mon - 1; //0からカウントするので-1
t.tm_hour = hour + 9; //日本:世界標準時から9時間ずれ
tim = mktime(&t); //UNIX時間からの経過
ltim = localtime(&tim); //ローカル時間に置き換え
year = ltim->tm_year + 1900;
month = ltim->tm_mon + 1;
Serial.print(year); //年を表示
}
GPSモジュールの時刻と日時は世界標準時が基準であるため日本での時間は世界標準時+9時間となります。世界標準時で15:00を超えたとき日本時間に換算すると24時を超えることになり日付も更新する必要がありますが、うるう年が絡んだ時など日本時間に換算しようとしたとき少し面倒なのが欠点です。
日本時間に換算にする際にtime.hのmktime()やlocaltime()を使用すると煩雑な計算をしなくても時間の管理ができます。
date[]にGPSモジュールから取得した時刻データの文字列が格納していますが10進数に変換してtmの型の変数に格納します。年・月・日・時・分・秒をそれぞれをtmの型の変数に格納した後はmktime()関数で時刻の補正を行います。
mktime()はUNIX時間(1970年1月1日0時0分0秒)からの経過時間を時刻の桁上がりなどを考慮しての補正を行いtime_tの型で戻り値として格納できる関数です。
localtime()はtime_tの型のUNIX時間をtmの型(年・月・日・時・分・秒に分けてtmの型に変換する関数です。localtime()で変換した時刻データをLCDやシリアルモニタ表示用のデータとして加工すると日本時間に換算した結果が表示されます。
動作確認

GPSモジュールからの受信データをLCDに表示するための回路図です。初期画面はGPS-TESTとVER1.00を表示しておき、GPSモジュールの時刻データが確定した後は時刻を表示するようにしています。すでに確定している場合は最初で2秒の表示としています。
GPS受信機キット:AE-GYSFDMAXBのデータシートを確認すると330 PMTK_API_SET_DATUMを発行してTOKYO-Mに合わせると時刻調整できると考えていましたが、発行してもうまく時刻が補正されませんでした。他のAPIで補正することができるかもしれません。
時刻補正がなかったためtime.hを使用して時刻の補正を行いました。

時刻が表示されると日本時間に換算した時刻が表示されていることを確認しました。シリアルモニタでも同様の時刻が表示されていました。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。
ファイルの拡張子をtxtからinoに変更するかファイルの内容をコピーすると使用できます。
本記事はGPSモジュールから受信したシリアル通信のデータを判断して時刻表示を行っていますが、GPSモジュール用のライブラリを追加して時刻情報を表示する方法もあります。
ArduinoのライブラリでGPSモジュールで時刻を取得する
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。
外部機器をマイコンから制御する場合、マイコン起動時に外部機器に対して初期化を行ってから動作させることが経験上多いです。