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

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

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

U-blox receiver module with cover removed

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

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

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

CSGShop M8T receiver next to Uputronics M8 receiver

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

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

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

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

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

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

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

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

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

One epoch from the Uputronics rinex observation file

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

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

Run 2: Uputronics board physically separated from Pi

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

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

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

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

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

RTKLIB: Tips for using a CORS station as base

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

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

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

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

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

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

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

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

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

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

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

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

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

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

RTKLIB Benchmarking: versions 2.4.2, 2.4.3, and demo5

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Updates to RTKLIB for the u-blox F9P receiver

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Sparkfun OpenLog board, front and back

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

RTKPLOT of observations collected with the OpenLog logger

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

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

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

A first look at the Broadcom BCM47755 dual-frequency receiver

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

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

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

Rover observations for B47755

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

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

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

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

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

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

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

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

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

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

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

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

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

Dual-frequency PPK solutions with RTKLIB and the u-blox F9P

With previous generations of u-blox receivers there has been a lower priced option available without an internal RTK engine, such as the popular M8T in the generation 8 modules. This does not appear to be the case with the new dual-frequency generation 9 modules, as the F9T, without internal RTK solution, is currently priced higher than the F9P with internal solution.

As the u-blox internal RTK solution in the F9P appears to be very robust, there is probably no good reason to ever use RTKLIB for real-time solutions with the F9P. However, it often still makes sense to use RTKLIB to post-process raw data previously collected by the F9P since the F9P is not capable of post-processing solutions.

Post-processed (PPK) solutions have several advantages over real-time solutions. The rover hardware is simpler, less expensive, lighter, and lower power since post-processing does not require a real-time data link between base and rover. Post-processed solutions also tend to be more robust than real-time solutions, both because they are not subject to data dropouts in the base data link and because they allow for solution techniques that take advantage of both past and future observations, not just past observations. When the solution is not required in real-time, it often makes more sense to collect the data first and then process it later.

Collecting data and processing RTK solutions for the dual-frquency F9P with RTKLIB is not very different for doing this for the single-frequency u-blox M8T, and if you are already familiar with doing that, you will probably not have much trouble adapting to the F9P. However, since it’s been a long time since I did a post on this subject, I thought it would be worth going over again with some updated tips for the new receiver.

Step 1: Configuring the receiver:

To process an RTKLIB solution, we will need raw observation messages from both rover and base receivers and navigation data messages from one of the receivers. The receivers do not output these messages by default so we will need to configure them to do this. With the u-blox M8T it was possible to do this directly with RTKLIB using a command file but this is not an option with the F9P as RTKLIB does not currently support the new F9P configuration messages.

Instead we will download the u-blox u-center app and use this to configure the receivers, then save the results to the on-board flash. There are detailed instructions on how to do this in the F9P documentation available on the u-blox website but here’s a quick summary of the process.

  1. Plug the receiver into a Windows PC using a USB cable if the board supports USB or with an FTDI serial/USB converter if the receiver only has a UART port.
  2. Start the “u-center” app and connect to the receiver with the “Connection” command on the “Receiver” tab. If it is a USB connection, baud rate won’t matter, but if it is a UART->USB connection through FTDI, then you will have to set the correct baud rate from the “Receiver” tab. If all is well, you should see the green connection indicator flashing at the bottom of the screen
  3. From the “View” tab, open the “Messages”, Configure”, “Gen 9 Configure”, and “Packet Console” windows
  4. If using the UART port, click on “PRT (Ports)” from the “Configure” window, set the Target to “1-UART1” and “Baudrate” to the desired baud rate, and click on “Send”. I typically set this to 115200 baud. You will then need to change the baud rate in u-center to the new baud rate. If you are using the USB port directly, you can skip this step.
  5. From the “Configure” window, click on “RATE”, and set “Measurement Period” to the desired time between observation samples, then click on “Send”. I typically set this to 200 ms which gives a 5 Hz sample rate.
  6. From the “Gen 9 Configure” window, select “GNSS Configuration”, enable the desired constellations and signals, select “RAM” and “Flash” under “Layer Selection”, then click on “Send Configuration”. The F9P supports GPS L1C/A and L2C, Glonass L1 and L2, Galileo E1 and E5b, BediDou B1 and B2, and QZSS L1C/A.
  7. From the “Messages” window, right click on “NMEA” and then click on “Disable Child Messages” to disable all the NMEA messages. None of these are needed for an RTK solution but if you want any of the messages for other reasons you can then individually enable the ones you need.
  8. From the “Messages” window, double click on “UBX” then “RXM”. Right click and enable “RAWX” to enable raw observation messages and “SFRBX” to enable navigation messages. Alternatively, you can enable the RTCM3 messages from the “Gen 9 Configure” window. In this case you will want to enable the 1077,1087,1097,and 1127 messages. I have occasionally had trouble enabling the RTCM3 messages on the F9P and have had to use the “Revert to default configuration” option under the “CFG” command first to get this working.
  9. If an antenna is connected to the receiver and is not completely blocked, verify that you see RAWX and SFRBX messages appear in the “Packet” window.
  10. From the “Configure” window, select “CFG”, then “Save current configuration” then “Send” to save these settings to the flash on-board the F9P module.
  11. Repeat this procedure for the base receiver except set the “Measurement Period” under “RATE” to “1000 ms” for a 1 Hz sample rate. You will only need one set of navigation data so you can choose not to enable the SFRBX messages on the base. I tend to leave them enabled just because it makes plotting slightly easier later if each set of observations has its own navigation data.

If you have any trouble with the above summary, you might find this YouTube video from Robo Roby useful. It is intended for setting up the F9P for real-time solutions, not post-processing, but there is a lot of overlap between the two.

In the descriptions below STRSVR, RTKCONV, RTKPLOT, and RTKPOST are all RTKLIB GUI apps. They can be opened individually or you can start by opening RTKLAUNCH and run the individual apps from there. I do not believe the official 2.4.2 or 2.4.3 versions of RTKLIB fully support the F9P receiver yet so I would recommend using the demo5 version of RTKLIB available here.

RTKLAUNCH used to open the different RTKLIB apps

Step 2: Collecting the data:

  1. For this exercise I will connect both base and rover directly to a Windows PC through the USB port. You can connect both receivers to one PC or each to a separate PC.
  2. Launch two instances of STRSVR, one for each receiver
  3. Set the input stream to “Serial”, click on the input “Opt” button and set the port and baud rate. Set the output stream to file and click on the output “Opt” to set the file name. Click on the “?” to get a list of keyword replacements for the file name. I like to add “_%h%M” to the end of the file name which will append the hour and minute of the data to the file name. If you are collecting long data sets you might want to set the “Swap Intv” to break up the data into manageable file sizes. Note that you will need to use the keywords in this case to avoid overwriting the same file repeatedly. Give the file name a “.ubx” extension to let RTKLIB know that it is u-blox binary data.
  4. Click “Start” to start collecting data.
STRSVR used to collect the raw data

Step 3: Convert the observation data to rinex format:

  1. Start the RTKCONV app
  2. Click on the “…” button to the right of the “RTCM, RCV RAW or RINEX OBS” field and select the observation file created in the previous step.
  3. If the file extension is not “.ubx” set the “Format” to “u-blox”, otherwise leave as “Auto”
  4. Click on the “Options” button and select “L1”, “L2/E5b”, and all GNSS constellations collected (usually “GPS”,”GLO”,”GAL”, and possibly “BDS” (Bediou) depending on your location. Then close the options menu.
  5. Click on “Convert” to convert from binary to rinex format.
RTKCONV used to convert the raw data from binary format to rinex text format

Step 4: Review the observation data:

  1. Before processing the solution, it is a good idea to look at the data first and make sure it is complete, of reasonable quality, and at the right sample rate.
  2. From the RTKCONV main window, click on “Plot” to plot the observations you just converted.
  3. Verify there are observations from all constellations. Green indicates dual frequency measurements, yellow is single frequency. The GPS observations will be a mix of single and dual frequency since only about half of the satellites currently support L2C used by the F9P, but the other constellations should be nearly all dual frequency.
  4. Red ticks indicate cycle slips. Too many of these will make it difficult to get a decent solution. Gaps in the data usually indicate the receiver lost lock and these are not good unless they are in the low elevation satellites.
  5. If all the satellites are in gray, this usually indicates you are missing the navigation data. The previous step should have generated a “.nav” file as well as a “.obs” file. If just a few satellites are in gray, this normally indicates that they are below the elevation threshold which can be adjusted in the options menu selected in the top right corner with the star-like icon.
  6. Check both rover and base observations.
  7. In some cases you may only have one set of navigation data and so not have a matching “.nav” file for one of your observation files. In that case you can manually specify the navigation data with the “Open Nav Data…” option in the “File” tab.
Plot of raw observations

Step 5: Generate the position solution

  1. Open RTKPOST
  2. From the “…” buttons on the right hand side of the GUI, select the rover observation file, the base observation file, and the navigation file as shown in the example below.
  3. Click on the “Options” button and then the “Load” button. Select the “demo5_m8t_5hz.conf” file from the same folder as the demo5 RTKLIB executables, and then click on “Open”
  4. From the “Setting1” tab in the “Options” menu, enable “Galileo” and if applicable “Bediou”. “GPS” and “GLO” should already be enabled.
  5. From the “Setting2” tab in the “Options” menu, set “Integer Ambiguity Res (GLO)” to “On”. We are able to use the “On” setting in this case because the receivers are identical and so the Glonass hardware biases cancel. If you are not using an F9P receiver for base, then leave this field set to “Fix-and-Hold” which will automatically calibrate out the biases.
  6. From the “Setting1” tab in the “Options” menu, change the “Frequencies” from “L1” to “L1+L2”. This is the only change you should need to make to switch from processing single-frequency data to dual-frequency data for the F9P. The Galileo second frequency for the F9P is actually E5b not L2 but to simplify and improve the processing speed, I have modified the demo5 code to include “E5b” processing as Galileo’s second frequency. This won’t be the case for the 2.4.2 or 2.4.3 code. I don’t believe it’s currently possible to include the E5b data with these versions of RTKLIB but if I’m wrong please let me know
  7. Click on “OK” to close the Options menu.
  8. Click on “Execute” to run the solution. The bar at the bottom of the GUI will show the solution status as it runs and will report any errors. You should see a mix of Q=1 and Q=2 as the solution runs. If you see only Q=0, something is wrong. In this case, open the “Options” window, select the “Output” tab and set “Output Debug Trace” to “Level3”, exit the Options menu, and rerun the solution. Then open the “.trace” file in the solution folder for additional information on what went wrong.
  9. Click on “Plot” to plot the solution with RTKPLOT
RTKPOST used to generate a PPK solution
Plot RTKPOST generated PPK solution

This was just meant to be a quick summary of the process. For more details please see the references below.

References:

  1. u-center User Guide
  2. u-blox F9P Interface Description
  3. RTKLIB manual
  4. Updated guide to the RTKLIB configuration file

A few simple debugging tips for RTKLIB

Unfortunately, error reporting is not one of RTKLIB’s strengths.  Sometimes it will halt with an error message that is less than helpful, other times it will just continue through the entire solution with Q=0 or Q=2 without ever getting a fix because of a minor error in the input file configuration or parameters. I suspect anyone who has used RTKLIB with any regularity has run into cases like this innumerable times. It still happens to me all the time, almost always because I have made a silly mistake somewhere. For a new RTKLIB user, it can be quite difficult to figure out why they are not getting a valid solution in cases like this.

In this post, I will demonstrate some examples of common errors and some techniques to help identify them. First let’s take a quick look at a couple of real-time solution examples with RTKNAVI, then I will jump to post-processing cases for the rest of the examples.

In my experience, the most common error when using RTKNAVI is to improperly specify the base location. To demonstrate this, I go to the “Positions” tab on the “Options” menu in RTKNAVI and switch the Base Station position type from “Average of Single Position” to “RTCM Position”. In this example, my base stream is u-blox binary and contains no RTCM messages so RTKLIB will not get any valid base location information and can not calculate a solution. However, rather than halting and letting the user know there is a problem, RTKNAVI just runs through the whole solution without ever reporting either an error or a valid position. To help understand what happened in a case like this, the first step is to click on the tiny unlabeled square above the “Start/Stop” button. This will bring up the RTK Monitor window. Choose “Error/Warning” from the Monitor options as shown in the image below to bring up additional error and warning information.

In this case, every sample reports an “initial base station position error”. This should be enough information to track down the problem fairly quickly. Often this will be the case. If the additional information in this window doesn’t help, then the next step is to enable the debug trace and look at the trace file. This is covered in more detail in the post-processing examples further below.

In my experience, the second most common reason to get no solution with RTKNAVI is that navigation messages haven’t been enabled for either base or rover receiver. If this is the case, then all the SNR bars will be gray. When navigation data is available, the bars will be colored as in the image above. Lack of SNR bar color is the easiest way to detect this problem but another symptom is that the “Error/Warning” window will report “No common satellites” for every sample even though satellites are appearing in the SNR window.

A more general troubleshooting technique that will work for either real-time or post-processing and for GUI or command line RTKLIB applications is the debug trace file. I will demonstrate a couple of examples of using this with RTKPOST but the same basic technique can also be used with RTKNAVI, RNX2RTKOP, RTKCONV, CONVBIN, or STRSVR.

To enable the debug trace, set “Output Debug Trace” in the “Options” menu to something other than “OFF”. For simple errors, level 2 is enough and will produce a less overwhelming amount of information. For more detailed information about the GNSS solution, especially the ambiguity resolution, level 3 is better. For more details about the communication streams, level 4 can be useful. Level 5 includes many of the solution matrices and is too detailed for most general debug. Level 1 only contains the error messages available in the error window and is not useful. The image below from the RTKPOST Options menu shows the trace level set to 3. I usually set it to level 3 since I am often looking at the details of the ambiguity resolution, but for simple debug I would recommend starting with level 2 and that is what I will use in the examples below.

Next, I will intentionally make some silly mistakes and show how they appear in the error window and the trace file.

Let’s start with an example where RTKPOST actually does a good job of reporting the error to demonstrate how it should work, and occasionally does. To keep the example simple I will use files in the root directory, with names: rover.obs, base.obs, and rover.nav.

In this first case, I have made a typo in the name of the .nav file. Now, when I click on “Execute” to start the solution, it almost immediately halts and reports “Error: no nav data” in the error window near the bottom of the window. If only RTKLIB would always be as straightforward as this!

Next, I intentionally make a typo in the rover observation file and click on “Execute”. Again, the solution halts almost immediately but in this case the error message in the error window is much less useful. Not only does it not tell me which file has a problem, but it also suggests there is something specific wrong with the input data, not that the file is completely missing. Fortunately, clicking on another tiny unlabeled square (circled in red below) brings up the trace file, assuming trace debug was enabled as described above. This tells us exactly what went wrong, that the input rover observation file could not be opened.

For the next example, I will correct the typo in the file name but then set the location in the base rinex file header to all zeros. This is another common problem and usually happens because the rinex file was generated by RTKCONV or other application from a binary receiver output file that did not contain navigation messages. Without navigation information, RTKCONV can not estimate the receiver location and sets the approximate position field in the rinex header to all zeros. Again, the solution halts almost immediately, and the error message is identical to the previous case. However, looking at the trace file, there is now no mention of a file open error. To understand the difference between these two trace files, it’s important to know that the entries in the trace file beginning with 2 are warnings, and those beginning with 1 are errors. RTKLIB treats the missing file as a warning since there may be other observation files that are present, and doesn’t get an actual error until it looks for the base position information. With just the error, it is impossible to tell these two case apart, but by looking at the warnings as well, it is fairly easy to differentiate them.

Another fairly common error is to use the wrong base observation file. If the base observations do not overlap the rover observations in time, then the solution will run all the way through the data, without error, but the solution status will always be Q=0. Here’s the trace output for this example. I aborted the run before it completed, after it was obvious that something is wrong which is why the error windows shows “aborted”. The “age of differential” is the time between rover observation and closest base observation so again in this case, looking at the trace file makes the problem much more obvious

If the wrong navigation file is used, the solution will again run to the end with no error, and the status will remain Q=0, just as above, but this time the trace file will report “no common satellites”

Another mistake I make fairly often, is when I am specifying the precise base location rather than using the approximate location in the rinex header, and I forget to change it when moving to a different data set with a different base location. In this case, the solution will run without error, but the fix status will remain in float (Q=2) for the entire solution. There are several reasons why you might never get a fix, including just poor quality data, but in this case when I look at the trace file I see extremely large outliers right from the first sample. If all the satellites are reporting outliers and they are as large as this, that almost always means that the base location has been specified incorrectly.

These are only a few examples but probably cover over 80% of cases I look at where people are unable to get a solution. Most of the rest are caused by low quality data.

If you have other examples of common failures that I haven’t covered here, please describe them in a comment, along with the signature seen in the trace file.

RTKLIB documentation tips

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

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

navi1

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

navi3

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

navi2

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

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

str2str_1

str2str_1a

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

str2str_2

str2str_2a

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

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

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

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

options

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

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

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

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

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

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

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

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

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

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

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

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

f9t_1

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

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

f9t_3

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

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

f9t_2

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

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

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