kta_0120
@kta_0120 (Kta)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

GPSモジュール:AE-GYSFDMAXBの通信精度を改善したい

この質問はteratailにも同じ文面で投稿しています。
teratailの同じ質問(https://teratail.com/questions/mgs84ogum3wxze)
マルチポストの理由:できる限り多くの人の目に触れることで、より多くの回答を得る。
Qiita内でいただいた回答で解決しました。

質問

Raspberry Pi PicoでGPS受信モジュール「AE-GYSFDMAXB」を使う必要があり、ネットで調べ、マイコンとの接続、プログラムまで進み、屋外で実行してみました。ですが、シェルの欄には、大半が

Request Timeout: No GPS data is found.

と表示され、稀に緯度・経度が表示されます。これは、プログラムの問題なのか、それとも、GPS受信モジュールの故障か、もしくは、通信環境が悪いか、何が原因と思われるか教えていただきたいです。もし、プログラムの問題であれば、修正案も教えていただけると助かります。わからないことがありましたら、聞いてください。
マイコン:Raspberry Pi Pico
GPSモジュール:AE-GYSFDMAXB

プログラムの出展を見つけられませんでした。すいません

↓実行しているプログラム。長いです。

from machine import Pin, UART, I2C

#Import utime library to implement delay
import utime, time

#GPS Module UART Connection
gps_module = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

#print gps module connection details
print(gps_module)

#Used to Store NMEA Sentences
buff = bytearray(255)

TIMEOUT = False

#store the status of satellite is fixed or not
FIX_STATUS = False

#Store GPS Coordinates
latitude = ""
longitude = ""
satellites = ""
gpsTime = ""


#function to get gps Coordinates
def getPositionData(gps_module):
    global FIX_STATUS, TIMEOUT, latitude, longitude, satellites, gpsTime
    
    #run while loop to get gps data
    #or terminate while loop after 5 seconds timeout
    timeout = time.time() + 8   # 8 seconds from now
    while True:
        gps_module.readline()
        buff = str(gps_module.readline())
        #parse $GPGGA term
        #b'$GPGGA,094840.000,2941.8543,N,07232.5745,E,1,09,0.9,102.1,M,0.0,M,,*6C\r\n'
        #print(buff)
        parts = buff.split(',')
        
        #if no gps displayed remove "and len(parts) == 15" from below if condition
        if (parts[0] == "b'$GPGGA" and len(parts) == 15):
            if(parts[1] and parts[2] and parts[3] and parts[4] and parts[5] and parts[6] and parts[7]):
                print(buff)
                #print("Message ID  : " + parts[0])
                #print("UTC time    : " + parts[1])
                #print("Latitude    : " + parts[2])
                #print("N/S         : " + parts[3])
                #print("Longitude   : " + parts[4])
                #print("E/W         : " + parts[5])
                #print("Position Fix: " + parts[6])
                #print("n sat       : " + parts[7])
                
                latitude = convertToDigree(parts[2])
                # parts[3] contain 'N' or 'S'
                if (parts[3] == 'S'):
                    latitude = -latitude
                longitude = convertToDigree(parts[4])
                # parts[5] contain 'E' or 'W'
                if (parts[5] == 'W'):
                    longitude = -longitude
                satellites = parts[7]
                gpsTime = parts[1][0:2] + ":" + parts[1][2:4] + ":" + parts[1][4:6]
                FIX_STATUS = True
                break
                
        if (time.time() > timeout):
            TIMEOUT = True
            break
        utime.sleep_ms(500)
        
#function to convert raw Latitude and Longitude
#to actual Latitude and Longitude
def convertToDigree(RawDegrees):

    RawAsFloat = float(RawDegrees)
    firstdigits = int(RawAsFloat/100) #degrees
    nexttwodigits = RawAsFloat - float(firstdigits*100) #minutes
    
    Converted = float(firstdigits + nexttwodigits/60.0)
    Converted = '{0:.6f}'.format(Converted) # to 6 decimal places
    return str(Converted)
    
    
while True:
    
    getPositionData(gps_module)

    #if gps data is found then print it on lcd
    if(FIX_STATUS == True):
        print("fix....................................................")
        print("緯度:",latitude)
        print("経度:",longitude)
        print(satellites)
        print(gpsTime)
        
        FIX_STATUS = False
        
    if(TIMEOUT == True):
        print("Request Timeout: No GPS data is found.")
        #--------------------------------------------------
        #updated on 5-May-2022
        #--------------------------------------------------
        TIMEOUT = False
        
0

2Answer

他のGPSモジュールを使ったことはありますか?

スマホ内臓のGPSと比較されがちですがモジュール特有のクセがあります

実は「位置がわからないとものすごく時間がかかることがある」
ちょっと何を言ってるのか?と思われるかも知れませんが
実はGPSは自分の現在位置を把握していてそこからの差を計算して位置情報を出すものがあります

この場合、屋外に出て電源を入れて緯度経度が表示されるまで最長で10分ほどかかりました
その後は電源を入り切りしてもすぐに緯度経度が表示されますので多分覚えているのでしょう
電源を切ってから鉄道で何駅か移動して電源を入れるとまた10分かかりました

衛星との通信自体は電源を入れてすぐに行えているはずですので
受信している衛星の数を問い合わせるコマンドとかあれば使ってみてはいかがでしょうか?

2Like

Comments

  1. @kta_0120

    Questioner

    ありがとうございます!いただいた助言の通りに、外で10分くらい待ってみます。また、受信している衛星の数を問い合わせるコマンドも探してやってみます。回答ありがとうございました。

AE-GYSFDMAXB は、GPSから電波を拾っていなくても、NMEA データを取得できます。
以下のプログラムを実行して、データを受信するか確認してください。

from machine import Pin, UART
import time

#gps =  UART(0, 9600)
gps = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

print("Start!!")

for _ in range(1000):
    while gps.any() > 0:
        buf = gps.readline()
        str = buf.decode().split('\r\n')[0]
        print(str)
        
    print("sleep 1s")
    time.sleep(1)
    
print("End;")

$GPGGAの7番目のパタメタが「受信衛星数」です。

手持ちの機器で実行させたところ、すぐにデータを受信しました。
(ただし、室内のため、測位できていません)

MPY: soft reboot
Start!!
sleep 1s
$GPGGA,000145.799,,,,,0,0,,,M,,M,,*4F
$GPGLL,,,,,000145.799,V,N*7D
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GPRMC,000145.799,V,,,,,0.00,0.00,050180,,,N*46
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPZDA,000145.799,05,01,3880,,*56
sleep 1s
$GPGGA,000146.799,,,,,0,0,,,M,,M,,*4C
$GPGLL,,,,,000146.799,V,N*7E
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GPRMC,000146.799,V,,,,,0.00,0.00,050180,,,N*45
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPZDA,000146.799,05,01,3880,,*55
sleep 1s
1Like

Comments

  1. 追伸;
    AE-GYSFDMAXB は、無事に測位した場合は、LEDが1秒ごとに点滅するので、外目からでも判断できます。

  2. @kta_0120

    Questioner

    回答ありがとうございます!手持ち機器での確認もしてくださり、感謝です。教えていただいたプログラムは次の部活で自分でも試してみますので、クローズまで少々お待ちください。具体的なプログラムまで教えていただき、本当にありがとうございます。

  3. 大半が Request Timeout: No GPS data is found.
    と表示され、稀に緯度・経度が表示されます

    実行してみましたが、プログラムの問題と思われます。
    以下のコードを試してみてください。

    from machine import Pin, UART
    import time
    
    def byte2string(bData):
        try:
            return bData.decode('utf-8')
        except:
            return ''.join(map(chr, bData))
    
    def convertToDigree(RawDegrees):
        RawAsFloat = float(RawDegrees)
        firstdigits = int(RawAsFloat/100) #degrees
        nexttwodigits = RawAsFloat - float(firstdigits*100) #minutes
        Converted = float(firstdigits + nexttwodigits/60.0)
        return Converted
    
    #gps = UART(0, 9600)
    gps = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
    
    print("Start!!")
    
    buf = ''
    satellites = []
    
    for _ in range(100): #⭐️
        if gps.any() > 0:
            while gps.any() > 0:
                buf += byte2string(gps.read())
            lines = buf.split('\r\n')
            for nmea in lines:
                if len(nmea) == 0 or nmea[0] != '$': continue
                items = nmea.split(',')
                if items[-1].find('*') < 0: #checksum
                    buf = nmea
                    break
                ##print(nmea) #すべてのNMEAデータを見る
                if items[0] == '$GPGSA':
                    satellites = []
                    for n in range(10):
                        if len(items[3 + n]) == 0: continue
                        st = int(items[3 + n])
                        satellites.append(st)
                if items[0] == '$GPGGA':
                    in_use, latitude, longitude, altitude = items[7], items[2], items[4], items[9]
                    if len(latitude) == 0 or len(longitude) == 0: continue
                    print(f'測位利用衛星:{in_use}{satellites}')
                    print("緯度:%.8f" % (convertToDigree(latitude)))
                    print("経度:%.8f" % (convertToDigree(longitude)))
                    print(f"海抜:{altitude}\n")
                    
            else: #end of for
                buf = ''
        
        time.sleep(0.1)
        
    print("End;")
    

    ⭐️無限ループでよい場合は、while True:に変更してください。

    (AE-GYSFDMAXB を窓際に置くと、10個ぐらい衛星を捕捉しますね)

  4. @kta_0120

    Questioner

    ありがとうございます!そのまま実行できるプログラムを教えていただけるのは本当に助かります。今日、部活の部活で試してみます。お時間使っていただきありがとうございます

  5. もう少しシンプルにしました。(すみません、一度アップしたコードは削除しました。)

    MicroPython v1.23.0 on 2024-06-02; Raspberry Pi Picoで動作確認済みです。

    from machine import Pin, UART
    import utime
    
    def byte2string(bData):
        try:
            return bData.decode('utf-8')
        except:
            return ''.join(map(chr, bData))
    
    def nmea2dd(val):
        val = float(val)
        d = val // 100
        m = ((val / 100 - d) * 100) // 60
        s = (((val / 100 - d) * 100 - m) * 60) / 3600
        return d + m + s
    
    #gps = UART(0, 9600)
    gps = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
    
    buf = ''
    satellites = []
    n_use, latitude, longitude, altitude = 0, 0, 0, 0
    
    print("Start!")
    
    for _ in range(100): #⭐️
        if gps.any() > 0:
            while gps.any() > 0:
                buf += byte2string(gps.read())
            sentences = buf.split('\r\n')
            for sentence in sentences:
                if len(sentence) == 0 or sentence[0] != '$': continue
                part = sentence.split(',')
                if part[-1].find('*') < 0: #checksum
                    buf = sentence
                    break
    
                #print(sentence) #すべてのNMEAデータを見る
                if part[0] == '$GPGGA':
                    if part[6] == '0': continue #is invalid
                    n_use, latitude, longitude, altitude = part[7], part[2], part[4], part[9]
                    if len(latitude) == 0 or len(longitude) == 0: continue
                    latitude, longitude = nmea2dd(latitude), nmea2dd(longitude)
                if part[0] == '$GPGSA':
                    satellites = []
                    for n in range(12):
                        if len(part[3 + n]) == 0: continue
                        satellites.append(int(part[3 + n]))
                if part[0] == '$GPRMC':
                    dmy, hms = part[9], part[1]
                    if len(dmy) == 0 or len(hms) == 0: continue
                    year, month, mday, hour, minute, second = \
                        int(dmy[4:6]) + 2000, int(dmy[2:4]), int(dmy[0:2]), \
                        int(hms[0:2]), int(hms[2:4]), int(hms[4:6])
                    # UTC to JST
                    secs = utime.mktime((year, month, mday, hour, minute, second, 0, 0))
                    secs += 9 * 3600 # +09:00
                    year, month, mday, hour, minute, second, weekday, yearday = utime.localtime(secs)
                    print(f"時刻:{year:04}/{month:02}/{mday:02} {hour:02}:{minute:02}:{second:02}")
                    if n_use == 0: continue
                    print(f'測位利用衛星:{n_use}{satellites}')
                    print("緯度:%.8f" % (latitude))
                    print("経度:%.8f" % (longitude))
                    print(f"高度:{altitude}")
                if part[0] == '$GPZDA':
                    print()
    
            else: #end of for
                buf = ''
    
        utime.sleep(0.1)
    
    print("End;")
    
  6. @kta_0120

    Questioner

    遅れましたが、今日、屋外で最初にいただいたプログラムを実行してみましたが、sleep 1sのみがずっと表示されていました。ですが、モジュール本体のLEDは1秒間隔でずっと点滅しているじょうたいです。ここで、初歩的な質問で申し訳ないのですが、モジュールとraspberry pi picoとの接続は以下のようで良いのでしょうか?
    AE-GYSFDMAXB ~ Pico
    5V ~ VBUS
    GND ~ GND
    RXD ~ GP0(1番ピン)
    TXD ~ GP1(2番ピン)

  7. @kta_0120

    Questioner

    こちらのほうで、もう一度インターネットで「AE-GYSFDMAXB」と検索をかけてみました。すると、次のサイトが出てきました。「https://nailiang.net/374/」
    こちらのサイトではGPSモジュールをUARTで接続していました。これまでI2Cで接続していたのですが、今、UARTに接続を変えて試してみますので、もう少しお待ちいただけると助かります。こちらの確認不足ですみません。

  8. このGPSモジュールは、I2Cではつながりませんよ。
    当然、UARTで接続していると思っていました。元々のコードもそう書いてあったので。

    gps_module = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
    

    GP4(Pin4)、GP5(Pin5)は、Raspberry Pi Pico の 6番、7番です。
    ↓こちらのピン配置図を参照してください。


    RXD ~ GP0(1番ピン)
    TXD ~ GP1(2番ピン)

    もし、↑このピンに接続したのなら、↓これです。
    (デフォルトピンなので、ピン番号は省略できる)

    gps = UART(0, 9600)
    
  9. @kta_0120

    Questioner

    コメントの連投すいません。初めにいただいたプログラム内で指定されているピンにTXDとRXDを接続したところ、すべてのプログラムでの動作が確認できました。UARTでの接続だと気づくのが遅れてすいませんでした。今回の質問の回答、解決ありがとうございました。

  10. 追伸;

    これまでI2Cで接続していたのですが、・・・

    GP4、GP5はi2cインタフェースだと考えたと思いますが、それ、質問者さんの勘違いですね。
    Raspberry Pi Pico は、i2cやUART、SPIなどの通信において、同じピン番号でも複数のインタフェースで使えます。

    通信の種類は、接続するセンサーやデバイスモジュールに依存します。たまに、i2CでもSPIでも接続できるセンサーモジュールがあったりしますが、その場合は、どちらのインタフェースで通信するかを、ジャンパーピンや特定のピンで指定します。

    i2cやUART、SPIも決められた中で複数のピンの組み合わせがあり、どのピンに接続したかを、プログラムで指定します。
    上のコードだと、UART番号の 0 や 1 、TX と RX のピン番号が、それに当たります。

    分からないことがあれば、いつでも質問してください。

  11. @kta_0120

    Questioner

    ありがとうございます。後からになって申し訳ないのですが、この質問の冒頭にteratailにおいてもマルチポストを行ったことを記していました。今回、Qiita内で解決するにあたり、teratailの規則に則りこのQiitaのURLを載せる必要があります。こちらの勝手ながら、すいません。マルチポストというものも今回の件で知ったため、これからはマルチポストがないように気をつけます。最後まで解決の手助けをしてくださりありがとうございました。もし、URLは載せないほうがいいなどの要望などがございましたらコメントをお願いします。長文失礼しました。

Your answer might help someone💌