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

Ublox M8N vs M8T: Part 3


This is the third part in a series of posts comparing data from a pair of Ublox M8T receivers with data from a pair of Ublox M8N receivers. In the first post I identified a problem with erroneous fixes in the RTKLIB solution for the M8T data. In the second post I found that the source of the problem appeared to be in the handling of cycle slips in the translation from the receiver binary output to the text input used by RTKLIB. I also showed that adopting an algorithm more similar to the one used to translate the M8N binary improved but did not entirely fix the problem. In this post I will show that if the cycle slip translation is more precisely adapted to the differences in binary output between the M8N and the M8T then we can virtually eliminate the erroneous fixes and get an M8T solution that is now slightly more accurate than the M8N solution.

One thing I may not have made clear in the previous posts is the way I am collecting and processing the data from the Emlid Reach M8T receivers. I am using their ReachView software to collect the raw GPS data but all the processing is done afterwards using my own demo4 version of RTKLIB. I don’t have access to Emlid’s RTKLIB source code and don’t know if they have made any changes that might have improved this issue in their own real-time version of RTKLIB. So this is more a comparison between the M8N and the M8T receivers using my version of RTKLIB and not an evaluation of anything specific to the RTKLIB version of Reach.

Last post I finished with this plot showing the difference between the M8N solution and the M8T. For reasons I’ve explained before, we know this should be a perfect circle with a radius equal to the distance between the receivers.


By looking at spikes in the z-axis accelerations we were able to show that the deviations from the circle in the above plot were due to errors in the M8T solution and not the M8N solution. This plot was taken after I had modified the binary to text translation in RTKCONV to make the M8T translation more similar to the M8N translation. Before that, the errors were much larger.

Let’s start by taking a closer look at the binary outputs of the two receivers. The details for the M8T are available in the M8 receiver spec and for the M8N in this document from Tomoji Takasu but I will summarize them here.

Both receivers provide three values for each satellite output sample that can be used to evaluate the quality of the carrier phase measurement. The first is the carrier-phase valid bit. Both receivers provide this in the tracking status byte. It indicates that the receiver is currently locked to the carrier-phase for this satellite. Note that it does not tell us if the receiver has been continuously locked since the previous output sample and it also does not indicate anything about the quality of the measurement.

The second output (locktime or lock2) indicates how long the receiver has been locked to the carrier-phase for this satellite. If this count is lower than the count for the previous sample, then an unlock, i.e. cycle-slip, has occurred. Both receivers also provide this value, and this is what RTKCONV uses to detect a cycle-slip.

The third value (mesQI or cpStdev) is a quality indicator and this is where the receivers differ. On the M8N this appears to be more of a state indicator than a true quality indicator. The comments indicate that it’s value corresponds to:


For the M8N, the RTKCONV code throws out any phase measurement for which this quality indicator is not between 4 and 7.

On the M8T, the quality indicator is actually a numeric estimate of the standard deviation of the carrier phase measurement. If the value is between 1 and 7, then the estimate of standard deviation is this value multiplied by 0.004 meters. If this value is 15, then there is no valid estimate. This value is currently ignored by the RTKCONV code and that is the root of the problem.

Before discussing how to modify the code to incorporate this information, we need to understand exactly how the cycle-slip bit is interpreted by RTKLIB. On a sample for which this bit is set, RTKLIB will reset the phase-bias state for that satellite based on that carrier-phase measurement. Note that the reset is done using the measurement from the current sample, not the following sample. What this means is that the cycle-slip bit should not be used to indicate that there is a cycle-slip on the current sample. Rather it should indicate that there has been a cycle-slip since the last good measurement and that the quality of the current sample is now high enough to use it to reset the phase-bias state.

So how high should the quality indicator be before resetting the phase-bias? It will be a trade-off between time and accuracy. I chose the value of four since it’s in the middle of the scale and somewhat equivalent to what is used on the M8N, but it could be argued that this value should be higher or lower. Forcing the cycle-slip to be set on a good sample also eliminated the problem we previously saw with samples subsequent to the cycle-slip not being flagged, so I was able to remove the code I added in the previous post.

Here are the changes I made to the original code for the M8T receiver. The new variable cpstd is the standard deviation of the carrier phase measurement as described above. The constant STD_SLIP is the quality threshold described above and is set to 4.


Here is the position solution calculated with the above code change.


Here is the difference in position between the two solutions. As you can see, the deviations from the circle have been significantly reduced.


The noise in the z-axis accelerations has also been significantly reduced and is now slightly lower than in the M8N data.

       M8N                                                                           Reach M8Twalker16

I have posted the raw data and config files to my data set library in the niwot2_car folder and the code changes to my Github page.

Ublox M8N vs M8T: Part 2

In my last post, I compared data from a pair of Ublox M8N receivers with data from a pair of Emlid Reach M8T based receivers, and found issues in both data sets. In this post I will look into those issues more closely.

To do this, I collected another data set with a few differences from the previous one.

  • On the M8N rover receiver, I replaced the original antenna that had only a 1” cable with a Ublox ANN-MS-0-005 antenna with a much longer cable. This allowed me to move the receiver from the car roof to inside the car, a friendlier thermal environment. The goal was to see if this would avoid the all-satellite cycle-slips I saw last time at the beginning of the data set. This antenna cost me $20 from CSG but is available from other places for less.
  • On both Reach M8T receivers, I modified the dynamic platform model setting from “Airborne < 4g” to lower acceleration settings; “Pedestrian” for the rover, and “Stationary” for the base. I also changed the setting on the M8N base receiver from “Pedestrian” to “Stationary” to make them consistent. The goal here was to avoid the erroneous fixed solution points I saw in the solution from the previous M8T data. To insure the modifications occurred correctly, I used the u-center software to read back the settings from the receivers after the data was taken.
  • I chose a measurement route with much more tree cover than the previous one to increase the difficulty of the solution and to help differentiate the two receiver sets.

Here is a Google map of the measurement route. In this area there are few native trees, so by driving through residential areas, rather than next to them as I did in the previous data set, I encountered many more trees. There were numerous locations with tall trees on both sides of the road as well as places where I drove directly under large tree branches.


Here’s an example of some of the more challenging tree cover encountered in the route. The route locations are only approximate since the base locations are not calibrated, that is why the lines are not actually on the road.


Here is the observation data from both rovers. The red ticks are cycle-slips, the gray ticks are half-cycle invalids.

                             M8N                                                                          Reach  M8Twalker3

The first thing to notice is that there are no simultaneous cycle-slips in the M8N data. Of course, this doesn’t prove we will never get them but it suggests that maybe moving the receiver inside the car did help. Unfortunately, there is one simultaneous cycle-slip in the M8T data very close to the start at 00:54. Apparently, both the M8N and the M8T are susceptible to these slips, which is not surprising, since the chips are part of the same family. We do see only a single slip, which is an improvement over the previous data. Since they always occur near the beginning of the data collection, I still believe they are most likely caused by thermal transients. I suspect that the best way to avoid them would be to turn on the receivers 15 minutes before starting to collect data in an environment as similar as possible to the data collection environment.

Next, let’s look at the SNR vs. elevation plots. Last time we saw noticeably better numbers with the Reach setup because of the more expensive antenna. This time, with the Ublox antenna on the M8N receiver, the two are much more similar. SNR isn’t everything, and there still are reasons why the more expensive Reach antennas are likely better than the Ublox antennas, but we’ve at least closed the gap some between them.

                            M8N                                                                          Reach  M8Twalker4

Here are the position solutions from both receiver sets.

                            M8N                                                                          Reach  M8Twalker5

The M8N solution is very good, with nearly 100% fix after the initial acquire until the very end when I parked the car under some trees in the driveway. This is despite a very challenging data set with many cycle-slips.

Unfortunately, the M8T solution is not as good. The initial acquire is delayed because of the simultaneous cycle-slip I mentioned earlier. It does eventually acquire, but then loses fix for quite a long period near the end of the data (the yellow part of the line in the plot above)

Even more concerning, when we look at the acceleration plots, we see the same spikes in the M8T data as we did in the previous data set.

                       M8N                                                                          Reach  M8Twalker6

Again, these spikes align with erroneous fixed solution points in the M8T data as can be seen in deviations from the circle in the difference between the two solutions.


Clearly, changing the dynamic platform model setting in the receiver did not fix the problem. A couple of experienced users have commented that this setting does affect the front end of the receiver on earlier Ublox models and it’s still possible it affects the M8T as well, but it does not appear to be the cause of this problem. We will need to look elsewhere for the solution.

We are running identical RTKLIB solution code on the two data sets and we have verified that the receiver setup is nearly identical for both data sets. So what else can be different between them? One possibility is the conversion utility, RTKCONV, that translates the raw binary output from the receiver into the RINEX observation files that are used as input to the solution. Since the raw measurements are output by different commands on the two receivers, there are two different functions in the RTKCONV code that process them.

Let’s look first at the RTKCONV code to convert the data from the UBX_TRK_MEAS command used by the M8N receiver. I won’t show the code here but just describe functionally what happens in the code. Each data sample from the receiver contains a status bit indicating carrier-phase lock and a count of consecutive phase locks. RTKCONV sets the cycle-slip flag for that sample if the carrier-phase is valid and an internal code flag is set. If the carrier-phase is not valid, then the cycle-slip flag is left in its previous state. The internal code flag is set if the phase lock count is zero or less than the phase lock count for the previous sample and is only reset for the next sample if the carrier-phase is valid.

For the UBX_RXM_RAWX command used by the M8T receiver, there is also a status bit indicating carrier-phase lock. Instead of a count, there is a time for consecutive phase locks but functionally it is equivalent. The RTKCONV code for this command does not use an internal code flag and the cycle-slip flag is set or cleared directly every sample that the carrier-phase is valid. The cycle-slip flag is set if the phase lock time is zero or less than the previous sample and cleared otherwise.

I know that’s a bit confusing and the difference is fairly subtle. The most important thing to understand is that RTKCONV is doing more than just translating from binary to text, it is deciding which samples to set cycle-slips for based on a somewhat complicated algorithm, and that algorithm is different for M8N and M8T.

Basically, the effect of this difference is that for the M8N, if a cycle-slip is followed by an invalid phase then the next valid phase will always be flagged as a cycle-slip while for an M8T it won’t necessarily be so. It’s easier to understand by looking at a picture. The observation plots below show the location of one of the acceleration spikes in the M8T solution which is caused by a cycle-slip on satellite G05. The plot on the left shows which samples were flagged as cycle-slips with the existing code, the plot on the right shows the cycle-slips after modifying the code to be functionally equivalent to the M8N code. Note the extra two cycle-slips with the change.  The extra cycle-slips in this case are a good thing because they are flagging samples with large phase errors and preventing RTKLIB from incorporating them into the solution.

              Reach M8T before change                                   Reach M8T after changewalker8

Modifying the RTKCONV code for the UBX_RXM_RAWX command in this way and re-running the solution gives us the the position plot on the right and the acceleration on the left.


Much better than before! Not quite as good as the M8N but still a significant improvement. The difference between the M8N solution and the improved M8T solution is shown below.


Remember this should be a circle if both solutions are free of errors.  Actually in this case not quite a circle because there is also a separation between the two base stations ,but that effect is small and we can ignore it for now.  This is much better than the equivalent plot from the original M8T solution shown earlier. There are obviously some erroneous fixes even after the improvement, so this is still a work in progress, but I think it is a big step in the right direction.

This data set was quite a bit more challenging than the previous one and in reality both solutions are quite good given the number of cycle-slips we saw, but there is always room for improvement.

The other thing to note with this data set is that the better quality antenna made a big difference on the quality of the M8N solution.  I suspect I will not be going back to the old antenna!  I knew the nicer antenna would help but I have resisted using it till now because of the extra cost.  There are similar looking antennas with similar gain specs available for as little as $4 so I may try some of these to see how they compare.

I’m not going to post the code to my Github repository or my binaries until I’ve had a little more time to understand the remaining errors but if anyone wants to take a closer look now, here are the code modifications to ublox.c that I made in the decode_rxmrawx function.