Get GPS Data on QNX with a USB GPS
How to get GPS data from a USB-connected GPS receiver on the QNX 8.0 quick start target image running on a Raspberry Pi 4B.
There's no question that location information can be a core part of the signals used by an embedded system.
Today let's take a look at how to get GPS data from a USB-connected GPS receiver. Specifically, we'll be working on the QNX 8.0 quick start target image running on a Raspberry Pi 4B. (Though these instructions should be generally applicable across other targets with USB-support.)
What you'll need
Here's a list of everything we're working with for this tutorial:
- A QNX 8.0 developer license (get yours for free at qnx.com/getqnx) with the QNX SDP installed
- Raspberry Pi 4B booted with the QNX 8.0 OS quick start target image
- A USB-based GPS device (we're using the VK-162)
Prepare your target with the USB driver
When you connect the GPS receiver to your Raspberry Pi, you can check to see the type of the device based on the device class:
# usb
...
Device Address : 2
Vendor : 0x1546 (u-blox AG - www.u-blox.com)
Product : 0x01a8 (u-blox GNSS receiver)
Class : 0x02 (Communication)
Subclass : 0x00
Protocol : 0x00
...
Checking the device class of this USB device.
Notice the Class of the device is 0x02
which, according to the USB defined class codes, means this device is a Communications and CDC Control device. To interact with the device, we're going to need the devc-serusb
driver which is a "Driver for USB-to-serial adaptors and USB CDC ACM devices" [see documentation]. Sounds like a perfect fit!
Add devc-serusb
to your QNX target
The driver is not included in the quick start image out of the box, so we'll need to copy the driver to our Pi. Thanks to the micro-kernel architecture of QNX, the driver is simply a process binary we can copy over and run.
- Find the driver binary on your host. It should be in your SDP installation directory at
qnx800/target/qnx/aarch64le/sbin/
. - Copy
devc-serusb
to your target:- From your host:
$ scp ~/qnx800/target/qnx/aarch64le/sbin/devc-serusb qnxuser@your-ip-address:~
- On your target switch to root (
su -
) and move the driver somewhere convenient:# mv /data/home/qnxuser/devc-serusb /system/bin/
- Since the documentation says we must run the driver as
root
, let's make it owned by root:# chown root:root /system/bin/devc-serusb
- And lastly, run it!
# /system/bin/devc-serusb
- From your host:
The serial USB driver is now present and running – time to get some data.
Connect your GPS and check for data
The easiest way to check if our driver is doing its job is to plug in our GPS device and check in /dev
. If the driver is working, we should see /dev/serusb1
. (It's possible you'll see multiple here if you have more than one serial USB device connected!). The device is shown here because the driver creates a filesystem endpoint for us to interact with the device. If you don't see it there, try to unplug it and plug it back in.
Let's check for data:
# cat /dev/serusb1
$GNTXT,01,01,02,ANTSTATUS=INIT*3B
$GNRMC,,V,,,,,,,,,,N,V*37
$GNVTG,,,,,,,,,N*2E
$GNGGA,,,,,,0,00,99.99,,,,,,*56
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,1*33
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,4*36
$GPGSV,1,1,00,0*65
$GBGSV,1,1,00,0*77
$GNGLL,,,,,,V,N*7A
$GNTXT,01,01,02,ANTSTATUS=OK*25
$GNTXT,01,01,02,u-blox AG - www.u-blox.com*4E
$GNTXT,01,01,02,HW UBX-M8130 00080000*61
$GNTXT,01,01,02,ROM CORE 3.01 (107888)*2B
$GNTXT,01,01,02,FWVER=SPG 3.01*46
$GNTXT,01,01,02,PROTVER=18.00*11
$GNTXT,01,01,02,GPS;GLO;BDS*06
$GNTXT,01,01,02,QZSS*58
$GNTXT,01,01,02,GNSS OTP=GPS;BDS*26
$GNTXT,01,01,02,LLC=FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFD*2F
$GNTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*3E
$GNTXT,01,01,02,ANTSTATUS=OK*25
$GNTXT,01,01,02,PF=3FF*4B
$GNRMC,,V,,,,,,,,,,N,V*37
$GNVTG,,,,,,,,,N*2E
$GNGGA,,,,,,0,00,99.99,,,,,,*56
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,1*33
$GNGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99,4*36
...
Excellent! The NMEA GPS sentences are flowing in and we're seeing some data. The GPS module will continue to stream information here as it obtains a fix.
Here's a resource for learning more about the different NMEA sentences: https://aprs.gids.nl/nmea/
Write a program to parse GPS NMEA sentences
Now the fun part begins! 😄 We have a connected device and it's streaming data – all that's left is to write a program to process the data for our purpose. Thankfully it's as easy as opening a file pointer and reading in the data.
Processing GPS data in C on QNX
Here is a simple C program for reading from /dev/devc-serusb
and parsing the NMEA sentences line by line.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 1024
void parseGPSData(char *data) {
char *token;
char *latitude, *longitude;
char lat_dir, lon_dir;
//Check the string for a GGA packet, which contains lat/long
if (strncmp(data, "$GNGGA", 6) == 0) {
token = strtok(data, ",");
int field = 0;
while (token != NULL) {
field++;
if (field == 3) {
latitude = token;
} else if (field == 4) {
lat_dir = token[0];
} else if (field == 5) {
longitude = token;
} else if (field == 6) {
lon_dir = token[0];
}
token = strtok(NULL, ",");
}
printf("GGA message: Latitude: %s %c, Longitude: %s %c\n", latitude, lat_dir, longitude, lon_dir);
} else {
printf("Unknown message: %s\n", data);
}
}
int main() {
FILE *file;
char buffer[BUFFER_SIZE];
//Open our serial device's filesystem path
file = fopen("/dev/serusb1", "r");
if (file == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}
//Read the data line by line. As long as the GPS is connected,
// this will continue to run.
while (fgets(buffer, BUFFER_SIZE, file) != NULL) {
parseGPSData(buffer);
}
fclose(file);
return EXIT_SUCCESS;
}
Running this on our target gives us some output! Notice the GGA message captures, with some lattitude data (in DDMM.MMM format) and longitude data (in DDDMM.MMM format):
Unknown message: $GNGLL,4331.03578,N,08030.79002,W,180905.00,A,A*6B
Unknown message: $GNRMC,180906.00,A,4331.03595,N,08030.79164,W,1.944,,240125,,,A,V*01
Unknown message: $GNVTG,,T,,M,1.944,N,3.600,K,A*30
GGA message: Latitude: 4331.03595 N, Longitude: 08030.79164 W
Unknown message: $GNGSA,A,2,09,14,,,,,,,,,,,4.06,3.94,1.00,1*01
Processing GPS data in Python on QNX
import re
def parse_gps_data(data):
# Check the string for a GGA packet, which contains lat/long
if "GGA" in data:
match = re.match(r'^\$GNGGA,(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),(.*?),', data)
if match:
latitude = match.group(2)
lat_dir = match.group(3)
longitude = match.group(4)
lon_dir = match.group(5)
num_sats = match.group(7)
horiz_dilution = match.group(8)
alt = match.group(9)
print(f"GGA message: Lat: {latitude}{lat_dir} Long: {longitude}{lon_dir} #Sats:{num_sats} Alt:{alt}")
else:
print(f"Unknown message: {data}")
def main():
try:
# Open our serial device's filesystem path and read line by line
with open("/dev/serusb1", "r") as file:
for line in file:
parse_gps_data(line.strip())
except IOError as e:
print(f"Error opening file: {e}")
if __name__ == "__main__":
main()
Like with the C program, we get some output that captures the GGA sentence and shows us some lattitude data (in DDMM.MMM format) and longitude data (in DDDMM.MMM format):
# python gps.py
Unknown message: $GNRMC,181310.00,A,4331.02196,N,08030.83793,W,0.770,,240125,,,A,V*08
Unknown message: $GNVTG,,T,,M,0.770,N,1.426,K,A*3C
GGA message: Lat: 4331.02196N Long: 08030.83793W Sats:04 HD:5.82 Alt:380.1
Unknown message: $GNGSA,A,3,09,04,14,,,,,,,,,,10.95,5.82,9.28,1*38
Next steps
To continue learning, there are some improvements you can make on this sample code. Here are some fun ideas for implementing some GPS functionality:
- Collect the time data and use it to synchronize the system time
- Write a resource manager to parse the messages and supply a cleaner API to apps requiring location
- Send the location information to another endpoint, like another device or a cloud service