Exploring kinematic single-receiver solutions with RTKLIB and the u-blox F9P.

Most of my work with RTKLIB has been done with differential solutions (RTK or PPK) using two receivers, a base and a rover. I have briefly explored static PPP solutions but have not previously looked at kinematic PPP solutions or analyzed the internal standard precision solutions of the u-blox F9P receiver. In this post, I will take a closer look at these options.

For this experiment I started by collecting a data set using an F9P receiver connected to a u-blox ANN-MB-00 antenna mounted on the roof of my car. The first 40 minutes were static followed by another 30 minutes of driving around residential and light industrial neighborhoods, all sparsely treed. The goal here was to start with something not overly challenging so I intentionally avoided any significant tree canopy, underpasses, or tall buildings. I enabled and logged u-blox raw observation and navigation messages (RXM-RAWX and RXM-SFRBX) as well as NMEA solution messages for the internal F9P standard precision solution ($GNGGA, $GNGLL, $GNGST).

I’ve uploaded this logged raw data file as well as all other necessary files to generate the solutions described below to the download section of my website in case anyone wants to download the data and duplicate my results. I did make some changes to the RTKLIB code as I worked through the experiment, so you will also want to download the executables for the latest b33f version of the demo5 RTKLIB code.

To generate a ground truth for the subsequent comparisons, I first converted the raw observation data file to rinex using RTKCONV and then ran a combined-mode PPK solution of the raw data against a nearby CORS station using the demo5 b33f version of RTKPOST and my standard configuration settings for the F9P (ppk.conf in the data download folder). The fix rate in the resulting solution is 99.9% and the CORS station is less than 10 km away, so I have a relatively high confidence in the accuracy of this solution. Note that the base coordinates in the CORS station rinex header are NAD83 while the single receiver solutions will all use the WGS84 datum. To correct for this, I have specified the base position in the config options in WGS84 coordinates.

[Note 1/11/21: As Brian pointed out in the comment section below, I made an error in this conversion. This adds a 5-10 cm DC bias to the E-W and N-S components in all the difference plots below. It doesn’t have a large effect on the first few plots but it becomes a significant percent of the error in the final PPP comparisons. I plan to make a follow-up post where I correct this error.]

In the image below, the raw observations are on the left, and the ground track of the PPK solution is on the right. The transition in the raw observations where the cycle slips (red ticks) begin indicate when the car started moving.

Raw observations on the left and RTKLIB PPK solution on the right

Since the raw log file from the F9P receiver includes NMEA position messages in addition to the raw observations, I can plot this file as a solution file directly with RTKPLOT. This will extract the NMEA positions from the file and ignore the raw observations.

If I use RTKPLOT to plot both the RTKLIB PPK solution and the F9P real-time (NMEA) solutions, I can then select the “1-2” button to plot the difference between the two solutions. Since the errors in the real-time solution will be much greater than the PPK solution, we can take this plot to indicate the error in the real-time solution.

Difference between RTKLIB PPK solution and real-time F9P solution

Note that the errors are larger (and lower frequency) during the static portion of the data set on the left half of the plot than they are on the right when the car is moving. This may seem counter-intuitive but it is because the multipath component of the error gets randomized by the movement of the receiver antenna relative to the satellite signals.

If you look at the datasheet for the F9P, you will see that horizontal accuracy is specified as 1.5 m CEP accuracy for a PVT solution and 1.0 m CEP accuracy for an SBAS solution. I didn’t calculate the exact CEP value for the plot above, but it would correspond to where the square root of the sum of the squares of the two horizontal components was less than the spec for 50% of the time. In this case the solution included SBAS augmentation so I would expect the 1.0 m accuracy spec to apply. Just eyeballing the plot, it looks we are getting at least this accuracy during the static portion and even better accuracy during the dynamic portion. This makes sense since the spec is for a static case. I could not find an F9P spec for dynamic accuracy.

Note that I have upgraded the firmware in the F9P module to version 1.13 which was released fairly recently. SBAS support was added to the F9P with the 1.13 upgrade so I suspect if you are running older firmware on your F9P, you may see larger errors. You can check the firmware version running on your module by querying the UBX-MON-VER message from u-center.

Sometimes it’s hard, at least for me, to look at the raw error numbers and visualize what they mean in the real world, so I have shown a snapshot of the two ground tracks below, the green dots are the PPK solution, and the yellow dots are the real-time F9P solution. I suspect for many applications, the level of error in the real-time solution would be acceptable. I was actually surprised to see how good it is.

Ground track of RTKLIB PPK solution (green dots) and F9P real-time solution (yellow dots)

So next, let’s look at the RTKLIB single frequency post-processing solutions. I will start with the “Single” positioning mode solution. This mode gives a very coarse solution and is really only suitable for initial approximate locations for the other solution types but we’ll take a quick look at it anyways. I ran an RTKLIB solution using the same config file as for the PPK solution, I just changed the “Positioning Mode” option from “Kinematic” to “Single. I then plotted the difference between this solution and my reference solution as I did before. Below is a plot of the difference between the two solutions.

Difference between RTKLIB PPK solution and RTKLIB single solution

As you can see, the errors are much larger than the real-time F9P solution and so of very little use.

Next, let’s look at the RTKLIB Kinematic PPP solution to see if there is any opportunity to improve upon the real-time solution here. To create a kinematic PPP solution I used the raw observation and navigation file from the F9P, along with precise ephemeris and clock files, a recent DCB (differential code bias) file, an antenna calibration file, and the ppp.conf config file, all included in the uploaded data set folder. I used very similar configuration settings to the PPK solution with a few exceptions. First, I enabled or configured all the PPP relevant parameters. For now, you can see the details of these settings in the ppp.conf configuration file, I hope to cover them in more detail in a future post. Next, I increased the minimum elevation mask from 15 degrees to 20 degrees based on earlier experiences showing that the RTKLIB PPP solutions are more vulnerable to errors and cycle slips in the low elevation satellites than are the PPK solutions. Also, I increased the outlier threshold from 1 meter to 30 meters since the residuals are much larger in the PPP solution and the outlier handling is different. I then ran two solutions, one with the first rapid precise ephemeris/clock files I was able to find online published after the data was collected (SHAOMGXRAP*.*), and the second solution was run with the final precise ephemeris/clock files (ESAOMGNFIN*.*). Both solutions were run with the most recent DCB files I was able to find which were based on analysis from Nov 2020. There are a number of online repositories of precise ephemeris data but many of these are GPS and GLONASS only, it is more difficult to find precise files that include Galileo and Beidou as well. The CDDIS, ESA, IGS, CODE, and other websites all have different variations of precise ephemeris files available for download but I have not yet found any one site that is best for both rapid and final multi-constellation ephemeris files.

Below are the differences between the two PPP solutions and the same PPK reference solution as used above, with the rapid ephemeris solution on the top, and the final ephemeris solution below.

Difference between RTKLIB PPK solution and RTKLIB PPP solutions. Top solution used early precise ephemeris/clock files, Bottom solution used final precise ephemeris/clock files.

[Note 1/11/21: See note above regarding the biases in this plot, they are a large part of the remaining error especially in the final ephemeris comparison plot as well as the CSRS PPP plot below]

In this case the rapid ephemeris/clock files were available the following day, the final ephemeris/clock files were not available until a week after the data was collected. Both solutions show smaller errors than the F9P real-time solution and the errors in the final solution are smaller than in the rapid solution, as would be expected.

PPP solutions typically have long convergence times, so some readers might be asking themselves why they don’t see any signs of this in these PPP solutions. The answer is because they were run in “combined” solution mode meaning the solution is run forwards and backwards and the two combined. Typically in a combined solution, and this includes the 2.4.3 version of RTKLIB, the kalman filter states are reset between the forward pass and the backwards pass to insure the two solutions are independent. In the demo5 code I have chosen not to reset the filter states unless it is a PPK solution with fix-and-hold enabled. This means the filter states will be fully converged at the beginning of the backwards pass and this will improve the overall accuracy of the solution at the possible expense of some theoretical loss of integrity in the solution, although I have not found this to be an issue in my limited testing. The convergence still needs time to occur however, so I would not recommend using this technique on data sets less than half an hour and even that might be marginal.

As you might expect, the errors in the PPP solutions are a fair bit lower than the real-time solution, while still quite a bit larger than the PPK errors. I suspect there are situations where these solutions would be of use, particularly where local CORS stations are not available. The biggest caveat is that the PPP solutions are less robust than either of the other two solution types and it is also more difficult to detect larger errors in the PPP solutions compared to the PPK solutions since there is no verification step from the ambiguity resolution.

Use of static PPP solutions seem to be quite common, I see less use of kinematic PPP solutions, so I was somewhat surprised and pleased to see how well the RTKLIB kinematic PPP solutions did work.

For static PPP solutions I prefer to use the free online CSRS PPP solution service I’ve described in other posts rather than RTKLIB since it is simpler to just submit the observation file than it is to find the precise ephemeris file and I also have more confidence in the accuracy estimates of the CSRS solution. It does take longer to converge than the RTKLIB solution since it is only using GPS and GLONASS satellites but this is only a minor inconvenience for a static measurement. For a kinematic measurement, the smaller number of satellites is a bigger problem but I thought it was worth a shot so I submitted the raw data file to CSRS and specified a kinematic solution. The CSRS solution is not directly plottable with RTKPLOT but I wrote a short python script to convert it to RTKLIB solution format and plotted the difference from the reference solution below.

Difference between RTKLIB PPK solution and CSRS online PPP kinematic solution

The solution is no better than the real-time F9P solution when the car is moving, so this is also probably not a useful solution. It is interesting that in this case, unlike all the other solutions, the errors are larger when the car is moving than when it is stationary. This is probably because of the increased number of cycle slips during this time.

While that is probably enough for an initial exploration, I hope to take a closer look at some of these results as well as potential improvements in future posts. Please comment below if you would like to add anything else to the discussion.

Sponsored Post Learn from the experts: Create a successful blog with our brand new courseThe WordPress.com Blog

Are you new to blogging, and do you want step-by-step guidance on how to publish and grow your blog? Learn more about our new Blogging for Beginners course and get 50% off through December 10th.

WordPress.com is excited to announce our newest offering: a course just for beginning bloggers where you’ll learn everything you need to know about blogging from the most trusted experts in the industry. We have helped millions of blogs get up and running, we know what works, and we want you to to know everything we know. This course provides all the fundamental skills and inspiration you need to get your blog started, an interactive community forum, and content updated annually.

Building RTKLIB code in Linux

In my last post I described building the RTKLIB code in Windows. In this post I will describe building the code in Linux. Unlike for Windows, pre-built executables are not available for either the demo5 or the official 2.4.3 versions of RTKLIB, so building your own code is more of a necessity on this platform.

I have built Linux versions of RTKLIB on both a PC running Ubuntu and on a Raspberry Pi and will describe these experiences. My description may be somewhat specific to these platforms but I believe the process should be very similar on any other Linux system.

The original RTKLIB did not support Linux for the GUI apps but a few years ago Jens Reimann created a version using Qt that supports Linux as well. Those changes are now in both the demo5 and the 2.4.3 versions of RTKLIB and I will describe building these on the Ubuntu platform below.

Building the RTKLIB CUIs on a Raspberry Pi

Let’s focus on just the CUI (Character User Interface) RTKLIB apps for the Pi since the GUIs apps are probably not a good choice for this hardware. We’ll start with just compiling a single app. I will assume that you are connected to a headless Pi through an SSH window and that the Pi is already connected to the internet.

In this case, the following set of commands should be all you need to build RTKRCV, the real-time app for generating RTK or PPP solutions.

git clone https://github.com/rtklibexplorer/RTKLIB.git
cd RTKLIB/app/rtkrcv/gcc
make

The first line pulls the source code from the Github repository, the second line gets you into the Linux build folder for RTKRCV, and the third line builds the code. The default compiler warning threshold is pickier than for Windows, so you will see some warning messages but you can ignore these. You can build the STR2STR, CONVBIN, RNX2RTKP, and POS2KML in the same way by changing the app name in the second line.

Most likely, the only two apps you would be using on the Pi would be the real-time apps, either RTKRCV or STR2STR. However if you would like to build all five CUI apps with a single command you can run “make” from the /RTKLIB/app folder.

If you do get any build errors it is most likely going to be because you are missing one of the build tools. However I have found that if you are using one of the standard releases of Debian Linux for the Pi, this should not be a problem.

Building the RTKLIB CUIs and GUIs on an Ubuntu PC

In this case I am building the code with Ubuntu 20.04 on a Linux partition on a Windows PC, but I don’t think other platforms should be very different.

Before building either the CUIs or the GUIs you will most likely need to update your build tools. I used the following commands to resolve various build errors I ran into when I started with a fresh installation of Ubuntu 20.04

sudo apt-get update
sudo apt install build-essential
sudo apt-get install libpng-dev
sudo apt-get install qt5-default libqt5serialport5-dev

The first line may not be necessary, it just updates the package lists for the following commands. The second line installs the most common build tools, the third line installs the “png” library, and the last line installs the Qt tools.

Once the tools are installed, you can build the CUI apps using the same process I described above for the Pi.

The GUI apps require the core code be built first before the individual apps, so it is easiest to build all the apps at once rather than one at a time. You can do this with the following commands.

git clone https://github.com/rtklibexplorer/RTKLIB.git
cd RTKLIB/app
qmake
cd ..
qmake
make

The first line pulls the source code from Github and is not necessary if you have already done this to build the CUIs. The second line gets you to the app folder in the RTKLIB code where the third line creates a makefile for the Qt apps. The fourth line gets you back to the top level folder where the fifth line creates the makefile for the full build process. The last line builds all the CUI and GUI apps.

It will take a fairly long time to build everything the first time but if you make any changes to the code, these will usually compile much faster. You will also see many warning messages which you can ignore. To rebuild all the apps, you only need to run the last line. Alternatively, after you have run the full process once, you can run “make” from any of the app folders to rebuild just that app.

Note that this process will overwrite the makefile in the RTKLIB/app folder that we used to build just the CUIs in the earlier step, so if you use this process, you won’t be able to build just the CUI’s as a group anymore.

I have tested these Qt GUI apps only briefly but they seem to be working. The core code is shared between all the apps, so as long as the interface is working, the code behavior should not be different regardless of which app or which version of it you are running.

Please add a comment below if you have any corrections or suggested improvements to the process I have described.

Building RTKLIB code in Windows

For most users running RTKLIB in Windows there is probably no need to build their own executables, it’s simpler to just download the pre-built demo5 executables from here or the 2.4.3 executables from here.

However, there are a few reasons why you might choose to download the source code and build your own executables instead. First of all, the pre-built executables are built with a large number of options enabled, but not all options. This means they will work for most user configurations but there are some less common configurations they won’t be able to handle. In addition, because they have so many options enabled, they will run slower and use more memory than executables built with only the minimum required number of options enabled.

Also, the latest available executables will not include the most recent updates to the code, you will need to build your own version to get these. And of course, anyone who wants to make any changes to the code will have to build their own executables.

OK, so let’s say you’ve decided to build your own versions. How do you go about doing it? It’s not a complicated process, most reasonably computer-savvy users would be able to figure it out on their own, but I’ll try to provide a few tips below to make it a little quicker and less painful.

The first step is to download the source code from the Github repository. I will assume you are using the demo5 code which is available here, but the same process will work with the official 2.4.2 or 2.4.3 code as well. Click on the “Code” box on the Github repository page to download the code. If you are familiar with Github you may choose to clone the repository or open with Github desktop but for most users it is probably simplest to just choose the “Download ZIP” option and then unzip the code to your desired location on your local drive.

The next step is to choose your compiler. You can use either Microsoft Visual Studio or Embarcadero C++ Builder. Both have community editions which can be downloaded and used for free for non-commercial use. However, Visual Studio will only build the RTKLIB command line apps, not the GUIs, so unless you already have Visual Studio on your computer and only need the command line apps, I would suggest using the Embarcadero compiler.

Now go back to the RTKLIB code folder you just downloaded and unzipped. Most of the common source code is in the “/src” folder but each RTKLIB app has its own sub-folder inside the “/app” folder. Let’s use RNX2RTKP, the RTKLIB command line post-processing solution app, as an initial example. In the “/app/rnx2rtkp” folder you will see a number of different sub-folders for different build methods.

Build a single app with the Embarcadero C++ Builder

Let’s assume for now you are using the Embarcadero compiler and want to build the 64-bit version of the code. You will want to open the “/bcc_win64” folder and within that folder you will see an Embarcadero project file called “_rnx2rtkp_win64.cbproj”. Double-click on this file to open the 64-bit version of the project in the Embarcadero compiler. If your hardware only supports 32-bit executables, you can select the “/bcc/_rnx2rtkp.cbproj” project file instead. When the compiler opens, it should look something like this:

From the “Project” menu, click on “Make _rnx2rtkp_win64” to compile the code. When done, the result should look like the image below. Make sure it says “Linking: Done” and “Errors: 0”

And that’s it. The result, “_rnx2rtkp_win64.exe”, will be in the “\app\rnx2rtkp\bcc_win64\Release_Build” folder.

The only differences for the GUI apps are that the project files are directly in the app folders rather than in sub-folders and in some cases there are separate app folders for the 32 bit and 64 bit versions. For example, for RTKNAVI, you will find a “app/rtknavi/rtknavi.cbproj” project file and a “app/rtknavi_win64/rtknavi_win64.cbproj” project file.

Build a single command line app with Microsoft Visual Studio.

The instructions for Microsoft Visual Studio are very similar, however you will only be able to build the CONVBIN and RNX2RTKP apps since Visual Studio does not support the GUI apps.

After installing Visual Studio, click on the “msc.sln” project file in the “\app\rnx2rtkp” folder to open the project.

Choose “Build Solution” from the “Build” menu to compile the code. The resulting executable will be in the “app\rnx2rtkp\msc\Release” folder.

The project will default to a 32-bit version but you can add a 64-bit version using the Configuration Manager, just be sure to to check the Build box in the new configuration as shown below.

Building and installing the entire RTKLIB library with the Embarcadero C++ Builder

If you are using the Embarcadero compiler and would like to build all the RTKLIB apps at once instead of one at a time, you can use the “rtklib_consapp.groupproj” and “rtklib_winapp.groupproj” project files in the “/app” folder to build the console apps and GUI apps respectively. From the “Project” menu, select “Make All Projects” for each of these group projects. You will see errors when the GUI project attempts to build the RTKVIDEO and RTKVPLAYER apps but you can ignore these, assuming you don’t plan to use these apps.

After completing the builds, run the batch files “app/install_consapp.bat” and “app/install_winapp.bat” to copy all the executables from the “Release” folders to the single “../RTKLIB_bin/bin” folder. You will need to create the destination folder first to avoid errors when it copies the files.

Modifying the build options with the Embarcadero C++ builder

So far, I’ve assumed you want to build with the default build options but sometimes you will want to change these. I will describe the procedure for the Embarcadero compiler, but the procedure is very similar for Visual Studio.

Open the project in Embarcadero for an individual app and then select “Options” from the Project Menu. Select “C++ Compiler->Directories and Conditionals” from the choices in the left column and then set the “Target” box near the top center to “All configurations – All platforms” as shown in the image below. Click on the three dots at the end of the “Conditional defines” list to open up a list of the current defines.

You can now add or remove any of the “Conditional defines” from the list. The definitions of the default defines are as follows:

WIN32: Use the WIN API – use for 32 bit or 64 bit apps
ENAGLO: Enable GLONASS
ENAGAL: Enable Galileo
ENAQZS: Enable QZSS
ENACMP: Enable Beidou
ENAIRN: Enable IRNSS
NFREQ=3: Max # of frequencies, 3=L1+L2+L5, must be >=2 for u-blox
NEXOBS=3: # of extra observations, used to store multiple codes per freq

Other optional defines are:

ENALEO: Enable LEO
ENALEX: Enable QZSS LEX extension
WIN_DLL: Generate Library as Windows DLL

I have not tested any of these optional defines, so no guarantee if any of them work.

Well, I think that’s about it. I’ve probably forgotten something, but this should be enough to get you started.

In my next post, I will cover building the code in Linux.

Converting GLONASS RTCM MSM messages to RINEX with RTKLIB

I’ve recently had a few questions about using RTKLIB to convert the newer RTCM Glonass MSM messages to RINEX so I thought that would make a good topic for a post.

First, I’ll include a little background for those not familiar with the details of RTCM messages. Until fairly recently, most receivers reporting raw observations in RTCM format were using the older legacy RTCM messages (e.g. 1004 and 1012), but over the last few years, it has become more common to use the newer MSM formats for RTCM messages since they support more constellations, are better standardized, and include a little more information. This RTCM cheat sheet from Snip is always a good place to look up info on RTCM messages. Here is a quick quote from that page, but I’d suggest clicking on it for more info.

An overall intent of the MSM message development effort has been to have more uniform and more modernized set of messages that can employed with any GNSS system (not just GPS and GLONASS).  To that end, seven new basic message types were defined (see the table below).  And then each of these was applied to each separate GNSS system.  So for example; an MSM7 style message for QZSS is nearly identical to an MSM7 style message for GPS, only their message ID assignment numbers vary.  The seven basic messages each have more informational details than the one that precedes it.

For the most part, RTKLIB supports all of the different forms of the MSM messages, but there is one significant exception. To translate the GLONASS phase observations into RINEX format requires knowing the frequency of each satellite signal. This is straightforward for the other constellations since all satellites in those constellations use the same frequency. However each GLONASS satellite is on a separate frequency. That information is available in the legacy GLONASS messages and in the GLONASS ephemeris messages, but not in all flavors of the MSM messages. Specifically, it is part of the MSM5 and the MSM7 GLONASS messages but not the others.

Until fairly recently, RTKLIB was not able to extract the frequency information from any of the MSM messages and would fail to include GLONASS phase observations for any of the MSM messages (1-7) if GLONASS ephemeris messages were not also present. Last year, I added a feature to the demo5 code starting with the b33a version which fixed this problem for the MSM5 and MSM7 messages, but the other messages will still fail to produce phase observations in the RINEX file. Also, at this time, the official RTKLIB code does not support GLONASS phase observations for any of the MSM messages unless ephemeris messages are also present.

There are several ways to get around this limitation. If you are using MSM7 messages and the official RTKLIB code, switching to the demo5 code will fix the problem. If you are using MSM4 or any of the other MSM messages that don’t include the extended satellite info, then configuring the receiver to output MSM7 format messages, or the older 1012 RTCM messages, or enabling GLONASS 1020 ephemeris messages will fix the problem. If you are unable to re-configure the receiver then it will be more difficult to get around this issue. One reader has reported success by appending downloaded ephemeris messages to the raw RTCM observation file. Since RTKLIB accepts wildcards in file name inputs, it shouldn’t be necessary to actually combine the files, simply specifying a filename with wildcards that will include both files should be sufficient. Note that the ephemeris file does not need to be current but this will only work if it includes all of the GLONASS satellites in the observation file and is not so far out of date that any of the GLONASS satellite slots have been replaced with another satellite with a different frequency. This does not happen very often so updating the ephemeris file should only need to be done quite infrequently.

Eventually it would be nice if RTKLIB were able to translate all of the GLONASS MSM message but it is a non-trivial fix to provide a continuously up to date version of the necessary satellite frequency information. In the meantime, these workarounds should help get around this limitation.

Review of a new lower cost alternative to the u-blox M8T receiver

A quick Google search will bring up several options for receivers based on the u-blox F9P dual frequency module but finding receivers based on the single frequency M8T receiver is more difficult. I am a regular user of the CSGShop M8T based receivers. Other than some often-counterfeit options available on Ebay or similar sites, CSGShop is the only reliable source I am aware of for M8T based receivers. CSGShop appears to have recently changed their name to GNSS OEM, but I haven’t got used to their new name yet so for this article I am going to continue to refer to them by their original name. I have been very happy with these receivers, have been recommending them for several years, and will continue to recommend them. However, I recently received a single frequency RTK capable receiver from Uputronics based on the u-blox M8 chip for review that is worth considering as an alternative.

The u-blox M8T module is based on the u-blox M8 chip combined with a crystal oscillator, flash, and several other discrete components. The photo below shows a u-blox receiver module with the cover removed. The chip in the middle of the board is the M8. In this case the module is an M8N but should look nearly identical to the M8T.

U-blox receiver module with cover removed

The Uputronics receiver does not use the M8T module but instead builds its own module starting with the M8 chip combined with a TXCO (temperature controlled crystal oscillator) with specs very similar to the M8T and adds the necessary discrete components. It is running similar or identical firmware to the M8T and like the M8T provides raw observations.

The price for a CSGShop receiver sent to the U.S is $79 +$10 shipping. The Uputronics receiver sent to the U.S is $41 +$11 shipping. It also appears to be available in the U.S from AirSpy.US for a couple dollars more but would presumably have faster shipping. CSG shipping times do tend to be quite long, sometimes taking several weeks to the U.S. so for some users this could be a significant advantage.

Here is a photo of the two receivers with the Uputronics receiver on the right.

CSGShop M8T receiver next to Uputronics M8 receiver

So how do they compare? Functionally they should be very similar since they are both based on the M8 chip and firmware. However, there are still some fairly significant differences between them. First of all, as you can see in the photo, the Uputronics is quite a bit larger despite most of the board being empty. This is because it is designed to fit as a hat onto a Raspberry Pi. The large connector in the foreground is designed to connect directly to the GPIO bus on the Pi. The CSGShop receiver in the photo has a USB interface connector but it is also available with a UART connector instead. The Uputronics board has the UART connections through the Pi connector. For someone just getting started with precision GPS, I prefer the USB interface since it can easily be plugged directly into a PC, but with a little work the UART connection can be translated through an FTDI module to USB and then also plugged into the PC. For connecting to a Pi, or Arduino or other embedded application, the UART interface is usually preferable.

After the interface connector, the most noticeable difference between the two receivers is the lack of non-volatile memory on the Uputronics board. Often, the easiest way to configure a u-blox receiver is to connect to it through the u-blox u-center app on the PC, set it up as desired and then save to flash. This is not an option on the Uputronics board so instead the receiver needs to be configured each time it is used. RTKLIB does provide an option to configure the receiver each time it is run so in principle this is only a minor inconvenience. Unfortunately, the receiver defaults to 9600 baud and this normally need to be increased to 115K baud to provide enough bandwidth for the raw observations. This makes it a two step process, first running RTKLIB at 9600 baud to configure the receiver settings and baud rate and then a second time with the baud rate set to the higher rate. It is not difficult to write a python script to take care of this two step process but it does add unnecessary complication for the beginner user. Fortunately Uputronics says they plan to change the default baud rate to 115K in the near future which will simplify this step.

[Update 9/15/20: See Clive’s comment in the comment section below for a link to instructions for permanently changing the baud rate to 115K. This makes things much easier since the rest of the settings can easily be handled with a .cmd file from within RTKLIB.]

Since the Uputronics board is designed to be attached directly to a Raspberry Pi, I chose this configuration for testing. For comparison purposes, I also connected a CSGShop M8T receiver by USB cable to the Pi4 I used for the experiment as shown in the image below.

Raspberry Pi4 with Uputronics M8 hat and USB-connected CSGShop receiver

I used the STR2STR application in RTKLIB to configure the Uputronics receiver and to stream both receiver outputs wirelessly over TCP/IP and a cell phone hot spot to my laptop where I ran two real-time solutions using RTKNAVI. I hope to write another post in the near future describing the details of configuring the Pi but for now the best I can do is refer you to a slightly out-of-date post I previously wrote to describe logging the results to a file on the Pi. I could have also run the real-time solutions on the Pi using RTKRCV but I will leave this to a future post as well.

So, now we have the two receivers up and running, let’s do a performance comparison. For this, I did my typical drive around the neighborhood with each receiver connected to its own antenna mounted on the roof of the car and used a u-blox F9P receiver at my house with a survey grade antenna on the roof as base station.

I had tested an earlier version of this board in a similar experiment and had got nearly identical results with both receivers so had expected the same for this experiment. Surprisingly, the Uputronics board performed significantly worse than the CSGShop board as seen in the image below. The Uputronics solution is on the left.

Run 1: Uputronics on the left, CSGShop on the right

In addition to a much higher percent of float solution points on the Uputronics solution, it also has many more missing samples. At first I suspected a communication problem but when I looked closely at the raw observation files I see that this is not the case. Here’s an example from the raw observation file from one of the missing epochs in the solution. The fourth column is the phase observation and the fifth column is the quality metric value from the receiver. The fifth field in a rinex file is normally an older rarely used SNR field but the demo5 version of RTKLIB reports the quality metric for u-blox receivers instead. Any phase observation with quality metric over 5 is discarded during the RTKLIB raw to rinex conversion which is why the fourth column is mostly blank in this example.

One epoch from the Uputronics rinex observation file

I ran the experiment a couple more times to make sure this was not a fluke and got similar results. The only significant difference between this experiment and the previous one I had done on the earlier version of the board was that in the previous experiment I had not used the Pi, I had connected the UART pins through FTDI directly to my laptop. This made me suspect that the close proximity of the Uputronics board to the Pi was allowing it to pick up some electromagnetic interference (EMI) from the Pi.

There are only four pins on the GPIO interface between the two boards that are necessary for the receiver (+3.3V, Gnd, RX, and TX) so I soldered a few wires to a couple of headers to separate the two boards as shown below.

Run 2: Uputronics board physically separated from Pi

The electrical tape on the back of the Uputronics board was part of an intermediate experiment with the board still attached since I realized that the painted metal heat sink on the Pi CPU was actually touching the back of the Uputronics board. The tape seemed to help a little but did not solve the problem.

The purpose of this experiment was to physically separate the two boards but it also reduced the number of connections between them since the Uputronics board, in addition to the receiver, provides a real time clock that uses some additional pins. I’m not sure if it was because of the increased separation or the reduced connections, but re-running the experiment in this configuration got me back to where I was on the previous board where both receivers performed very similarly. Here’s the results from this experiment, again the Uputronics results are on the left.

Run 2: Uputronics on the left, CSGShop on the right

The CSGShop still performed slightly better (97% fix vs 94% fix) but I think this difference is too small to be meaningful, especially since the two receivers were using similar but not identical antennas.

So, to summarize, I think this receiver is definitely worth a second look. It has a few shortcomings still, but hopefully these will improve over time, and the cost difference is probably significant enough to justify the extra effort for some users. I’m planning to use it myself in an upcoming project and will try to keep readers up to date with my experiences going forward.

RTKLIB: Tips for using a CORS station as base

Generally, you will get the best RTK/PPK solutions using two matched receivers, one as rover, and one as a local base. This will give you the shortest baseline, maximum number of usable constellations, and no issues with the Glonass hardware biases that come from using unmatched receivers. To do this, you will first need to determine the precise location of your local base receiver. Running a post-processed kinematic (PPK) solution with your own local base receiver as rover and a nearby Continuous Operating Reference Station (CORS) as base is usually the easiest way to do this.

Of course, If you have a CORS station near enough, it is also possible to run solutions directly from your rover to the CORS base but I generally opt for the local base since the solutions will be more accurate and more robust.

For the most part, running a PPK solution against a CORS base is very similar to the local base solutions I describe in many of my other posts but there are a few important differences that I will try to go over in this post. I will describe using CORS stations inside the U.S. with u-blox receivers and the demo5 RTKLIB code since this is what I am most familiar with, but most of the information in this post will apply to other receivers as well as to CORS stations in other countries.

The first step is to find a nearby CORS station. In the U.S, the easiest way to do this is to go to the NOAA User Friendly CORS website and click on the CORS Map link. Zoom into your local area and if you are lucky you should see something like the screen shot below with several nearby choices. If you’re not so lucky you may find only one or two distant choices.

Notice that some stations are GPS only and some are labelled as “GNSS” which indicates that they support multiple constellations, usually GPS and Glonass, but sometimes Galileo as well. Ideally you will find a “GNSS” site within 20 kilometers but if not you may be successful generating fixed solutions with stations up to 100 km or more away. If you are using a dual frequency receiver and solving for a stationary rover you can usually use more distant stations. Be aware that longer baselines will increase accuracy errors so it is a good idea to run longer solutions and average the results from multiple stations if possible. Station sample rate is less important than distance and number of constellations but a higher sample rate is always better if you have the choice.

In addition to the NOAA CORS website, it is also worth checking if your state has its own network of reference stations. These are usually run by the state department of transportation and their data is normally free although you may need to sign up to access it. There is a slightly dated state by state list available here.

If your base station is sampling slower than a 1 second rate then I often find that interpolating between base observation samples improves the solution. In RTKPOST, this is done by setting enabling “Time Interpolation of Base Station Data” in the “Misc” tab of the options menu. In RNX2RTKP, this is done by setting “misc-timeinterp =on” in the config file.

Assuming your rover is stationary, you can run the solution mode as “Static” and this will improve your chance of getting a fixed solution. I prefer to run a “Kinematic” solution if possible, however, since the variation in solution position over time gives some insight into what the accuracy of the solution is, especially if you have collected several hours of observation data.

Since the receiver manufacturers will almost always be different between base and rover in these solutions, you will usually need to deal with the Glonass hardware biases. I describe the different options to account for these in detail in this post so I would recommend reading or reviewing that post for the details. To quickly summarize though, the simplest option is to set “Integer Ambiguity Resolution” in the options menu for Glonass to “Fix-and-Hold” in which case RTKLIB attempts to calibrate the biases. In some cases, this will be good enough to get a fixed solution. However, you are more likely to get a fixed solution if you account for the biases directly. You can do this by setting Glonass ambiguity resolution to the poorly named “Autocal” option and then specifying the biases directly with the “GLO HW bias” option in RTKPOST or “pos2-arthres2” option in the config file. Again, see the above post for the details on how to determine the correct biases and the table of biases by receiver manufacturer. Note that these ambiguity resolution options are only available in the demo5 version of the RTKLIB code.

If the HW biases for both base and rover are close to zero then it’s generally OK to ignore them and set Glonass ambiguity resolution to “on”. This will not be true for a u-blox M8T rover since it’s bias is not zero. However the u-blox F9P bias is close to zero so if you are using it as rover and your base receiver is manufactured by Trimble, Septentrio, Topcon, Spectra Physics, Javad, or Ashtec then go ahead and set Glonass ambiguity resolution to “on”. The receiver manufacturer will be listed in the header of the base rinex file.

The base rinex file header also includes the base antenna type and it’s precise location. Note that the precise location is in the header field labelled “APPROX POSITION XYZ”. Setting the base station position in the options menu to “RINEX Header Position” will cause this value to be used in the solution. If you would also like to include the base station antenna calibration in the solution, then check the “Antenna Type” box and specify “*” in the box below. This tells RTKLIB to use the antenna type listed in the rinex header. You will also need to point to an antenna calibration file in the “Files” tab of the options menu. You can use the “igs14.atx” file that is included with the demo5 binaries for this.

It is important to keep in mind that the accuracy of the solution is going to get worse as the distance to the base station increases as shown in this chart taken from a Novatel tutorial.

If you are using a single frequency receiver then there are usually no good alternatives to using a distant CORS station. The best you can do is collect multiple longer data sets from multiple stations and average the results. If you are using a dual frequency receiver then you also have the option of running a Precise Point Positioning (PPP) solution, either with RTKLIB or through an online service as I describe in this post.

Well, that’s all I can think of at the moment. If anyone else has any additional tips or questions, please add them to the comments below.

RTKLIB Benchmarking: versions 2.4.2, 2.4.3, and demo5

It’s been about four years now since I created the demo5 branch of RTKLIB. During that time I have added a number of features and enhancements to the code with a focus on low-cost receivers (primarily u-blox) and moving rovers while at the same time keeping synced with the latest 2.4.3 code from the official RTKLIB code base. I thought it would be interesting to do a little benchmarking between the the two official versions of RTKLIB (2.4.2 and 2.4.3) and the demo5 code to see to what extent this evolution of the code has affected the results.

First of all, though, it’s probably worth a little discussion about versions 2.4.2 and 2.4.3. Source and executables for both versions are available on Github at https://github.com/tomojitakasu. The source is in the RTKLIB repository and the executables are in the RTKLIB_BIN repository. Both of these repositories default to the “master” branch which is the 2.4.2 code. This is what you will get unless you specifically request the “2.4.3” branch of the code. For several years, almost all the development activity was on the 2.4.3 branch and only very minimal changes were being made to the master branch. In Jan 2018, there was a merge of the 2.4.3 changes back to the master branch although it appears that not all of the changes in 2.4.3 were merged back into the master branch. Since then development has continued on 2.4.3 without another merge back to the master branch.

For most of my data analysis posts, I focus on a single data set, spending a fair bit of time to make sure I am only analyzing the usable parts of the data, possibly tweaking the configuration file for that specific data, and digging into any issues that crop up. In this case I didn’t do that. I picked nine raw data sets, all with u-blox M8T receivers and a moving rover, made no effort to filter out bad data, and used a single generic configuration file for all nine data sets. Eight of the data sets are those that I have previously uploaded to my website at http://rtkexplorer.com/downloads/gps-data/ and the ninth was from my most recent drive around the neighborhood with a u-blox M8T and an antenna on top of the car. I ran solutions for each of the three RTKLIB versions on all nine data sets. For each solution, I converted the data from u-blox binary to rinex using the same code version as I did for the solution, since the different codes will affect this conversion as well as the solution.

I ran the post-processed solutions in “combined” mode, meaning that the solution is run both forward and backward and the results are then combined. Not only does this tend to produce better results, but the results also have higher confidence since RTKLIB compares the forward and backwards solutions, sample by sample, and downgrades any sample where the solutions in both directions are fixed and the results differ by more than four standard deviations. This tends to do a good job of detecting and rejecting any false fixes in the results. However, it is not foolproof. If the solution is fixed in only one direction and float in the other, then there is no additional validation.

I used the same configuration settings for running each of the three RTKLIB versions on each of the data sets with a few exceptions. Versions 2.4.2 and 2.4.3 do not have the “arfilter” feature that automatically holds off new satellites until their phase bias estimates have converged enough to not break the ambiguity resolution so I increased the fixed hold off (arlockcnt) from 0 to 10 for the 2.4.2 and 2.4.3 codes. The outlier detection scheme is also different in the demo5 code from the other two versions, making it necessary to increase the outlier threshold (rejionno). In this case I increased the threshold from 1.0 to 30.0 for versions 2.4.2 and 2.4.3. Lastly, the 2.4.2 code runs very slowly if dynamics is enabled, so I turned dynamics off for this code.

The specific code versions for the experiment were: 2.4.2 p13, 2.4.3 b33 and demo5 b33b2.

I used fix percentage as a metric for the test. This isn’t a perfect metric because it doesn’t take into effect the accuracy of the non-fixed results and it can be affected by false fixes. Overall, though it is probably the best single metric for this kind of test, especially since running the solutions in combined mode should reject many of the false fixes.

The fix percentages for each test are listed below. The data set names correspond to the names of the sample data sets on my website.

TestV 2.4.2V 2.4.3V2.4.3ademo5data set
137.7%93.9%96.3%94.4%union_0705
235.7%9.8%60.6%85.5%niwot2_car
352.4%16.2%89.0%80.2%drone_0414
485.8%51.2%93.8%98.8%m8t_niwot_0606
559.7%21.4%83.7%97.1%swift_m8t_road_0606
663.1%10.0%83.6%99.0%car_1114
754.6%26.6%73.3%94.2%car_0320
839.4%11.2%54.4%98.1%comnav_car
925.0%9.2%53.4%98.1%not uploaded
median52.4%16.2%83.6%97.1%
Experiment results in fix percentage

For the most part, the results match my expectations. Version 2.4.2 has the lowest fix percentage, 2.4.3 is in the middle, and demo5 has the highest. However, I ran into one very significant issue with the 2.4.3 code that I do not fully understand. In the table above, the “V2.4.3” column is the results using version 2.4.3 for both the conversion from raw binary to rinex as well as for the solution. As you can see, the fix percentages were very low for this test for all data sets except the first, significantly lower than even the 2.4.2 results. I did not fully debug this issue but the problem appears to be in the conversion from raw binary to rinex, not in the solution itself.

The V2.4.3a column is the results for running the 2.4.2 code for the raw binary to rinex conversion, and then the 2.4.3 code for the solution. This result is much more within my expectations for the 2.4.3 code. I suspect that the issue with the 2.4.3 rinex conversion is that when it is filtering out low quality observations it is not preserving the cycle-slips. RTKLIB can be very sensitive to unflagged cycle slips.

I am very curious if anyone who is a regular user of the 2.4.3 code can duplicate this result. As you can see from the first data set, it does not always occur, and is much less of a problem if the data does not have a large number of cycle slips, so it would need to be tested on a more challenging data set to see this issue.

Regarding the demo5 results, seven of the nine tests had over 94% fix rate and the median fix percentage was 97.1%. I consider this quite reasonable since all of these were fairly challenging data sets and most of them included at least a small amount of unusable data. The two data sets that did not perform as well (80-86% fix percentage) were both older. One did not include Galileo and the other was from a drone that had one particularly poor quality section of data. However both solutions had well over a 90% fix rate when run in the forward only direction which indicates the fixes in the combined solution were downgraded because of mismatches between the two directions. In one of these cases (test 3), the 2.4.3 solution obtained a higher fix rate than the demo5 code but it only got fixes in the forward direction, not in the backward direction so had no additional validation. Based on some discrete jumps in that solution, I suspect it would have also downgraded the fix percentage if it had achieved fix in the backwards direction.

Looking more closely at the cases where the demo5 solution points were downgraded to float for mismatches, it’s interesting because, at least at first glance, it appears that these were not false fixes, but discrepancies from using different combinations of satellites that were large enough to trigger the four standard deviation threshold. This is a little concerning and worthy of further investigation. Fortunately it only appears to occur when the data quality is fairly poor. However, this does emphasize the importance of insuring the best quality measurements possible, and not over-relying on RTKLIB to reject inaccurate solutions.

As always, I’d like to emphasize that these tests are intended only as one users snapshot of one fairly particular use case of RTKLIB and are not intended to be any kind of comprehensive analysis. Also, it’s important to understand that 99+% of the code in all versions of RTKLIB including the demo5 code are the result of many years of dedicated effort by Tomoji Takasu and his team at Tokyo University of Marine Science and Technology. My only contribution has been to add a few changes on top of this code to make it a little more focused on practical application for specific uses rather than a more generic academic tool.

Lastly, for reference, here’s a partial list of the most important configuration settings I used for this experiment for the demo5 code. The other two codes used the same settings with the exceptions I describe above.

pos1-posmode =kinematic # solution mode
pos1-soltype =combined # solution type (forward, backward, combined)
pos1-frequency =l1 # (l1, l1+l2, l1+l2+l5)
pos1-elmask =15 # min sat elevation to include in solution (deg)
pos1-snrmask_r =off # SNR mask rover (off, on)
pos1-snrmask_b =off # SNR mask base (off, on)
pos1-dynamics =on # add dynamic states to kalman filter (off, on)
pos1-navsys =15 # (1:gps+2:sbas+4:glo+8:gal+16:qzs+32:comp)
pos2-armode =fix-and-hold
pos2-gloarmode =on # Glonass AR mode (off, on, fix-and-hold, autocal)
pos2-bdsarmode =off # Bediou AR mode (off, on)
pos2-aroutcnt =50 # outage count to reset sat ambiguity (samples)
pos2-arminfix =50 # min # of fix samples to enable AR hold (samples)
pos2-rejionno =1.0 # phase bias outlier threshold (m)
pos2-maxage =30 # max age of differential (secs)
pos2-arthres =3.0 # minimum AR ratio for fix (m)
pos2-arthres1 =0.1 # max variance of position states to attempt AR (m)
pos2-varholdamb =0.1 # variance of fix-and-hold tracking feedback (cyc^2)

pos2-arfilter =on # automatic hold off for adding new sats to AR
pos2-arlockcnt =0 # fixed hold off for adding new sats to AR (samples)
pos2-minfixsats =4 # min sats required for fix
pos2-minholdsats =5 # min sats required for AR hold
pos2-mindropsats =10 # min sats required to drop sats from AR
stats-eratio1 =300 # ratio of input stdev of code to phase observations
stats-eratio2 =300 # ratio of input stdev of code to phase observations

Updates to RTKLIB for the u-blox F9P receiver

I recently released a new version of the demo5 RTKLIB code (demo5 b33b2) with some more updates for the u-blox dual-frequency F9P receiver. Its taken a lot longer than I would have hoped, but for the first time, I feel like the demo5 code now has reasonably complete support for this receiver. Many of these improvements should help with other receivers as well, especially other dual-frequency receivers. In this post, I’ll describe some of the recent changes to the code. If you are more interested in a getting-started guide to using RTKLIB with the F9P rather than the latest code changes, then you might prefer this earlier post.

Here are the most important code changes to be aware of. They include changes made in the last couple of demo5 code versions.

  1. Support for F9P constellations/frequencies:
    I believe the latest code now supports all the F9P L1, L2, E1, E5b, B1, and B2 codes for the GPS, Glonass, Galileo, and Beidou constellations, both for u-blox raw binary formats and for the RTCM3 MSM7 messages. Raw and RTCM3 files are correctly converted to rinex format and processed in the position solution for all these codes. However, I did recently notice that there are still a few issues with RTKPLOT correctly plotting all the different codes, particularly the Beidou B2 observations.
  2. Frequency selection:
    Another change in the new code is a slight shuffling of the frequency grouping between constellations. Since RTKLIB memory use and CPU load increases significantly when enabling additional frequencies, I eliminated Galileo E5b processing in RTKLIB as a separate frequency and it is now processed with the other “L2” frequencies. This means to run a dual-frequency solution for all constellations on the F9P, you need to only enable “L1+L2” as the frequency option. Galileo E5a is still part of the “L5” frequencies so if you are using a receiver that outputs E5a instead of E5b you will need to enable “L1+L2+L5” as the frequency option when running an RTKLIB solution. Running dual frequency solutions still take longer than running single frequency solutions but the difference is smaller than it was.
  3. Receiver Configuration:
    A more significant change to the new code is that RTKLIB now much more fully supports configuration of the F9P receiver using a “.cmd” file. In the previous code, some commands still worked on the F9P such as turning on or off receiver messages or changing the observation output rate but other commands, particularly those to enable or disable constellations were broken since the format of these commands changed when going from the M8T to the F9P. In addition, only a small subset of the total commands available for either the u-blox M8T or F9P receivers were ever supported by RTKLIB.

    To make things more complicated, with the F9P, u-blox is also transitioning from the legacy UBX-CFG messages to a new configuration protocol. Although most of the legacy configuration messages still work on the F9P, they are recommending switching to the new format as stated in this quote from the F9P Integration Manual:

    “3.1.6 Legacy configuration interface compatibility There is some backwards-compatibility for the legacy UBX-CFG configuration messages. It is strongly recommended to adopt the new configuration interface, as the legacy configuration messages support will be removed in the future.

    Fortunately the latest demo5 code now fully supports the new configuration interface, thanks to code contributed by Nagarjun Redla. Under the new interface, instead of having different commands, each with its own format and various numbers of input parameters, configuration parameters are set individually using the VALSET command . For example, the legacy CFG-RATE command had 3 input parameters to set time source, measurement period, and navigation rate. Under the new interface each of these parameters has its own key and each is set with a call to the VALSET command with parameter key and value. The two examples below set the measurement rate to 200 msec and enable the Galileo observations.

    !UBX CFG-VALSET 0 1 0 0 CFG-RATE-MEAS 200
    !UBX CFG-VALSET 0 1 0 0 CFG-SIGNAL-GAL_ENA 1

    Currently, only the second of the first four numeric parameters is used and that is set to to the configuration layer to write to. In this case “1” writes to the RAM layer. Use “4” to write to the flash layer or “5” to write to both flash and RAM. “2” is used to write to the BBR (battery-backed up RAM). See section 6 of the F9P Interface Description document for a list of all configuration key values as well as for more details on the VALSET command.

    All of the available key values are supported by RTKLIB, so any receiver configuration parameter that can be modified from u-center can now be modified from a command file in RTKLIB and the results can be saved to either RAM, flash, or both. The legacy commands that RTKLIB previously supported have not been removed so they will all still work as well.

    The one exception I am aware of is that the key values to set the individual frequencies do not seem to work from either u-center or RTKLIB. This is either a bug in the F9P or a misunderstanding on my part. However, in the Generation 9 Configuration View in u-center, the “Advanced Configuration” command is used for all parameters except GNSS configuration which uses the legacy command. This makes me believe it is actually a bug. Since RTKLIB does not support the legacy GNSS configuration command for dual-frequencies, this means that RTKLIB can enable and disable constellations but not individual frequencies within a constellation.
  4. Ambiguity Resolution:
    Although as I demonstrated in an earlier post, the demo5 code was working well for solutions with moving rovers, the results were less consistent for stationary rover solutions. In some examples, especially those with moderate amounts of multipath in the rover observations, the RTKLIB solution was not getting a fix even after several minutes, while the real-time internal solution and even the single frequency RTKLIB solutions were converging much more quickly.

    Most of my experiments focus on moving rovers and I tend to think of them as more challenging than stationary rovers because of the large number of cycle slips and the continuously changing set of satellites available as the rover’s sky view keeps changing and different satellites come in and out of view behind obstructions. However, stationary rovers have their own set of challenges and in particular multipath is a greater challenge in the stationary rover case than it is in the moving rover case.

    This is because, for the stationary rover, the paths from satellites to receiver antenna stay relatively constant and change only slowly as the satellites move across the sky. This means that the errors introduced by the combination of direct and indirect paths (reflections) between satellite and antenna have very long time constants. In the moving rover case, the errors are still present but are changing much more quickly with rover movement, have much shorter time constants, and average out to zero much more quickly.

    The existing partial ambiguity resolution algorithm in the demo5 code turned out to be more sensitive to the long time constant multipath when the number of observations increased with the dual frequency receivers. I had previously added a step to the ambiguity resolution algorithm to exclude a different satellite each epoch whenever the number of satellites was above a defined minimum (Min Drop Sats) to try and detect “bad” satellites. To minimize the increased risk of false fixes while the kalman filter was still converging, I had only done this after first fix. It turns out that with large numbers of observations, it becomes important to extend this test to before the first fix, and this is the change I made to the code.

    False fixes appear to be much less common with the F9P than the M8T, presumably due to the increased number of observations, so the increased vulnerability while the filter is converging is not much of a concern with the F9P. However if using the latest code with the single frequency M8T, you may need to adjust the configuration parameters slightly to avoid increasing the risk of false fixes while the kalman filter is converging. The best way to do this is usually to adjust the maximum position variance threshold (Max Pos Var for AR) to a slightly lower value. This will delay the start of ambiguity resolution attempts until after the filter is better converged. The trade-off is that if set too low it can delay time to first fix so it may require a little experimentation for best results. I might suggest starting at a value of 0.05 meter for the M8T and 0.1 meter for the F9T but optimal values will vary with configuration so adjust as needed. Time to first fix is less important if you are post-processing with a “combined” solution so you can tighten these numbers even more in this case. For post-processing high quality observations with the M8T I usually set this value to 0.004 meters.
  5. Precise ephemeris:
    This last change is not related to the F9P receiver but is worth mentioning anyways. I don’t use precise ephemeris files very often but other people do, especially for PPP solutions. Use of the MGEX files has become much more popular since they include the Galileo and Beidou orbital data as well as GPS and Glonass. These files usually have capitals in their extension unlike the older files. (.SP3 vs .sp3). Due to a bug in the RTKLIB code it was rejecting the “.SP3” files without reporting an error which could be very confusing to the user. The newest code now accepts files with “.SP3” extensions as well as “.sp3” extensions.

I think those are the most important changes but you can always review the demo5 Github repository for more details on code changes or if you want to build the code for linux platforms.

I do my best to test the code before I release it but I don’t have the time or resources to do this properly on all the different variations that RTKLIB can support so please treat new releases as beta, and if you see results that don’t make sense, it’s a good idea to compare results between the newest version of code and an older version that you have confidence in. If you do find degraded results in the new version, please let me know, and if possible, send logs of the observations and config file along with a detailed description of the issue. I rely on users to validate the code and treat any reported issues where a newer version of the demo5 code performs poorly compared to either a previous version or the official 2.4.3 code with the highest priority I can.

One last thing. You may notice that I have added an optional contribution button to the code download page. I do this work to promote low-cost precision GNSS and because I enjoy it, not to get rich and I want to emphasize that contributions are completely optional. However, if you are a regular user of the demo5 code, find value in the software, and would like to share that value, then any contribution is much appreciated.   I have also added a field to the contribute page to describe any feature or bug fix that you would like to see added to the demo5 code and will try to prioritize popular requests when making future code updates.

Building a simple u-blox F9P data logger with a Sparkfun OpenLog board

Based on the number of views, one of my all time most popular posts is one I wrote nearly three years ago describing how to build a GPS data logger using a Raspberry Pi Zero. Although it specifically describes using a Pi Zero to do nothing except configure the receiver and log raw data to an SD card, the platform itself is quite general and can easily be extended to more complete options including running a real-time RTKLIB solution on the Pi. It is a little out of date now but I think it can still be useful.

However, if what you want is just a raw GPS data logger and nothing more, then, while the above choice may still be the lowest cost solution, it is not the simplest option to implement.

In this post I describe another inexpensive alternative using the very popular OpenLog data logger board available from Sparkfun which currently sells for $15.50. In this particular example, I have used it to log data from an Ardusimple u-blox F9P receiver but with a few minor modifications the design should work with many other popular receivers. For reasons described below it does require the board to have both a USB and a UART port available as well as easy access to 3.3 volts.

Here’s a couple of photos of the front and back of the Sparkfun OpenLog board. As you can see, it is only slightly larger than the size of the microSD card it uses to store the data.

Sparkfun OpenLog board, front and back

The chip on the front side of the board is an Arduino processor which comes pre-loaded with code to automatically log all incoming data from the RX/TX lines directly to the microSD card. The source code is open and available on Github, so if you need to modify it you can, but in most cases it should work fine as is.

To connect the OpenLog to the receiver is very simple. First connect VCC and GND on the OpenLog to 3V3_OUT and GND on the Ardusimple receiver. Fortunately, the pin spacing for the two boards is the same so I was able to use a two pin header to connect power and ground and rigidly mount the OpenLog at the same time. You can see the details in the two photos below. I also added a small piece of double sided foam tape between the two boards to strengthen the physical connection between them.

Back of Ardusimple board with OpenLog. The two boards are physically attached using the two pin header shown on right to connect power and ground. The top right cable is connected to the antenna and the bottom right cable is connected to a USB charger for power or a PC to configure the receiver.
Front of Ardusimple board. The GND/3.3V holes used to connect the two boards are visible just below the u-blox chip. The edge of the microSD card is visible just below the power LED

Then connect the RX/TX data lines on the Open Log to TX1/RX1 on the Ardusimple making sure to swap the two so that RX goes to TX1 and TX goes to RX1. On many boards this will be all you need, but the Ardusimple also has a IOREF input which selects the voltage levels of the GPIO pins. In this case we will connect that to 3.3 volts. You can see these three jumpers on the top photo above.

That’s it for the hardware. The next step is to configure the data logger. To do this you will first need to format a microSD card. Then create a file with the name config.txt, cut and paste the text below into this file and save it to the microSD card.

115200,26,3,0,1,1,0
 baud,escape,esc#,mode,verb,echo,ignoreRX

This is what the data logger uses when it powers up for configuration parameters. The only detail that matters in the above text is the first number which should be set to the same baud rate that UART1 on the receiver is configured to. The maximum baud rate that the OpenLog supports is 115200, so that is what I am using. For more information on the other numbers in this file or about the OpenLog board in general, Sparkfun has an excellent tutorial available here.

The last step is to configure the F9P receiver to output the appropriate messages to UART1. This is where things become much simpler by having both UART and USB ports available on the receiver board. To configure the receiver we will use the u-blox u-center app running on a PC and connected to the receiver through the USB port. Once the receiver is configured and the results saved to flash, we can then unplug the USB cable from the computer and plug it into a USB charger to provide power to the receiver and data logger while they are collecting data.

For this tutorial I will assume you have already downloaded the u-center app from u-blox and are somewhat familiar with using it to configure u-blox receivers. If not, there are many tutorials out there including some of my earlier posts that will help you.

One thing to be aware of is that the messages going out of the receiver to the USB port will generally be different from those going out to UART1, so just because you have the right messages coming out the USB port does not mean they will also be coming out of the UART port. Also be aware that if you use the “Messages View” in u-center to enable and disable messages, this method only affects the active port and so you will be enabling them for USB but not for UART1.

To enable and disable the messages for UART1, you can use the CFG-MSGOUT option from the “Generation 9 Configuration View” which is opened from the “View” tab on the main menu in u-center. I find this method nice for listing which messages are enabled and disabled but rather clunky for actually changing their status, so I used the MSG message from the “Configuration View” ( also opened from the “View” tab) to actually enable and disable the messages. I show this in the screenshot below. If you do it this way, make sure you issue a CFG-CFG message when you are done to save everything to flash.

There are many NMEA messages enabled by default on UART1. You will probably want to disable most or all of these to avoid using unnecessary UART bandwidth and microSD file space.

If you already have raw base observations streaming to the receiver and are using the internal RTK engine to calculate position solutions real-time then you will just need to enable the NMEA GLL messages to log position. These will give you the LLH positions in text format.

If in the more likely case that you are logging raw data for later post-processing, then you can enable either the appropriate RTCM3 messages or the UBX-RXM-RAWX messages to get the raw observations. To minimize the load on the logger, I would suggest using RTCM3 messages for the raw observations and not logging the navigation messages. You only need a single set of navigation messages and in most cases it is easier to log those on the base station. It is simplest to enable the same set of RTCM3 messages for both the USB and UART port. If you need to make these different for any reason, be aware that the F9P does not handle the RTCM3 end of epoch flag independently for both ports, so the last message in each epoch must be the same for both ports or you will have problems.

That should be it. At this point, every time you power up the receiver/logger, it should start a new log file on the microSD card and log all incoming messages from the receiver. The log file names will be in the format LOGxxxx.TXT where “xxxx” increments each run. Simply remove the microSD card from the OpenLog and plug it through an adapter into a PC to transfer the log files. Be sure to be careful when removing the microSD card from the OpenLog. The card locks in with a spring and needs to be pushed in to unlock it. Occasionally I have found that the card catches on the spring and needs to be gently coaxed out or you will break the spring as I found out the hard way.

If using RTKCONV to convert the raw data, you will need to specify the data type (UBX or RTCM3) since the file extension will not be correct for auto format detection.

Here’s a plot of some raw observations I collected this way, then converted to Rinex format with RTKCONV, and then plotted with RTKPLOT, using navigation messages collected separately on my base station.

RTKPLOT of observations collected with the OpenLog logger

The data logger performance is limited by a relatively small buffer size and it is possible that you will see missing samples in your data if the logger buffer overflows while writing to the microSD card. However I did not see any missing samples in my data while collecting dual-frequency GPS, Glonass, and Galileo RTCM3 observations at 5 HZ.

This is the simplest method I am aware of to turn an eval board into a relatively fully functional receiver but if anyone has an easier way, please leave a comment. All it needs now is a 3D printable box to protect it and enclose it from the weather to turn this into a general purpose low-cost dual receiver for real world data collection, at least for post-processing.

For another interesting option, check out this post from the Ardusimple website that describes combining an Ardusimple board with a bluetooth module and smartphone for real-time RTK.

A first look at the Broadcom BCM47755 dual-frequency receiver

Over the last few months, readers have sent me several data sets collected from cell phones using the Broadcom BCM47755 dual-frequency GNSS chip. In most cases, however, the quality of the data was low and the number of cycle slips made it difficult to do any meaningful analysis. More recently I was sent some BCM4755 data collected from a Xiaomi Mi8 phone mounted on a tripod with a ground plane underneath that was noticeably better quality than the previous data sets. This data came from Julian who is trying to use the phone for forestry applications and has an open project he is working on here. In this post, I will describe my experiences attempting to use RTKLIB to analyze this data.

This is the first time I have worked with L5 data with RTKLIB so I wasn’t quite sure what to expect. Fortunately, with a few minor updates to the code handling code in RTKLIB which I have included in the most recent b33 version of the demo5 code, the code was able to see and process the L5 observations. Both rover and base observation files were sent to me in rinex format.

According to the rover file header, the rover rinex file was generated by Geo++ RINEX Logger App for Android (Version 2.1.3). It contains L1/E1/B1 observations for GPS, Glonass, Galileo, and Bediou and L5/E5a observations for GPS and Galileo. Like L2C, not all of the GPS satellites support L5 yet. In this case, only two of the six GPS satellites supported L5 data. All four of the Galileo satellites supported E1 and E5a observations but the rover was unable to pick up both frequencies for the one satellite between 10 and 15 degrees elevation. Below is a plot of the rover observations using RTKPLOT. Satellites in gray are below 10 degrees elevation. The rest of the colors indicate the frequencies of the observations according to the color key at the bottom of the plot. There is an error in the Galileo plot colors in which E5a-only observations are being plotted in green rather than gray. Overall though, the data is of reasonably good quality and has only a small number of receiver-reported cycle slips (red ticks).

Rover observations for B47755

The base data was from a CORS station about 20 km away and had matching signals for all the rover observations plus many more. Here is a plot of the base observations. This data is very clean.

For my initial attempt to run an RTKLIB solution on this data, I used the same config file I use for u-blox L1-only solutions except I changed the frequency mode from “l1” to “l1+l2+l5” Even though, there is no L2 data in these observations, RTKLIB does not allow you to individually select frequencies, just the number of frequencies, so valid choices are “l1”, “l1+l2”, and “l1+l2+l5”.

This run did not go well and digging into the trace file I found that RTKLIB was detecting many false cycle slips. The code attempts to detect cycle slips using the geometry-free linear combination of the L1 and L5 phase measurements. Either the threshold ( pos2-slipthres) is too tight for this data, or there is something wrong with this check. For the time being, I increased the slip threshold from the default of 5 cm to 50 cm and this eliminated the false slip detections.

Even with this change however, the solution was still very poor. Digging back into the trace debug file, I found more problems with cycle slips. This time I was finding that real cycle slips were not being flagged by the rover receiver in the rinex data. These unreported cycle slips were introducing large errors into the bias states in the kalman filter and preventing convergence. RTKLIB has always had trouble dealing with unflagged cycle slips. The u-blox receivers are very good at consistently flagging cycle slips which is why RTKLIB tends to work better with u-blox receivers than many other receiver types.

RTKLIB has some code to detect and reject outliers, but this code has never worked very well and I have generally recommended setting the outlier threshold (pos2-rejionno) to 1000 meters to effectively disable this feature.

For the BCM47755 receiver however, it was clear that this was not going to work. So I made some changes to the RTKLIB code to more fully ignore the outliers, and to reset the phase bias estimates when an outlier was detected. I also changed the way this threshold is interpreted for phase and code observations. Previously the threshold was used without adjustment for both phase and code observations. Since the code errors are much larger than the phase errors, this meant that the limit had to be set large enough so as to catch code outliers only. For the b33 demo5 code I changed this so that the unadjusted threshold is still used for the phase observations but the threshold is multiplied by the error ratio between phase and code observations (pos2-eratio1) before being used with code observations. This means that it can be set much lower and now becomes useful for detecting cycle slips instead of just code errors.

I have actually been using this fix for custom versions of RTKLIB for a while and usually get good results setting this to roughly one GPS L1 cycle or 20 centimeters. In this case though the rover data appears to be too noisy for this and I had to set the threshold to 50 centimeters (pos2-rejionno=0.50) to avoid triggering an excessive number of outliers.

With this change, the solution was much better and provided a solid fix after about four minutes with a “forward” solution as shown in the plot below. Even though the rover was static, I ran the solution as kinematic to get a better indication of the unfiltered errors. I also verified that this solution is using all the available measurements in the rinex file.

With a “combined” solution, the result was 100% fix.

This appears to be a fairly promising start for the BCM47755. It is still not nearly as solid as a u-blox receiver but some of this can be attributed to the fact that this data was collected with the internal phone antenna which is likely to be fairly low quality.

Unfortunately, other data sets from the same experimental setup did not generate solutions as solid as this. In particular the Galileo observations sometimes were not consistent with observations from the other constellations and prevented the kalman filter from converging.

Overall, my impression is that using BCM47755 and RTKLIB together for PPK solutions is still fairly immature and not ready for any real applications but hopefully this will change with time.

I am very interested in anyone else’s experiences with the BCM47755 for RTK or PPK solutions, particularly in combination with RTKLIB and hoping anyone with experience with this chip will add a comment below.