#include "AP_Baro.h" #include /* 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 . */ /* 1976 U.S. Standard Atmosphere: https://ntrs.nasa.gov/api/citations/19770009539/downloads/19770009539.pdf?attachment=true The US Standard Atmosphere defines the atmopshere in terms of whether the temperature is an iso-thermal or gradient layer. Ideal gas laws apply thus P = rho * R_specific * T : P = pressure, rho = density, R_specific = air gas constant, T = temperature Note: the 1976 model is the same as the 1962 US Standard Atomsphere up to 51km. R_universal: the universal gas constant is slightly off in the 1976 model and thus R_specific is different than today's value */ /* Model Constants R_universal = 8.31432e-3; // Universal gas constant (J/(kmol-K)) incorrect to the redefined 2019 value of 8.31446261815324 J⋅K−1⋅mol−1 M_air = (0.78084 * 28.0134 + 0.209476 * 31.9988 + 9.34e-3 * 39.948 + 3.14e-4 * 44.00995 + 1.818e-5 * 20.183 + 5.24E-6 * 4.0026 + 1.14E-6 * 83.8 + 8.7E-7 * 131.30 + 2E-6 * 16.04303 + 5E-7 * 2.01594) * 1E-3; (kg/mol) M_air = 28.9644 // Molecular weight of air (kg/kmol) see page 3 R_specific = 287.053072 // air specifc gas constant (J⋅kg−1⋅K−1), R_universal / M_air; gama = 1.4; // specific heat ratio of air used to determine the speed of sound R0 = 6356.766E3; // Earth's radius (in m) g0 = 9.80665; // gravity (m/s^2) Sea-Level Constants H_asml = 0 meters T0 = 288.150 K P0 = 101325 Pa rho0 = 1.2250 kg/m^3 T0_slope = -6.5E-3 (K/m') The tables list altitudes -5 km to 0 km using the same equations as 0 km to 11 km. */ #ifndef AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED // default to using the extended functions when doing double precision EKF (which implies more flash space and faster MCU) // this allows for using the simple model with the --ekf-single configure option #define AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED HAL_WITH_EKF_DOUBLE #endif /* return altitude difference in meters between current pressure and a given base_pressure in Pascal. This is a simple atmospheric model good to about 11km AMSL. */ float AP_Baro::get_altitude_difference_simple(float base_pressure, float pressure) const { float ret; float temp_K = C_TO_KELVIN(get_ground_temperature()); float scaling = pressure / base_pressure; // This is an exact calculation that is within +-2.5m of the standard // atmosphere tables in the troposphere (up to 11,000 m amsl). ret = 153.8462f * temp_K * (1.0f - expf(0.190259f * logf(scaling))); return ret; } #if AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED || AP_SIM_ENABLED /* Note parameters are as defined in the 1976 model. These are slightly different from the ones in definitions.h */ static const float radius_earth = 6356.766E3; // Earth's radius (in m) static const float R_specific = 287.053072; // air specifc gas constant (J⋅kg−1⋅K−1) in 1976 model, R_universal / M_air; static const struct { float amsl_m; // geopotential height above mean sea-level (km') float temp_K; // Temperature (K) float pressure_Pa; // Pressure (Pa) float density; // Density (Pa/kg) float temp_lapse; // Temperature gradients rates (K/m'), see page 3 } atmospheric_1976_consts[] = { { -5000, 320.650, 177687, 1.930467, -6.5E-3 }, { 11000, 216.650, 22632.1, 0.363918, 0 }, { 20000, 216.650, 5474.89, 8.80349E-2, 1E-3 }, { 32000, 228.650, 868.019, 1.32250E-2, 2.8E-3 }, { 47000, 270.650, 110.906, 1.42753E-3, 0 }, { 51000, 270.650, 66.9389, 8.61606E-4, -2.8E-3 }, { 71000, 214.650, 3.95642, 6.42110E-5, -2.0E-3 }, { 84852, 186.946, 0.37338, 6.95788E-6, 0 }, }; /* find table entry given geopotential altitude in meters. This returns at least 1 */ static uint8_t find_atmosphere_layer_by_altitude(float alt_m) { for (uint8_t idx = 1; idx < ARRAY_SIZE(atmospheric_1976_consts); idx++) { if(alt_m < atmospheric_1976_consts[idx].amsl_m) { return idx - 1; } } // Over the largest altitude return the last index return ARRAY_SIZE(atmospheric_1976_consts) - 1; } /* find table entry given pressure (Pa). This returns at least 1 */ static uint8_t find_atmosphere_layer_by_pressure(float pressure) { for (uint8_t idx = 1; idx < ARRAY_SIZE(atmospheric_1976_consts); idx++) { if (atmospheric_1976_consts[idx].pressure_Pa < pressure) { return idx - 1; } } // pressure is less than the smallest pressure return the last index return ARRAY_SIZE(atmospheric_1976_consts) - 1; } // Convert geopotential altitude to geometric altitude // float AP_Baro::geopotential_alt_to_geometric(float alt) { return (radius_earth * alt) / (radius_earth - alt); } float AP_Baro::geometric_alt_to_geopotential(float alt) { return (radius_earth * alt) / (radius_earth + alt); } /* Compute expected temperature for a given geometric altitude and altitude layer. */ float AP_Baro::get_temperature_from_altitude(float alt) const { alt = geometric_alt_to_geopotential(alt); const uint8_t idx = find_atmosphere_layer_by_altitude(alt); return get_temperature_by_altitude_layer(alt, idx); } /* Compute expected temperature for a given geopotential altitude and altitude layer. */ float AP_Baro::get_temperature_by_altitude_layer(float alt, int8_t idx) { if (is_zero(atmospheric_1976_consts[idx].temp_lapse)) { return atmospheric_1976_consts[idx].temp_K; } return atmospheric_1976_consts[idx].temp_K + atmospheric_1976_consts[idx].temp_lapse * (alt - atmospheric_1976_consts[idx].amsl_m); } /* return geometric altitude (m) given a pressure (Pa) */ float AP_Baro::get_altitude_from_pressure(float pressure) const { const uint8_t idx = find_atmosphere_layer_by_pressure(pressure); const float pressure_ratio = pressure / atmospheric_1976_consts[idx].pressure_Pa; // Pressure ratio is nonsensical return an error?? if (!is_positive(pressure_ratio)) { INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control); return get_altitude_AMSL(); } float alt; const float temp_slope = atmospheric_1976_consts[idx].temp_lapse; if (is_zero(temp_slope)) { // Iso-thermal layer const float fac = -(atmospheric_1976_consts[idx].temp_K * R_specific) / GRAVITY_MSS; alt = atmospheric_1976_consts[idx].amsl_m + fac * logf(pressure_ratio); } else { // Gradient temperature layer const float fac = -(temp_slope * R_specific) / GRAVITY_MSS; alt = atmospheric_1976_consts[idx].amsl_m + (atmospheric_1976_consts[idx].temp_K / atmospheric_1976_consts[idx].temp_lapse) * (powf(pressure_ratio, fac) - 1); } return geopotential_alt_to_geometric(alt); } /* Compute expected pressure and temperature for a given geometric altitude. Used for SITL */ void AP_Baro::get_pressure_temperature_for_alt_amsl(float alt_amsl, float &pressure, float &temperature_K) { alt_amsl = geometric_alt_to_geopotential(alt_amsl); const uint8_t idx = find_atmosphere_layer_by_altitude(alt_amsl); const float temp_slope = atmospheric_1976_consts[idx].temp_lapse; temperature_K = get_temperature_by_altitude_layer(alt_amsl, idx); // Previous versions used the current baro temperature instead of an estimate we could try to incorporate this??? non-standard atmosphere // const float temp = get_temperature(); if (is_zero(temp_slope)) { // Iso-thermal layer const float fac = expf(-GRAVITY_MSS / (temperature_K * R_specific) * (alt_amsl - atmospheric_1976_consts[idx].amsl_m)); pressure = atmospheric_1976_consts[idx].pressure_Pa * fac; } else { // Gradient temperature layer const float fac = GRAVITY_MSS / (temp_slope * R_specific); const float temp_ratio = temperature_K / atmospheric_1976_consts[idx].temp_K; // temperature ratio [unitless] pressure = atmospheric_1976_consts[idx].pressure_Pa * powf(temp_ratio, -fac); } } /* return air density (kg/m^3), given geometric altitude (m) */ float AP_Baro::get_air_density_for_alt_amsl(float alt_amsl) { alt_amsl = geometric_alt_to_geopotential(alt_amsl); const uint8_t idx = find_atmosphere_layer_by_altitude(alt_amsl); const float temp_slope = atmospheric_1976_consts[idx].temp_lapse; const float temp = get_temperature_by_altitude_layer(alt_amsl, idx); // const float temp = get_temperature(); float rho; if (is_zero(temp_slope)) { // Iso-thermal layer const float fac = expf(-GRAVITY_MSS / (temp * R_specific) * (alt_amsl - atmospheric_1976_consts[idx].amsl_m)); rho = atmospheric_1976_consts[idx].density * fac; } else { // Gradient temperature layer const float fac = GRAVITY_MSS / (temp_slope * R_specific); const float temp_ratio = temp / atmospheric_1976_consts[idx].temp_K; // temperature ratio [unitless] rho = atmospheric_1976_consts[idx].density * powf(temp_ratio, -(fac + 1)); } return rho; } /* return current scale factor that converts from equivalent to true airspeed */ float AP_Baro::get_EAS2TAS_extended(float altitude) const { float density = get_air_density_for_alt_amsl(altitude); if (!is_positive(density)) { // above this height we are getting closer to spacecraft territory... const uint8_t table_size = ARRAY_SIZE(atmospheric_1976_consts); density = atmospheric_1976_consts[table_size-1].density; } return sqrtf(SSL_AIR_DENSITY / density); } /* Given the geometric altitude (m) return scale factor that converts from equivalent to true airspeed used by SITL only */ float AP_Baro::get_EAS2TAS_for_alt_amsl(float alt_amsl) { const float density = get_air_density_for_alt_amsl(alt_amsl); return sqrtf(SSL_AIR_DENSITY / MAX(0.00001,density)); } #endif // AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED || AP_SIM_ENABLED /* return geometric altitude difference in meters between current pressure and a given base_pressure in Pascal. */ float AP_Baro::get_altitude_difference(float base_pressure, float pressure) const { #if AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED const float alt1 = get_altitude_from_pressure(base_pressure); const float alt2 = get_altitude_from_pressure(pressure); return alt2 - alt1; #else return get_altitude_difference_simple(base_pressure, pressure); #endif } /* return current scale factor that converts from equivalent to true airspeed valid for altitudes up to 11km AMSL assumes USA 1976 standard atmosphere lapse rate */ float AP_Baro::get_EAS2TAS_simple(float altitude, float pressure) const { if (is_zero(pressure)) { return 1.0f; } // only estimate lapse rate for the difference from the ground location // provides a more consistent reading then trying to estimate a complete // ISA model atmosphere float tempK = C_TO_KELVIN(get_ground_temperature()) - ISA_LAPSE_RATE * altitude; const float eas2tas_squared = SSL_AIR_DENSITY / (pressure / (ISA_GAS_CONSTANT * tempK)); if (!is_positive(eas2tas_squared)) { return 1.0f; } return sqrtf(eas2tas_squared); } /* return current scale factor that converts from equivalent to true airspeed */ float AP_Baro::_get_EAS2TAS(void) const { const float altitude = get_altitude_AMSL(); #if AP_BARO_1976_STANDARD_ATMOSPHERE_ENABLED return get_EAS2TAS_extended(altitude); #else // otherwise use function return get_EAS2TAS_simple(altitude, get_pressure()); #endif } // lookup expected temperature in degrees C for a given altitude. Used for SITL backend float AP_Baro::get_temperatureC_for_alt_amsl(const float alt_amsl) { float pressure, temp_K; get_pressure_temperature_for_alt_amsl(alt_amsl, pressure, temp_K); return KELVIN_TO_C(temp_K); } // lookup expected pressure in Pa for a given altitude. Used for SITL backend float AP_Baro::get_pressure_for_alt_amsl(const float alt_amsl) { float pressure, temp_K; get_pressure_temperature_for_alt_amsl(alt_amsl, pressure, temp_K); return pressure; } /* return sea level pressure given a current altitude and pressure reading this is the pressure p0 such that get_altitude_difference(p0, pressure) == altitude this function is used during calibration */ float AP_Baro::get_sealevel_pressure(float pressure, float altitude) const { const float min_pressure = 0.01; const float max_pressure = 1e6; float p0 = pressure; /* use a simple numerical gradient descent method so we don't need the inverse function. This typically converges in about 5 steps, we limit it to 20 steps to prevent possible high CPU usage */ uint16_t count = 20; while (count--) { const float delta = 0.1; const float err1 = get_altitude_difference(p0, pressure) - altitude; const float err2 = get_altitude_difference(p0+delta, pressure) - altitude; const float dalt = err2 - err1; if (fabsf(err1) < 0.01) { break; } p0 -= err1 * delta / dalt; p0 = constrain_float(p0, min_pressure, max_pressure); } return p0; }