A look at the recently released RTKLIB b34 code

In January 2021, after a fairly long gap of 17 months without code updates, Tomoji Takasu released a b34 update to the 2.4.3 RTKLIB code. He and his team apparently were very busy over that time as, according to Github, the new release has 1064 changed files, 279,767 additions and 312,550 deletions! While some of these numbers come from reorganizing the file structure, this still constitutes a major rewrite of the codebase and a significant challenge to merging the updates into the demo5 version of RTKLIB.

After a fair bit of effort I have completed a first pass at this merge. Given the magnitude of the changes, I have decided to keep this update on a separate branch in the demo5 repository until it is better tested and more stable. It is now on the demo5_b34_dev branch and the beta executables, along with the more stable b33f version, are available here. All of the Windows GUI and CUI apps appear to run as well as all the linux CUI apps. I have done a very limited amount of testing and am not aware of any major issues in any of the apps at the moment but expect that with more testing, issues will be found. Unfortunately the linux GUI Qt apps have not been updated and given the amount of work involved to do this they will most likely be dropped from both the 2.4.3 and the demo5 code.

At this point I am looking for feedback from regular (or new) users of the demo5 version of RTKLIB. In particular, I would like to focus on finding and fixing features or capabilities that are functional in either the demo5 b33 code or the 2.4.3 b34 code but that are not functional in the demo5 b34 code. Ideally, if you find an issue of this sort, if you can send me an email with your data set, config file, and results with both the good code and the bad code, and a detailed description of the problems, it will make it easier for me to track them down. I will also be monitoring issues reported to the demo5 Github repository, so you can use that mechanism as well, it may just be harder to share data that way.

One thing I should mention is that at this point, the Swiftnav, Comnav, and Tersus receivers are not supported by the demo5 b34 code since they are not supported in the 2.4.3 code but I hope to eventually bring these receivers back into the demo5 code. In the meantime you can still convert files from these receivers to RINEX using the b33 code and run post-processed solutions with the b34 code.

In my limited testing I did not find significant differences in the results between the b33 and b34 code but I believe the emphasis of the changes was on basic structural improvements as well as improvements for the newer constellations and signals which for the most part were not included in my testing. I ran a data set collected from a u-blox F9P moving rover with a PPK solution using a CORS reference as base as well as a kinematic PPP solution. The PPK solution was virtually identical between the two codes but the b34 code did run about 30% faster which is a nice improvement. The PPP results were similar but the b34 results were no better and maybe a little worse than the b33 results so there may still be some room for improvement there. I do think that many of the structural changes will be valuable in the long term even if they do not have an immediate payoff.

There are also some new features in the code that I am looking forward to exploring after I’m more comfortable that the basic functionality is there.

So give it a try and let me know what you find!

I will try to update the beta source code and executables fairly frequently as I make fixes and will eventually move them to the main branch of Github but will probably keep the b33 code around on a separate branch for the foreseeable future.

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 manually specified the base position in the config file options in WGS84 coordinates adjusted for the date of the data set.

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 F9P real-time standard precision 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 rapid precise ephemeris/clock files, Bottom solution used final precise ephemeris/clock files.

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 kinematic PPP solution

The solution is excellent while the car is stationary but not much better than the real-time F9P solution when the car is moving, so this is also probably not a useful solution for a moving rover. 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.

[Updated 1/16/21 to correct for an error in the original translation of the CORS station coordinates to WGS84 coordinates for the date of the data set]

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.

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.

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.

RTKLIB documentation tips

The RTKLIB software package has an enormous amount of capability and flexibility. Between all of the different applications, the configuration parameters and input options, it is very powerful, but it can also be a little difficult to navigate at times.  I get a lot of questions on how to do various tasks in RTKLIB so I thought it would be worth going over the available documentation as well as mentioning a couple of tips and tricks for finding information on some of the less documented features.

The first place to look is the official RTKLIB manual .  It is well written and fairly thorough, at least in many parts.  If you are using  RTKLIB and have not been through the manual recently (or have never read it), it is well worth going back and taking another look.  Almost every time I go back to it, I find some detail I have missed in the past, not because it is difficult to find, but just because there is so much information in there.  Especially when using the GUI apps there is a tendency to think the manual is not necessary since everything should be intuitive, but in this case there are many features that are not so obvious.  For example, at least to me, it is not overly intuitive that the tiny little unlabeled square (the RTK Monitor Window) above the Start/Stop button in the image below is one of the most important buttons in RTKNAVI and will give you access to up to 35 different windows of useful information.

navi1

Here is an image of  a few of the various monitor windows I frequently find useful.

navi3

Click on a few more of the small inconspicuous triangles, squares and rectangles on the RTKNAVI, and you can change the solution coordinates to pitch/yaw, open up sky views for the base and rover, open up a baseline compass heading plot, and many other things.  If you haven’t read the manual, it’s easy to miss some of these options.

navi2

Unfortunately, although the manual has lots of useful information, both for the command line and GUI apps, it has not been updated in six years, so some of the newer features are not included.

This is especially true for the linux command line apps like STR2STR and RTKRCV.  One tip for getting more up to date information for any  of the command line apps is to use the embedded help messages, since, for the most part, these have been kept up to date.  For example,  let’s first look in the manual at the stream options and command line options for the STR2STR app.  We find that there are 6 stream options and 13 command line options listed.

str2str_1

str2str_1a

Now let’s bring up the embedded help message in STR2STR by typing “str2str -h” and we get the following:

str2str_2

str2str_2a

Notice that the number of stream options has increased from 6 to 8 and the number of command line options from 13 to 24.  This is a significant number of additional options to work with!

Another thing to be aware of  is that all of the RTKLIB apps are using a common core code, and for the most part it is only the user interfaces that are different.  This means that if a feature is available in one app, say for example RTKNAVI, then it is very likely also available in another app, say RTKRCV.  It may just be harder to find information on how to enable it there.  However, if you know what you are looking for, and you know that it’s most likely there somewhere, then this usually makes it easier to find.

Another resource that can be helpful is this post I wrote a while back which includes description of most of the additional features I have added to the demo5 version of RTKLIB to optimize it for low cost receivers, as well as additional information on all of the previously existing config parameters that I typically find useful to modify.  This post is not guaranteed to be fully up to date but I do try and update it with new information as I add new features to the demo5 code.

The above resources will be enough to answer many questions but sometimes they will not be enough and you will need to dig a little deeper.  The next step is to look at the source code which is available on Github, both for the official and the demo5 versions of RTKLIB.  The first place to look is the “options.c” file as it lists all of the supported input configuration parameters as well as their valid input values in a format that is easy to read.  Here is the beginning of the tables of valid option values and parameters.

options

Going through these tables, I found about a dozen parameters in the official code that are not mentioned in the manual and a few more in the demo5 code that are not mentioned in my documentation.

If you still need more information, the next level is to look at the top-level source file for each app.  For example in the “rnx2rtkp.c” file you will find the help menu listed at the top and then in the main() function lower down you will see where the options are actually parsed.  This should answer most questions.  You can go even deeper into the code if you have to, but I will warn you, things do get a bit more challenging once you get to the next layer.

The last tip that I can offer is that I have written about a number of RTKLIB features in my posts over the last few years, so using the search window at the top right corner of this page will occasionally bring up some useful information.

Anyway, hope at least some of this is helpful to those of you trying to learn a little bit more about some of RTKLIB’s less well documented features.

A first look at the u-blox ZED-F9T dual frequency receiver

Back in November last year, I wrote a post on my first experiments with a dual frequency u-blox F9P  based receiver.  At the time it was quite difficult for those without good connections to u-blox to get a hold of the F9P and even now, nearly three months later, it still is not readily available.  Ardusimple, the lowest price provider of F9P receivers still has all their receivers on back order till next month and low cost dual frequency antennas are even harder to get.  Hopefully all that will change fairly soon though.

Meanwhile, thanks to “clive1” and “cynfab” from the u-blox forum, I have been lucky enough to have been given a prototype receiver based on the dual frequency u-blox F9T, the next product from u-blox in the Generation 9 series.  Like the previous generation M8T, this is intended for timing uses and does not include an internal RTK engine.  Otherwise I believe the F9T hardware is nearly identical to the F9P.  In theory it should be less expensive than the F9P, just as the M8T is less expensive than the M8P but meaningful pricing is not yet available.

In many of my posts, I have focused on post-processing short baseline data sets using a local base station and identical receivers for base and rover.  For this particular  combination, I have shown that the differences between a single frequency solution and a dual frequency solution are typically fairly small.  This assumes that the single frequency solution includes Galileo and possibly SBAS while the dual-frequency solution includes only GPS and Glonass.  This makes the total number of observations fairly similar between the two cases.   At least until very recently this has been a reasonable assumption given that most existing CORS or other reference base stations and reasonably priced dual frequency receivers offered only GPS and Glonass.  It’s also true that time to first fix is longer in the single frequency solutions but post-processing with a combined solution generally eliminates the need for a fast fix.

However there are many other cases where there are definite advantages to using a dual frequency solution.  In particular the most important advantages occur for:

  • Longer baselines where linear combinations of L1 and L2 can cancel ionospheric errors
  • Use of an existing CORS or other reference base station which typically has only GPS and Glonass and hence is not an ideal match-up with a single frequency receiver using additional constellations
  • Real-time solutions where time to first fix is more critical
  • PPP (Precise Point Positioning) solutions for the same reasons as the long baseline cases.

So for my initial experiments with the F9T I focused on including some of these conditions.  In particular I ran two experiments, the first a real-time RTK solution with an existing UNAVCO reference base (P041) located 17 km away.  For the second experiment I compared an online PPP solution from the Canadian Spatial Reference System (CSRS) with an RTKLIB SSR based PPP solution.

For the first experiment, I connected the F9T receiver to the dual frequency antenna on my roof and ran a quick five minute RTKLIB real-time solution against the UNAVCO base station using the demo5 b31 RTKLIB code.  Other than changing the frequency mode from L1 to L1+L2 I used the exact same configuration file I normally use for the u-blox M8T single frequency receiver.  Even though the rover was stationary in this case, I ran the solution as kinematic for better visibility to any variation in the solution.  Here’s the result.

f9t_1

Overall the solution looked excellent.  First fix occurred within a few seconds, fix rate was 100% after first fix, horizontal variation was  roughly +/-0.5 cm and vertical variation was roughly +/-1 cm.

The solution residuals, both pseudorange and carrier-phase also looked very clean.

f9t_3

I only made a brief look at the raw observations but did not see anything unusual there either.  At only five minutes of data, it is not much more than a quick sanity check, but so far, so good.

For the second experiment I collected four hours of raw observations, again with the F9T receiver and my rooftop antenna, a ComNav AT330.  I then submitted this data to CSRS for their online PPP solution as well as running an RTKLIB SSR solution as I described in this post.  Below are the results for both solutions.  The plots are all relative to my best estimate of the location of the rooftop antenna based on previous PPP solutions with Swift and ComNav receivers as well as RTK solutions from nearby CORS stations.  The left plots shows the first hour of solution with a +/-0.25 meter vertical scale.  The right plot shows the second through fourth hours with a +/-0.06 meter vertical scale.

f9t_2

Both solutions get to below 6 cm of error in each axis after 1 hour and below 3 cm of error after four hours.  The CSRS solution gets down to almost zero error in all three axes after four hours but I don’t believe my reference is this accurate so I think this was partially luck.  The reported accuracies (95%) for the CSRS solution were 1 cm, 4 cm, and 5 cm for latitude, longitude, and height respectively.  My previous experience running RTKLIB SSR PPP solutions with other low cost dual frequency receivers is that after running many solutions, they generally all fall within +/-6 cm accuracies in all axes after four hours.  Both solutions include only GPS and Glonass observations because both the SSR correction stream I used from the CLK93 source, and the CSRS online PPP algorithm use only GPS and Glonass.

Being able to run accurate PPP static solutions can be a big advantage since it can make it much simpler to precisely locate a base station for RTK solutions with a dynamic rover, especially in more remote areas where there may not be any nearby CORS or other reference stations to run an RTK solution against.

As always, this post is intended to be just a quick snapshot and not an extended analysis of any type, but so far I have been very impressed with both the F9P and F9T and with their compatibility with RTKLIB.