Evaluating the quality of moving position solutions

Stationary position solutions are easy to evaluate, an ideal solution is a single point, any deviation from this point must be error. It is possible that the point itself is in error in an absolute sense but since I am only interested in the distance between receivers I am not concerned with absolute error.

Evaluating a solution for a moving receiver is more difficult since it is not easy to know what is the correct solution to compare to. If I had an expensive high precision receiver/antenna I could mount it on the same platform as the test receiver and use that as a reference, but I don’t have that, so I need to come up with another solution.

Mounting two low cost receivers on the moving platform gives us a couple of options for evaluation. First of all, since I have data from two nearby base stations, I can calculate two solutions, each using one of the low cost moving receivers as rover, and one of the COREX base stations for base data. Since the two low cost receivers remain a fixed distance apart, the difference between the two solutions should be constant in magnitude. The direction of the difference however is varying as the orientation changes. This means the difference between the two solutions should be a circle with radius equal to the distance. Since each solution is based on different base stations, and different receivers they should be fairly independent. The fact that one of the base stations is reporting GPS only, and the other is reporting GPS and GLONASS should also increase the independence of the solutions, since they are also using different sets of satellites,at least if we include GLONASS satellites in the solution. This not the case at the moment since the default config has GLONASS turned off, but we will turn it on soon.

RTKPLOT has a nice feature which allows us to plot the difference between two solutions. Click on the “1-2” button on the menu bar to see this, after opening both solutions from the file menu.

Below are the two solutions plotted on top of each other. Since the scale is 20 meters per division, it is impossible to see any difference between them.

sol3

Here is a plot of the difference between the two solutions while the car was stationary and driving in low velocity circles. Now the scale is 10 cm per division. We can see the expected circle, and the error from that circle is generally less than 5 cm once the solution converges to a fixed solution. I’ve jumped ahead a bit, since the solution in the plots includes some input parameter changes as well as some code changes, so the default solution we just calculated won’t be as clean, but we’ll get to those changes soon. The good news is that it looks like we are getting errors of only a few centimeters after the solution has converged, even when both receivers are moving.

sol4

There is also another, simpler way to evaluate the quality of the solutions if, as in my case, we are interested only in relative error and not absolute error. Instead of generating absolute solutions using the COREX base stations, we can calculate a differential solution directly, using one of the low-cost receivers as the base and the other as the rover. The result will then be the position difference between the two receivers. Since the distance between the two receivers is fixed but the orientation between them is varying as the platform they are on is moving, the solution should be a circle, again with radius equal to the distance between the two receivers. In this case, unlike the previous one, we do not get any absolute position information, only the distance between the two receivers. Note that even though the base is moving in this case, we do not use the “Moving-Base” solution option from the positioning mode choices, but stick with “Kinematic”. The solution algorithm does not need exact position of the base, only approximate location. We use the approximate starting location of the base receiver which is in the header of the RINEX observation file and ignore the fact that the base is moving. As long as we are concerned only with relative position between the two receivers and the base does not move a large distance from its starting point, this is a valid assumption to make.

Here is a plot of the result from using both base and rover on the moving platform. Note that it is very similar to the previous plot, generated from the difference between two absolute solutions, and again the error is only a few centimeters once the solution has converged.  As in the last plot, it does include some changes from the default input parameters and code that we will get to later.

sol5

This is the form of solution I will use for evaluating the effectiveness of various input parameters and algorithm changes.

Kinematic solution with RNX2RTKP

 

In the last post we used the RTKPOST GUI to generate a kinematic position solution using “ebay.obs” for the rover data and “zdv13480.15o” for the base station data. To do the equivalent run from RNX2RTKP we use the following command line, run from the folder containing the data files.

rnx2rtkp -k test1.conf -o out.pos ebay.obs zdv13480.15o ebay.nav

The -k option is to specify the configuration file, the -o option is the output file. In this case we use the configuration file we saved from RTKPOST in the previous post.

To plot the calculated solution use the following command line:

rtkplot out.pos

This calls up the same plot GUI we previously accessed by clicking on the “Plot” button in the RTKPOST GUI.

The plots should be identical between this solution and the previous solution generated with RTKPOST.

To keep track of changes between various runs with the same data, I use a matlab wrapper to create a sub-folder, copy the config file, trace file, stats file, and result file to that sub-folder, input a short description of the run, and also save that to the sub-folder.

Kinematic solution with RTKPOST

Kinematic solution with RTKPOST

RTKPOST is the GUI tool in RTKLIB to calculate position solutions. Most of the time I find the CUI version (RNX2RTKP) better fits my needs, but just to check everything is working, it is probably easier to use RTKPOST the first time.

For a demonstration of using RTKPOST to find a kinematic position solution, I will use the ZDV1 (COREX station data) for base station data and the “EBAY” (Ublox M8N receiver data) for rover data. Zipped versions of this data is available here. (Update 1/24/20:  This data is no longer available, but similar data can be downloaded from  here.)  Since the exact location of ZDV1 is known and is in the observation file header, the kinematic solution will give us an absolute position by solving for the relative distance between the rover and the base, and then adding that to the base location. I set up the GUI inputs as shown below to point the program to the correct observation and navigation files. If you use my data, be sure to change the paths to match where you saved the data to.

rtkpost

For this first run, to keep things as simple as possible, we will make just two changes from the default setup. Use the “Options” button to get to the options menu. Under the “Setting 1” tab, change “Positioning mode” from “Single” to “Kinematic”. This will give us a differential solution using carrier phase info instead of an absolute solution using only pseudorange. Next, under the “Positions” tab, change the first field under “Base Station” from “Lat/Lon/Height” to RINEX Header Position. This will tell RTKPOST to get the base station location from the header of the observation file.

While you are in the options menu, click the “Save” button, and save the options setup to a location you will remember later. We will use this file as the configuration input file for the CUI version. Then click OK to exit the Options menu.

Click the “Execute” button to calculate position and then “Plot” to see the solution. Select “Gnd Trk” and zoom in and it should look like this. The two rectangles are parking lots. The yellow represents a float solution, the green a fixed solution. The fact that we were able to get a fixed solution at least part of the time is a sign things are working reasonably well.

sol1

Zooming into the initial time period when the car was stationary we see the plot below. Since the receiver is not moving during this time, any movement in the solution represents error. During the initial convergence of the kalman filter we see quite a lot of error, but once it does converge, we do get a fixed solution for 5 of the 20 minutes which appears as green in the plot below. During this time you can see the error is roughly +/- 1cm in the xy direction which again is a good sign things are working.

sol2

Note about restoring RTKPOST default options:  There is no button in RTKPOST to reset the options to defaults and it remembers the options from the previous session when restarted, so there is no obvious way to put it back to defaults.  The best way I have found to do this is to delete the “rtkpost.ini” file saved in the rtklib\bin folder before starting RTKPOST.

My reference raw GPS data set

In many of the posts to follow I hope to compare the effectiveness of different input options and algorithm modifications to the position solution. To do this it will be useful to have a single representative set of raw data to use for comparisons as well as a set of metrics to compare. I will discuss the metrics in a future post, here I put together a data set that I believe will be a good test, at least for my goals.

My interest is in relatively low velocity scenarios with open skies (I’ll talk more about the details in another post). To generate my reference data sets, I mounted two Ublox M8N receivers with basic patch antennas on top of a car, both antennas sitting on the metal roof for a decent ground plane. I then collected about twenty minutes of data with the car stationary, followed by driving around in circles in a open parking lot (no nearby trees or buildings) for roughly another twenty minutes, followed by another twenty minutes of driving on open roads with some nearby trees at higher speeds. The low velocity circles are what I am most interested in, but collected the other data to allow additional comparisons under different conditions.

I am fortunate enough to have two CORS base stations within 10 km of where I collected my data and so I downloaded the data for both stations (ZDV1 and TMGO) for the same time period. ZDV1 is GPS data only, TMGO includes both GPS and GLONASS data.  That gives me four sets of data. Two are from Ublox M8N receivers with patch antennas, both moving but staying a constant distance from each other, both containing only single frequency (L1) data. The other two are dual-frequency (L1 and L2), fixed position. For consistency, I will use this set of data when comparing results in most of my future posts. Here is a link to a zipped copy of all four data sets in case they may be of use to anybody else. In the file names, “cga” refers to data from the M8N receiver bought from CGA, and “ebay” refers to data from the M8N receiver bought on Ebay.

Base station data for RTKLIB

RTKLIB has a number of different algorithms it can use to calculate position. The first two in the list are “Single” and “DGPS”. Both of these methods use only the pseudorange data and not the carrier phase information. Without the carrier phase information, the precision of these methods is quite low, and probably worse than what the GPS receiver would provide without RTKLIB. In general they are not very interesting other than possibly for some initial debug during setup. The rest of the algorithms can be divided into two groups, differential and PPP. The differential algorithms determine position relative to a known nearby location while the PPP (precise point positioning) algorithms determine absolute position. In general, the data quality of the low cost hardware we are using is only good enough to use with the differential methods and not the PPP methods, so we will focus on those. RTKLIB supports four differential modes: Static, Kinematic, Moving-Base, and Fixed. The kinematic mode is designed to calculate the relative position between a fixed base and a moving rover and that is what we will use. It can also be used with a moving base if we are concerned only with finding the distance between the two and not trying to translate that to a fixed position, and we will use it this way as well.

In my particular application, I am interested only in the distance between two receivers and not in any absolute locations. In this case I will collect data from two low-cost receivers and use one as the base and the other as the rover. In many cases, though, it is useful to use an existing ground station as the base and the low cost receiver as the rover. I use this method for testing and verification, even though I don’t plan to use it in my final solution.

In the US, base data is available online for free from many GPS stations in the CORS network.  Here’s a map of CORS station locations.

cors_map.PNG

For the ground stations I have used, it is generally available online less than an hour after it is taken. It is fairy easy to pull it manually using a user-friendly form from the CORS webpage, or you can use the RTKGET utility in RTKLIB. Be aware that some stations have only GPS data, and some have both GPS and GLONASS data.

Converting raw receiver data to standard text format using CONVBIN

At this point, we have collected raw receiver output data in a Ublox binary format file. We need to convert this to RINEX format before RTKLIB will process it. RINEX (Receiver Independent Exchange Format) is a standard text data format supported by many receivers and post‐processing analysis software.

This can be done with RTKCONV, the GUI conversion program in RTKLIB, but I prefer to use CONVBIN, the CUI version.  To avoid having to remember the exact command format, I automate the call with a simple matlab wrapper. To convert binary data from a file named “testdata.ubx” the format of the command to CONVBIN will look like this:

convbin -od -os -oi -ot -f 1 -ts 2015/12/14 17:25:00 -te 2015/12/14 18:25:00 testdata.ubx

The command line options are all well documented in the RTKLIB user manual and there are many I am not using but here’s a brief explanation of the options I am using:

-od: include doppler frequency data in observation output file

-os: include SNR data in observation output file

-oi: include iono correction in nav output file header

-ot: include time correction in nav output file header

-f 1: one frequency (L1 only)

-ts: start time

-te: end time

I leave off the start time and end time options when I choose to process the complete file.

The output of this command will be a set of text files. The .obs file contains the observation data which in this case is the pseudorange, carrier phase, doppler, and SNR from each satellite for each epoch. The start of this file should look something like this:

obsfile

The top section is the file header, followed by a timedate stamp and list of satellites for the first epoch. After that is a list of pseudorange, carrier phase, doppler, and SNR numbers for five satellites from the first epoch, each row being one satellite. See here for a complete description for the RINEX observation file data format.

There will also be a .nav file containing navigation data for the GPS satellites, a .gnav file for GLONASS navigation data, a .hnav file for geosynchronous satellite navigation data and a .sbs file containing SBAS correction data. All of these files are in text format and we will need most, if not all of them, as inputs to generate position data.

The data is now in a standard format that can be processed by RTKLIB to determine position.

Collecting raw Ublox data with RTKLIB

[Update 2/8/20:  At this point in time, much of this post is out of date. Please see some of my more recent posts for more up to data information. Specifically though:

  • Another RTKLIB choice is my demo5 code which has been optimized for low-cost receivers, especially the u-blox receivers.  This is the code I would recommend now.  The source code is available here and Windows executables are available here]
  • The TRK-MEAS and TRK-SFRBX messages on the most recent version of the M8N are now scrambled, making raw observations unavailable on this receiver. You will need to use either the u-blox M8T, M8P, or F9P receiver, and enable the RXM-RAWX and RXM-SFRBX messages.
  • These days I typically configure the receiver using u-blox u-center and save to flash rather than using the RTKLIB CMD messages, since the user interface is simpler
  • The Embarcadero compiler is now available for free and has been for quite a while]

At this point, we have verified that the GPS hardware and the link to the PC are working properly, and are ready to start using RTKLIB to collect the raw GPS data.

Start by downloading RTKLIB from Github.  Choose either the main branch (2.4.2.11) which is the stable branch or 2.4.3 which is a beta branch (currently at revision b8).

Recent check-ins have all been made on the beta branch, but most or all of the recent work appears to be unrelated to Ublox receivers or the  kinematic mode normally used for low cost receivers, so the functionality of the code should be nearly identical regardless of which branch you choose.  I am using 2.4.3 because I think it may be easier to merge in any future changes.  The repository includes all the executables, so there is no need to build any code before running it.

There are actually two sets of executables built from the same codebase, a GUI set, and a CUI set.  I am using a combination of the two.  For collecting the raw data, and plotting the results, I use the GUI versions (STRSVR and RTKPLOT) for convenience.  For converting the raw data to RTCM and processing the data, I use the CUI versions (CONVBIN and RTKPOST).  This is for two reasons.  First, because making any changes to the GUI versions requires  an Embarcadero/Borland VCL compiler to re-build the code and this compiler is not available for free.

The CUI versions can be rebuilt with the Microsoft Visual C++ compiler, which is free and one I am also much more familiar with.  The second reason I use the CUIs is because I have found it easier to keep track of all the input and output files for each run when using the CUIs.  I use simple Matlab wrappers to call the CUIs which save all the configuration, input, and output information to a separate folder.  Python wrappers would probably work just as well and it is available for free, but I already have Matlab and again am more familiar with it.

Once RTKLIB is downloaded, and the GPS receiver is connected to your PC via USB, start the STRSVR program (strsvr.exe).  It can be found in the rtklib\bin folder.  Set the Input Stream to “Serial” and use the “Opt” button to set the port and baudrate to match your GPS receiver.  If you’ve been communicating to the receiver with U-center or any other application, make sure you have disconnected them from the com ports to avoid any conflicts.

Next we need to configure the receiver to output the raw GPS signals, pseudorange, carrier phase, doppler, and SNR.   For the Neo-M8N receiver this requires us to use the normally undocumented commands TRK-MEAS and TRK-SFRBX.  First select the “Cmd” button for the input stream and copy the following commands into the “Commands at startup” window.

!UBX CFG-GNSS 0 32 32 1 0 10 32 0 1
!UBX CFG-GNSS 0 32 32 1 6 8 16 0 1
!UBX CFG-MSG 3 15 0 1 0 1 0 0
!UBX CFG-MSG 3 16 0 1 0 1 0 0
!UBX CFG-MSG 1 32 0 1 0 1 0 0

Make sure the “Commands at startup” box is selected, then click the “Save” button to save.  STRSVR will send these commands to the receiver at startup.  The third and fourth commands in this list enable the TRK-MEAS and TRK-SFRBX commands to the USB and UART ports.  See pages 9-13 of this document for an explanation of exactly what these numbers do.  The first and second commands are documented commands to configure how many channels the receiver should allocate to GPS and GLONASS.  Details are explained in the M8 Receiver Description under the CFG-GNSS command.  We could have configured these with the Ublox eval software in the last post, but I thought it was easier to do it all in one place, and also more consistent with other references.

strsvr

Next, set Output (1) to “File” and use the “Opt” button to select a file name to save the data to.  I use a “.ubx” extension for this raw binary data.  Finally, click on “Start” to start collecting receiver data.  If everything is working properly, it should look something like this:

strsvr_run

In the next post we will use RTKLIB to convert this Ublox proprietary binary data to a more friendly text version.

Configuring the GPS receiver

At this point, the GPS receiver is connected to your PC through the USB port and is ready to configure and verify that all is working OK.

Ublox provides a nice evaluation software package for Windows called u-center that makes this very easy.  You can download it for free from here.  It makes it easy to explore all the configuration options for your receiver and make sure everything is working properly before we move to RTKLIB.

After you have downloaded the program and started it up, use the “Port” option in the “Receiver” tab to select the USB port that the receiver is connected to.  It will probably be the only option, and in my case it is “COM3”.

[Update 11/27/16:  If you don’t see your receivers listed in the Port menu it is probably related to some recent windows driver changes from COM ports to location sensors.  See this post for details]

You should see the connection status box at the bottom of the window go to green and list the baud rate that the receiver is configured for, probably 9600 baud if you haven’t changed it.  If everything is working properly, you should now be able to click on the various display icons and see sky positions, signal strengths, status, etc for all the satellites the receiver is tracking.

To configure the receiver, select the “Configuration View” from the View menu.   All of the receiver configuration options for this receiver will appear in the menu and you can read what the currently is set to with the “Poll” button or change the configuration by changing the settings and hitting “Send”.  For details on what all these settings mean, see the Neo-M8 Receiver Description.

I recommend first increasing the baud rate to something faster than the 9600 default.  I found 115200 worked fine with my setup.  To do this, select “PRT” from the “Configure” window and set the “Baudrate” field to 115200, then  select the “Send” button at the bottom of the page.  You may need to re-select the port to let the eval software match its baudrate to the receiver.

 

 

ucenter1

 

Next, select the “Messages View” from the “View” tab.  From here, you can see which NMEA messages are enabled and being output by the receiver.  The enabled messages are displayed in bold.  You may want to disable all of them to reduce unnecessary information from being continuously transferred over the serial port.  We will be using Ublox specific binary messages for RTKLIB so do not need any of the NMEA messages enabled.  Be aware, though, that the eval software is using these messages, so if you disable them, the display windows will stop updating.  To enable or disable a message, right click on it and select the appropriate action.

Once you have the receiver configured properly, you will want to save the settings to the on-board flash.  Do this from the “CFG” menu item on the “Configuration View” by selecting “Save current configuration” and then the “Send” button.

We still need to enable the raw receiver outputs for pseudorange and carrier phase, but since they require using unsupported commands, we will do that from RTKLIB.

Connecting the GPS receiver to a PC

My long term goal is to connect the GPS receivers to battery powered low cost single board computers, probably Raspberry Pi, to make them stand-alone.  For now, though, I am connecting them to my Windows laptop for simplicity.  The Ublox receiver board interface is a UART which can be directly connected to the Raspberry PI, but to connect to the PC we need to first convert the UART signals to USB.  I am using a USB to UART converter board from Sparkfun.  There are many similar boards available, I use this one because Sparkfun happens to be a few miles from my house.  If I place an order in the morning, I can pick it up in the afternoon, which is very convenient (and avoids any shipping charges).

SparkFun FTDI Basic Breakout - 5V
USB to UART converter board from Sparkfun

 

SparkFun FTDI Basic Breakout - 5V
Back of board

 

 

 

 

 

 

 

 

 

 

rcvr
USB to UART converter board connected to GPS receiver (and antenna)

 

 

To connect the two boards requires four wires.

VCC-> VCC
GND -> GND
RX -> TX
TX -> RX

You can’t see in the photo, but I soldered the connecting wires directly to the receiver board and the other ends to a four pin header which I then plugged into the connector on the back of the UART converter board (see connector in the photo above).

The board can now be connected to the PC with a USB cable, making sure you have the right connector for the UART board you are using, in this case, a mini-USB connector for the Sparkfun board.

Note on voltage levels:  USB/UART converter boards come in various combinations of 3.3V and 5V levels for Vcc and I/O.  Make sure you pay attention to the voltage levels when you select a converter board or cable.  The Vcc input on the GPS board is fed into a voltage regulator so Vcc can be either 3.3V or 5V.  RX and TX, however, are connected directly to the M8N chip and must be 3.3V.

I originally used a 5V version of the Sparkfun board and it worked fine for months but eventually I was not able to transmit commands to the receiver any more although everything else seemed to work fine.

m8n_schematic

In the next post, I will cover using the Ublox eval software to talk to and configure the receiver before we start using RTKLIB.

Selecting a GPS receiver

[Update 11/25/16:  See here for a more recent version of this post]

The first thing you will need is a GPS receiver that provides access to the raw GPS position signals;  pseudorange and carrier phase.  There are  only a few low cost GPS chips that provide these signals.  I chose the Ublox receivers because they seem to be the most available and lowest cost option out there, and I was able to find examples of other people successfully using them with RTKLIB, including Tomoji Takasu, the author of RTKLIB (see here).  The NEO-M8 series is the latest generation from Ublox.  The NEO-M8N chip from this family does not officially support output of the raw GPS signals but can be configured to do so with undocumented and unsupported commands over the serial port.

I started with two receivers, the first from CSG.  This is the receiver used by Tomoji in his work in the link above.  It is available for $65.99 + $19.99 for an antenna.  With shipping this came to $92.98.  I do not intend to use this receiver in my final project but bought it for comparison to the lower priced options described below.

UBLOX NEO-M8N GPS GNSS receiver board with SMA for UAV, RobotsHigh performance active GPS antenna

The second receiver is marked as a GY-GPSV3-NEOM8N. It is also use an M8N receiver and is designed for use on drones.  It is available from several suppliers, I bought it on Ebay for $25.78 including antenna and shipping.  This board came with a battery for storing the GPS configuration when the board is powered down.  The CSG board included a battery holder but not a battery.

There is also a version available with an on-board magnetometer (GY-GPSV5-NEOM8N) for an additional $5.  I have bought one of these but have not yet had a chance to test it.  I intend to use these receivers from Ebay with their included antennas for my project given their very low cost.

Both receivers have given me good results with RTKLIB that appear to be quite similar, but I hope to do a more detailed comparison in the future.

 

Update: 5/18/16

For those of you trying to get the best results you can today for a reasonably low price, the ultra-low cost options I have chosen are probably not your best choice.  I’ve cut and paste below some comments from JB that he made on a more recent post.  He makes a good argument for buying the Ublox NEOM8T instead of the NEOM8N, and also suggests spending a little more on the antennas.  I would agree with almost everything he says and am in fact considering switching to the M8T myself at some point.

From JB 5/16/16:

Very impressive work. Thanks for sharing.

I know you are focusing on the cheapest possible equipment (~$30 for receiver and antenna), but if that can be increased to $100 or $150 the capability improves quite a bit and we are still well below the cost of the least expensive dual frequency receivers. If this is too high a price for your project perhaps it may not be for others.

Improving the antenna is often mentioned and those on your typical $30 combo are pretty bad. Even a $10-$15 one like those supplied with the Ublox evaluation kit will help a lot. A $40 or $80 Tallysman will help a bit more. A $1000 NovAtel Pinwheel will be even better, but we’ve completely left the low cost realm. I would really suggest upgrading the couple dollar patch antenna typical of the cheapest equipment.

The other thing to improve of course is the receiver. Starting at several hundred dollars you can get a better performing one than the Ublox, but this is getting expensive. For ~$80 a Ublox M8T receiver can be obtained. This has a major advantage over the M8N as signals from GPS, Glonass, Galileo, and SBAS can all be used for fixed integer work with RTKLIB. With the M8N only GPS can be used.

As a simple example to show the benefits of lots of satellites for fixing the integers I recorded 90 minutes
of static data with two M8Ts attached to Tallysman antennas on a roof of a vehicle under ideal receiving conditions. They were set to record GPS, Glonass, Galileo, and SBAS data at a 1 Hz rate. I then ran the data through RTKPOST chopping it up into 30 three minute segments and attempted to fix the integers in Kinematic mode with a ratio of 3.

First, using only GPS sats (6-8) a fixed integer solution was obtained (within 3 minutes) in 20 of the 30 trials. One of these was incorrect. The average time to a fix for those that did was 59 seconds.

Then the Glonass sats were added (13-15 total sats) and a correct fixed integer solution was obtained every time. On average it took 10 seconds.

Then Galileo and SBAS sats were added (20-22 total) and a correct fix was obtained every time with an average of 1 second to get there.

Of course this data is correlated and real world kinematic performance won’t be as good, but the idea is to show the relative improvement that large numbers of satellites can make.

From JB 5/17/16:

My comments were really more for those who want to try low cost RTK with RTKLIB the way it is now. There are people relatively new to this watching your site and we agree that if they can afford it the M8T route would be the way to go.

I do think you can get more out of an M8N with some software mods (which you are doing), but since Ublox doesn’t support raw data from it – and they’ve already changed things as to enabling raw data with their latest firmware – the M8N seems to be at a dead end for high accuracy work. As you mentioned though, for your purposes it has value.

Of course I haven’t tried every cheap patch antenna out there, but in general you get what you pay for. Under really good conditions I’ve done kinematic work with a $5 antenna, but a $40 one will be a bit more robust. For one of my M8N’s I got a good deal on a Tallysman 1421 antenna ($29) to replace the stock patch and some before and after tests near a pond and metal sided building (multipath) showed substantial improvement. When doing kinematic work, especially RTK, every little advantage helps.

When I added SBAS sats in the test I mentioned that was just the pseudorange and carrier data. I don’t think RTKLIB will use any other SBAS info when differencing. Again, just showing that more sats are better.