こんにちは、ENGかぴです。
GPSモジュールは現在地の情報や時刻情報などを衛星からの電波を受信してユーザーに通知するモジュールです。Seeeduino XIAOを使ってGPSモジュールから時刻と位置情報を取得してLCDに表示して動作確認したことをまとめています。GPSモジュールはGPS受信機キット:AE-GYSFDMAXB(秋月電子)を使用しLCDはAQM1602XA-RN-GBW(秋月電子)を使用しています。GPSモジュール用のライブラリを追加して時刻情報を取得して動作確認したことを下記記事にまとめています。
ArduinoのライブラリでGPSモジュールで時刻を取得する
Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
GPSモジュールの使い方
GPSモジュールはSeeeduino XIAOのシリアル通信と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モジュール側で無効なパケットとして処理されてしまいます。
出力制御のフォーマット
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,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0";
//正式なフォーマットは$,*checksum,CR,LFを追加する
例のように設定番号の0番と17番目以外のインターバルを0にすることでGPSモジュールからの情報を制限することができます。GPGLLとGPZDAはともに1にしているため1秒周期でGPGLLとGPZDAが出力されます。他の項目についても出力したい場合は0~5までの任意のタイミングに設定します。
電源ON以降(モジュールは電源ON後で最大1500ms準備時間が必要)にウェイトを置いた後で、314フォーマットによる出力制御設定を行います。
時間データの受け取りタイミング
void loop(){
if( digitalRead(PIN_DI1) == 0 ){ //LOWアクティブなので0で判定
gpsok = true; //GPSのデータが確定
timgpswait = GPS_WAIT;
}
if( timgpswait == TIME_UP ){ //1ppsが規定時間途絶えた
timgpswait = TIME_OFF;
gpsok = false; //GPSのデータが未確定とする
}
}
/* 表示データ作成 */
void ShowData(void){
if( gpsok ){ //GPSのデータが確定していれば以下の処理
//LCDに時刻を表示する
}
}
GPSモジュールは衛星を測位する前からシリアル通信でデータを通知します。データが確定していない場合のデータで時刻や測位情報として採用してしまうと不確定な情報が表示されるてしまいます。
GPSモジュールは測位が完了しデータが確定できた時1ppsピンからパルスを出力して通知する機能を持っています。1ppsピンの状態を監視してパルスが生成されていれば正常な情報であると判断してGPSモジュールの情報として処理するようにします。
広告
GPSモジュールからの受信データの受け入れ
/* 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); //サム値の計算
sprintf((char*)&buf[0],"%X", sum); //サム値をGPSと同じ文字に変換
sum_h = buf[0];
sum_l = buf[1];
if( RxData[sz] == sum_h && RxData[sz+1] == sum_l ){
ControlSet(); //チェックサムで受け入れた後の処理
}
}else{
if(++rxgps.rp >= RING_SZ ){
rxgps.rp = 0;
}
}
}
GPSモジュールから受信するデータもNMEAパケットに従っているためNMEAパケットの’$’から’*’までのデータを確認することでパケットタイプによるデータの管理ができます。
データ長はGPSモジュールから出力される種別に応じて異なるため、データ長の確認を行ってから必要なデータ数のみ一時的に受け入れチェックサム(EOR)の計算を行い、異常がなければデータを受け入れる処理を行います。
サム値の計算を行った後GPSモジュールが送信しているサム値と比較するために文字列に置き換えています。GPSモジュールからは16進数の数値が大文字でA~Fが送信されるためsprintf()内でて%Xを指定して大文字で変換しています。
サム値の比較の方法はGPSモジュールから受信したサム値を文字からバイナリデータに変換して比較する方法もあります。
void ControlSet(){
if( RxData[0] =='$' && RxData[1] =='G'
&& RxData[2] =='P' && RxData[3] =='Z'
&& RxData[4] =='D' && RxData[5] =='A' ){
//LCD表示用のデータ(時刻)の表示
}
else if( RxData[0] =='$' && RxData[1] =='G'
&& RxData[2] =='P' && RxData[3] =='G'
&& RxData[4] =='L' && RxData[5] =='L' ){
//LCD表示用のデータ(位置情報)の表示
}
}
受け入れたデータのパケットタイプから必要なパケットのデータであるかの確認を行い対象の処理を行います。上記の例では「$GPZDA」のパケットであることを判断してLCD用のデータを生成しています。
位置情報を示すパケットに対してデータを作成する場合は「$GPGLL」のパケットであることを確認して処理を追加します。
GPSの情報の時間を補正する
void ControlSet(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-SeeeduinoとVER1.00を表示しておき、GPSモジュールの時刻データが確定した後は時刻を表示するようにしています。すでに確定している場合は最初で2秒の表示としています。
GPS受信機キット:AE-GYSFDMAXBのデータシートを確認すると330 PMTK_API_SET_DATUMを発行してTOKYO-Mに合わせると時刻調整できると考えていましたが、発行してもうまく時刻が補正されませんでした。他のAPIで補正することができるかもしれません。
時刻補正がなかったためtime.hを使用して時刻の補正を行いました。
時刻が表示されると日本時間に換算した時刻が表示されていることを確認しました。シリアルモニタでも同様の時刻が表示されていました。
GPGLLフォーマットは経度と緯度が度分で取得できるので表示方法を加工すると表示できます。
例)1122.3456,N,15044.5566,Eの場合はGoogle Mapでは 11 22.3456,150 44.5566と入力します。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <TimerTC3.h>
#include <time.h>
#include <Wire.h>
#define RING_SZ 512
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define TIM_RX_WAIT 50
#define GPS_WAIT 500
#define PIN_DI1 1
#define SLAVE_ADRS 0x3E
#define LINE1_ADRS 0x40
#define LINE2_TOP (0x40 +0x80)
#define FUNC1_SET 0x38
#define FUNC2_SET 0x39
#define INT_OSC 0x14
#define CONST_SET 0x70
#define PWR_ICON_SET 0x56
#define FOLLOWER_SET 0x6B
#define CLR_DISP 0x01
#define DISP_ONOFF_SET 0x0C
const char buf314[]= "PMTK314,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0";
const char buf330[]= "$PMTK330,1*2F";
const char buf351[]= "$PMTK351,1*28";
struct RING_MNG{
uint16_t wp;
uint16_t rp;
uint8_t buf[RING_SZ];
};
struct TYP314{
uint8_t header;
uint8_t dat[sizeof(buf314)];
uint8_t endchar;
uint8_t sum_h;
uint8_t sum_l;
uint8_t cr;
uint8_t lf;
};
TYP314 Tx314;
RING_MNG rxgps;
uint8_t RxData[RING_SZ/2];
int8_t cnt10ms;
int16_t timRxWait = TIME_OFF;
int16_t timgpswait = TIME_OFF;
String lcd_1;
String lcd_2;
String lcd_1_init = "GPS-Seeeduino";
String lcd_2_init = "Ver1.00 ";
bool gpsok;
/* Local function prototypes */
uint8_t sumbcc( uint8_t *adrs, uint8_t sz );
void mainTimer(void);
void TimerCnt(void);
void RxDataChk(void);
void ControlSet(void);
void LcdInit(void);
void LcdComand(uint8_t cmd);
void LcdWriteData(uint8_t *data, uint8_t sz);
void DspLine2Top(void);
void DspClear(void);
void ShowData(void);
void setup() {
uint8_t buf[3];
uint8_t i;
uint8_t sum;
pinMode( PIN_DI1, INPUT_PULLUP ); //1pps用のDI
Serial.begin(115200);
Serial1.begin(9600);
Wire.begin();
LcdInit();
TimerTc3.initialize(1000);
TimerTc3.attachInterrupt(TimerCnt);
//GPSからの出力を変更(314)
Tx314.header = '$';
for( i=0; i < sizeof(buf314);i++ ){
Tx314.dat[i] = buf314[i];
}
Tx314.endchar = '*';
sum = sumbcc(&Tx314.dat[0],sizeof(Tx314.dat)); //チェックサムの計算
sprintf((char*)&buf[0],"%X", sum);
Tx314.sum_h = buf[0];
Tx314.sum_l = buf[1];
Tx314.cr = 0x0D;
Tx314.lf = 0x0A;
Serial1.write( &Tx314.header, sizeof(Tx314)); //314フォーマット送出
Serial.write( &Tx314.header, sizeof(Tx314));
}
void loop() {
mainTimer();
if( digitalRead(PIN_DI1) == 0 ){
gpsok = true;
timgpswait = GPS_WAIT;
}
if( timgpswait == TIME_UP ){ //1ppsが規定時間途絶えた
timgpswait = TIME_OFF;
gpsok = false;
}
while( Serial1.available()){
rxgps.buf[rxgps.wp] = Serial1.read();
if( ++rxgps.wp >= RING_SZ ){ //次にデータを入れる場所を更新
rxgps.wp = 0;
}
}
RxDataChk();
}
/* callback function add */
void TimerCnt(void){
++cnt10ms;
}
/* Timer Management function add */
void mainTimer(void){
if( cnt10ms >= BASE_CNT ){
cnt10ms -=BASE_CNT;
//10msごとにここに遷移する
if( timRxWait > TIME_UP ){
timRxWait--;
}
if( timgpswait > TIME_UP ){
timgpswait--;
}
}
}
/* GPSモジュールのデータを処理 */
void RxDataChk(void){
int rxsz;
uint16_t sz;
uint16_t allsz;
uint8_t *adrs;
uint16_t rp = rxgps.rp;
uint8_t sum;
uint8_t sum_h;
uint8_t sum_l;
uint8_t buf[3];
uint8_t chksum;
bool flg;
if( timRxWait == TIME_UP ){
timRxWait = TIME_OFF;
if(++rxgps.rp >= RING_SZ ){
rxgps.rp = 0;
}
}
rxsz = rxgps.wp - rxgps.rp; //受信データ数の算出
if( rxsz < 0 ){
rxsz = rxsz + RING_SZ;
}
if( rxsz == 0 ){
timRxWait = TIME_OFF;
}
else{
if( timRxWait == TIME_OFF ){
timRxWait = TIM_RX_WAIT;
}
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;
}
if(++rp >= RING_SZ ){
rp = 0;
}
}
if( rxsz >= allsz && flg){
timRxWait = TIME_OFF;
adrs = &RxData[0];
for(uint16_t i=0; i< allsz; i++ ){
*adrs = rxgps.buf[rxgps.rp];
adrs++;
if(++rxgps.rp >= RING_SZ ){
rxgps.rp = 0;
}
}
sum = sumbcc(&RxData[1], sz-2);
sprintf((char*)&buf[0],"%X", sum);
sum_h = buf[0];
sum_l = buf[1];
if( RxData[sz] == sum_h && RxData[sz+1] == sum_l ){
//チェックサムで受け入れた後の処理
ControlSet();
for(uint16_t i=0; i< allsz; i++ ){
//Serial.write(RxData[i]);
}
}else{
Serial.println("NG");
}
}
}else{
if(++rxgps.rp >= RING_SZ ){
rxgps.rp = 0;
}
}
}
}
/* checksum(EOR) function add */
uint8_t sumbcc( uint8_t *adrs, uint8_t sz ){
uint8_t bcc=0;
uint8_t i;
for( i=0; i < sz;i++ ){
bcc ^= *adrs;
++adrs;
}
return bcc;
}
/* GPZDA GNGLL data function add */
void ControlSet(void){
uint8_t i;
uint8_t date[19];
tm t;
time_t tim;
tm* ltim;
uint16_t year;
uint8_t mon;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t sec;
uint8_t week;
if( RxData[0] =='$' && RxData[1] =='G'
&& RxData[2] =='P' && RxData[3] =='Z'
&& RxData[4] =='D' && RxData[5] =='A' ){
date[0] = RxData[24];
date[1] = RxData[25];
date[2] = RxData[26];
date[3] = RxData[27];
date[4] = '/';
date[5] = RxData[21];
date[6] = RxData[22];
date[7] = '/';
date[8] = RxData[18];
date[9] = RxData[19];
date[10] = '-';
date[11]= RxData[7];
date[12]= RxData[8];
date[13]= ':';
date[14]= RxData[9];
date[15]= RxData[10];
date[16]= ':';
date[17]= RxData[11];
date[18]= RxData[12];
year = strtol((char*)&date[0],NULL,10);
mon = strtol((char*)&date[5],NULL,10);
day = strtol((char*)&date[8],NULL,10);
hour = strtol((char*)&date[11],NULL,10);
minute = strtol((char*)&date[14],NULL,10);
sec = strtol((char*)&date[17],NULL,10);
t.tm_year = year - 1900;
t.tm_mon = mon - 1; //0からカウントするので-1
t.tm_mday = day;
t.tm_hour = hour + 9; //日本:世界標準時から9時間ずれ
t.tm_min = minute;
t.tm_sec = sec;
tim = mktime(&t); //UNIX時間からの経過
ltim = localtime(&tim); //ローカル時間に置き換え(経過時間をtm構造体に置き換え)
year = ltim->tm_year + 1900;
mon = ltim->tm_mon + 1;
day = ltim->tm_mday;
hour = ltim->tm_hour;
minute = ltim->tm_min;
sec = ltim->tm_sec;
week = ltim->tm_wday;
lcd_1 ="";
lcd_2 ="";
Serial.print(year);
Serial.print("/");
lcd_1 += year;
lcd_1 += "/";
if( mon > 9 ){
Serial.print(mon);
lcd_1 += mon;
}
else{
Serial.print("0"); Serial.print(mon);
lcd_1 += "0"; lcd_1 += mon;
}
Serial.print("/");
lcd_1 += "/";
if( day > 9 ){
Serial.print(day);
lcd_1 += day;
}
else{
Serial.print("0"); Serial.print(day);
lcd_1 += "0"; lcd_1 += day;
}
Serial.print("/");
if( hour > 9 ){
Serial.print(hour);
lcd_2 += hour;
}
else{
Serial.print("0"); Serial.print(hour);
lcd_2 += "0"; lcd_2 += hour;
}
Serial.print(":");
lcd_2 += ":";
if( minute > 9 ){
Serial.print(minute);
lcd_2 += minute;
}
else{
Serial.print("0"); Serial.print(minute);
lcd_2 += "0"; lcd_2 += minute;
}
Serial.print(":");
lcd_2 += ":";
if( sec > 9 ){
Serial.print(sec);
lcd_2 += sec;
}
else{
Serial.print("0"); Serial.print(sec);
lcd_2 += "0"; lcd_2 += sec;
}
Serial.print("-");
switch(week){
case 0:
Serial.println("SUN");
lcd_1 +=" SUN";
break;
case 1:
Serial.println("MON");
lcd_1 +=" MON";
break;
case 2:
Serial.println("TUE");
lcd_1 +=" SUN";
break;
case 3:
Serial.println("WED");
lcd_1 +=" WED";
break;
case 4:
Serial.println("THU");
lcd_1 +=" THU";
break;
case 5:
Serial.println("FRI");
lcd_1 +=" FRI";
break;
case 6:
Serial.println("SAT");
lcd_1 +=" SAT";
break;
}
ShowData();
}
else if( RxData[0] =='$' && RxData[1] =='G'
&& RxData[2] =='P' && RxData[3] =='G'
&& RxData[4] =='L' && RxData[5] =='L' ){
//for(uint8_t i= 0; i <24;i++){
// Serial.write(RxData[i+7]);
//}
//Serial.println();
}
else{ //ZPA以外であれば314電文が受け付けられていないので受け付けるまで繰り返す
if( RxData[5] =='0' && RxData[6] =='0'
&& RxData[7] =='1' && RxData[9] =='3'
&& RxData[10] =='1' && RxData[11] == '4'
&& RxData[13] == '3'){
Serial.println("314 ok");
}else{
Serial.println("314 retry");
Serial1.write( &Tx314.header, sizeof(Tx314)); //314フォーマット送出
//for( i=0; i < sizeof(buf330);i++ ){
// Serial1.write(buf330[i]);
//}
//Serial1.write(0x0D);
//Serial1.write(0x0A);
//for( i=0; i < sizeof(buf351);i++ ){
// Serial1.write(buf351[i]);
//}
//Serial1.write(0x0D);
//Serial1.write(0x0A);
}
}
}
/* LCD初期化 */
void LcdInit(void){
LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
LcdComand(FUNC2_SET); //拡張コマンド
LcdComand(INT_OSC); //内部周波数調整
LcdComand(CONST_SET); //コントラスト1
LcdComand(PWR_ICON_SET); //コントラスト2
LcdComand(FOLLOWER_SET); //フォロワー制御
LcdComand(FUNC1_SET); //拡張コマンドをオフ
DspClear();
LcdComand(DISP_ONOFF_SET);
LcdWriteData( (uint8_t*)&lcd_1_init[0], lcd_1_init.length()); //1段目の表示
DspLine2Top(); //カーソル移動
LcdWriteData( (uint8_t*)&lcd_2_init[0], lcd_2_init.length()); //2段目の表示
delay(2000);
}
/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x00);
Wire.write(cmd);
Wire.endTransmission();
delay(1);
}
/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x40);
Wire.write(data, sz);
Wire.endTransmission();
}
/* 2段目にカーソル移動 */
void DspLine2Top(void){
LcdComand(LINE2_TOP);
delayMicroseconds(40);
}
/* ディスプレイクリア */
void DspClear(void){
LcdComand(CLR_DISP);
delayMicroseconds(40);
}
/* 表示データ作成 */
void ShowData(void){
if( gpsok ){
DspClear();
LcdWriteData( (uint8_t*)&lcd_1[0], lcd_1.length()); //1段目の表示
DspLine2Top();
LcdWriteData( (uint8_t*)&lcd_2[0], lcd_2.length()); //2段目の表示
}
}
本記事はGPSモジュールから受信したシリアル通信のデータを判断して時刻表示を行っていますが、GPSモジュール用のライブラリを追加して時刻情報を表示する方法もあります。
ArduinoのライブラリでGPSモジュールで時刻を取得する
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。
シリアルモニタに表示していた位置情報をGoogle Mapで入力して確認しましたが100m程度ずれてしまうことがありました。