Servers by jDrones

RTK and general GNSS inaccuracies


(WickedShell) #9

@skyveyor Depends by where you mean logged. On Septentrio it’s logging it’s own format. ArduPilot does not log raw messages from the GPS, rather it logs it’s own internal summary of the GPS status. For more details on whats logged scroll down to the GPS entry on:

What this means is that any detail you ever extract from an Ardupilot log will be limited to 1e7.

(Mike Boland) #10

@WickedShell We do only use the Septentrio logs, due to this lack of decimal accuracy, but unfortunately, the way RTK is done on Ardupilot is by injecting the RTK signal through Mission Planner to Ardupilot which then routes it to the GPS.
And this is where we lose our decimal accuracy.
As ardupilot, and hence Mission Planner, use only 1e7 then no matter how good our RTK is, it is truncated by the transmission to our GPS board.
Even if this were improved to at least 1e9 it would greatly improve things.

Edit: I have been informed by Michael O’Borne that this is rubbish, the RTK solution is passed through to the GPS unmodified so our RTK GPS will be getting full precision from the base station.
This is great news, and as long as your GPS does the logging, and the Septentrio does, we get full precision logs.
For me that is the problem solved but in the long run I think we should work towards better accuracy in the Ardupilot code.

(David) #11


Thanks for the reply. It’s helpful.

Going off on a slight tangent, I am hoping to get your thoughts on the following:

I am working with the boys from North Surveying to get an RTK solution with NMEA sentences. I was told that is not possible, However North Surveying thinks a slight code modification will do the trick.

Yes, its perfectly possible, but the official code is not updated for this. See the “TO-DO” note

// To-Do: add support for proper reporting of 2D and 3D fix
state.status = AP_GPS::GPS_OK_FIX_3D;
We have solved in this way (our version)

// To-Do: add support for proper reporting of 2D and 3D fix
if ((_gps_status != 4) && (_gps_status != 5)) {
state.status = AP_GPS::GPS_OK_FIX_3D;
} else {
// 4 means RTK_FIX and 5 means RTK_FLOAT but it anyway better than plain 3D Fix
state.status = AP_GPS::GPS_OK_FIX_3D_RTK;

Is that all that is necessary to get an RTK solution?



(Francisco Ferreira) #12

That’s surprising David. North Surveying opened a PR months ago ( to add a driver to support their RTK solution. On the discussion of that PR you can see that it was asked that, instead of creating a whole new driver (very similar to the NMEA driver), they improved the NMEA one. Unfortunately we didn’t hear back from them since then.

(George Zogopoulos Papaliakos) #13

Hello everyone!

A friend asked me about this topic and pointed me to this thread, so I came for an update and some further questions. Plus, hunting error sources is fun.

I’ll try to make this a “tutorial” post, present the whole image I have on the topic and ask some questions on top of that.


Obtain accurate positioning data with the intention of:

  1. stamping photos, for use in mapping applications
  2. position feedback for UAV position control

Note: The discussion is based on the assumption that very accurate RTK GPS systems are available to the UAS.

Information Path

  1. A GNSS receiver on the ground station records position solutions: X_g
  2. X_g is converted to RTCM correction data [2] and sent to a GCS, which records it as X_gcs [3]
  3. The GCS sends the solution to the UAV through the GPS_RTCM_DATA MAVLink message [4], containing X_m. The corrections are sent as a binary blob, possibly fragmented into up to 4 messages and sent across multiple messages.
  4. Ardupilot captures the MAVLink message and unpacks the RTCM packets into an ‘rtcm_buffer’ struct [5]. It stores the correction data struct ΔX_a until it is complete.
  5. Ardupilot sends ΔX_a to the onboard GNSS receiver [6], repackaging it as ΔX_o (the original form), according to the receiver driver [7].
  6. The onboard GNSS receiver calculates the correction, creating a more accurate solution X_s
  7. X_s is sent from the onboard receiver to Ardupilot, which stores it as X_a’
  8. Ardupilot uses X_a’ in its EKF [8], producing a final position solution X_e [9]

The following image visualizes this information path:

Quantity Representation and Precision

The following table summarizes the representation of each quantity and the precision it can achieve.
All quantities are in degrees when expressed in floats and degrees * 1e7 when expressed in integers.

Quantity Representation Representation Precision Actual Propagated Precision
X_g (Based on driver) (maximum) (maximum)
X_gcs (RTCM) (maximum) (maximum)
X_m MAVLink GPS_RTCM_DATA (maximum) (maximum)
ΔX_a uint8_t rtcm_buffer::buffer (maximum) (maximum)
ΔX_o (Based on driver) (maximum) (maximum)
X_s (Based on driver) (maximum) (maximum)
X_a’ int32 full (integer) (degrees, 7 digits at best)
X_e float32 7.2 digits [1] (meters, decimeter-level)

Specifics on EKF implementation

For this section, we shall examine EKF3, which is the latest EKF revision.

Every Kalman filter has

  • a measurement update step, where sensor data is made available and read
  • a model update step, where the internal model is propagated and
  • an output step, where the filtered data is made available

In this case, EKF3 reads from the GPS driver

const struct Location &gpsloc = gps.location();

where the GPS location is made available in WGS84 coordinates:

struct PACKED Location {
    union {
        Location_Option_Flags flags;                    ///< options bitmask (1<<0 = relative altitude)
        uint8_t options;                                /// allows writing all flags to eeprom as one byte
    // by making alt 24 bit we can make p1 in a command 16 bit,
    // allowing an accurate angle in centi-degrees. This keeps the
    // storage cost per mission item at 15 bytes, and allows mission
    // altitudes of up to +/- 83km
    int32_t alt:24;                                     ///< param 2 - Altitude in centimeters (meters * 100) see LOCATION_ALT_MAX_M
    int32_t lat;                                        ///< param 3 - Latitude * 10**7
    int32_t lng;                                        ///< param 4 - Longitude * 10**7

The current coordinates are subtracted from the EKF origin to obtain the current XY offset:

gpsDataNew.pos = location_diff(EKF_origin, gpsloc);
gpsDataNew.hgt = (float)((double)0.01 * (double)gpsloc.alt - ekfGpsRefHgt);

which is a Float32 variable:

struct gps_elements {
    Vector2f    pos;            // horizontal North East position of the GPS antenna in local NED earth frame (m)
    float       hgt;            // height of the GPS antenna in local NED earth frame (m)
    Vector3f    vel;            // velocity of the GPS antenna in local NED earth frame (m/sec)
    uint32_t    time_ms;        // measurement timestamp (msec)
    uint8_t     sensor_idx;     // unique integer identifying the GPS sensor

These are used during the model update to propagate the vehicle state. This state is declared as:

struct state_elements {
    Quaternion  quat;           // quaternion defining rotation from local NED earth frame to body frame
    Vector3f    velocity;       // velocity of IMU in local NED earth frame (m/sec)
    Vector3f    position;       // position of IMU in local NED earth frame (m)
    Vector3f    gyro_bias;      // body frame delta angle IMU bias vector (rad)
    Vector3f    accel_bias;     // body frame delta velocity IMU bias vector (m/sec)
    Vector3f    earth_magfield; // earth frame magnetic field vector (Gauss)
    Vector3f    body_magfield;  // body frame magnetic field vector (Gauss)
    Vector2f    wind_vel;       // horizontal North East wind velocity vector in local NED earth frame (m/sec)

In turn, this state is manipulated to produce the filter output:

posNE.x = outputDataNew.position.x + posOffsetNED.x;
posNE.y = outputDataNew.position.y + posOffsetNED.y;

declared as:

struct output_elements {
    Quaternion  quat;           // quaternion defining rotation from local NED earth frame to body frame
    Vector3f    velocity;       // velocity of body frame origin in local NED earth frame (m/sec)
    Vector3f    position;       // position of body frame origin in local NED earth frame (m)


  1. EKF reads the GPS location in WGS84 coordinates which is already constrained in accuracy by its int32 representation at 11cm, truncating potential RTK precision. I don’t know if the otherwise superior position accuracy is correctly downgraded to reflect this truncation.
  2. The position measurement is stored as a relative coordinate in meters (float32) , converting from WGS84 coordinates to the NED Cartesian frame.
  3. The filtered position is returned in the NED frame in meters (float32).


  1. Correction data is transmitted through MAVLInk and the telemetry channel, not via a dedicated RF channel.


  1. The autopilot internal location representation is a bottleneck for the RTK solution precision, limiting it to 7 digits (about 11mm)
  2. It looks like the precision could me immediately improved by creating a more accurate GPS driver. The EKF is otherwise ready to accept the improved precision.

Further Questions

Is everything clear? Do you need better wording someplace?


[1] See for actual error calculations.


  • 2018/11/23: Incorporated RTCM data into the information flow.
  • 2018/11/25: Updated information flow image, ArduPilot RTCM handling
  • 2019/01/08: Traced the EKF3 internals

(WickedShell) #14

There are a couple of reductions in precision that you listed that aren’t correct. RTK corrections are almost universially managed with RTCM, and even when they aren’t there is not a MAVLink encoding of the correction data. Instead it’s just a way to transport arbitrary blobs and inject them into the autopilot then the GCS. There is no loss of precisision anywhere between the ground correction source and the aircraft GPS output. That means that propregated precision would be maximum all the way through X_s. From there the precision varies depending upon the specific GPS driver you are using, but in general all the GPS drivers actually have 7 decimal places of precision. (Either the location is actually sent as an integer of location * 1e7 and done on the GPS, or it’s sent as a double (float64), and we extract it into an integer * 1e7 via double math)

This is also purely a precision discussion, which ignores other errors which will be on the same scale to significantly larger (correction latency, base station location accuracy, baseline length, timing and sensor accuracy on other inputs to the EKF).

MAVLink packet for sending corrections is not a bottleneck, although it does represent an precision limit if you are attempting to extract output coordinates from the aircraft via MAVLink.

(George Zogopoulos Papaliakos) #15

Thank you for your reply,

I’ll hunt into the code the variable declarations for documentation purposes and edit my post for completeness.

Editing the drivers to use double instead of floats doesn’t sound do gnarly. Double math is much slower than float, but sparse use won’t break the loop rate.

On the other hand, converting the EKF to double could have a significant impact on memory and loop rate.

Is any of the above the reason why this hasn’t been done yet, or it’s just that demand isn’t high enough?

Regarding precision loss, it’s true that hunting down all of the error sources and propagating them down to the EKF is an ugly business. That’s why one would pay $$$$$ for a good INS and still have have to be careful with the integration.

(George Zogopoulos Papaliakos) #16

Hunted down some sources for RTCM correction representation. Inserted links where appropriate.
Will follow up with more info on the UAV side.

(George Zogopoulos Papaliakos) #17

Updated flow diagram and fixed information up to on-board GNSS receiver.

(Mike Boland) #18

Thank you @Georacer for inputting some useful information on this.
While RTK still seems to be the rising ‘catch phrase’ it would be good to get this precision shortcoming sorted.

I feel it would be of great benefit in the long run to Ardupilot to have this extra precision added.
Your work has at least narrowed down where we should be looking for improvements.

(George Zogopoulos Papaliakos) #19

You’re welcome! I’m not done yet, although I am sure @WickedShell has already answered the question on which are the pain points.

Still, I hope that if I do a good enough job in documentation, it may become easier to someone to make some progress on the matter.

(WickedShell) #20

X_a' and X_e are both stored in int32’s and have exactly 7 decimal places of precision.

(George Zogopoulos Papaliakos) #21

Happy new year, everyone!

I went through EKF3, traced the location update and populated the inner workings in the previous, aggregate post.
Please correct me if I have misunderstood any part of it.

The inner position state and output are expressed in NED and in meters, in float32. This means that there is plenty of resolution to accept RTK accuracy, being a bit conservative to account for flat-Earth modeling.

However, the GPS location is read from the GPS driver as the well-known int32_t degree*10^7 representation which kills all resolution below 11cm.
From my point of view, if somehow the GPS drivers could offer higher resolution, the EKF could happily oblige and produce better results.

One more thing: During the measurement update, the used position accuracy is set to:

float alpha = constrain_float(0.0002f * (lastTimeGpsReceived_ms - secondLastGpsTime_ms),0.0f,1.0f);
gpsPosAccuracy *= (1.0f - alpha);
float gpsPosAccRaw;
if (!gps.horizontal_accuracy(gpsPosAccRaw)) {
    gpsPosAccuracy = 0.0f;
} else {
    gpsPosAccuracy = MAX(gpsPosAccuracy,gpsPosAccRaw);
    gpsPosAccuracy = MIN(gpsPosAccuracy,100.0f);

meaning that, depending on the size of gpsPosAccuracy its value may overshadow a better RTK accuracy.

On the other hand, if gpsPosAccuracy is actually smaller than the actual RTK accuracy, then the truncated RTK values, there is a mismatch between the final measurement uncertainty gpsPosAccuracy and the actual resolution of the GPS position.
This may result in an underperforming filter.

Of course, all of the above is probably already known to the EKF developers, but it was a good exercise for me and a piece of documentation I could not find elsewhere.

(George Zogopoulos Papaliakos) #22

Do you know if a dev could weigh in on this and give an opinion on how much of what I have written is true and how easily RTK precision could be added in the EKFs?

I have a friend who’s doing GIS professionally and is interested in it.

(Nathan E) #23

I’m not a developer, but @WickedShell might be able to weigh in on this. I’m guessing most of what you say is true, but I can’t say for sure.

In my opinion, improving the EKF accuracy down to the sub decimeter level would be neat, but most vehicles wouldn’t respond a lot differently due to other navigational inaccuracies (tuning, etc.).

Since most of us who us GNSS for GIS and surveying conduct post-processing, I don’t think there’s much advantage to real-time position estimates by the EKF anyway. Post-processing will probably always win because reliance on a wireless link for real-time corrections isn’t as good. Post-processing can also have custom filtering, data manipulation, etc. Since logging and post-processing already narrows us to sub-cm accuracy in many cases, I don’t think it matters if aircraft are a few cm off in their navigation routes.

In general, I don’t think many users have an interest in hitting the same 3-cm point every time with their vehicles. I guess rovers conducting lawn-mowing and precision navigation could benefit.

(Mike Boland) #24

Only the Dev’s that have had input to this discussion

At least you have highlighted that there is good decimal accuracy that is being truncated from float32 to int32_t in the code.
It seems good practise to me that it should remain float32 throughout to provide at least consistency.

(Kenny Trussell) #25

@Naterater I am mowing and would like to see the accuracy greater. My tuning is not quite what it should be yet, so I certainly would not say any inaccuracy in my navigation is really due to the 11cm limit. But, it would just be nice to know that this one variability was eliminated.

(George Zogopoulos Papaliakos) #26

Well, even if you want to to offline processing, don’t you need to have a log of the onboard RTK receiver somehow somewhere, correlated with Roll-Pitch-Yaw values?
Where is this captured right now? On an SD card in the receiver? Not 100% informed on this topic.

And one other thing, for which I’m in the dark: What about indoor navigation, with small quads etc.
Is the same resolution truncation happening for UltraWideBand positioning sensors as well?

(Nathan E) #27

Most survey-grade GNSS units have separate on-board logging of the GPS data from the flight that can be post-processed. The easiest way is without lever-arm corrections (keep the GPS directly above the camera), however when the GPS moves a lot relative to the camera, then the lever-arm offsets can be a little more important and then are extracted and estimated from the autopilot logs. Roll-Pitch-Yaw are generally discarded as modern photogrammetry software uses automatic triangulation anyway. If you want to read-up on an inexpensive system that does this, Emlid has a good amount of basic information.

The truncation of some of these EKF items is actually a little concerning, especially for more precise applications like optical flow and as @Georacer said, indoor navigation.

(Kenny Trussell) #28

So, I need straightening out on one thing. Is the current precision in the navigation routines of Ardupilot (not logging) 11 cm or 11 mm? Here and the wikipedia article it refers to say millimeters. In this thread, I see centimeters. That should be at the equator for longitude and everywhere for latitude, I believe. Longitude gets more accurate as you move away from the equator.

mm or cm ???