Back in part 1 of this series I mentioned that one of my design goals for the PiCar was to integrate a Global Positioning System (GPS) receiver into the radio. I really didn’t need a full blown map system. I prefer paper maps or tablets for that work. But some of my motivation comes from the need to log navigation, as I am a volunteer in local Sheriff’s Office Search and Rescue teams.
I put spent time researching which device would be a good fit. I require that the receiver work well in valleys and under the cover of trees, and still maintain some fair accuracy in those conditions.
My other requirement is that the GPS be able to reacquire the satellites quickly on power up. As I described in part 2, I worked around the absence of a sleep mode in the Raspberry Pi by shutting it down entirely; removing power from both the Pi and its peripherals, including the GPS. This necessitates that the GPS have some form of persistent memory for a quick time-to-first- fix during boot up.
GPS also comes in handy to keep the PiCar clock synchronized. I can’t use NTP to set the clock because it’s not practical to depend on network access in this vehicle.
u-blox NEO-M9N
I investigated a number of GPS receiver chips. My research lead me to the u-blox M9 standard precision GNSS module. The NEO-M9N is incredible hybrid circuit (HIC) that integrates an GPS RF receiver, digital microprocessor and power management all in one 16 x 12 mm module. It is a 92-Channel GNSS Receiver with 1.5m horizontal accuracy.
You supply 3v power, and antenna and some form of serial interface and the module provides you a stream of NMEA data.
Rather than make my own PCB for the module I chose to use the SparkFun GPS Breakout. The board was a slightly larger than an inch and half square. It included a integrated SMA connector, 3v voltage regulator, hot start battery, and Qwiic I2C as well as USB connectors. Other module pins are also directly available on the PCB side.
Initial hacking
The USB connector is incredibly useful for debugging. I hooked up the NEO-M9N to my MacBook using a USB-C connector. The device shows up in the macOS as a modem device.
$ls /dev/cu.*
/dev/cu.usbmodem14101
Using screen set to 38400 baud, I was able to open a connection and examine the GPS data.
Note that to exit screen you can use Ctrl-a Ctrl-\
$screen /dev/cu.usbmodem14101 38400
If you plug the NEO-M9N onto this on a Raspberry Pi USB port
, the device will probably show up as /dev/ttyACM0
. In which case you will need to setup the port speed and configure the port to disable echo.
$stty -F /dev/ttyACM0 38400 -echo
You can then examine the packets directly using cat
$cat /dev/ttyACM0
If you have an antenna hooked up and in a clear place you will should see an endless stream of ASCII NMEA sentences. In particular look for the lines that start with $GNRMC
and $GNGGA
.
Incidentally - you might ask why is the device on port/dev/ttyACM0
and not/dev/ttyUSB0
. The goes back to my early days of computer hacking, where we used dial in modems to talk to our timeshare systems. I came across a good document that explains this rational. It is worth a read, if at least for the historical perspective.
What is NMEA?
NMEA is an acronym for the National Marine Electronics Association. Not surprisingly, navigation is a very important tool for marine navigation. As such marine device manufacturer created a standard for their electronics to communicate with each other.
When you look at the serial date coming from the GPS device you will see something like:
$GPGGA, 161229.487, 3723.2475, N, 12158.3416, W, 1, 07, 1.0, 9.0, M, , , , 0000*18
Decoding this, the sentence starts with a $
and ends with a <CR><LF>
.
The first item is the NMEA talker id, it tells you where the information is coming from. For instance, if the sentence starts with $GP
we know that we are listening to a packet from a US GPS satellite. Where as $GL would be a Russian GLONASS.
If a sentence begins with “$GL”
this indicates that it is combination of multiple satellite systems. You can find a full list of all talker IDs see the NMEA 0183 standard.
Following the talker ID is the sentence header, typically three letters long, this tells you what kind of sentence this is. Some of the more useful sentences include:
To really get your head around the data format I highly recommend that you spend a few minutes and read the the NMEA Revealed document.
gpsd — a GPS service daemon
There is an immense amount of code available in the linux dedicated to working with GPS. The most popular is is gpsd
. It can monitor and decode most GPS devices over serial ports. To quote the gpsd project page
GPSD is everywhere in mobile embedded systems. It underlies the map service on Android phones. It's ubiquitous in drones, robot submarines, and driverless cars. It's increasingly common in recent generations of manned aircraft, marine navigation systems, and military vehicles.
On the Raspberry Pi you can install the gpsd
system by doing the following command:
$sudo apt-get install gpsd gpsd-clients
Here is a step by step walk though on how to do this
UBX protocol
The NEO-M9N also speaks the UBX
binary protocol which is proprietary to u-blox devices. It is more compact that the NMEA protocol and is pretty useful for simpler devices such as Arduino. In addition to getting the usual positioning data you can configure a number of other parameters, and get yourself in trouble pretty quickly.
If you want to hack intros space, there is a good Arduino code library called UbxGps available. Since the Raspberry Pi gave me ample processing power, I decided to stick to NMEA ASCII sentences for my PiCar project.
How PiCar did it.
As it turns out, I chose to not fool with gpsd
nor UBX
. Instead I wrote my own code in C++ to open the connection to the NEO-M9N and process the NMEA streams. It used the POSIX threads as I described in Part 4. This module was called the GPSMgr.
As for hardware and the physical layout constraints of the PiCar I decided not to wire to the USB ports. Rather, I placed the SparkFun breakout board just below my power management card that I described in an earlier article. This allowed my to stack all my cards vertically for better airflow and heat dissipation. It also arranged the SMC antenna connector so that it was facing the back of the radio. This made sense for installation in the Jeep.
My first attempt was to interface the NEO-M9N using the primary I2C ports (GPIO3/5
) on the Pi. Using the i2cdetect utility, the GPS device showed up on usb port 0x42 as I expected.
I wrote a gps test program to talk to the GPS and configure it to using my C++ I2C class. You can use this code too by setting the USE_SERIAL_GPS define to 0 in the GPSmgr.hpp
file. It wasn’t long before I started having connection problems with the NEO-M9N device. I fooled with with pull-up resistors, and tried altering the I2C speeds, but no joy.
This is what happens when you follow the rules.
After more debugging with a scope and much web drawing, I discovered the root of the problem. The Broadcom chip used on the Raspberry Pi has a faulty I2C implementation. The Pi doesn’t implement I2C clock stretching properly, but what’s worse is that the u-blox folks actually followed the I2C standard.
Ok lets work around that. I setup an additional I2C port (GPIO 19 and 26) using the software bit-banging code in the driver. Added this to /boot/config.txt
file:
dtoverlay=i2c-gpio,i2c_gpio_sda=19,i2c_gpio_scl=26
This created another I2C port at /dev/i2c-22
.
I hooked up the NEO-M9N to that, and it worked for while…
Then some weird stuff started to happen. My code detected spurious button presses and knob changes on my DuPPA encoders. Causing the radio ti turn on randomly and change channels on its own. Usually in the middle of the night.
This made no sense since the encoders were rock solid.
In the end, it turned out that the load on the 3.3v voltage regulator that supplies the knob and rings I2C devices and the NEO-M9N was overloaded enough to lower the voltage below operating parameters for the encoders. Causing the encoders to freak out. So that’s not going to work.
Back to the drawing board.
I fixed the the power problem. SparkFun went through the trouble of putting a 3.3v LDO on the breakout, I figured I could go to the trouble of bringing 5v to the board and using it. That was easy.
OK I still don’t want to use up a USB port. Finally I decided to wire the TXD/RXD serial line on the Sparkfun breakout directly to one of my Raspberry Pi 4’s UART ports. The Pi 4 has the ability to support five UART ports. (ignore UART1)
UART Linux tty TX Pin RX Pin
UART0 /dev/ttyAMA0 GPIO 14 GPIO 15
UART1 /dev/ttyS0 / /
UART2 /dev/ttyAMA1 GPIO 0 GPIO 1
UART3 /dev/ttyAMA2 GPIO 4 GPIO 5
UART4 /dev/ttyAMA3 GPIO 8 GPIO 9
UART5 /dev/ttyAMA4 GPIO 12 GPIO 13
Pins GPIO4 and GPIO5 happen to be available and so I chose UART3. This was easy enough, just add this to the /boot/config.txt
and restart
dtoverlay=uart3
Note that if I wanted flow control pins it would say dtoverlay=uart3,ctsrts
. We don’t use them in this application.
This resulted in the UART showing up in /dev/ttyAMA1
. Note what I said above about modems. Since I am not going through USB, the OS doesn’t see it as a modem, just a port.
I ran the gps test program again and it worked flawlessly.
Processing the NMEA sentences.
The GPSMgr section of PiCar works as follows.
Creating the GPSMgr class fires up a thread to handle serial input from GPS device.
The begin() method will record the path to the device and the baud rate and makes an attempt to open the port. If it fails, the GPSReader() thread will attempt again in a few seconds.
Opening the serial port is handled by the openGPSPort() method. It does a standard file open() and then saves a copy of the existing port speeds and configuration with tcgetattr(). We restore it if we quit the app. We then use tcsetattr() to setup our port speeds and options. in particular we disable local echo.
The GPSReader() thread will then sleep waiting for character input using select(). When a character is read, the GPS reader will consume character using read() until it hits a newline and then parses it with the minmea library.
minmea, is an open source lightweight GPS NMEA 0183 parser library that I use to process information from the NMEA sentences
When enough data is received, the GPSReader will call processNMEA method which updates the appropriate GPSLocation_t, GPSVelocity_t and GPSTime_t structures.
The GPSReader() will then sleep until more data arrives on the serial port.
Occasionally the DisplayMgr will call into the GPSReader to get the most recent data using the GetLocation or GetVelocity methods.
More to come
At present, the GPS subsystem just displays the latest location and velocity. I chose to decode the lat/long into Universal Transverse Mercator (UTM) format. I happen to use it more than lat/long. But that’s not always so. If you are communicating with aircraft they use degrees, minutes and seconds. Or sometime dealing with USGS maps you need to decode survey township coordinates. Hence, I will probably add a setting’s option to change the display format.
The other thing that the PiCar system does is to use the GPS system to occasionally checks if the system clock needs to be synchronized.
A lot of work, but now I have a GPS on my radio.
I updated my code to use minmea from Kosma Moczek to parse GPS
https://github.com/kosma/minmea