Communicating with the GPS

The code communicates with the GPS by sending and receiving specially formatted sentences. The format used is the NMEA 0183 protocol specified by the National Marine Electronics Association. This was designed for boat navigation and control systems and is widely used by GPSs.

In general, you configure the device to send the sentences that you want at the frequency you need and then receive a flow of GPS update messages.

Sentences received from the GPS module use the same format, irrespective of the manufacturer. Sentences sent to the GPS module to control it, and answers to these commands, are proprietary to the manufacturer.

NOTE: All of the example commands used in this documentation, and the examples folder, are for the MediaTek 333X GPS chips used in Adafruit products. Make sure to check the datasheet for your GPS chip if it is different.

Sentence format

$TAG[,DATA[,DATA…]]*hh<CR><LF>

  • ‘$’ is the opening delimiter

  • TAG is the tag describing the type of message.

    • The tag for a proprietary (chipset specific) message is composed of

      • ‘P’ for proprietary.

      • ‘ABC’, a 3 letter code for the manufacturer, eg. ‘MTK’ for MediaTek.

      • ‘CODE’, a manufacturer specified code for the command or answer. Note: This can be made up of letters and numbers and there is no required length.

      ‘PMTK220’ is the Mediatek command for setting the update rate.

      Note: not all commands have an answer counterpart

    • The tag for a received data sentence is of the form TTDDD, where:

      • TT is the talker sending the data. The list of talkers is large but we are only interested in ones starting with a ‘G’:

        • GA - Galileo (Europe)

        • GB - BeiDou (China)

        • GI - NavIC (India)

        • GL - GLONASS (Russia)

        • GP - GPS (US)

        • GQ - QZSS (Japan)

        • GN - GNSS, a combination of the above

      • DDD is the data type of the sentence, this determines how to decode it. Again, the list of data types is long but we are only interested in a few:

        • RMC - Recommended Minimum Navigation Information

        • GLL - Geographic Position - Latitude/Longitude

        • GGA - Global Positioning System Fix Data

        • VTG - Track made good and Ground speed (not currently parsed)

        • ZDA - Time & Date - UTC, day, month, year and local time zone (not currently parsed)

        • GSA - GPS DOP and active satellites

        • GSV - Satellites in view

        • GRS - GPS Range Residuals (not currently parsed)

        • GST - GPS Pseudorange Noise Statistics (not currently parsed)

  • DATA is separated from the TAG by a comma and is a comma separated list of data. Proprietary commands, and answers, will specify on their datasheet what the list of data is. The normal sentences generated by GPS modules are specified by NMEA. An unofficial list is here.

  • ‘*’ is the end of data delimiter.

  • hh is the 1-byte checksum of all characters between ‘$’ and ‘*’ in hexadecimal.

  • <CR><LF> is the mandatory sentence terminator

Checksums

When sending commands with the send_command() method it will add the necessary delimiters and calculate the checksum for you, eg.

gps.send_command(b'PMTK220,1000')

When receiving answers or data from the GPS module, if you use the update() method to poll the device it will reject any sentences with an invalid checksum and then try to parse the data. However, you can choose to manually pull data with the read() or readline() which will do no parsing or checksum validation.

Initial Configuration

import board
import busio
import adafruit_gps

USE_UART = True # Change this to False to connect via I2C

if USE_UART:
    # Create a serial connection for the GPS connection.
    uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)

    # for a computer, use the pyserial library for uart access
    # import serial
    # uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10)

    # Create a GPS module instance.
    gps = adafruit_gps.GPS(uart, debug=False)  # Use UART/pyserial
else:
    # If using I2C, we'll create an I2C interface to talk to using default pins
    i2c = board.I2C()

    # Create a GPS module instance.
    gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False)  # Use I2C interface

Configuring the GPS

# Set update rate to 1000 milliseconds (1Hz)
gps.send_command(b"PMTK220,1000")

# Ask for specific data to be sent.
#                          A B C D E F G H                   I
gps.send_command(b'PMTK314,1,1,5,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0')

#   A - send GLL sentences
#   B - send RMC sentences
#   C - send VTG sentences
#   D - send GGA sentences
#   E - send GSA sentences
#   F - send GSV sentences
#   G - send GRS sentences
#   H - send GST sentences
#   I - send ZDA sentences

# The number is how often to send the sentence compared to the update frequency.
# If the update frequency is 500ms and the number is 5, it will send that message
# every 2.5 seconds.

Note: Be aware that some data types send multiple sentences per update. So if you ask for 5 different types of data at 1Hz, you need to be able to handle at least 10 sentences per second. If the data is not read fast enough, the internal buffer and backlog behaviour is not specified.

Poll for data

while True:
    if gps.update():
        # A valid sentence was received - do something
        if gps.has_fix:
            print(f"{gps.latitude:.6f},{gps.longitude:.6f}")
        else:
            print("Waiting for a fix...")
    else:
        # No valid sentence was received, wait a moment.
        time.sleep(100)

The update() call takes care of reading data from the device and parsing it into usable data. This can then be accessed using the property accessors, eg. has_fix, datetime, latitude, longitude etc.

Selected Data Types

GGA - Global Positioning System Fix Data

       1         2       3 4        5 6 7  8   9  10 11 12 13  14   15
       |         |       | |        | | |  |   |   | |   | |   |    |
$--GGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
$GNGGA,001043.00,4404.14036,N,12118.85961,W,1,12,0.98,1113.0,M,-21.3,M,,*47
  1. Time (UTC)

  2. Latitude

  3. N or S (North or South)

  4. Longitude

  5. E or W (East or West)

  6. GPS Quality Indicator:

    1. Fix not available

    2. GPS fix

    3. Differential GPS fix

    4. PPS fix (values above 2 are NMEA 0183 v2.3 features)

    5. Real Time Kinematic

    6. Float RTK

    7. Estimated (dead reckoning)

    8. Manual input mode

    9. Simulation mode

  7. Number of satellites in view, 00 - 12

  8. Horizontal dilution of precision

  9. Antenna altitude above/below mean-sea-level (geoid)

  10. Units of antenna altitude, meters

  11. Geoidal separation, the difference between the WGS-84 earth ellipsoid and mean-sea-level (geoid), “-” means mean-sea-level below ellipsoid

  12. Units of geoidal separation, meters

  13. Age of differential GPS data, time in seconds since last SC104 type 1 or 9 update, empty field when DGPS is not used

  14. Differential reference station ID, 0000-1023

  15. Checksum

Info about NMEA taken from here (2001). and here (2021)