Another look at L1/L5 cellphone PPK with RTKLIB and an Xiaomi Mi8 phone

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

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

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

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

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

Raw observations from Xiaomi Mi8 cell phone

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

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

Base codes before and after RTKCONV rinex->rinex conversion

Rover codes before and after RTKCONV rinex->rinex conversion

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

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

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

The above names are what are used in the config file. For those modifying the parameters directly in the RTKPOST GUI, the equivalent changes are:

Frequencies: L1+L2/E5b -> L1+L2/E5b+L5/E5a
SNR Mask:Rover: unchecked -> checked
SNR Mask:Base: unchecked -> checked
SNR Mask: SNR values: 35->34
Min Lock: 0 -> 5
Min Fix: 20 -> 10
Reject Threshold of Innov: 2 -> 1
Carrier-Phase Error a+b: 0.003 -> 0.006
Carrier-Phase Error sinEl: 0.003 -> 0.006
Carrier-Phase Bias: 0.0001 -> 0.001

Here’s a brief explanation of each of the above changes:

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

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

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

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

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

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

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

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

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

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

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

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

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


15 thoughts on “Another look at L1/L5 cellphone PPK with RTKLIB and an Xiaomi Mi8 phone”

    1. Hi Костя. There are several Android apps that will log raw observations on the phone. GNSSLogger and Geo++ are two that I have used. The collected logs can either be sent to the final destination using the “Send” feature or I tend to download the logs using a USB cable connected between the phone and my computer.


      1. Hi. I used Geo ++ Rinex logger to record over one hour of data. I accessed the data in my file internal storage saved as RINEX_STREAMER_MORE_OBS. I’m having issues opening the data. Each time i click on it, it says ‘you don’t have any apps that can open this type of file’. Any suggestions on how to go about this. Thanks.


        1. Hi Tope. I have not tried to open any observation data on the phone since I don’t have any way to process it there. I first transfer the files to my computer, then open them with RTKLIB.


        2. Right click the file and choose “OPEN WITH”, then choose “NotePad”, or a spreadsheet(probably comma delimited) I’m starting to experiment with multiple Cellphone GPS … in a small area(a few miles?), simultaneous GPS’s RINEX or TXT files, should be equally inaccurate in location … imagine a table with 2 mark points moving around … the relative location between the 3 marked X’s never changes … simple DIFFERENTIAL GPS, to remove atmospheric errors(the largest portion of error)


  1. Very impressed work, I appreciate your Unselfish Sharing. I followed your steps and duplicate the fixed solutions successfully. However , what still confuses me is that I have no ideal about the benchmark coordinates(‘true’ coordinates) for the kinematic solutions, I mean may be the STD of the solutions is satisfactory but the RMS is unexpected. The rover and base rinex file is available on the website, the benchmark coordinates of both is absent. So how can I assess the precision and accuracy respectively?

    Liked by 1 person

    1. Hi Gong. The STD and RMS statistics generated by RTKPLOT are only meaningful for a static solution for which you know the ground truth and enter that as the origin of the plot. In this particular solution, the baseline is very short and the solution is known to be a circle since the phone was rotated around a central pivot. The deviation in the solution from a perfect circle is very likely to be a reasonable estimate of the errors in the solution even though it does ignore any possible constant offset error in the short fixed distance between the center of the circle and the base. Remember that an RTK or PPK solution is effectively a relative solution from base to rover and not an absolute solution. It is generally converted to an absolute solution by adding the precise base location to the relative solution.


      1. Thank you for your reply! Actually, in my previous study, I can’t fix the ambiguity and only float solution are
        available. According to the benchmark coordinate, there is a decimeter-level bias can’t be removed in solutions. That makes me fairly confused. However, thanks to your sharing, I have found a way to get the fixed solution and high precision results. I would keep learning your contributions .


  2. Hello! First of all, thank you for sharing your large experience on using RTKLIB with low cot receiver. I am starting to investigate the use of a (dual frequency) Huawei P30 PRO for precise point and relative positioning. I just downloaded your demo5 b34b version. Is this the best one for me to use? I ask you because you mentioned the b34_d3 version in the text. Thank you very much, Luiz

    Liked by 1 person

      1. Hi, thank you for your reply! I am using the b34b version then. I have an additional question: I would like to generate a dual frequency solution using L1/L5 from GPS and Galileo but I tried selecting L1+L2/E5b+L5/E5a and got no results. I am not sure if this selection requires that L2 is present as well – and it is not, as the smartphone does not track it. Would I need an option “L1+L5/E5a” which does not exist?
        As per the b34b version, I noticed that the RTKPLOT graph on satellite visibility is showing 1 frequency short for BeiDou and Galileo, based on a CORS station rinex data. The 2.4.3 b34 shows it correctly. Thank you!


        1. Hi Luiz. The L1/L2/L5 solution option does not require L2 observations so you should be OK. I would set debug trace level to 2, rerun the solution and then look at the debug trace file for clues to why it did not give results. Be aware though that getting decent RTK results from cellphone data is currently very difficult due to the low performance antennas used in the phones. I am not aware of the frequency display issue in the b34b version of RTKPLOT. If you could send the obs file you are trying to plot to I will take a look.


          1. Hi Luiz. Thanks for sending the obs file. The demo5 version of RTKLIB is primarily focused on low cost receivers so I have set the build options to default to just three frequencies per constellation, generally L1/L2/L5 to reduce memory consumption and improve performance.  Your observation file includes some additional frequencies and requires RTKLIB to be built with the NFREQ build option increased from 3 to 5 if you want to see these plotted.  If you are using the Embarcadero compiler in Windows you can set this in the project options under the C++ compiler conditional defines, or under Linux, in the makefile.  In general, RTKLIB doesn’t have full support and isn’t well tested for the additional frequencies but it will at least plot them.

            Liked by 1 person

  3. Very interesting article, thank you very much ! A lot of the intricacies are above my head but with all the details you share I can see a “path” to improving 😉 I’ll try to record some logs on my Mi 8 in unobstructed environments and run the code/config files to see what kind of results I get. With my previous attempts I never got a PPK Fix but the data was logged with obstructions around.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: