ardupilot/libraries/AP_GPS/GPS_Backend.cpp
Andrew Tridgell f1cbfb3e46 AP_GPS: change handling of moving baseline yaw
this changes yaw handling in a few ways:

 - GPS yaw now has a timestamp associated with the yaw separate from
   the timestamp associated with the GPS fix

 - we no longer force the primary to change to the UBLOX MB rover when
   it has a GPS yaw. This means we don't change GPS primary due to GPS
   loss, which keeps the GPS more stable. It also increases accuracy
   as the rover is always less accurate in position and velocity than
   the base

 - now we force the primary to be the MB base if the other GPS is a
   rover and the base has GPS lock
2021-07-23 10:19:46 +09:00

398 lines
13 KiB
C++

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_GPS.h"
#include "GPS_Backend.h"
#include <AP_Logger/AP_Logger.h>
#include <time.h>
#include <AP_RTC/AP_RTC.h>
#include <AP_InternalError/AP_InternalError.h>
#define GPS_BACKEND_DEBUGGING 0
#if GPS_BACKEND_DEBUGGING
# define Debug(fmt, args ...) do {hal.console->printf("%s:%d: " fmt "\n", __FUNCTION__, __LINE__, ## args); hal.scheduler->delay(1); } while(0)
#else
# define Debug(fmt, args ...)
#endif
#include <GCS_MAVLink/GCS.h>
extern const AP_HAL::HAL& hal;
AP_GPS_Backend::AP_GPS_Backend(AP_GPS &_gps, AP_GPS::GPS_State &_state, AP_HAL::UARTDriver *_port) :
port(_port),
gps(_gps),
state(_state)
{
state.have_speed_accuracy = false;
state.have_horizontal_accuracy = false;
state.have_vertical_accuracy = false;
}
int32_t AP_GPS_Backend::swap_int32(int32_t v) const
{
const uint8_t *b = (const uint8_t *)&v;
union {
int32_t v;
uint8_t b[4];
} u;
u.b[0] = b[3];
u.b[1] = b[2];
u.b[2] = b[1];
u.b[3] = b[0];
return u.v;
}
int16_t AP_GPS_Backend::swap_int16(int16_t v) const
{
const uint8_t *b = (const uint8_t *)&v;
union {
int16_t v;
uint8_t b[2];
} u;
u.b[0] = b[1];
u.b[1] = b[0];
return u.v;
}
/**
fill in time_week_ms and time_week from BCD date and time components
assumes MTK19 millisecond form of bcd_time
*/
void AP_GPS_Backend::make_gps_time(uint32_t bcd_date, uint32_t bcd_milliseconds)
{
struct tm tm {};
tm.tm_year = 100U + bcd_date % 100U;
tm.tm_mon = ((bcd_date / 100U) % 100U)-1;
tm.tm_mday = bcd_date / 10000U;
uint32_t v = bcd_milliseconds;
uint16_t msec = v % 1000U; v /= 1000U;
tm.tm_sec = v % 100U; v /= 100U;
tm.tm_min = v % 100U; v /= 100U;
tm.tm_hour = v % 100U;
// convert from time structure to unix time
time_t unix_time = AP::rtc().mktime(&tm);
// convert to time since GPS epoch
const uint32_t unix_to_GPS_secs = 315964800UL;
const uint16_t leap_seconds_unix = GPS_LEAPSECONDS_MILLIS/1000U;
uint32_t ret = unix_time + leap_seconds_unix - unix_to_GPS_secs;
// get GPS week and time
state.time_week = ret / AP_SEC_PER_WEEK;
state.time_week_ms = (ret % AP_SEC_PER_WEEK) * AP_MSEC_PER_SEC;
state.time_week_ms += msec;
}
/*
fill in 3D velocity for a GPS that doesn't give vertical velocity numbers
*/
void AP_GPS_Backend::fill_3d_velocity(void)
{
float gps_heading = radians(state.ground_course);
state.velocity.x = state.ground_speed * cosf(gps_heading);
state.velocity.y = state.ground_speed * sinf(gps_heading);
state.velocity.z = 0;
state.have_vertical_velocity = false;
}
void
AP_GPS_Backend::inject_data(const uint8_t *data, uint16_t len)
{
// not all backends have valid ports
if (port != nullptr) {
if (port->txspace() > len) {
port->write(data, len);
} else {
Debug("GPS %d: Not enough TXSPACE", state.instance + 1);
}
}
}
void AP_GPS_Backend::_detection_message(char *buffer, const uint8_t buflen) const
{
const uint8_t instance = state.instance;
const struct AP_GPS::detect_state dstate = gps.detect_state[instance];
if (dstate.auto_detected_baud) {
hal.util->snprintf(buffer, buflen,
"GPS %d: detected as %s at %d baud",
instance + 1,
name(),
gps._baudrates[dstate.current_baud]);
} else {
hal.util->snprintf(buffer, buflen,
"GPS %d: specified as %s",
instance + 1,
name());
}
}
void AP_GPS_Backend::broadcast_gps_type() const
{
char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];
_detection_message(buffer, sizeof(buffer));
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "%s", buffer);
}
void AP_GPS_Backend::Write_AP_Logger_Log_Startup_messages() const
{
#if HAL_LOGGING_ENABLED
char buffer[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN+1];
_detection_message(buffer, sizeof(buffer));
AP::logger().Write_Message(buffer);
#endif
}
bool AP_GPS_Backend::should_log() const
{
return gps.should_log();
}
void AP_GPS_Backend::send_mavlink_gps_rtk(mavlink_channel_t chan)
{
#ifndef HAL_NO_GCS
const uint8_t instance = state.instance;
// send status
switch (instance) {
case 0:
mavlink_msg_gps_rtk_send(chan,
0, // Not implemented yet
0, // Not implemented yet
state.rtk_week_number,
state.rtk_time_week_ms,
0, // Not implemented yet
0, // Not implemented yet
state.rtk_num_sats,
state.rtk_baseline_coords_type,
state.rtk_baseline_x_mm,
state.rtk_baseline_y_mm,
state.rtk_baseline_z_mm,
state.rtk_accuracy,
state.rtk_iar_num_hypotheses);
break;
case 1:
mavlink_msg_gps2_rtk_send(chan,
0, // Not implemented yet
0, // Not implemented yet
state.rtk_week_number,
state.rtk_time_week_ms,
0, // Not implemented yet
0, // Not implemented yet
state.rtk_num_sats,
state.rtk_baseline_coords_type,
state.rtk_baseline_x_mm,
state.rtk_baseline_y_mm,
state.rtk_baseline_z_mm,
state.rtk_accuracy,
state.rtk_iar_num_hypotheses);
break;
}
#endif
}
/*
set a timestamp based on arrival time on uart at current byte,
assuming the message started nbytes ago
*/
void AP_GPS_Backend::set_uart_timestamp(uint16_t nbytes)
{
if (port) {
state.uart_timestamp_ms = port->receive_time_constraint_us(nbytes) / 1000U;
}
}
void AP_GPS_Backend::check_new_itow(uint32_t itow, uint32_t msg_length)
{
if (itow != _last_itow) {
_last_itow = itow;
/*
we need to calculate a pseudo-itow, which copes with the
iTow from the GPS changing in unexpected ways. We assume
that timestamps from the GPS are always in multiples of
50ms. That means we can't handle a GPS with an update rate
of more than 20Hz. We could do more, but we'd need the GPS
poll time to be higher
*/
const uint32_t gps_min_period_ms = 50;
// get the time the packet arrived on the UART
uint64_t uart_us;
if (port) {
uart_us = port->receive_time_constraint_us(msg_length);
} else {
uart_us = AP_HAL::micros64();
}
uint32_t now = AP_HAL::millis();
uint32_t dt_ms = now - _last_ms;
_last_ms = now;
// round to nearest 50ms period
dt_ms = ((dt_ms + (gps_min_period_ms/2)) / gps_min_period_ms) * gps_min_period_ms;
// work out an actual message rate. If we get 5 messages in a
// row with a new rate we switch rate
if (_last_rate_ms == dt_ms) {
if (_rate_counter < 5) {
_rate_counter++;
} else if (_rate_ms != dt_ms) {
_rate_ms = dt_ms;
}
} else {
_rate_counter = 0;
_last_rate_ms = dt_ms;
}
if (_rate_ms == 0) {
// only allow 5Hz to 20Hz in user config
_rate_ms = constrain_int16(gps.get_rate_ms(state.instance), 50, 200);
}
// round to calculated message rate
dt_ms = ((dt_ms + (_rate_ms/2)) / _rate_ms) * _rate_ms;
// calculate pseudo-itow
_pseudo_itow += dt_ms * 1000U;
// use msg arrival time, and correct for jitter
uint64_t local_us = jitter_correction.correct_offboard_timestamp_usec(_pseudo_itow, uart_us);
state.uart_timestamp_ms = local_us / 1000U;
// look for lagged data from the GPS. This is meant to detect
// the case that the GPS is trying to push more data into the
// UART than can fit (eg. with GPS_RAW_DATA at 115200).
float expected_lag;
if (gps.get_lag(state.instance, expected_lag)) {
float lag_s = (now - state.uart_timestamp_ms) * 0.001;
if (lag_s > expected_lag+0.05) {
// more than 50ms over expected lag, increment lag counter
state.lagged_sample_count++;
} else {
state.lagged_sample_count = 0;
}
}
}
}
#if GPS_MOVING_BASELINE
bool AP_GPS_Backend::calculate_moving_base_yaw(const float reported_heading_deg, const float reported_distance, const float reported_D)
{
constexpr float minimum_antenna_seperation = 0.05; // meters
constexpr float permitted_error_length_pct = 0.2; // percentage
bool selectedOffset = false;
Vector3f offset;
switch (MovingBase::Type(gps.mb_params[state.instance].type.get())) {
case MovingBase::Type::RelativeToAlternateInstance:
offset = gps._antenna_offset[state.instance^1].get() - gps._antenna_offset[state.instance].get();
selectedOffset = true;
break;
case MovingBase::Type::RelativeToCustomBase:
offset = gps.mb_params[state.instance].base_offset.get();
selectedOffset = true;
break;
}
if (!selectedOffset) {
// invalid type, let's throw up a flag
INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
goto bad_yaw;
}
{
const float offset_dist = offset.length();
const float min_dist = MIN(offset_dist, reported_distance);
if (offset_dist < minimum_antenna_seperation) {
// offsets have to be sufficently large to get a meaningful angle off of them
Debug("Insufficent antenna offset (%f, %f, %f)", (double)offset.x, (double)offset.y, (double)offset.z);
goto bad_yaw;
}
if (reported_distance < minimum_antenna_seperation) {
// if the reported distance is less then the minimum seperation it's not sufficently robust
Debug("Reported baseline distance (%f) was less then the minimum antenna seperation (%f)",
(double)reported_distance, (double)minimum_antenna_seperation);
goto bad_yaw;
}
if ((offset_dist - reported_distance) > (min_dist * permitted_error_length_pct)) {
// the magnitude of the vector is much further then we were expecting
Debug("Exceeded the permitted error margin %f > %f",
(double)(offset_dist - reported_distance), (double)(min_dist * permitted_error_length_pct));
goto bad_yaw;
}
#ifndef HAL_BUILD_AP_PERIPH
{
// get lag
float lag = 0.1;
get_lag(lag);
// get vehicle rotation, projected back in time using the gyro
// this is not 100% accurate, but it is good enough for
// this test. To do it completely accurately we'd need an
// interface into DCM, EKF2 and EKF3 to ask for a
// historical attitude. That is far too complex to justify
// for this use case
const auto &ahrs = AP::ahrs();
const Vector3f &gyro = ahrs.get_gyro();
Matrix3f rot_body_to_ned = ahrs.get_rotation_body_to_ned();
rot_body_to_ned.rotate(gyro * (-lag));
// apply rotation to the offset to get the Z offset in NED
const Vector3f antenna_tilt = rot_body_to_ned * offset;
const float alt_error = reported_D + antenna_tilt.z;
if (fabsf(alt_error) > permitted_error_length_pct * min_dist) {
// the vertical component is out of range, reject it
goto bad_yaw;
}
}
#endif // HAL_BUILD_AP_PERIPH
{
// at this point the offsets are looking okay, go ahead and actually calculate a useful heading
const float rotation_offset_rad = Vector2f(-offset.x, -offset.y).angle();
state.gps_yaw = wrap_360(reported_heading_deg - degrees(rotation_offset_rad));
state.have_gps_yaw = true;
state.gps_yaw_time_ms = AP_HAL::millis();
}
}
return true;
bad_yaw:
state.have_gps_yaw = false;
return false;
}
#endif // GPS_MOVING_BASELINE