Hacking on a Pytrack GPS module

Image 1

During tests with Pycom's FiPy i had some time to test the pytrack, as part of a tangle-based tracking demo. Pytrack combines a GPS with an accelerometer, an SD card suitable for data logging and a jack and charger for a LiPo battery - everything included for a tracking-based application. I was interested in the GPS first.

Pycom has library code in their Github repository, pycom-libraries. It's a collection of several libraries, and the pytrack folder contains code for accelerometer ("LIS2HH12") and GPS ("L76GNSS"). The first try-out of the GPS library and the example did not work, and i was interested where the problem could possibly be located.

Pytrack contains a GPS module from Quectel, the L76-L. It is connected via I2C and the mode of operation is, your pycom board is the I2C master and reads bytes from I2C address 0x10. Bytes form ASCII text messages such as

$GPGSA,A,3,03,17,11,23,193,,,,,,,,3.72,2.85,2.39*3C<CR><LF>

Each message is framed by a leading $ and a terminating *, followed by a hex checksum, and a CR/LF. Within $ and * are comma separated data parts, starting with a message ID (e.g. GPGSA above). Depending on this id, the rest is either coordinates, time data, satellite data or status bits & pieces. Pycom's Library looks for a GNGLL message for a given amount of time, converts the rest of the data and prints it out. Only that in my case it did not work, so i started to dive into the spec of Quectel's GPS module.

The module supports multiple location services such as GPS, GLONASS, Galileo and more, and the first two characters of the Message ID specify the "source" of the message, followed by three characters for the type of message. So for GPS satellites, it could be a GPGLL message, whereas for Galileo it could be a GNGLL as well. The remainder --GLL is short for "Geographic Latitude and Longitude". I felt like parsing only GNGLL messages could possibly lead to missing out meaningful messages from the module.

Posts in Github issues and forums suggested another library, inmcm/micropyGPS from Calvin McCoy, which (from the repo title) is a full-featured GPS NMEA sentence parser, NMEA being the standard behind the messages.

Simple sketch

From this library repo, micropyGPS.py is relevant. I downloaded this file into a folder, and added my own main.py. MicropyGPS has a great documentation, so it was easy to get it up and running. At the core is Pycoms machine.I2C class, connecting it to pins P22 for SDA and P21 for SCL. In a loop (together with a Timer.Chrono based timeout), it reads bytes from I2C, feeding it into the GPS class. In the MicropyGPS class, methods are available to ask for validity and completeness of data, and to print data out in a nice way. As it parses byte by byte via an update method, the source of the data can as well be UART or SPI (or unit-tests).

This is main.py:

import machine
from machine import I2C
import pycom
import sys
import time
# see https://github.com/inmcm/micropyGPS/blob/master/micropyGPS.py
from micropyGPS import MicropyGPS
pycom.heartbeat(True)
print("Initializing...")
GPS_TIMEOUT_SECS=10
# init I2C to P21/P22
i2c = machine.I2C(0, mode=I2C.MASTER, pins=('P22', 'P21'))
# write to address of GPS
GPS_I2CADDR = const(0x10)
raw = bytearray(1)
i2c.writeto(GPS_I2CADDR, raw)
# create MicropyGPS instance
gps = MicropyGPS()
# start a timer
chrono = machine.Timer.Chrono()
chrono.start()
# store results here.
last_data = {}
print("Start reading GPS data...")
def check_for_valid_coordinates(gps):

    '''
    Given a MicropyGPS object, this function checks if valid coordinate
    data has been parsed successfully. If so, copies it over to global last_data.
    '''
    if gps.satellite_data_updated() and gps.valid:
        last_data['date'] = gps.date_string("long")
        last_data['latitude'] = gps.latitude_string()
        last_data['longitude'] = gps.longitude_string()
while True:
    # read some data from module via I2C
    raw = i2c.readfrom(GPS_I2CADDR, 16)
    # feed into gps object
    for b in raw:
        sentence = gps.update(chr(b))
        if sentence is not None:
            # gps successfully parsed a message from module
            # see if we have valid coordinates
            res = check_for_valid_coordinates(gps)
    elapsed = chrono.read()
    if elapsed > GPS_TIMEOUT_SECS:
        break
print("Finished.")
if 'date' in last_data:
    print("@ ", last_data['date'])
if 'latitude' in last_data and 'longitude' in last_data:
    print("> ", last_data['latitude'], " ", last_data['longitude'])
i2c.deinit()

At first, this did not produce any output. It received messages from the GPS module, but with more or less empty frames. No coordinates, unfortunately. This changed when i went outside with my notebook and the module and after a few seconds, it dumped out coordinates, and i could move around and see the coordinates update.

Initializing...
Start reading GPS data...
Finished.
@  July 28th, 2018
>  50° 44.44431' N   7° 11.6576' E

Thanks to the GPS library, it's really easy to query additional data and print it out.

Andreas

This blog post originally appeared on Thingstruction.online

Follow ThingForward on Twitter, Facebook and Linkedin!