Comparing the u-blox F9P to the Comnav K803 receiver

In my last post I compared the internal u-blox F9P RTK solution to an RTKLIB demo5 RTK solution for a couple of cases, one with easy and one with moderately challenging conditions. In this post I will extend that comparison to some more difficult conditions, specifically under more dense tree foliage. To make things more interesting I will add a pair of ComNav K803 eval boards to the experiment.

The ComNav K803 is a relatively new module from Comnav that supports triple frequencies, L1, L2 , and L5. It is more expensive than the u-blox F9P at about $1000, but should have higher performance. ComNav generously gave me two of these boards for evaluation purposes.

As far as user interface goes, they are very similar to the older K501G or K708 ComNav boards and I was able to configure the K803 boards using RTKLIB command files very similar to what I previously described for the K708 in this post.

Here is a photo of the ComNav board on the left and the Ardusimple F9P board on the right. The ComNav board is inside a plastic case which is a nice feature. It is supposed to work with just the USB port for power and communication but I did not find this to be reliable and had to fall back to using the RS232 port with USB adapter for communication which is why the photo shows two cables for the K803. ComNav is aware of the issue and hopefully they will sort this out relatively soon.

In my previous experiment, I used a nearby CORS station for the base which limited the solution to GPS L1/L2, GLONASS L1/L2, and Galileo L1 and also extended the baseline to several kilometers . To take full advantage of both receivers available constellations and bands in this experiment, and to minimize the baseline, I used local base receivers for this experiment.

I used two Harxon geodetic GPS-500 antennas, one on the roof as base and one for the rover which was located on a tripod in an area underneath some moderate tree foliage. Both antennas were fed to an antenna splitter and then to an F9P receiver and a ComNav receiver. I tested three locations, each one further under the trees and so more challenging than the previous one. To avoid too much complexity in the experiment, I did not run a real-time RTKLIB solution but logged the F9P and ComNav raw data for post-processing.

Like in the previous experiment, I disconnected the antenna and then reconnected it multiple times to measure the time to first fix (TTFF) for both receivers. The plot below shows only the fixed epoch results for the first two locations. The F9P results are in green, and the ComNav in blue. The constant offset between the two is not real, I accidently configured the ComNav receiver to use a self-surveyed approximate position for the base location instead of an independently determined precise position.

Fix epochs for u-blox F9P (green) and ComNav K308 (blue) for location 1 and 2

In the first and least challenging rover location, both receivers performed well. The average ComNav TTFF was slightly faster, but both were below 20 seconds.

In the second location, both receivers were were still providing usable results but the ComNav receiver started to become noticeably better than the F9P. Average TTFF was 54 seconds for the F9P and 12 seconds for the ComNav receiver. In addition, the F9P had a couple of short false fixes and intermittent float after first fix that were not present in the ComNav data.

Here is the data for the third and most challenging location. Again, the F9P results are green and the ComNav results are blue.

Fix epochs for u-blox F9P (green) and ComNav K308 (blue) for location 3

In this case the differences were quite significant. The ComNav reliably found the same location on each re-acquire but more than 50% of the F9P fix epochs were false fixes, sometimes in error by several meters.

I also ran the RTKLIB demo5 solution with the same config settings as in the previous post. The results are below for the first two locations. The RTKLIB fixed epochs are in green and the ComNav fixed epochs are in blue. The performance at the first (least challenging) location was similar between RTKLIB and the F9P internal solution but the RTKLIB solution struggled with the second location and for the third location did not find any fixes except for a single short false fix. At least in these conditions it looks like there is still some room for improvement in the RTKLIB solution. However, it is also true that it did not get as much false fixes as the F9P solution which is important as well.

Fix epochs for u-blox F9P RTKLIB solution(green) and ComNav K308 (blue) for location 1 and 2

As in my other experiments, this is just a quick comparison of relative performance and is not intended to be any type of in-depth analysis.

To summarize the results, The u-blox F9P is a very capable dual-frequency receiver for easy to moderately challenging conditions, but for the most challenging conditions you will still get better performance from a more expensive receiver.

Comparing RTK solutions for the u-blox F9P

The u-blox F9P internal RTK solution engine is very good and I have been recommending using it rather than RTKLIB for real-time RTK solutions, saving RTKLIB for F9P post-processing solutions. However, in this post, I will take another look at that recommendation.

Tomoji Takasu, the creator of RTKLIB, recently published some data also confirming the high quality of the F9P RTK solutions. He ran comparisons of the F9P against several other receivers of various price and quality. You can see his results in the 2021/07/01 entry of his online diary. His conclusion (at least the Google translation of the original Japanese) is that “Considering the price, there is no reason to choose other than F9P at this time.”

He did his comparisons by splitting an antenna output, feeding it to the two receivers being compared, then cycled the antenna output on and off. He fed the output of the receivers to RTKPLOT to produce plots like the example shown below from his diary which show acquire times and consistency of the fix location. In this case the upper plot is a Bynav C1-8S receiver and the lower plot is a u-blox F9P. The yellow intervals in the plot indicate float status and show the acquire times for each signal cycle. The green intervals indicate fix status and show the consistency of the fix location. Clearly the F9P outperformed the C1-8S in this example.

Receiver comparison data from Tomoji Takasu online diary

Inspired by his comparisons and the recent changes I incorporated into the demo5 b34c code, particularly the variable AR ratio threshold option described in my previous post, I decided it was time to do a similar head to head comparison between the internal F9P RTK solution and the latest demo5 RTKNAVI RTK solution.

To setup this experiment I used u-center to configure an Ardusimple F9P receiver to output both NMEA position messages (GGA) and raw observation messages (RAWX, SFRBX) to the USB port. I then setup STRSVR and RTKNAVI on my laptop as shown in the image below.

RTKLIB configuration for the comparison

The first instance of STRSVR is configured to stream NTRIP data from a nearby UNAVCO reference base (P041) to both the u-blox receiver and a local TCP/IP port. The output of the u-blox receiver is streamed to a second local TCP/IP port by selecting the “Output Received Stream to TCP Port” in the “Serial Options” window. The second instance of STRSVR then streams the second TCP/IP port containing the output of the u-blox receiver to a file for logging the internal F9P solution. This file will contain the raw observations in binary format as well as the receiver solution but RTKPLOT will ignore the binary data and plot just the NMEA message data.

The demo5_b34c version of RTKNAVI is configured to use the two TCP/IP ports configured above, one from the receiver, and one from the UNAVCO base, as inputs for rover and base. I also configured two instances of RTKPLOT so that I could see the real-time status of both solutions in addition to saving the results to files.

To setup the RTKNAVI config file for this experiment, I started with the PPK config file from the U-blox F9P kinematic PPP data set 12/24/20 data set and made a few changes to make it more suitable for a real-time solution. Time to first fix is not as important in post-processed solutions since they are usually run in both directions, so the config parameters in that file are set to minimize the chance of a false fix at the expense of relatively long acquire times. To shift this balance towards faster acquire times, I increased the maximum filter variance allowed for fix (Max Pos Var for AR / pos2-arthres1) from 0.1 to 1.0 and decreased the number of fix samples required for hold (Min Fix / pos2-arminfix) from 20 to 10. I also changed the solution type from combined to forward since combined mode is not possible for real-time solutions.

Most importantly, I enabled the new variable AR ratio threshold described in my previous post by setting the AR ratio threshold min and max (pos2-arthresmin, pos2-arthresmax) to 1.5 and 10. I left the nominal AR ratio threshold at 3.0. This means that instead of using a fixed AR ratio threshold of 3.0 for all epochs, the AR ratio threshold used in each epoch is selected from the blue curve below, with the limitation that it can not drop below the specified minimum of 1.5

AR ratio threshold curves

This will allow the AR ratio to drop well below 3.0 when there are many available satellites and the model strength is very high, while still protecting against false fixes by increasing the AR ratio when the number of satellites and the model strength are low.

To run the experiment, I connected the antenna to the receiver, waited until both solutions had acquired a fix, then disconnected the antenna for 10-20 seconds to force the receiver to lose lock to all the satellites, then reconnected the antenna again. I repeated this sequence for about 15 cycles.

I ran this experiment twice. The first time, I connected the receiver to a Harxon geodetic GPS-500 antenna mounted on my roof with a reasonably good view of the sky. The second time, I connected the receiver to a smaller, less expensive u-blox ANN-MB antenna with ground plane on a tripod in my backyard in a moderately challenging environment with the sky view partially blocked by the house and nearby trees. In both cases, the base station (P041) was 17 kms away and included Galileo E1 signals in addition to GPS and GLONASS.

Below is the result from the first experiment. As previously, yellow is float, and green is fix. The internal F9Psolution is on the top and the RTKNAVI solution is below. The blue in the F9P plot indicates a dead reckoning solution which only occurs when the antenna is disconnected, so can be ignored.. To reduce clutter in the plots I am showing only the U/D axis. In this case I know the precise location of my roof antenna so the y-axis values are absolute errors.

F9P vs RTKNAVI, GPS-500 antenna on roof

Here is a zoomed in version of the same plot. Both solutions acquire first fix fairly quickly in most cases. The absolute errors during fix are small for both solutions but do appear to be a little larger in the internal F9P solution. None of the errors are large enough to be considered false fixes.

F9P vs RTKNAVI, GPS-500 antenna on roof (zoomed in)

Below is the same plot for the second experiment where the smaller ANN-MB antenna is located in a more challenging environment. Again, both solutions tend to acquire first fix quite quickly, and again the internal F9P errors appear to be larger than the RTKNAVI errors. In this case I don’t know the precise location of the antenna so the errors are relative.

F9P vs RTKNAVI, ANN-MB antenna in backyard (zoomed in)

Here is a summary of the acquire times for both solutions measured from the above plots. The plots don’t show precisely when the antenna was reconnected so I measured both acquire times starting from the first solution output sample after the disconnect gap, regardless of which solution it came from. The first column in the table is the number of acquisitions measured, the other columns show the minimum, maximum, mean, and standard deviations of the time to first fix in seconds.

GPS-500 antenna on roof
ANN-MB antenna in backyard
Time to first fix comparison between F9P and RTKNAVI

Both solutions performed quite well in both experiments. The RTKNAVI solution outperformed the F9P internal solution in both cases, but given the very small amount of data and testing conditions, I would be reluctant to declare RTKNAVI the winner. I does suggest though, that the RTKLIB solution has closed the gap and should be considered at least roughly equal in performance to the internal RTK engine for real-time solutions.

In many cases it will still be simpler for real-time solutions to use the internal solution than RTKNAVI since it requires less configuration. There are cases, however, where it makes more sense to use an external solution even in real-time. For example, one user recently shared data with me where the rover is measuring real-time buoy activity. In order to avoid needing a bi-directional radio link, he sends the rover raw observations back to shore and calculates the solution there. Otherwise he would need to send the raw base observations to the buoy, and then send the resulting solution back to shore.

If anyone else runs a similar experiment comparing RTKNAVI to the internal F9P solution and wants to share their results, please leave a comment below.

A variable ambiguity resolution threshold for RTKLIB

The ambiguity resolution ratio (AR ratio) threshold is used by RTKLIB to determine if there is enough confidence in an integer ambiguity resolution solution to declare it as a fixed solution. If not, the float solution is used instead. More specifically, the AR ratio is the ratio of the sum of the squared residuals of the second-best fixed solution to the sum of the squared residuals of the best fixed solution.

The higher this ratio is, the higher the confidence that the best solution is the correct solution. However there is no simple way to determine what the optimal value for this threshold should be. In practice, it is usually set based on empirical results, and typical values vary from 1.5 to 3.0. RTKLIB uses 3.0 as a default value for this threshold which is a fairly conservative value, especially when large numbers of satellites are available for ambiguity resolution. If too low a value is chosen, false fixes will be common, but too high a value will lead to unnecessarily low fix rates. In general, if the underlying GNSS model strength is higher, a lower threshold can be used. The GNSS model strength will be affected by many parameters including the number of tracked satellites, the measurement precision, the number of measurement epochs , the number of used frequencies, and many others. If the model strength is high, then this can be reduced, if the model strength is very low, the threshold should be increased.

Teunissen and Verhagen make a compelling argument in this paper (and several others) that the AR ratio threshold should be adjusted not only for different solution environments but also on a epoch by epoch basis within a single solution. They argue that as the model strength varies, the threshold should be adjusted such that there is a fixed possibility of an incorrect solution. It turns out that the exact model strength is very difficult to calculate and that the relationship between model strength and the optimal threshold is also complicated. They propose a solution in which model strength is estimated from two parameters; (1) the number of satellites, and (2) an estimate of the ambiguity resolution failure rate based on the covariance matrix of the float ambiguities. These two values are then applied to a set of lookup tables, along with a desired failure rate, to calculate an optimal threshold value. The lookup tables are generated using Monte Carlo simulations of various models and model strengths. They named this technique the fixed-failure rate ratio test (FFRT).

They provide examples of Matlab and python code to demonstrate their algorithm which can be downloaded from here along with some documentation to explain how it works. For anyone who wants to understand their methods more completely, this is a very useful tool.

One of the things they predicted in their paper from a number of years ago, is that as the number of available satellites increases with the newer constellations, the need to move away from a fixed threshold will become greater. I believe with the introduction of Galileo and Beidou, and the newer low cost dual-frequency receivers, we have reached that point where a fixed threshold is no longer adequate. The problem is that especially for a dynamic rover scenario, the number of available satellite pairs can vary from just a handful in obstructed sky conditions to several dozen in open skies. This results in a huge variation of model strength in a single solution. If a fixed threshold is chosen to be adequate for the obstructed sky conditions, it is much too conservative for the open sky conditions.

So, the next step was to try and add some of this capability to the demo5 version of RTKLIB. I decided early on to reduce the complexity of the solution by making the goal simply an improvement over the existing algorithm rather than trying to achieve an optimal solution. The first step in this direction was to replace the lookup tables with a set of cascaded polynomial coefficients to approximate the contents of the tables with a much smaller set of numbers. I then rewrote the FFRT pieces of the above mentioned python code into the RTKLIB C code. The example code also allowed the user to choose one of two targeted failure rates. In order to further simplify the solution, I eliminated the higher fixed failure rate target option and only supported the 0.001 failure rate target.

So far, so good. I was able to get very similar results between the python example code and the RTKLIB code. Unfortunately I was not completely happy with these results. The threshold adjustments based on the number of satellites seemed to behave well but the adjustments based on the covariance matrix of the float ambiguities was often too optimistic and would cause the threshold to be adjusted too low. I suspect the reason why is very similar to the reason why the accuracy estimates of the kalman filter also tend to be very optimistic. The math is based on the assumptions that the input measurements all have zero mean errors and are uncorrelated with each other and in time, none of which is true in the real world. To some extent, the lookup tables should compensate for this, but that assumes the error characteristics in their simulations matched the actual errors which is difficult to do for a general case.

My conclusion was that the ambiguity covariance matrix is not sufficient in many cases to accurately estimate the remaining model strength after the number of satellites component is accounted for. Fortunately however, I believe that the largest variation in model strength within a solution is generally going to be caused by the variation in number of available satellites, and not from the other components. I ended up with, at least as an initial implementation, one where the threshold is adjusted for the number of satellites but the remaining components of the model strength are still specified by the user. To keep the user interface more compatible with the existing RTKLIB code, I used the existing convention that model strength is adjusted by adjusting the AR ratio threshold directly. However, now it is the nominal AR ratio threshold that is adjusted rather than a fixed threshold. The nominal AR ratio threshold is defined as the threshold for the case where there are eight satellite pairs used for ambiguity resolution. If the number of satellite pairs in the ambiguity set increases, the threshold is reduced, and if it decreases, the threshold is increased.

The plot below attempts to show how this method correlates to the FFRT algorithm given a fixed target failure rate of 0.1%. I won’t bother to describe what Pf_ILS is in detail here, it is enough to know that it is the previously mentioned estimate of model strength calculated in FFRT from the float ambiguity covariance matrix.

The three trajectories of dots in the plot below are calculated in the FFRT python code using the lookup tables for specified model strengths (Pf_ILS) of 0.005, 0.035, and 0.85. The x-axis is the number of satellites used in ambiguity resolution and the y-axis is the corresponding AR ratio threshold. The three colored lines are the equivalent values calculated in RTKLIB for the nominal AR ratio thresholds that correspond to those model strengths.

Comparison of AR ratio threshold for FFRT and RTKLIB

One detail worth mentioning is that the FFRT method uses the number of satellites as input, while the RTKLIB implementation uses the number of satellite pairs as input. They are aligned in the computations for the one frequency, one constellation case. Hence you can see that the nominal AR ratio thresholds in the legend appear when the number of satellites on the x-axis is nine (one reference satellite and eight differencing satellites). As the number of reference satellites increases with multiple constellations and frequencies the difference between number of satellites and number of satellite pairs will increase.

I’ve introduced these code changes into the demo5 b34c code which is available from the rtklibexplorer Github page or the download section of

I’ve added two new config parameters to RTKLIB in an attempt to make this feature as flexible as possible. The two new features, minimum AR ratio, and maximum AR ratio are highlighted in the image below from RTKPOST. These allow the new feature to be completely disabled (the default), partially enabled, or fully enabled. The AR ratio threshold for each epoch is first calculated as shown in the plot above based on the nominal AR ratio threshold and the number of satellite pairs. It is then clipped to the minimum or maximum values if it exceeds either one.

New input options for variable AR ratio threshold

The default values are 3.0 for minimum, maximum, and nominal. This will force the ratio to be always equal to 3.0 and is equivalent to the previous single default value of 3.0. To completely enable the feature set the minimum value to something less than or equal to 1.0 and the maximum value to 10.0 or greater. This means the calculated threshold will never be clipped. In the example above (min=1.5,nom=3,max=10), I have partially enabled the feature. The calculated value from the blue line in the above plot will be used as long as it is greater than or equal to 1.5. If the number of satellite pairs is greater than 25, the calculated threshold will drop below 1.5 but the final value will be set to 1.5 since it will be clipped by the minimum threshold.

These changes make the largest difference when running real-time solutions with the u-blox F9P or other dual frequency receivers with multiple constellations since the number of satellite pairs used in ambiguity resolution can be quite large and time to first fix is more critical. In this case it can significantly reduce time to first fix for the nominal case while still protecting against false fixes if only a few satellites are available.

Be aware however that a variable threshold will not always improve fix rate and in some cases will lower it. Any time the number of available satellite pairs is below eight, the AR ratio threshold will increase. This will reduce the number of false fixes but will also reduce the fix rate. Overall, though, it should give a more constant failure rate over the full range of satellite counts.

In my next post, I will compare the internal F9P real-time solution with the RTKNAVI real-time solution using this feature.

Sub-meter GNSS accuracy with standard precision solutions?

Being able to get centimeter level errors with RTK or PPK solutions is a very powerful technique, but it does involve a certain amount of complexity and lack of robustness in more challenging conditions. In many cases, users don’t need quite this much accuracy and are looking for a simpler, more robust solution. In a recent post, I demonstrated that a u-blox F9P receiver can achieve sub-meter accuracy with it’s standard precision solution for static measurements and even better accuracy for dynamic measurements. This is a good choice for users who are primarily interested in a RTK/PPK solution but would like the standard precision solution as a backup. It is a relatively expensive solution however for those looking for just a standard precision solution. The Allystar 1201 dual-frequency GNSS module recently caught my eye as a possible answer for a lower cost option. It advertises sub-meter CEP standard precision solution and is available for only $35 on an EVK board or $13.50 for just the bare module in quantities of 10.

It does not have an internal RTK engine or provide access to the raw observations like the u-blox F9P does. This means that it can not be used for anything other than standard precision solutions, but if that is not a requirement, then this could be quite an attractive alternative to the F9P.

Here are the GNSS signals received and the accuracy spec for the Tau1201

Specs from Allystar 1201 datasheet

For comparison, here is the equivalent specs for the u-blox F9P

Specs from u-blox F9P datasheet

As you can see, the Tau1201 claims <1 meter CEP while the F9P claims 1.0 meter CEP. CEP stands for Circular Error Probability and is defined as the radius of a circle centered on the true value that contains 50% of the actual GNSS measurements. In other words, one half of the measurements should have an accuracy better than or equal to the CEP. Real-world performance will vary depending on atmospheric conditions, GNSS antenna, multipath conditions, satellite visibility and geometry. Neither spec lists specific test conditions but generally the specs assume good visibility, low multipath, nominal atmospheric conditions, and a reasonably high quality antenna.

I was curious to see how these two receivers compared in a real-world test so I ordered a Tau1201 EVK board from Digikey and collected some data. I ran for 24 hours with both receivers connected through an antenna splitter to a Harxon GPS500 survey grade antenna on the roof of my house. I configured both receivers to use all available constellations and output the internal solution with NMEA GGA messages.

Here are the resulting ground track plots for both receivers, u-blox F9P on the left, and the Allystar Tau1201 on the right. They are plotted with RTKPLOT and have statistics enabled from the options menu. I also set the coordinate origin in the options menu to the precise location of the antenna in WGS84 coordinates.

Ground tracks for 24 hours for the F9P and Tau1201

Here are the positions plotted versus time.

Position vs time for 24 hours for the F9P and Tau1201

The statistics may be difficult to read in the above plots, so I’ve copied them below.

u-blox F9P plot statistics
Allystar Tau1201 plot statistics

The standard deviations (STD) include only the measurement noise. The root-mean-squares (RMS) include both measurement noise and bias, so are the more relevant statistic in this case. We can combine the East and North measurements using the square root of the sum of the squares to get the horizontal RMS values (sometimes called 2drms). This gives us a value of 0.38 meters for the F9P and a value of 0.96 for the Tau1201. It is not possible to calculate the exact CEP metrics directly from the RMS values but we can estimate them assuming gaussian distributions and circular distributions. Neither of these assumptions is entirely true, but the estimate should still be reasonably accurate. Using a conversion factor of 1.19 derived in this article from GPS World, we get estimated CEP values of 0.32 for the F9P and 0.81 for the Tau1201.

In this test, both receivers achieved their advertised spec for CEP. Obviously, though, the more expensive F9P receiver outperformed the lower cost Tau1201. Exact results will vary based on some of the factor mentioned earlier (visibility, multi-path, atmospheric conditions, etc) but I would expect the relative differences between the two receivers to be fairly consistent.

These are for static measurements. The CEP values for dynamic measurements would be noticeably smaller due to the averaging out of the multipath errors as I demonstrated in the post referenced above.

Another interesting difference between the two measurements is the position errors averaged over 24 hours. The F9P average errors were less than 10 cm in both horizontal axes while the Tau1201 was small in the east direction, but was relatively large in the north direction at 63 cm. It’s hard to say from one measurement how consistent this difference would be over multiple measurements, but if it did hold up, then averaging the measurements over time to reduce the errors would be much more effective with the F9P than it would be with the Tau1201

In order to focus on the performance of the receiver, I used a relatively expensive antenna for this experiment, at least compared to the cost of the Tau1201. Although I did not repeat the test with any lower cost antennas, I would not expect significantly different results with any antenna intended for dual-frequency RTK solutions such as those from Ardusimple or DataGNSS, provided they are used with the appropriate ground plane. Even lower cost antennas, as long as they are intended for dual-frequency use, might work as well, but would require more testing and would probably have a larger effect on the results. If anybody has run a similar experiment with very low cost antennas and would like to share their results, please leave a comment below.

So, to summarize, the Tau1201 receiver is not able to match the performance of the F9P, but it is significantly less expensive, does achieve sub-meter accuracy, and combined with a low cost antenna, could be a good choice for some applications.

Using U-blox receiver options in RTKLIB

One of the less well documented features in RTKLIB is the capability to set receiver options. These are different from the solution options that are specified in the configuration file or through the GUI option menus. They are also different from the receiver commands that are used to send configuration messages directly to the receivers. The receiver options are specific to each receiver type. Actually, to be more precise, they are unique to each receiver output format, not each receiver. If a u-blox receiver is outputting RTCM3 messages, then only RTCM3 specific receiver options will be applied, not any u-blox options.

The receiver options are applied when translating the raw receiver output messages into internal RTKLIB observations or rinex files. In this post, I will describe the most commonly used options specific to u-blox receivers and the demo5 version of RTKLIB. Some of these options are also supported by other receivers and other versions of RTKLIB and I will try to make that clear in the discussion as well. I will also explain how to apply receiver options in the different RTKLIB apps.

First of all, here is a list of the receiver options available for the u-blox receivers in the demo5 RTKLIB code. They are listed along with options for all the other receivers in Appendix D.5 of the RTKLIB user manual. I have recently updated the demo5 version of the manual for the u-blox receivers but other listed receivers may not be fully up to date.

U-blox Receiver Options in Demo5 RTKLIB

– EPHALL: Input all of ephemerides
– INVCP: Invert polarity of carrier-phase
– TADJ=tint: Adjust time tags to multiples of tint (sec)
– MULTICODE: Preserve multiple signal codes for single freq
– MAX_STD_CP=n: Reject observations with StDev > n
-STD_SLIP=n: Set cycle slip for phase obs with StDev > n


The most commonly used option is “-TADJ”. Unlike most other receivers, the u-blox receivers include the estimated receiver clock error in the observation time stamps. This causes them to deviate from nice round numbers.   With most receivers, if you set the sample interval to one second, you will get observation timestamps at 1.0000, 2.0000, 3.0000… . With the u-blox receivers you will see something more like 0.9973, 1.9973, 2.9973… . This doesn’t cause any issues with downstream RTKLIB processing and so there is normally no need to adjust the timestamps. However, other applications sometimes expect the timestamps to occur on the rounded intervals and complain if they don’t. One of the most common questions I get is from readers asking how to deal with this problem.

Setting the receiver option to “TADJ=1.0” will adjust each observation so that it falls on the nearest rounded second. “TADJ=0.1” will do the same thing, except round to the nearest tenth of a second. Note that it is not enough for the code to just change the timestamp. To maintain consistency in the observations, the pseudorange and carrier phase measurements are also adjusted by a distance equivalent to the change in time of the timestamp. This option is available in all versions of RTKLIB.


The u-blox binary observation messages include a standard deviation estimate for each pseudorange and carrier phase measurement. The internal RTKLIB observation variables and the rinex file format only handle the observations, not the uncertainty estimates, so this additional information is lost. Instead of just discarding this information, RTKLIB uses it to filter the observations. In the 2.4.3 code and until the most recent version of demo5 code, the default behavior was to discard all carrier phase observations with standard deviation estimates greater than 5. Typically, discarding the lowest quality observations gives better solutions than including them. However, I found that the scale of the standard deviation estimates of the Gen9 u-blox modules (e.g. F9P) seem to be a little different than the Gen8 modules (e.g. M8T) Because of this I have increased the default threshold to discard the carrier phase observations for Gen9 modules from 5 to 8 in the most recent version of the demo5 code. I have not modified the default threshold for Gen8 modules.

The “MAX_STD_CP=n” option allows the user to choose this threshold themselves if they don’t feel the default is optimal. The optimal value can also vary depending on use case. For example, when assisting with some research on precise tracking of birds, I found that when using the M8T modules, the default value of “5” was fine for lower acceleration migratory birds, but increasing the threshold to “8” improved the solutions when tracking higher acceleration birds of prey.

Be aware this option is only available if you are logging the binary u-blox messages. If you are using the RTCM3 messages, then all observations are passed on, regardless of quality. In environments with good sky views where most of the observations are of high quality, this difference will be negligible, but in more challenging environments with obstructed sky views, this adjustment can make a bigger difference. This option is only available in the demo5 code, the 2.4.3 code uses a fixed threshold of five for all u-blox modules.


The “STD_SLIP=n” option is similar to the previous option. However, in this case, instead of discarding the observations with standard deviations greater than a threshold, it sets a cycle slip if the standard deviation is greater than or equal to the threshold. The two options can be used together to set a cycle slip on the lower quality observations but discard the lowest quality observations. However I prefer to just use the previous option and not this one. This option is also available in the 2.4.3 code but since observations with standard deviations greater than 5 are always discarded in that code, it is of only very limited use.


This option only applies to the F9P dual frequency module and is only in the demo5 code. The F9P outputs different GPS L2C observation codes depending on whether it decoded the CM or the CL codes in the raw signal. It usually reports the long code (CL) but will occasionally report some medium code (CM) messages as it is acquiring a new satellite. The 2.4.3 version of RTKLIB reports these observation codes as is. Unfortunately RTKLIB does not handle multiple code observation types for a single constellation frequency well and they tend to degrade the solution. To avoid this issue, I have followed the lead of the Emlid version of RTKLIB code and modified the default behavior of the demo5 version to combine both observation codes into a single combined observation code (L2X) which is reported regardless of whether the F9P reports the CM observation code (L2S) or the CL observation code (L2L). For processing RTKLIB solutions this is the preferred option, but for other uses, users might prefer to keep the original codes. Using the “-MULTICODE” option, reverts back to this behavior.

I have never found a need to use the remaining two options (-EPHALL, -INVCP) and am not familiar with any situation where they would be used.

Setting Receiver Options

How to set these options varies depending on which RTKLIB app you are using. It only applies to those apps which work directly with the raw receiver messages or files. These include RTKCONV and CONVBIN for post-processing, and RTKNAVI and RTKRCV for real-time processing.

In RTKCONV, use the “Receiver Options” box in the “Options” menu as shown on the left below. In RTKNAVI, use the “Receiver Option” box selected from the “Opt” option in the “Input Streams” menu as shown on the right below.

Setting receiver options in RTKCONV (left) and RTKNAVI (right)

You can include multiple options in the box as shown in the examples above.

In CONVBIN, use the “-ro” option on the command line. For example

convbin run1.ubx -ro “-MAX_STD_CP=3 -TADJ=1.0”

The quotes are only necessary if including more than one option.

At this point, RTKRCV does not appear to support receiver options but I hope to add this capability to the demo5 version in the near future.

I think that’s all there is to receiver options but if I’ve left anything out, let me know in the comments below and I will try to answer any questions.

Another look at L1/L5 cellphone PPK with RTKLIB

I last looked at a L1/L5 dual frequency cellphone data set from the Xiaomi Mi8 cell phone about 18 months ago in this post. Recently I had some questions and discussion in the comment section regarding a data set from another Mi8 phone, in this case collected by a group at the Universität der Bundeswehr München and available for download here. I gave a few hints for getting a reasonable PPK solution with RTKLIB in my responses to the comments but thought it deserved a more complete explanation.

The data itself is quite unique. It was collected while the phone was sitting on a choke ring antenna base to reduce multipath as shown in this image from the above link. The choke ring base and phone were then rotated around a pivot to create a circular trajectory.

Universität der Bundeswehr München: Phone on choke ring base

For this analysis I used the latest b34_d3 version of the demo5 RTKLIB code for which the Window executables are available on the rtkexplorer website or the source code is on the b34_dev branch on Github. I did make some recent changes to this code to improve the L1/L5 solutions so you will need to use this code version or something more recent if you are attempting to duplicate my results.

First let’s take a look at the raw observations from the cell phone. The quality looks quite good, there are relatively few cycle slips (red ticks) or missing samples. The blue lines indicate L1 and L5 measurements and the red lines indicate L1-only measurements. The accompanying base data includes matching observations for all of these satellites and frequencies.

Raw observations from Xiaomi Mi8 cell phone

Before running a solution, I did a little pre-processing of the raw observations to reduce the number of codes since RTKLIB sometimes does not handle large numbers of different codes well. Using RTKCONV to convert from rinex format to rinex format, I reduced the number of codes for both base and rover from the original rinex headers below on the left, to the new rinex headers on the right. I used the frequency and code buttons in the Options menu to select the desired frequencies and codes. Not only does this tend to give better results when multiple codes are provided for single frequencies, but it also makes debugging easier if things don’t go well on the initial solution attempt.

[Note for b34_d3 version of RTKCONV: Use the “L3” button to select “L5/E5a” frequencies. I will be updating this in the next release to label it as “L5/E5a” to match the naming convention used in the other demo5 apps]

Base codes before and after RTKCONV rinex->rinex conversion

Rover codes before and after RTKCONV rinex->rinex conversion

Next, I downloaded a multi-GNSS navigation file for the date of this data from CDDIS. There was a navigation file included with the data but it did not seem to use the standard format for the Galileo ephemeris and RTKLIB was not able to extract valid ephemeris for the Galileo satellites from that file.

For a config file I started with the sample PPK config file for the u-blox F9P that is included with the demo5 b34 executables (f9p_ppk.conf). However I made a few changes to optimize for the dataset. Here is a list of the changes I made:

pos1-frequency: l1+l2 -> l1+l2+l5 # enable L5
pos1-snrmask_r: off -> on # enable SNR mask for rover
pos2-snrmask_b: off-> on # enable SNR mask for base
pos1-snrmask_L1: 35,35,… -> 34,34,… # reduce threshold for low SNR
pos2-arlockcnt: 0 -> 5 # improved cycle slip detection
pos2-arminfix: 20 -> 10 # adjust for 1 Hz sample rate
pos2-rejioinno 2 -> 1 # tighten outlier threshold

stats-errphase 0.003 -> 0.006 # adjust for larger residuals
stats-errphaseel 0.003 -> 0.006 # adjust for larger residuals
stats-prnbias 0.0001 -> 0.001 # adjust for larger residual biases

I prefer to run solutions with the SNR mask enabled but don’t leave it on in the default file since the optimal SNR threshold can vary for different receivers. In this case, the SNR values of the rover data are fairly low, presumably due to the relatively low quality antenna in the phone, so I somewhat arbitrarily decreased the minimum SNR threshold to 34 dbHz.

After running an initial solution I also noticed that the solution residuals plotted with RTKPLOT were a fair bit higher than I am used to seeing with u-blox solutions. I’m assuming this is related to the low SNR’s and due to the very small, relatively low quality antenna in the phone. To adjust for this I increased the two terms used to calculate the standard deviations of the carrier phase measurements (errphase, errphaseel) from 0.003 to 0.006. The biases in the carrier phase residuals also looked unusually large so I increased the standard deviation of the carrier phase biases (prnbias) from 0.0001 to 0.001.

I generally set arlockcnt=5 for all receiver types except u-blox, since I find most other receivers allow more unflagged cycle slips than the u-blox receivers do.

The default config file is optimized for a 5 Hz sample rate and this data is 1 Hz so I lowered arminfix from 20 to 10 since it is measured in samples not seconds.

I normally prefer to set the outlier threshold (rejionno) to 1 meter but have recently increased it to 2 meters in the default config file. This is because I have found that 1 meter is occasionally too low and a too low value causes more problems than a too high value. For this experiment, I put it back to 1 meter.

For the most part, these changes are relatively minor optimizations for the specifics of this dataset and are not necessarily related to differences between L1/L2 solutions and L1/L5 solutions. For more information on any of these config parameters see appendix F in the demo5 version of the RTKLIB manual. This is also included with the demo5 executables.

I then ran a combined (forward+backward) PPK solution using RTKPOST. The result was quite decent with a fix rate of 98.2% with relatively small deviations from the expected circular trajectory.

One thing however that was somewhat unusual about this solution is the relatively large differences in the vertical component between the forward and backward solutions. A useful tip with RTKPLOT is that if you plot the “.pos.stat” residual file instead of the “.pos” solution file, you will see both forward and backward solutions before they are combined as shown in the plot below.

Notice that the differences between forward and backward solutions are reasonable for the horizontal components, but quite large, as much as +/-10 cm for the vertical component. Usually large differences between forward and backward are caused by false fixes and appear in all components. I don’t fully understand this result but I don’t believe it is a false fix and suspect that it is due to the large amount of noise in the raw observations. These differences were actually large enough to cause RTKPOST to downgrade some of the fixed forward and backward solution points to float before I increased the “stats-xxx” config parameters and in fact were part of the reason I increased these.

It’s always a little hard to know how well a config file optimized for a single data set will generalize to other similar data sets. To get at least a sanity check for this config file, I ran it on the Mi8 data set from the earlier post. The data was actually static but I ran it as a kinematic solution. The result matched the previous solution position with 100.0% fix rate and reduced the time to first fix by more than a minute from the previous solution, so it passed the sanity check.

Previous Mi8 data set run with the new code and config file

If you do run this code and config file on your own cell phone data, I would be interested to know how it goes and I think other readers would be as well. You can leave a comment below.

One thing to consider when collecting data is the ground plane. I suspect that most people won’t have an extra choke ring antenna base lying around but I would suggest using at least a metal disk under the phone as a ground plane to reduce multipath.

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 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
cd RTKLIB/app/rtkrcv/gcc

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
cd RTKLIB/app
cd ..

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 functional. However, the GUI interface does not include some of the config parameters added to the demo5 code that are not in the official 2.4.3 code. I believe as a partial workaround, you can most likely update the config file and load it to the GUI to configure these parameters but I have not actually tested this. 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.

[Update 6/23/21: Jens Reimann has recently merged the b34 changes into his fork of RTKLIB which does support the Qt GUIs. I have pulled his set of Qt build files into the demo5 code. The demo5 Qt GUIs now build and are reasonably functional but will still require some updating to include the config parameters unique to the demo5 code. RTKCONV and RTKPOST appear to be working fine but I notice that RTKPLOT does not seem to be fully functional in either his version or the demo5 version. My bandwidth to support this platform is fairly limited so I would be happy to accept pull requests to the demo5 code for any Qt related fixes and improvements.

The build instructions have changed slightly to work with his code

cd app/qtapp

[Update 1/19/21: Version b34 of the official 2.4.3 RTKLIB code has just been released. It is a major rewrite of the user interface and the changes have not been incorporated into the Qt files. The Qt files are still in the repository but are planned to be dropped in the next release since they are no longer functional. I will be following the lead of the 2.4.3 code in the demo5 version, so unless this changes, the Qt code will not have any future in either codebase. Other readers have reported success using WINE to run the Windows executables, so for those who don’t need to build custom versions of the code, this might be a better choice.]

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
ENAGAL: Enable Galileo
ENACMP: Enable Beidou
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:

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.