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 real-time precise (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.

Sub-meter GNSS accuracy with standard precision solutions? – a look at the Allystar 1201

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.

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

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 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 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 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
925.0%9.2%53.4%98.1%not uploaded
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

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

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

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

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

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

Sparkfun OpenLog board, front and back

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

RTKPLOT of observations collected with the OpenLog logger

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

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

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