Another look at L1/L5 cellphone PPK with RTKLIB

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

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.

A first look at the Broadcom BCM47755 dual-frequency receiver

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

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

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

Rover observations for B47755

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

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

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

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

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

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

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

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

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

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

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

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

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