Categories
Electronics

Learning Python via GPS – Part 1

This shouldn’t be seen as a tutorial – there’s enough of them on the internet. This is just my notes as I wrote some stuff. Part 3 should have a complete write up with full code examples!

My last foray with Python turned out pretty neat – but you could tell I literally copied and pasted code together to make it work. Thats fine and all – but I wanted to actually start learning Python.

My first step in any language is the ubiquitous “Hello World!”, which, in Python, isn’t that difficult. Its a simple

print("Hello World")

Next, I figure out how to display the Fibonacci Sequence. I normally try this one as it starts to involve setting and getting variables, loops, includes math operators and combines it with outputting data. Again, not overly difficult.

loopCount = 1
num1 = 0
num2 = 1
maxIter = 100
print("0: 0")
while loopCount < maxIter:
    output = num1 + num2
    display = str(loopCount) + ": " + str(output)
    print(display)
    num1 = num2
    num2 = output
    loopCount = loopCount + 1

I still prefer CamelCase for my variables. I’ve been doing it for years and probably won’t stop.

I don’t use any shortcuts for this program and no shorthand for iterations – its designed just so I get used to the basic principles.

So after that I choose a project and dive into. Given the release of the Raspberry Pi Pico on 21/01/2021 that runs on either C++ or MicroPython I figured that it was the best tool for the job!

I grabbed one of the (many) cheap GPS Modules, paired it with a Pico, an Omnibus Board and a Pico Display (the last three all from Pimoroni) and set about making something.

I have a soft spot for GPS. I’m completely fascinated by it. I think it stems from my interest in the whole concept of time. GPS is known for (and its in its name) as a Positioning System – maps and directions. But it’s also a very very accurate time source. So accurate that when working out positions calculations have to be computed to figure out if parts of the earths atmosphere will interfere with time measurements. I’ve completed a fair few GPS projects in the past – and still working on others so with some limited knowledge of how these GPS Receivers works I figured it was the best to ease me into a new language.

The GPS receiver I’m using in this example is a Neo-6M. It has 4 pins, Power, Ground, TX and RX. The receiver can take a varying voltage level, but I’m used to providing it with 5V. Pins are, as best as I can figure from various internet searches, both 3.3 and 5v tolerant. I did have a level shifter ready to go should it be required but there’s been no real issues yet.

To make working with receiver and Pico Display easier I’m using the Omnibus Board. This just exposes the Pico’s pins for easier prototyping.

I started by program by laying out the basics. The Pico has a built in UART with pins defined for serial transmission and receiving. Hooking my receivers power, then TX up to the Pico’s RX, and likewise the RX up to the Picos TX was all the wiring required.

Using MicroPython getting the UART enabled is as simple as

from machine import UART,Pin
uart = UART(0,baudrate=9600, tx=Pin(0), rx=Pin(1), bits=8, parity=None, stop=1)
if uart.any(): rcvChar = uart.readline()
    line = rcvChar.decode("ascii") #print(line)

and that nets you some responses:

$GPRMC,112435.00,V,,,,,,,120321,,,N*7E
$GPGGA,112435.00,,,,,0,00,99.99,,,,,,*66
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30

These responses confirm to the NMEA (National Marine Electronics Association) 0183 standard . Each one begins with a $ symbol, the next 5 characters is made up of a two then three character code. The two character code called an Identifier – GP in this case – lets us know that this data has came from a Global Positioning System receiver.

GPS isn’t the only system in use – you also have BeiDou (China), Galileo (Europe), GLONASS (Russia), QZSS (Japan), and others. Some only work in certain regional areas, others provide worldwide coverage. The majority of receivers I have only work with GPS but I’m eyeing up a GLOSNASS one next – hopefully with a Galileo receiver also (as long as it dissent go down).

The next 3 are the Sentence Identifier – and signal what kind of information is being transmitted. The most common are RMC, GSA, GGA, GSV, GLL, VTG. Each one provides relevant information for use – some more relevant than others.

RMC – Recommended Minimum Navigation Information C
GSA – GPS DOP and active satellites
GGA – Global Positioning System Fix Data
GSV – Satellites in view
GLL – Geographic Position – Latitude/Longitude
VTG – Track made good and Ground speed

A phenomenal resource I use while dealing with NMEA sentence sis gpsd service daemon. I’ve used that program itself numerous times but the documentation they provide is excellent – view all about the sentences here.

In my example above I have only selected 3 specific sentences. All have came from a GPS receiver. The first, RMC is Recommended Minimum Navigation Information C, GGA is Global Positioning System Fix Data and GSA is GPS DOP and active satellites.

With the three sentences above (when I have a good signal) this allows me to get the current location , speed (in knots), type of GPS Signal, quality of GPS Signal, date and time. To filter all these sentences into the three I need a simple

if "$GPGSA" in line or "$GPRMC" in line or "$GPGGA" in line:

can be used. This was my first “Gotcha” in Python. I spent an unreasonable amount of time trying to figure out why

if "$GPGSA" or "$GPRMC" or "$GPGGA" in line:

wasn’t working and I didn’t realise I had to explicitly confirm each part of the or statement. Thanks to the Python Discord I was swiftly corrected!

An actual set of sentences (with my location removed) would look similar to

$GPRMC,102011.00,A,4444.55555,N,55555.55555,W,0.471,,120321,,,A*6D $GPGGA,102011.00,4444.55555,N,55555.55555,W,1,05,3.20,63.5,M,50.7,M,,*76
$GPGSA,A,3,24,10,23,12,15,,,,,,,,5.59,3.20,4.58*02

and then picking out what I need in Python turned out to be pretty simple. As each sentence is comma delimitated, I just had to split the sentence into an array using that comma. The end portion (with the *) is a checksum but I’ll come to that later.

if "$GPRMC" in line:
    RMC = line.split(",")
    fixTypeB = RMC[2] # FixTypeB : Status, A = Valid, V = Warning
    dateGPS = RMC[9] # dateGPS : Date, ddmmyy
    timeGPS = RMC[1] # timeGPS : UTC Time of position, hhmmss.ss
    speedKnots = RMC[7] # speedKnots : Speed over ground, knots

and this allowed me to access each item in this array individually.

I’m going to cap this post here as I think its getting long enough already. Without revealing all the code this should be enough to get you hooked up and receiving what data you need. Some loops and print statements will get you data right onto your terminal. Part 2 will cover the screen and dealing with errors (hopefully!).