Raw Residuals Analysis and Measurement Quality

A couple posts back I mentioned that it should be fairly easy to measure the solution residuals in order to validate or disprove my somewhat arbitrary choice to increase the “stats-eratio1” input parameter from its default value of 100 to 300. So, I decided to go ahead and do it. Like many things, it turned out to be not quite as easy or as definitive as I had hoped it would be, but I will share in this post what I found.

For those of you who don’t have the definitions of all 99 different RTKLIB input options at the tips of your fingers, eratio1 is basically an estimate of how much more accurate the receiver’s carrier-phase measurements are than the pseudorange measurements. Specifically it is defined as the ratio of the standard deviations of the two. The standard deviations of the measurements are used by the kalman filter to weight the relative importance of each measurement.

RTKLIB has a couple of features that will help do this analysis. First of all, the residuals for each satellite for each epoch are recorded in the stats file. The residuals are the difference between the estimated distance from the receiver to each satellite (range) and the actual range measurements after adjusting them to remove errors. There are separate residuals recorded for both the pseudorange and the carrier-phase measurements.

What we are interested in is the measurement errors rather than the measurement residuals, which are slightly different from each other. The residuals are calculated as the difference from the estimated ranges while the errors are calculated as the difference from the actual ranges. The estimated ranges in this case are derived from a best fit of the measurement data .

Fortunately, RTKLIB has a solution mode we haven’t talked about before, the “Fixed” mode which is specifically intended for doing this sort of residual analysis. It is similar to static in that it is assumed that the rover is stationary.  In this mode, however, we give RTKLIB the exact locations of both the base and the rover and it then calculates the residuals based on the inputted rover location rather than the estimated rover positions.  The position output does not vary from sample to sample as it does in “Static” mode and remains fixed at the value specified in the input parameters.  In reality I’m not sure how much difference this makes for the level of analysis we are doing here. I actually ran the experiment twice, once in “Fixed” mode and once in “Static” mode and got fairly similar answers. The results I show here though are all from the “Fixed” mode solution.

To collect some data for this experiment, I set up three receivers within a couple of meters of each other, two M8T receivers and one M8N receiver. I chose a location fairly close to a house and also close to several trees to replicate a less than ideal situation since I’d like to optimize the input parameters for a more challenging environment. There is more margin in ideal conditions so tuning of the input parameters should be less critical then. I collected roughly four hours of measurements at a one second sample rate from each of three receivers and also pulled the same four hours of data from a nearby CORS reference station. This gave me several combinations of different receiver combinations and baselines to include in the analysis.

RTKLIB estimates measurement uncertainties as a function of satellite elevation and satellite system so I split my data up that way as well. I won’t bother to label each line in the plots below but each line represents one satellite system and one combination of receivers. The upper plot is for a short baseline, and the lower plot is for a long baseline. The x-axis represents satellite elevation. Each point is the standard deviation of errors from all epochs from all satellites in that system over a range of five degrees of elevation. The data below 15 degrees is not very relevant to this experiment since I usually set the elevation threshold to throw out those measurements. There is no SBAS data in the long baseline plots because the CORS station does not include the SBAS satellites.


So how we do interpret this data. As you can see from the plots, the ratio of the standard deviations varies from roughly 100 to 300 most of the time. That doesn’t help a lot since the purpose of this experiment was to help decide between 100 and 300. Maybe it should make everybody happy since if you like 100, you can probably use this data to justify that number and if you like 300, you could use this data to justify that instead. Still I find it useful to know neither answer is terribly wrong and that it is probably OK to adjust the value within this range to what you find works best. Sometimes in cases like this it makes sense to use a conservative value which could be defined as either 100 or 300, and sometimes it makes sense to use a mean, which in this case would be about 200.

I will probably change my default setting from 300 to 200 and use that unless I find that causes a degradation in my results.

Below I have also plotted the standard deviations for the two measurements since they are of interest as well, particularly the carrier-phase numbers since RTKLIB also estimates those based on the input parameters stats-errphase, stats-errphaseel and stats-errphasebl. I will leave analysis of this data to a future post as well as looking at independent estimates that are made by the Ublox receivers themselves.



In order to avoid cluttering the discussion above I left out some of the details of calculating the standard deviations, particularly the handling of non-zero means. The errors in the measurements range in frequency from very low to high. The very low frequency or possibly DC errors do not average to zero in four hours and show up as non-zero means over the full data set. I removed these means since I felt they were caused by factors not relevant to this experiment and they do not affect the standard deviations anyways. Other errors are higher frequency and do average to zero over the length of time each satellite spanned five degrees of elevation. Since this was the bucket size of my analysis, I did not need to do any mean adjustment for these.

There are also errors that are slowly varying and while they average out to zero over the full four hours, do not average to zero over the five degree buckets. These errors get under-estimated in the standard deviation since the slicing of the errors into five degree segments effectively acts as a high-pass filter to the standard deviation calculation. To compensate for this effect, instead of calculating the standard deviation of [x], I used [x -x]. For a zero-mean population the two calculations will give identical results but for a non-zero mean population, the second calculation will give a better estimate of what the standard deviation would have been without the high-pass filtering. The reason I removed the means of the full data set as described above is that even though they don’t affect the normal standard deviation measurement they would have affected this modified calculation.

I don’t believe any of these adjustments significantly affected the result, but I do believe they should improve its accuracy. This is somewhat subjective though and another person’s analysis would probably be at least slightly different.

AR Filter:A RTKLIB cycle-slip enhancement

Some of you may remember, one of the first code changes I made to RTKLIB was fixing a bug in the arlockcnt feature. Arlockcnt is an input parameter that specifies how many samples delay occurs before a new satellite (or a satellite that just recovered from a cycle-slip) is used for ambiguity resolution. Holding off use of the new phase-biase estimate from the kalman filter until it has had enough time to converge prevents corruption of the ambiguity resolution integer set. This in turn prevents a loss of fix.

Although waiting a fixed number of samples is fairly effective, it is not an optimal solution. Ideally we would use information from a new satellite as soon as it was converged and not after a fixed amount of time since some satellites will converge faster than others. When your data looks like this one, then every additional sample you can process is going to help.


This is what the AR filter attempts to do. In the current code implementation, a new satellite is unconditionally added to the integer ambiguity set when the arlockcnt expires regardless of the effect it has on the AR (ambiguity resolution) ratio. This means that the arlockcnt must be set conservatively, to insure the slowest satellite has converged, and means that most satellites will not be used for ambiguity resolution as early as they could be. In the case of frequent cycle-slips, this could mean loss of fix from having too few satellites available or it could mean a false fix since less satellites gives a less robust ambiguity resolution.

When the AR filter is enabled, a new satellite is still added to the integer ambiguity set when the arlockcnt expires but the effect of adding each new satellite is evaluated and if it causes a significant degradation in the AR ratio, that satellite’s use in ambiguity resolution will be delayed for a few more samples before being re-evaluated. Exactly how to define “significant degradation” is a bit subjective. I have chosen to disqualify a new satellite if it causes the AR ratio to drop below the AR fix threshold or if drops by more than a factor of two and the result is within 10% of the AR fix threshold. Exactly how long a satellite should be delayed is also subjective. I chose to delay a disqualified satellite for five samples plus a stagger of one sample for additional satellites. If two satellites are disqualified on the same sample, it could be either satellite or both that caused the disqualification. By adding a stagger to the delay for the second satellite, they will be re-evaluated independently on different samples.

To evaluate the change, I ran two solutions on the Ublox M8T data from my previous series of “M8N vs M8T” posts. This is my most challenging data set from a cycle-slip perspective. The solution below on the left is with arlockcnt=0 and AR filter disabled. The solution on the right is with arlockcnt=0 and AR filter enabled. As always, the yellow represents a float solution, and the green, a fixed solution. As you can see, enabling the AR filter significantly improved the number of fixes. Normally I would not set arlockcnt to zero if the AR filter was not enabled, this was for comparison purposes only.


As you would expect, with the AR filter disabled, increasing arlockcnt from 0 to 75 samples (15 sec) improves the solution for this data set as shown below but it still loses fix relatively often compared to the solution above with the AR filter enabled.


The plot below compares the number of satellites available for ambiguity resolution between the “arlockcnt=75/filter off“ solution and the “arlockcnt=0/filter on” solution”. Notice that we have significantly reduced the number of samples with less than 10 satellites available for AR by enabling the AR filter. More satellites should mean less chance of losing fix and also less chance of a false fix.


In this example, the accuracy of the fixed solution points did not seem to be noticeably affected by enabling the AR filter. As usual, I evaluate accuracy by comparing the receiver position relative to the position of a second receiver mounted on the same rover, both relative to the same base receiver. The difference between the two rovers should be a perfect circle, so any errors will appear as deviations from the circle. Plotting for only the fixed points, the “arlockcnt=75/filter off“ solution is on the left and the “arlockcnt=0/filter on” solution on the right. In both cases the errors appear to be very similar and within a few centimeters. This probably makes sense since the same satellites were used to calculate position in both cases, it was only the ambiguity resolution that differed. Any advantage from having more satellites in the AR calculations could be offset by the fact that the additional satellites were probably noisier since they may not have been fully converged. Also, the plot on the left does not include many of the points on the right, since samples without a fix are not included.


I actually created the AR filter feature quite a while ago but never got around to describing it or even fully testing it by reducing arlockcnt to zero. I have now done that, and made some small improvements to it in the last few days. I have updated my Github repository and executables folder with the latest version.

That pretty much completes my general explanation of this feature but there are a few details to be aware of if you are interested in trying it out yourself.

First of all, enabling the AR filter will slightly increase the code execution time since if a satellite is rejected, the ambiguity resolution has to be re-run without the rejected satellite. The difference is small enough however that I don’t think it will be an issue in the vast majority of cases.

The second thing is to understand is how the arlockcnt interacts with the half-cycle valid bit. A typical cycle-slip (at least on a Ublox receiver) looks like the plot below. There is usually a gap of no data, then a cycle-slip (red tick), then a number of half-cycle invalid samples (gray tick), then a final cycle-slip. The second cycle-slip is actually not reported by the receiver, but is added during the translation from raw data to RINEX format when the half-cycle valid bit transitions. Any time the half-cycle status is invalid, that satellite will not be used for ambiguity resolution regardless of the arlockcnt. The arlockcnt will be reset by the second cycle-slip and count from there. So, in this example, if arlockcont were set to 10, all the samples from the beginning of the gap until 10 samples after the second cycle-slip will be ignored for ambiguity resolution.


The last thing to mention is that one of the recent code changes I referred to above was to add a pseudo half-cycle invalid bit for the SBAS satellites for the M8T receiver. For some reason, the Ublox receivers don’t seem to report the half-cycle status for the SBAS satellites. The change I made was in the raw data to RINEX translation where I set the half-cycle invalid bit for a fixed delay after a cycle-slip on a SBAS satellite.  This makes cycle-slips on the SBAS satellites look very similar to the rest of the satellites.  I had previously done this for the M8N receiver and that change has been migrated to the release code but hadn’t got around to doing it for the M8T. This attempts to avoid the half-cycle uncertainties from possibly causing a false fix if the SBAS satellites were used too early for ambiguity resolution.

RTKLIB: Customizing the input configuration file


One of the nice things about RTKLIB is that it is extremely configurable and has a whole slew of input options available. Unfortunately these can be a bit overwhelming at times, especially for someone new to the program. The RTKLIB manual does briefly explain what each option does, but even with this information it can be difficult to know how best to choose values for some of the parameters.

I won’t try to give a comprehensive explanation of all the input options here, but will explain the ones I have found useful to adjust in my experiments and include a little about why I chose the values I did. I describe them as they appear in the configuration file rather than how they appear in the RTKNAVI GUI menu but the comments apply to both. I created this list by comparing my latest config files to the default config file and noting which settings were different. The values in the list below are the values I use in my config file for a 5 Hz rover measurement rate.  The same config files can be used for either RTKNAVI, RTKPOST, or RNX2RTKP.

The settings and options highlighted in blue below are available only in my demo code and not in the release code but otherwise much of what I describe below will apply to either code.  Most of my work is done with Ublox M8N and M8T receivers with short baselines and these settings will more directly apply to those combinations but should be useful at least as a starting point for other scenarios.


pos1-posmode = static, kinematic, static-start, movingbase, fixed

If the rover is stationary, use “static”. If it is moving, “kinematic” or “static-start”. I always require the rover to be stationary long enough to get first fix, in which case “static-start” usually works better because it take advantage of the knowledge that the rover is not moving initially. Use “movingbase” if the base is moving as well as the rover. In this case be sure to set “pos2-baselen” and “pos2-basesig” as well. Use “fixed” if you know the rover’s exact location and are only interested in analyzing the residuals.

pos1-frequency = l1

All my receivers are single frequency so I turn off L2

pos1-soltype = forward, backward, combined

This is the direction in time that the kalman filter is run. “Combined” combines the result of forward and backward. For real-time processing, “forward” is your only choice. “Combined” will often give a better solution than “forward” for post-processed data but I don’t use it much since I am more interested in solutions that will work for either real-time or post-process. The kalman filter will have to acquire in both directions, so you will need the rover to be stationary for a period of time at the end of your data as well as the beginning, especially if you are using “static-start” mode. I sometimes use the “backward” setting for debug when I am having trouble getting an initial fix and want to know what the correct satellite phase-biases are.

pos1-dynamics = on

Enabling rover dynamics adds velocity and acceleration states to the kalman filter for the rover. It will improve “kinematic” and “static-start” results, but will have little or no effect on “static” mode. The release code will run noticeably slower with dynamics enabled. Be sure to set “prnaccelh” and “prnaccelv” appropriately for your rover acceleration characteristics.

pos1-navsys = 7

I always include GLONASS and SBAS sats, as more information is generally better.



pos2-armode = fix-and-hold, continuous

Integer ambiguity resolution method.  I almost always use “fix-and-hold” because I find it is difficult to get robust solutions with low cost receivers, at least for moving rovers, without it.  Be careful though, “fix-and-hold” can cause more false fixes than “continuous” and will hold them longer.  I will sometimes use “continuous” for static data sets. If “armode” is not set to “fix-and-hold” then any of the options below that refer to holds don’t apply, including gloarmode.

pos2-gloarmode = on, fix-and-hold

Integer ambiguity resolution for the GLONASS sats.  If your recievers are identical, you can usually set this to “on” which is the preferred setting since it will allow the GLONASS sats to be used for integer ambiguity resolution during the initial acquire. If your receivers are different or you are using two Ublox M8N receivers you will need to calibrate the inter-channel biases with the “fix-and-hold” setting. In this case the GLONASS sats will not be used for inter-channel ambiguity resolution until after they have been calibrated which begins after the first hold.

pos2-arfilter = on

Setting this to on will qualify new sats or sats recovering from a cycle-slip. If a sat significantly degrades the AR ratio when it is first added, its use for ambiguity resolution will be delayed. Turning this on should allow you to reduce “arlockcnt” which serves a similar purpose but with a blind delay count.

pos2-arthres1 = 0.004

Integer ambiguity resolution is delayed until the variance of the position state has reached this threshold. It is intended to avoid false fixes before the kalman filter has had time to converge. If you see AR ratios of zero extending too far into your solution, you may need to increase this value. The “arthres1” option exists in the release code config file but is not used for anything.

pos2-arlockcnt = 75  (15*sample rate)

Number of samples to delay a new sat or sat recovering from a cycle-slip before using it for integer ambiguity resolution. Avoids corruption of the AR ratio from including a sat that hasn’t had time to converge yet. Use in conjunction with “arfilter”. Note that the units are in samples, not units of time, so it must be adjusted if you change the rover measurement rate.

pos2-minfixsats = 3

Minimum number of sats necessary to get a fix. Used to avoid false fixes from a very small number of satellites, especially during periods of frequent cycle-slips.

pos2-minholdsats = 5

Minimum number of sats necessary to hold an integer ambiguity result. Used to avoid false holds from a very small number of satellites, especially during periods of frequent cycle-slips.

pos2-arelmask = 15

Functionally no different from the default of zero, since elevations less than “elmask” will not be used for ambiguity resolution but I changed it to avoid confusion.

pos2-arminfix = 100  (20*sample rate)

Number of consecutive fix samples needed to hold the ambiguities. Increasing this is probably the most effective way to reduce false holds, but will also increase time to first hold. Note that this value also needs to be adjusted if the rover measurement rate changes.

pos2-elmaskhold = 15

Functionally no different from the default of zero, since elevations less than “elmask” will not be used for holding ambiguity resolution results but I changed it to avoid confusion.

pos2-aroutcnt = 100 (20*sample rate)

Number of consecutive missing samples that will cause the ambiguities to be reset. Again, this value needs to be adjusted if the rover measurement rate changes.

pos2-maxage = 100

Maximum delay between rover measurement and base measurement (age of differential) in seconds. This usually occurs because of missing measurements from a misbehaving radio link. I’ve increased it from the default because I found I was often still getting good results even when this value got fairly large, assuming the dropout occurred after first fix-and-hold.

pos2-rejionno = 1000

Reject a measurement if its pre-fit residual is greater than this value in meters. I have found that RTKLIB does not handle outlier measurements well, so I set this large enough to effectively disable it. There was a recent bug fix in the release code related to outliers but even with this fix I found that I got better results with a larger value.



out-solformat = enu, llh

I am usually interested in relative distances between rover and base, so set this to “enu”. If you are interested in absolute locations, set this to “llh” but make sure you set the exact base location in the “ant2” settings.

out-outhead = on

No functional difference to the solution, just output more info to the result file.

out-outopt = on

No functional difference to the solution, just output more info to the result file.

out-outstat = residual

No functional difference to the solution, just output residuals to a file. The residuals can be very useful for debugging problems with a solution.

stats-eratio1 = 300

Ratio of the standard deviations of the pseudorange measurements to the carrier-phase measurements. I have found a larger value works better for low-cost receivers, but that the default value of 100 works better for more expensive receivers. Actually measuring the residuals is not hard and I have planned on doing that for a long time but have still not got around to it. Larger values tend to cause the kalman filter to converge faster and leads to faster first fixes although it is possible it also increases the chance of a false fix. If you change this value, you may also need to change the “pos2-arthres1” value.

stats-prnaccelh = 1.0

If receiver dynamics are enabled, use this value to set the standard deviation of the rover receiver acceleration in the horizontal components. This value should include accelerations at all frequencies, not just low frequencies. It should characterize any movements of the rover antenna, not just movements of the complete rover so it may be larger than you think. It will include accelerations from vibration, bumps in the road, etc as well as the more obvious rigid-body accelerations of the whole rover.

stats-prnaccelv = 0.25

The comments about horizontal accelerations apply even more to the vertical acceleration component since in many applications the intentional accelerations will all be in the horizontal components. It is best to derive this value from actual GPS measurement data rather than expectations of the rigid-body rover. It is better to over-estimate these values than to under-estimate them.

ant2-postype = rinexhead, llh, single

This is the location of the base station antenna. If you are only interested in relative distance between base and rover this value does not need to be particularly accurate. For post-processing I usually use the approximate base station location from the RINEX file header. If you want absolute position in your solution, then the base station location must be much more accurate since any error in that will add to your rover position error. If I want absolute position, I first process the base station data against a nearby reference station to get the exact location, then use the ”llh” option to specify that location. For real-time processing, I use the “single” option which uses the single solution from the data to get a rough estimate of base station location.

ant2-maxaveep = 1

Only used for real-time processing. Specifies the number of samples averaged to determine base station location if “postype” is set to “single”. I set this to one to prevent the base station position from varying after the kalman filter has started to converge since that seems to cause long times to first fix.

Please help me update this list if you have had success adjusting other options or using different settings for these options, or if you disagree with any of my suggestions. I will treat this as a working document and continue to update it as I learn more.

Adding a radio link

In the last post I described setting up RTKNAVI in a simple configuration with both receivers connected directly to a laptop. While this is a good way to become familiar with RTKNAVI, it is not a useful configuration for actual measurement since the rover can’t rove for more than a few feet before running out of cable.

In this post I will describe adding a pair of HobbyKing SiK V2 Telemetry radios to separate the base from the laptop and rover. These radios are based on the same open-source design as the 3DR radios previously made by 3DRobotics and sell for $33 dollars for the pair. They are supposed to be good to up to about 300 m with the supplied antennas. There is a 915 Mhz version and a 433 Mhz version available, you will need to choose the one that is legal in your location. Both transceivers have both a USB connector and a UART connector. We will use the USB connector to connect one radio to the laptop and the UART connector on the other radio to connect to the GPS receiver. Here’s what they look like coming out of the box.

The first thing I did after opening up the package was to screw the antennas onto the transceivers since it is possible to damage the radios if they are accidentally powered up without the antennas attached.

To create the base station, I connected one of my Ublox M8N receivers to the radio and to a USB battery pack by cutting and reconnecting the cables that came with the devices.  I connected VCC for all 3 cables together, and the same for all 3 GND wires. I then connected RX to TX and TX to RX between the GPS receiver and the radio. This is what it looked like when I was done.


If you haven’t already set the baud rate on the GPS receiver it is possible to set it through the radios but it is probably easier to do it beforehand with the receiver connected directly to the laptop. In my case, I had previously set it to 115K from the RTKNAVI demo in the previous post and continued to use that baud rate for this exercise.

I then plugged the second radio into the laptop using a USB cable. I also plugged the second GPS receiver, which will be the rover, into a second USB port on the laptop, using an FTDI board to convert from UART to USB as I’ve described before.


Next I downloaded MissionPlanner, an open-source software package developed for drone users. I used this to configure the radios. It’s fairly straightforward and there’s some good documentation here to help you through it so I won’t go through all the details. This is the configuration that I ended up using after a little experimentation:


It is important to match the baud rates for the different pieces of the link. Set the kilo-baud rate (and the port number) for the laptop com port up in the top right corner. This needs to match the “Baud” setting for the local radio on the left. The “Air Speed” setting is the kilo-baud rate the radios operate at, and the two radios (local and remote) need to have the same value. The “Baud” setting on the remote radio must match the kilo-baud rate of the base GPS receiver.

Often when I changed these settings, it was difficult for me to get the complete link working again and I had to fiddle with it. Sometimes this meant clicking on “Save Settings” more than once, sometimes I would restart the Mission Planner app, sometimes the RTKNAVI app, and at least once I had to reboot the laptop. This was all rather frustrating and I don’t really know which steps helped and which didn’t, but once I stopped changing the settings, things seemed to be more stable.

You will need to be careful not to overwhelm the data link with too much data. In the previous demo I had reduced the base station sample rate to 1 Hz which is where I left it for this exercise.  In many cases, people convert the raw measurement data to RTCM format to reduce its size before sending it over the radio but this is not an option in this case because the receiver won’t output the raw measurements in RTCM format and we do not have a CPU in the base station to do the conversion.  As long as we are careful not to exceed the bandwidth of the radio link this should be OK although our rover distances may be limited since higher data rates are supposed to decrease the range of the radio.

At this point you should be able to communicate with the GPS receiver in the base station through the radio link. I started up the Ublox u-center eval software at this point just to verify that I could communicate in both directions. Make sure you disconnect or close it when you are done, or it will prevent RTKNAVI from accessing the com port.

Once you have established the radio link is working, you should be able to startup RTKNAVI and follow the instructions from the previous post to configure and run it. The only difference will be that you will probably find the radio is using a different com port than the GPS receiver so you will need to change that in the Input data stream menu.

I placed my base station on a tripod for convenience and to get the radio antenna further off the ground. I used a 8” pizza pan (88 cents at Walmart) for a ground plane. Here’s a photo of the assembled base station.


I placed the radio underneath the ground plane and the antenna pointed down in case that helped reduce possible interference between the radio and the GPS receiver but I did not do any testing to evaluate how effective this was. I probably should have also mounted the USB battery pack underneath as well just to keep things cleaner but didn’t get around to it.

I then mounted the other radio and GPS receiver antennas on top of my car to use as the rover. As I do for all my data sets, I started the data collection and then remained stationary until I got a fix. Typically this takes about 3 or 4 minutes and that is what happened in all of my runs. After starting RTKNAVI, I opened two plot windows. In the first I selected “Gnd Trk” and in the second I selected the “Nsat” plot option because this option includes a plot of age of differential, the delay in time between the rover measurement and the base station measurement. When close to the base station the age of differential remained between 0.2 and 1.2 seconds which makes sense since the base station is sampling every second and there will be a short delay for the radio link. As I got further from the rover I started to see this number increase as the radio link started to breakdown and I started to lose base observations. Here is the plot with the age of differential shown in the middle window.


Here is the ground plot and position plot from the same run.


In general, I seemed to start losing the radio link at about 100 meters. This is less than the 300 meters I was expecting, but maybe optimization of the radio settings and antenna locations would help. I did spend a little time adjusting these without seeing much difference in the results, but it was far from an exhaustive effort.

Here’s another short run where I drove out 350 meters and back showing age of differential and position. In this case I again lost the radio connection at about 100 meters and the age of differential increased all the way to the “Max Age of Diff” option (75 sec) without losing fix. It then regained a fix immediately after the age of differential dropped back below 75 seconds.


In another run, I reduced the base station sampling rate from 1 Hz to 0.2 Hz and also reduced the air speed setting of the radio from 64 to 16 to see if this would affect either the range of the radios or the reliability of the solution. I did not find it made much difference to either one. I did lose the fix after exceeding the max age of differential in this run but that may just be because I exceeded it for a longer time than in the previous example. Here is the age of differential and position plots for this run:


Overall, the radios were a little frustrating to configure, and their range was a little disappointing, but otherwise the experiment was a success.

Getting started with RTKNAVI

Up until now I have used RTKLIB entirely for post-processing previously collected data and have not tried to process any data real-time. Now that RTKNAVI, the real-time GUI version of RTKLIB, is successfully compiling in the 2.4.3 b17 release of RTKLIB, I decided to give it a try.

I first had to update the GUIs to add all of my additional input configuration parameters and options. I started a new “Demo5” branch in my Github repository with these changes. While I was at it, I also updated the RTKPOST GUI for post-processing so both applications now support all of what was available previously in my code only in the RNX2RTKP CUI version. I’ve uploaded the executables and they are available here along with all the input configuration file and receiver startup files I used for this exercise.

As a starting point I chose to connect both M8N receivers directly to my laptop PC. This is not a very useful configuration, since the rover can only travel as far as the USB cable extends, but it greatly simplifies things, avoiding have to deal with radios or other real-time links while getting started. I connected both receivers to my laptop USB ports using FTDI boards to translate from UART to USB as I’ve previously described in this post. We’ll connect a pair of  3DR 915 Mhz radios later to make this a useful setup.

Below I will describe how I set up and ran RTKNAVI in the hope it will be useful to other people just starting out. I will assume you are using M8N receivers and my version of the code but much of this will also apply to the most recent 2.4.3 release version of code and to other receiver types as well.

When you first bring up RTKNAVI it should look something like this:


In the top left corner you will see what version of code you are running. If you are running my code, you should see the demo4 (I need to update this to demo5) tag. In the top right corner are the menus for setting up the input, output, and log streams. We will start here.

Click on the “I” button to bring up the “Input Stream” menu. The red ovals below show what we need to change here. Check the boxes for both the rover and the base station, set both Types to “Serial” and the “Format” for both to “u-blox”. Plug the rover receiver USB cable into the laptop, then click the “Opt” button for the rover to bring up the “Serial Options” menu. Click on the arrow next to the “Port” box and select the com port for the rover. It should be the only choice at this point. Set the baud rate to match the GPS receiver, then click on OK. Plug in the USB cable for the base station receiver and then click the “Opt” button for the “Base Station”. Set the “Port” and baud rate as you did for the rover.


Next, click the “Cmd” button for the rover to specify the commands that RTKNAVI will use to initialize the GPS receiver. Click the “Load” button in the “Serial/TCP Commands” pop-up and select the “m8n_rover_5hz.cmd” file. (You should have downloaded this when you downloaded the executables). Do the same for the base, but choose the “m8n_base_1hz.cmd” file. These files will configure the rover to output raw GPS, GLONASS, and SBAS measurements at 5 Hz, and the base station at 1 Hz. We run the base station at a lower sample rate since it is not moving and later we will need to relay this information over a real-time link which may have limited bandwidth. Check both boxes to enable the “Commands at startup” and the “Commands at shutdown”, then click OK to close the two windows. If you are not using the M8N receivers you will need to provide your own startup files.


Next we’ll configure the output stream to send the solution to a file. Click the “O” button to open the “Output Streams” pop-up. Check the box next to “Solution 1” to enable the ouput stream, set the “Type” to “File” and the “Format” to “E/N/U-Baseline”. This will format the output to give us the distance between rover and base. Enter a file name and path in the “Output File Path” box.


Next we will set up the log files. Although these are not necessary to run RTKNAVI, they are very useful for debugging any issues that may come up later. Check the boxes for both “Rover” and “Base Station”, and set both “Types” to “File”. Enter file names for both logs. They will be in raw ublox format so I give them a “.ubx” extension. If you check the “Time-Tag” box, you will be able to re-run the log files with RTKNAVI. If you don’t check this box, you can still re-run the logs, but only with one of the post-processing apps (RTKPOST or RNX2RTKP)


OK, that should take care of all of the data streams. Next we will set up the solution configuration options. Click on the “Options” button in the bottom row of buttons. Select the “rtknavi_5hz_m8n.conf” file as shown below.


Again, you should have downloaded this file with the demo5 executables. We’ll go over the details of these settings in this file later, for now I’ll just mention that the solution mode is set to “Static-start”. This option is only available in the demo5 code and will assume the rover’s location is stationary (“Static” mode) until a fixed solution is achieved, at which point it will assume the rover is moving (“Kinematic” mode). In this exercise we could use “Static” mode instead of “Static-start” if we didn’t plan to move the rover, or “Kinematic” mode if we did.

There is no need to enter the base station location if we are only concerned with relative distance between the rovers which is what we are doing in this exercise.  The configuration file specifies that we will use the measured “Single” position for the base station location.  I have limited the number of averages for the base station location to one because allowing the base to move while we are running the solution can cause it to converge quite slowly.  If you were trying to calculate absolute position with any accuracy you would need to enter accurate coordinates of the base station in the position sub-menu.

At this point, before we setup all the output windows, it would be a good time to verify the receivers are communicating properly with the laptop. I suggest using the ublox evaluation software, u-center, to do this. Open u-center, connect to each receiver, check it’s baud rate is correct and monitor the packet output window for a few seconds. You should see readable NMEA text messages if everything is working right for each receiver. You can read this post for more details on using u-center. If you need to change a baud rate, don’t forget to save the configuration to the receiver so it will come up correctly after a power-cycle. Also don’t forget to disconnect u-center from both receivers, or close it when you are done or it will prevent access to the com ports when you start RTKNAVI.

Coming back to RTKNAVI, the last thing to set up the output windows. Clicking on the two arrows in the top right corner will cycle through various options for the main display window. The right arrow cycles through plot types and the left arrow through sub-types. I’ve chosen “Rover:Base SYS SNR (db Hz)” here which allows us to see signal strengths for all satellites for both rover and base. Satellites are colored by system (GPS, GLONASS, SBAS) and only satellites with sufficient quality in both receivers to be used in the solution are colored, the others are grayed out. Clicking on the small box above the “Start” button brings up additional monitor windows. Each window allows you to choose what that window will monitor. For this exercise, we will click the box three times to open three windows and set them to “RTK”,“Obs Data”,and “Error/Warning”. You can re-size and move around the windows to make all of them visible at the same time as I have done in the screen capture below.  The  example below shows what the screen looks like after hitting start, at the moment your boxes should be mostly empty.


Once you’ve done this, we are almost ready for the big moment! First check your antennas, make sure both have open views of the sky with no nearby obstructions and you have ground planes under both antennas. If all looks good, go ahead and click the “Start” button in RTKNAVI.  The monitor windows should start to fill with information and hopefully look something like the example above. In this case, the GPS receivers should have had enough time to converge to an internal solution while we were verifying them above and before we pushed “Start”, but if you skipped that step let the receivers run for a minute or two after power-up before clicking the “Start” button.

OK, now let’s check a few things to make sure everything is working right. First look at the main display window, shown in the upper left corner above. If you are in North America you should see colored bars for both receivers for all three systems (GPS, GLONASS, and SBAS). In Europe the SBAS satellites will be grayed out since the EGNOS SBAS satellites don’t broadcast range information.

Next check the observation monitor window, shown on the far right above. You should see valid pseudorange (P1) and carrier-phase (L1) measurements for both receivers (1 and 2). You should see both GPS (Gxx) and GLONASS (Rxx) and possibly SBAS (Ixx) in the list, again for both receivers. The rover observations should update five times per second and the base observations one time per second. If they are not continuously changing, something is wrong with your setup.

Next, check the Error/Warning monitor window shown on the bottom left above. In the first couple minutes you will see “large residual” errors and “position variance too large” errors and maybe a few “slip detected” errors as the solution converges. This should switch to mostly “ambiguity validation failed” errors as the solution converges but before the ambiguities are resolved. If you are getting frequent occurrence of any other message, then something is probably wrong and needs to be investigated. If not, the ambiguity resolution ratio (AR ratio) listed in the main display window and in the Error/Warning messages will fluctuate up and down but eventually should reach 3.0 at which point the solution status in the main window should switch from “Float” to “Fixed” and hopefully stay there. For me, this typically occurs in less than 5 minutes but this number will vary depending on your configuration. At that point the “Positioning mode” in the RTK window should switch from “Static-start” to “Kinematic” and now if you like you should be able to move the rover antenna without losing lock.  Make sure you don’t block the antenna’s view of the sky when moving it.  Of course the movement will have to be pretty limited since both receivers are hard-wired to the laptop.  (Note: while writing this tutorial I noticed that the RTK window is incorrectly displaying“Moving-base” instead of “Static-start” before the switch over. This is a bug I must have created when I added the “Static-start” mode but it only affects the display window and not any functionality. I’ll plan on fixing this in my next code update)

Assuming you’ve got a fix, then I would suggest playing around with all the display options since there are many of them. Below I show a screen capture after achieving a fix. I’ve switched the main display over to baseline so it shows the distance between the two antennas. I’ve also clicked the “Plot” button to start real-time plotting of the solution and let it run for 5 or 10 minutes. You can see the solution is moving around by at least a couple of centimeters. If I had run the solution as “Static” instead of “Static-start” this variation would be much smaller but I would not be able to move the rover around.  I also suspect if I had waited a few more minutes before collecting this data, the errors would be smaller.


Hopefully everything goes well and you quickly get an accurate fix. If you don’t get a fix, I would first check the error/warning window, see if there are any clues there. If not, and all the other checks I mentioned above look good, then the next step would be to look at the log files we enabled in the data stream menus. They will be in raw ublox format but can easily be converted to RINEX observation and navigation files with the RTKCONV GUI. Plot the observation files using RTKPLOT and look for cycle-slip issues or other quality problems with the measurements. Check that there are no missing or extra observations. If they look good, the next step would be to post-process the log files with RTKPOST to see if that makes a difference. If all that looks good and you still can’t get a fix, send me a copy of the raw data files and I’ll take a look.

Good luck!!

In the next post I will add the radios to make this a more useful experiment.

RTKLIB: Demo 4 vs 2.4.3 B16 code

Over the last six months or so I have made a number of changes to the RTKLIB code. A couple of these (ArlockCnt bug and M8N half-cycle bit) have made it back into the officially released code on Github but most of them have not. While I was in the mode of comparing solutions, I thought it would be worthwhile comparing solutions from the latest demo4 code from my Github repository to the latest released code (2.4.3 B16) to see how much they differ. The demo4 code is based on the 2.4.3 B12 released code but I do not believe the differences between releases B12 and B16 should affect this comparison.

I used the same data sets and input configuration files I used for the M8T vs M8N comparisons I described in the last few posts. The only differences being that I removed the options and input parameters that do not exist in the released version from the configuration files for the released code runs.

Here are the solutions from the previous post for both the pair of M8N receivers and the pair of M8T receivers using the latest demo4 code.

     M8N                                                                             Reach M8Trelease1

Here are the solutions for the same data sets using the 2.4.3 B16 code, both RTKCONV and RNX2RTKP.

     M8N                                                                             Reach M8Trelease2

A quick look at the vertical scales as well as the shape of the plots shows that the M8N solution is completely invalid whereas the M8T solution looks reasonably close although with a noticeably lower fix ratio. The difference between the M8N and the M8T is much greater with the release code than it was with the demo4 code. This is consistent with observations from other users that the M8T is significantly better than the M8N, something I did not really see in the demo4 solutions.

Let’s also look at the differences between solutions since they can indicate errors. Here is the difference between the demo4 M8N and M8T solutions from the previous post. Remember that this difference should be a circle with a radius equal to the distance between the rover antennas since the data is from two different receivers mounted on the same rover. I have only plotted data for the fixed points since those are the ones we have highest confidence in.


There is no point in looking at the release M8N solution since it is so poor, but we can compare the M8T solutions from the two code versions. In this case, because we are using two solutions from the same data set, the difference should be zero. Here is a plot of the difference between those two solutions for the M8T data set, again only for the fixed solution points.


Here is the same data, plotted by axis.


The differences are quite large, up to nearly a meter in x and y, and more in the z axis. Based on the previous comparison between the M8N and the M8T with the demo4 code, I have fairly high confidence in the demo4 solution so I suspect the errors are in the release solution. We can verify this though by looking for discontinuities in the solutions at the erroneous points.

Let’s pick the point at 01:22:47 which shows a large difference between solutions in all 3 axes. Here are the two solutions zoomed into this point in time.

Reach M8T: demo4                                Reach M8T: 2.4.3 B16release6

Notice the large jump in the released code solution. Since this data is from a car driving on roads, the large discontinuities in the y and z axes are not real and confirm the error is in the release code and not the demo4 code.

So, several people now have asked if these changes will make it into the official code. As I’ve mentioned before in one of my comments, I’ve chosen not to submit all of my code changes to the official code repository. This is because my changes are focused on one small corner of the world of GPS (low cost) and tested on an even smaller corner (low-cost Ublox) and I am not sure how well they all will translate into the larger realm that RTKLIB supports. At minimum, it seems they would require a fair bit of testing on multiple hardware platforms which I don’t have the time or resources to do. I’m also not sure some of them would all be considered sufficiently mathematically rigorous for general widespread use. But I will continue to make them available in my Github repository for anyone that would like to incorporate them into their code.

I have just submitted my most recent change, the improvement in decoding cycle-slips from the M8T raw data, as an issue to the official Github repository.


Update 8/28/16:  I now have a demo5 version of this code in which I have made the GUI versions (RTKNAVI and RTKPOST) fully functional with all of the demo4 features.  The code is in a new demo5 branch in my Github repository and the executables are here