To demonstrate the usage of the GPS module in CircuitPython using UART, let's look at a complete program example, the gps_simpletest.py file from the module's examples.
CircuitPython Microcontroller
With a CircuitPython microcontroller, save this file as code.py on your board, then open the serial console to see its output.
Linux/Computer/Raspberry Pi with Python
On the Raspberry Pi, comment out the uart = busio(...)
line, and uncomment the import serial
and uart = serial.Serial(...)
lines, changing /dev/ttyUSB0
to the appropriate serial port. Now you can run the program with the following command:
python3 gps_simpletest.py
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT # Simple GPS module demonstration. # Will wait for a fix and print a message every second with the current location # and other details. import time import board import busio import adafruit_gps # Create a serial connection for the GPS connection using default speed and # a slightly higher timeout (GPS modules typically update once a second). # These are the defaults you should use for the GPS FeatherWing. # For other boards set RX = GPS module TX, and TX = GPS module RX pins. 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) # If using I2C, we'll create an I2C interface to talk to using default pins # i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # Create a GPS module instance. gps = adafruit_gps.GPS(uart, debug=False) # Use UART/pyserial # gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface # Initialize the GPS module by changing what data it sends and at what rate. # These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and # PMTK_220_SET_NMEA_UPDATERATE but you can send anything from here to adjust # the GPS module behavior: # https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf # Turn on the basic GGA and RMC info (what you typically want) gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") # Turn on the basic GGA and RMC info + VTG for speed in km/h # gps.send_command(b"PMTK314,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0") # Turn on just minimum info (RMC only, location): # gps.send_command(b'PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Turn off everything: # gps.send_command(b'PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Turn on everything (not all of it is parsed!) # gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0') # Set update rate to once a second (1hz) which is what you typically want. gps.send_command(b"PMTK220,1000") # Or decrease to once every two seconds by doubling the millisecond value. # Be sure to also increase your UART timeout above! # gps.send_command(b'PMTK220,2000') # You can also speed up the rate, but don't go too fast or else you can lose # data during parsing. This would be twice a second (2hz, 500ms delay): # gps.send_command(b'PMTK220,500') # Main loop runs forever printing the location, etc. every second. last_print = time.monotonic() while True: # Make sure to call gps.update() every loop iteration and at least twice # as fast as data comes from the GPS unit (usually every second). # This returns a bool that's true if it parsed new data (you can ignore it # though if you don't care and instead look at the has_fix property). gps.update() # Every second print out current location details if there's a fix. current = time.monotonic() if current - last_print >= 1.0: last_print = current if not gps.has_fix: # Try again if we don't have a fix yet. print("Waiting for fix...") continue # We have a fix! (gps.has_fix is true) # Print out details about the fix like location, date, etc. print("=" * 40) # Print a separator line. print( "Fix timestamp: {}/{}/{} {:02}:{:02}:{:02}".format( gps.timestamp_utc.tm_mon, # Grab parts of the time from the gps.timestamp_utc.tm_mday, # struct_time object that holds gps.timestamp_utc.tm_year, # the fix time. Note you might gps.timestamp_utc.tm_hour, # not get all data like year, day, gps.timestamp_utc.tm_min, # month! gps.timestamp_utc.tm_sec, ) ) print("Latitude: {0:.6f} degrees".format(gps.latitude)) print("Longitude: {0:.6f} degrees".format(gps.longitude)) print( "Precise Latitude: {} degs, {:2.4f} mins".format( gps.latitude_degrees, gps.latitude_minutes ) ) print( "Precise Longitude: {} degs, {:2.4f} mins".format( gps.longitude_degrees, gps.longitude_minutes ) ) print("Fix quality: {}".format(gps.fix_quality)) # Some attributes beyond latitude, longitude and timestamp are optional # and might not be present. Check if they're None before trying to use! if gps.satellites is not None: print("# satellites: {}".format(gps.satellites)) if gps.altitude_m is not None: print("Altitude: {} meters".format(gps.altitude_m)) if gps.speed_knots is not None: print("Speed: {} knots".format(gps.speed_knots)) if gps.speed_kmh is not None: print("Speed: {} km/h".format(gps.speed_kmh)) if gps.track_angle_deg is not None: print("Track angle: {} degrees".format(gps.track_angle_deg)) if gps.horizontal_dilution is not None: print("Horizontal dilution: {}".format(gps.horizontal_dilution)) if gps.height_geoid is not None: print("Height geoid: {} meters".format(gps.height_geoid))
When the code runs it will print a message every second, either an update that it's still waiting for a GPS fix:
Note: Due to the antenna being built in, the PA1010D Mini GPS module may need a more unobstructed view of the sky than other GPS modules with eternal antennae. With any GPS module, if you are having trouble getting a fix, try moving it to a more ideal location.
Once a fix has been established, it will print details about the current location and other GPS data:
Let's look at the code in a bit more detail to understand how it works. First the example needs to import a few modules like the built-in busio
and board
modules that access serial ports and other hardware:
import board import busio import time
Next the GPS module is imported:
import adafruit_gps
Now a serial UART is created and connected to the serial port pins the GPS module will use, this is the low level transport layer to communicate with the GPS module:
# Define RX and TX pins for the board's serial port connected to the GPS. # These are the defaults you should use for the GPS FeatherWing. # For other boards set RX = GPS module TX, and TX = GPS module RX pins. RX = board.RX TX = board.TX # Create a serial connection for the GPS connection using default speed and # a slightly higher timeout (GPS modules typically update once a second). uart = busio.UART(TX, RX, baudrate=9600, timeout=3000) # for a computer, use the pyserial library for uart access #import serial #uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)
Once a UART
object is available with a connected GPS module you can create an instance of the GPS parsing class. You need to pass this class the UART
instance and it will internally read new data from the GPS module connected to it:
gps = adafruit_gps.GPS(uart)
GPS Example Code Explained
Before reading GPS data the example configures the module by sending some custom NMEA GPS commands that adjust the amount and rate of data. Read the comments to see some options for adjust the rate and amount of data, but typically you want the defaults of core location info at a rate of once a second:
# Initialize the GPS module by changing what data it sends and at what rate. # These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and # PMTK_220_SET_NMEA_UPDATERATE but you can send anything from here to adjust # the GPS module behavior: # https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf # Turn on the basic GGA and RMC info (what you typically want) gps.send_command(b'PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Turn on just minimum info (RMC only, location): #gps.send_command(b'PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Turn off everything: #gps.send_command(b'PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Tuen on everything (not all of it is parsed!) #gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0') # Set update rate to once a second (1hz) which is what you typically want. gps.send_command(b'PMTK220,1000') # Or decrease to once every two seconds by doubling the millisecond value. # Be sure to also increase your UART timeout above! #gps.send_command(b'PMTK220,2000') # You can also speed up the rate, but don't go too fast or else you can lose # data during parsing. This would be twice a second (2hz, 500ms delay): #gps.send_command(b'PMTK220,500')
If you want you can send other custom commands to the GPS module with the send_command
function shown above. You don't need to worry about adding a NMEA checksum to your command either, the function will do this automatically (or not, set add_checksum=False
as a parameter and it will skip the checksum addition).
Now we can jump into a main loop that continually updates data from the GPS module and prints out status. The most important part of this loop is calling the GPS update function:
# Make sure to call gps.update() every loop iteration and at least twice # as fast as data comes from the GPS unit (usually every second). # This returns a bool that's true if it parsed new data (you can ignore it # though if you don't care and instead look at the has_fix property). gps.update()
Like the comments mention, you must call update
every loop iteration and ideally multiple times a second. Each time you call update
, it allows the GPS library code to read new data from the GPS module and update its state. Since the GPS module is always sending data you have to be careful to constantly read data or else you might start to lose data as buffers are filled.
You can check the has_fix
property to see if the module has a GPS location fix, and if so there are a host of attributes to read like latitude
and longitude
(available in degrees):
if not gps.has_fix: # Try again if we don't have a fix yet. print('Waiting for fix...') continue # We have a fix! (gps.has_fix is true) # Print out details about the fix like location, date, etc. print('=' * 40) # Print a separator line. print('Fix timestamp: {}/{}/{} {:02}:{:02}:{:02}'.format( gps.timestamp_utc.tm_mon, # Grab parts of the time from the gps.timestamp_utc.tm_mday, # struct_time object that holds gps.timestamp_utc.tm_year, # the fix time. Note you might gps.timestamp_utc.tm_hour, # not get all data like year, day, gps.timestamp_utc.tm_min, # month! gps.timestamp_utc.tm_sec)) print('Latitude: {} degrees'.format(gps.latitude)) print('Longitude: {} degrees'.format(gps.longitude)) print('Fix quality: {}'.format(gps.fix_quality)) # Some attributes beyond latitude, longitude and timestamp are optional # and might not be present. Check if they're None before trying to use! if gps.satellites is not None: print('# satellites: {}'.format(gps.satellites)) if gps.altitude_m is not None: print('Altitude: {} meters'.format(gps.altitude_m)) if gps.track_angle_deg is not None: print('Speed: {} knots'.format(gps.speed_knots)) if gps.track_angle_deg is not None: print('Track angle: {} degrees'.format(gps.track_angle_deg)) if gps.horizontal_dilution is not None: print('Horizontal dilution: {}'.format(gps.horizontal_dilution)) if gps.height_geoid is not None:
Notice some of the attributes like altitude_m
are checked to be None
before reading. This is a smart check to put in your code, because those attributes are sometimes not sent by a GPS module. If an attribute isn't sent by the module it will be given a None
/null value and attempting to print or read it in Python will fail. The core attributes of latitude
, longitude
, and timestamp
are usually always available (if you're using the example as-is) but they might not be if you turn off those outputs with a custom NMEA command!
That's all there is to reading GPS location with CircuitPython code!
Text editor powered by tinymce.