Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ekf2: Add support for use of multiple GPS receivers #9765

Merged
merged 23 commits into from
Jul 22, 2018

Conversation

priseborough
Copy link
Contributor

@priseborough priseborough commented Jun 26, 2018

Submitted in response to #9431

This PR gives ekf2 the ability to use two GPS receivers if available to create a blended solution where the blending weights and position/height offset correction adapts to the reported accuracy. If one receiver loses lock or data, it will automatically switch across to the good receiver.

Because a long term position offset is calculated separately for each receiver, switching between either GPS1, GPS2 and the blended solution can be performed without large position and altitude jumps.

This feature relies on use of the reported accuracy metrics contained in the vehicle_gps_position.eph, epv and s_variance_m_s messages. The EKF2_GPS_MASK parameter controls which accuracy metrics are used to set the blending ratios. By default it is zero which disables the blending. If different model receivers are used, they must both have the same update rate and both output the accuracy metrics selected by the EKF2_GPS_MASK parameter. An RTK and non RTK receiver can be used.

The EKF2_GPS_TAU parameter controls the time constant used to adjust the position offset that is applied to each GPS solution.

The blended GPS solution is published as a third GPS instance and can be found in the log as vehicle_gps_position_2

Bench test log here: https://logs.px4.io/plot_app?log=1bfcfab1-1652-48e3-b349-a42cd493e3cd

Method used to log blended GPS to a third instance may not be correct. I am relying on the uORB orb_publish_auto function to correctly assign a new instance.

Noise on blended lat/lon in log needs investigating. It was not present on logs generated using earlier replay data.

Copy link
Contributor

@RomanBapst RomanBapst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priseborough I'm 80% done with with review but need to run to another project now. Will finish this after work.

_gps_select_index = 1;

} else if (_gps_select_index == 2) {
// use last recever we received data from
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix

@@ -572,6 +628,13 @@ void Ekf2::run()
{
bool imu_bias_reset_request = false;

// becasue we can have a second GPS used as a truth reference to asisst with tuning and algorithm development using replay
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix


for (uint8_t i = 0; i < GPS_MAX_RECEIVERS; i++) {
if (_gps_state[i].fix_type >= 3 && _gps_state[i].sacc >= 0.001f) {
spd_blend_weights[i] = speed_accuracy_sum_sq / (_gps_state[i].sacc * _gps_state[i].sacc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priseborough Just for my understanding. You could also write
spd_blend_weights[i] = 1.0f / (_gps_state[i].sacc * _gps_state[i].sacc);
correct? It should not matter because below you are normalizing the weights again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that is correct

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// report the best valid accuracies and DOP metrics

if (_gps_state[i].eph > 0.0f
&& _gps_state[i].eph < _gps_state[GPS_BLENDED_INSTANCE].eph) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priseborough Did you mean to write
_gps_state[i].eph > _gps_state[GPS_BLENDED_INSTANCE].eph ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to take the smallest non zero value so that won't work either. I need to initialise _gps_state[GPS_BLENDED_INSTANCE].eph to the largest float.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

if (_gps_state[i].epv > 0.0f
&& _gps_state[i].epv < _gps_state[GPS_BLENDED_INSTANCE].epv) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priseborough Also here and all the other cases below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -572,6 +628,13 @@ void Ekf2::run()
{
bool imu_bias_reset_request = false;

// support for dual GPS inputs if available
int gps_subs[2];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete? _gps_subs in class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have both gps_subs[2] and _gps_subs[ORB_MULTI_MAX_INSTANCES].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I needed a separate one for publishing. will fix.

int gps_subs[2];

for (unsigned i = 0; i < 2; i++) {
gps_subs[i] = orb_subscribe_multi(ORB_ID(vehicle_gps_position), i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be with _gps_subs in constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix

@@ -512,6 +565,8 @@ Ekf2::Ekf2():
_vehicle_land_detected_sub = orb_subscribe(ORB_ID(vehicle_land_detected));

for (unsigned i = 0; i < ORB_MULTI_MAX_INSTANCES; i++) {
_gps_subs[i] = -1;
_gps_subs[i] = orb_subscribe_multi(ORB_ID(vehicle_gps_position), i);
Copy link
Member

@dagar dagar Jun 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GPS_MAX_RECEIVERS for _gps_subs, not ORB_MULTI_MAX_INSTANCES.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -584,6 +647,12 @@ void Ekf2::run()
vehicle_status_s vehicle_status = {};
sensor_selection_s sensor_selection = {};

// Position of local NED origin in GPS / WGS84 frame
map_projection_reference_s ekf_origin = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move back?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dagar Can you elaborate on this?


ekf2_timestamps.gps_timestamp_rel = (int16_t)((int64_t)gps.timestamp / 100 - (int64_t)ekf2_timestamps.timestamp / 100);
}
}

// check for second GPS receiver data
bool gps2_updated = false;
orb_check(gps_subs[1], &gps2_updated);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gps_subs[1] -> _gps_subs[1]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix

@@ -165,11 +198,21 @@ class Ekf2 final : public ModuleBase<Ekf2>, public ModuleParams
const float _vel_innov_spike_lim = 2.0f * _vel_innov_test_lim; ///< preflight velocity innovation spike limit (m/sec)
const float _hgt_innov_spike_lim = 2.0f * _hgt_innov_test_lim; ///< preflight position innovation spike limit (m)

// GPS blending and switching
struct gps_message _gps_state[GPS_MAX_RECEIVERS + 1]; ///< internal state data for the physical and blended GPS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, the code below might be a bit easier to follow if you kept the blended GPS in a separate struct rather than the end.
_gps_state[GPS_BLENDED_INSTANCE] -> _gps_blended

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -165,11 +198,21 @@ class Ekf2 final : public ModuleBase<Ekf2>, public ModuleParams
const float _vel_innov_spike_lim = 2.0f * _vel_innov_test_lim; ///< preflight velocity innovation spike limit (m/sec)
const float _hgt_innov_spike_lim = 2.0f * _hgt_innov_test_lim; ///< preflight position innovation spike limit (m)

// GPS blending and switching
struct gps_message _gps_state[GPS_MAX_RECEIVERS + 1]; ///< internal state data for the physical and blended GPS
struct gps_message _gps_output[GPS_MAX_RECEIVERS + 1]; ///< output state data for the physical and blended GPS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to store _gps_output? Couldn't it be discarded at the end of each update_gps_blend_states?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't discard it until after GPS data has been written to the EKF buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will refactor the code to remove the need for that class variable. Refactoring needs to happen anyway because there is a corner case that is not being handled correctly at the moment that results in the output not updating if one of the receivers loses lock. This was picked up in dynamic testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've split the calc_gps_blend_outputs into 3 functions for updating of receiver offsets, application of receiver offsets and calculation of blended receiver output.

I'm using a class variable for outputs because I am unsure how to pass an array of structs by reference without creating a new type, and the compiler would not let me #include and use std::array to create a new type

@RomanBapst
Copy link
Contributor

@priseborough I'm done with review (can't approve on my phone). We will test it tomorrow.

@priseborough priseborough force-pushed the pr-ekfDualGps branch 2 times, most recently from 027b2b1 to 115711a Compare June 28, 2018 02:12
@priseborough
Copy link
Contributor Author

There was a bug found during dynamic ground testing that caused the GPS output state to not be updated f one of the receivers was disconnected or lost lock. I've split the calc_gps_blend_outputs() function into three separate functions for updating steady state (filtered) offsets, correcting physical receiver data using the offsets and calculating a blended output using the corrected receiver data.

Testing on replay shows the algorithm is working properly. However, although printing the lat and lon values in the gps struct one line above where they are published shows the correct values, when I review the log generated by the ekf2 replay, the vehicle_gps_position_2.lat and lon values are not correct so I have a mistake in the publishing of the blended GPS.

@priseborough
Copy link
Contributor Author

The CI is showing px4fmu v2 to be out of flash space.

@priseborough
Copy link
Contributor Author

Bench testing at commit 935b701 shows logging of the third (blended) GPS instance working as expected, so the logging problem appears to a replay issue.

The remaining issue is the large initialisation error in the blended solution for lat and lon that takes several seconds to remove - see bench test result below:

screen shot 2018-06-28 at 3 58 45 pm

Replaying the same data gives a much smaller startup transient - see

screen shot 2018-06-28 at 4 05 42 pm

@priseborough
Copy link
Contributor Author

Last commit 79f41ce seems to have resolved remaining issues. Here are GPS alt, lat and lon from the last test:

screen shot 2018-06-28 at 8 10 56 pm

screen shot 2018-06-28 at 8 11 11 pm

screen shot 2018-06-28 at 8 11 24 pm

@priseborough
Copy link
Contributor Author

After experimenting with some logging options with bench testing, it appears that unless estimator replay data logging is enabled via SDLOG_PROFILE, then the GPS blending does not work correctly and the blended instance logged closely tracks the second receiver . I suspect that publishing of the blended instance could be overwriting data from a physical receiver.

@RomanBapst
Copy link
Contributor

@priseborough We did two flights with dual gps:

In the first one something went wrong, one gps position was showing showing somewhere in the pacific
https://review.px4.io/plot_app?log=0a88185a-28b7-4ce8-b048-328b961d44a6

The second try was good, we managed to fly in position control mode. Maybe it was drifting a bit more than usual but it might not be related to the changes
https://review.px4.io/plot_app?log=4d30ebe3-48f0-433b-8fb9-b1eaf6da5392

I haven't had a chance to look into them.

@hamishwillee
Copy link
Contributor

I hope that when this support is in system through to QGC "someone" will let me know so we can update the docs.

@priseborough
Copy link
Contributor Author

@hamishwillee Good reminder. We will be setting the parameters so that the feature is disabled by default. Is there a way to flag a feature in master as 'beta' until we are happy it has had enough community test coverage?

@hamishwillee
Copy link
Contributor

Good reminder. We will be setting the parameters so that the feature is disabled by default. Is there a way to flag a feature in master as 'beta' until we are happy it has had enough community test coverage?

There is no "formal" way. What I normally do is either
a) Document after it is "ready"
b) Put a big warning near where the feature is documented.

Is there any specialized QGC UI for setup, or is this just going to be a parameter? Obviously we now have to explain what ports you can add the second GPS to, how to turn it on, any tuning, and how to turn off anything else that might be using the port.

I would probably add doc here: https://docs.px4.io/en/gps_compass/, and possibly also here: https://docs.px4.io/en/advanced_features/

@priseborough
Copy link
Contributor Author

@hamish Just one parameter for most applications. There is second time constant parameter controlling how fast the individual GPS offsets are corrected, but I am hoping we can tune that so that 99% of users don't need to touch it.

@priseborough
Copy link
Contributor Author

@RomanBapst The first log appears to be afflicted by the same initialisation bug affecting the lat and lon output that I was chasing and fixed yesterday. The second log shows the blending working as expected for alt, lat, lon and velocity other than 0.4m 'switching' noise on the lat/lon output that appears at certain times. Running the data through the latest version reproduces the noise so this can be investigated and fixed. It does not affect the altitude, so is not an issue with the weighting calculation and appears to be a problem in the way global coordinates are converted to local position deltas and vice versa.

Data from the the second flight:

screen shot 2018-06-29 at 5 33 48 pm

screen shot 2018-06-29 at 5 35 37 pm

screen shot 2018-06-29 at 5 36 52 pm

@hamishwillee
Copy link
Contributor

@hamish Just one parameter for most applications. There is second time constant parameter controlling how fast the individual GPS offsets are corrected, but I am hoping we can tune that so that 99% of users don't need to touch it.

Thanks. YOu could put in a Note in the param definition itself warning that the feature is still not robust, but you would have to remove it later.

I suspect users will also need to use a param to turn off whatever is using the port to which you attach the second GPS. ie SYS_COMPANION

@priseborough
Copy link
Contributor Author

Writing the blended GPS to a third instance of the GPS message is causing problems on replay with the lat,lon values in vehicle_gps_position_2 not matching the values logged using a custom message because the replay and ekf2 are both publishing to that instance.

@dagar is there any way I can make ekf2 write the blended output to a specified instance (eg index 3) when doing replay?

@dagar
Copy link
Member

dagar commented Jul 22, 2018

Rebased on master and currently fitting into px4fmu-v2.

@dagar
Copy link
Member

dagar commented Jul 22, 2018

Let's get this in!

@dagar dagar merged commit fc65939 into PX4:master Jul 22, 2018
@priseborough priseborough deleted the pr-ekfDualGps branch July 23, 2018 05:19
@lingbuweibu
Copy link

If the eph or epv or evh is not change,but the lon or lat is jump,how can I do?

@lingbuweibu
Copy link

// Use the oldest non-zero time, but if time difference is excessive, use newest to prevent a disconnected receiver from blocking updates
uint64_t max_us = 0; // newest non-zero system time of arrival of a GPS message
uint64_t min_us = -1; // oldest non-zero system time of arrival of a GPS message

for (uint8_t i = 0; i < GPS_MAX_RECEIVERS; i++) {
	// Find largest and smallest times
	if (_gps_state[i].time_usec > max_us) {
		max_us = _gps_state[i].time_usec;
	}

	if ((_gps_state[i].time_usec < min_us) && (_gps_state[i].time_usec > 0)) {
		min_us = _gps_state[i].time_usec;
	}
}

The min_us in here is a bug???

@dagar
Copy link
Member

dagar commented Nov 19, 2018

@lingbuweibu no, that's initializes min_us to the largest uint64 value so that the comparison sets min_us to an actual gps time.

@lingbuweibu
Copy link

@lingbuweibu no, that's initializes min_us to the largest uint64 value so that the comparison sets min_us to an actual gps time.

OK

@lingbuweibu
Copy link

Today, I did an experiment,the first was RTK,the fix was six,the second gps was M8N,the fix was four,the "_gps_select_index" was always = "2",when I plug the RTK gps,the data of RTK gps was not changed,the fix was always six, the "_gps_select_index" was always = "0",what can I do to solve the problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants