こんにちは、ENGかぴです。
ArduinoのWireライブラリとシリアル通信を使ってGPSモジュールとRTCモジュールの双方から時刻データを取得することができます。双方のモジュールの利点と欠点を補完しあうように組み合わせることでGPS時計を構成することができます。
秋月電子の「GPS受信機キット:AE-GYSFDMAXB」と「RTCモジュールDIP化キット:AE-RX8900」組み合わせて日付と時刻情報を取得してLCDに表示する方法を説明しています。LCDはQAPASS1602(スターターキットに付属)を使用しています。
本記事の内容は以下のリンクの応用例になります。GPSモジュールとRTCモジュールの使い方については下記記事にまとめています。
ArduinoとGPSモジュールで時刻を取得してLCDに表示する
ArduinoでRTCモジュールの情報をLCDを使って更新する
Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
RTCモジュールとGPSモジュールの関係

GPSモジュールとRTCモジュールの双方のメリットを組み合わせることで時刻の管理を行うシステム構成です。それぞれのモジュールの特徴は以下の通りです。
GPS受信機キット:AE-GYSFDMAXBにおいてUTC時刻を東京の標準時に合わせるパケットが見つけ切れていない(330 PMTK_API_SET_DATAUMを発行してもオフセットしなかった)ため世界標準時を基準としています。
GPSモジュールの特徴:
- 電波を受信している限り時刻が正確に得られる。一度測位できていると途中で受信できなくなっても内部で時刻管理ができるが1PPS出力が停止する。
- 簡易的なモジュールの場合、世界標準時が基準なので日本時間に換算(+9時間)が必要となる。
RTCモジュールの特徴:
- 時刻を書き込むとうるう年を含めてモジュールで管理できる。
- 時刻設定を手動で行う必要があるため秒まで正確に管理しにくい。
2つのモジュールの特徴を補いながら時間管理を行う手順は以下の通りです。
- GPSモジュールが測位開始し取得するまではRTCによる時間管理で動作する。受信できない場合はRTCの時間をボタンを使って設定する。
- GPSモジュールがGPZDAパケットを出力と同時に1PPSパルスを出力するようになれば測位完了としてGPZDAパケットを受け入れる(①と②の処理)
- GPZDAパケットをRTCモジュール用のデータに変換し、1分ごとにRTCモジュールに時刻を書き込む。(③の処理)
- RTCモジュールの時刻を読み出して、LCD表示する。(④と⑤の処理)
GPSモジュールの受信が途中で途絶えてもRTCモジュールに書き込んだデータが時間管理するので秒単位での管理が可能になります。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
GPSモジュールのデータを変換する
GPSモジュールは衛星から時刻データを受信すると1PPSが出力されるようになるため1PPSのパルスを確認した後、受け入れたGPZDAパケットをもとにRTCモジュールに書き込むためのデータを生成します。
/*ControlSet()内のGPZDAパケットを受け入れた後の処理を一部抜粋*/
if( gpsok ){ //GPS測位できているかの判定がOKなら以下の処理
hour_now = charToDecimal(RxData[7])*10 + charToDecimal(RxData[8]);
if( hour_now < 15 ){
setdata.year = charToDecimal(RxData[26])*10 + charToDecimal(RxData[27]);
setdata.month = charToDecimal(RxData[21])*10 + charToDecimal(RxData[22]);
setdata.day = charToDecimal(RxData[18])*10 + charToDecimal(RxData[19]);
setdata.hour = charToDecimal(RxData[7])*10 + charToDecimal(RxData[8])+9;
setdata.minute = charToDecimal(RxData[9])*10 + charToDecimal(RxData[10]);
setdata.second = charToDecimal(RxData[11])*10 + charToDecimal(RxData[12]);
setdata.week = ( 1 << WeekSet(setdata.year));
smin = setdata.minute;
if(smin ^ oldsmin ){ //分に変更があったか?
RX8900.setDateTime(&setdata);
}
oldsmin = smin;
}
}
GPSが測位できているとgpsokのフラグがセットされ内部の処理を行うようにしています。GPSからの時刻データは文字コードになっているので10進数に変換してRTCモジュールへの書き込みデータを生成する必要があります。
/* 文字コードを10進数に変換 */
int8_t charToDecimal(uint8_t chr){
int8_t ret;
ret = chr - 0x30;
if( ret < 0){
ret = 0;
}
return ret;
}
10進数の0~9は文字コード(アスキーコード)に変換すると0x30~0x39になります。この逆をすると文字コードから10進数に変換することができます。基本的にGPSから受信したGPZDAパケットの時刻情報を使用しているため単純に0x30を引くだけの変換にしていますが、汎用的に変換したい場合は少しバグ対策が必要です。
GPSデータからRTC用のWEEKデータを計算する
GPSモジュールによってはGPS週番号やGPS週秒の情報が取れるものもありますが、今回使用しているGPSモジュールには曜日のデータは付属されません。(あるかもしれませんが、気づいていないだけかもしれません・・)曜日を表示したい場合はGPZDAの年と日付のデータから曜日を算出する計算を行う必要があります。
#define YEAR4CNT 1461 //366+365+365+365 //4年経過後の日数
const uint16_t yearplus[4] = {366,365,365,0}; //年経過のテーブル
const uint8_t monthtype[2][12] = {
{0,31,28,31,30,31,30,31,31,30,31,30}, //通常年のテーブル
{0,31,29,31,30,31,30,31,31,30,31,30} //うるう年のテーブル
};
uint8_t WeekSet(uint8_t y ){
uint8_t week;
uint8_t y_1;
uint8_t bb;
uint8_t cc;
uint16_t summonth = 0;
uint16_t sumyear = 0;
uint8_t uru = 0;
y_1 = y - 20; //基準年(20:2020)からの経過年
bb = y_1 / 4; //4年経過したら商として表示
cc = y_1 % 4; //0-3年までの経過年数
if( cc == 0 ){ //うるう年の場合は0
uru = 1;
}
for(uint8_t i=0; i< setdata.month; i++ ){ //該当年の1月からの経過日数(月ベース)
summonth += monthtype[uru][i];
}
for(uint8_t i=0; i< cc; i++ ){ //0-3年経過まで経過日数(年ベース)
sumyear += yearplus[i];
}
sumday = YEAR4CNT * bb + sumyear + summonth + setdata.day -1; //基準日からの経過日数
week = sumday % 7; //基準曜日(WED)からの差
week +=3; //RTCモジュールではWEDは3回シフトなので+3する
if( week > 6 ){ //+3して6より大きい場合は週をまたぐので1週間分(-7)を引く
week =- 7;
}
return week;
}
//RTCモジュールへの曜日データをセット
setdata.week = ( 1 << WeekSet(setdata.year));
うるう年は4年に一度来るので4年間のうち1年間の年日数が366日なります。このため4年間を一区切りとして基準日(2020年1月1日水曜日)からの日数を計算して曜日の計算を行っています。
例)2025年2月11日までの経過日数と曜日
引数が25なのでy_1が5になり5年経過したことになります。bbを計算すると1になり4年経過後の日数の単位を1回経過したことになります。次にccを計算すると1になるので2024年から1年経過したことになります。2024年はうるう年なので366日経過するので2024年経過時の1461日に366日を加えた1827日が2025年1月1日を経過した日数になります。
2025年1月1日から2月11日までの経過日数は月ベースのテーブルから31日と11日となり当日を含むため-1すると41日になります。合計すると1827+41=1868日になります。これを1週間(7日)で割ったときの余りによって基準曜日から進んだ曜日が分かります。1868を7で割ると余りは6になるので基準曜日の水曜日から6日分進んでいることになるので火曜日になります。
RTCモジュールのデータに合わせるために+3して6より大きいと1週間経過したことになるので-7しています。これを戻り値として返すことでRTCモジュールのWEEKデータとしてシフトする回数が指定できます。
動作確認

GPSモジュールとRTCモジュールとLCDを使っておりArduinoのDIDOピンがいっぱいになってしまったためRTCモジュールの/INT(1秒カウント通知)を接続していません。そのためGPSモジュールの1PPSによってRTCモジュールデータの読み込みタイミングを同期しています。
SW1~SW3はGPSが受信できない時に時刻を設定する際に使用するものですが、任意の時間をセットしてもGPSからの測位情報で時刻を更新するためほとんど使用することはありません。

電源を入れると初期画面としてバージョンを表示しています。2秒ほどしてからメインの時刻表示しますが、最初はRTCの初期時間から時刻のカウントをスタートします。GPSは環境によりますが、窓際で30秒~40秒で測位が完了しGPSからの受信時間が表示されます。
LCDに表示している時間はRTCモジュールから読み出した時間ですが、GPSモジュールからの測位データを書き込んでいるので間接的にGPSの時刻を表示していることになります。
ソースコード全体
ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。
リンクからZIPファイル形式のファイルをダウンロードし、任意の場所に展開していただくとテキストファイルが生成されます。
下記ソースコードを使用する際はRTCモジュールのAPIのファイルを追加する必要があります。以下のリンクのソースコード全体にあるものと同じですので、ご使用の際は下記リンクを参照ください。
ArduinoでRTCを使用してファイル管理とAPIの実装する
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:企業で求められる即戦力技術を身に付ける テックキャンプエンジニア転職
最後まで、読んでいただきありがとうございました。
世界標準時で15時になる前まで(日本時間で23:59まで)にGPSから受信できれば正確な時間を刻むことができます。スマホの時計や部屋に置いているGPS時計と同じように時を刻んでいるのを確認しました。