mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
* SIH: explicitly use local velocity for all aerodynamic calculations no functional change * SIH: add param & vars for wind and apparent vel no functional change * SIH: replace all relevant vels with apparent vel Only places where _v_E is remaining: - ecefToNed to calculate _v_N - generate_rover_ackermann_dynamics - equations_of_motion - parameters_updated, init_variables and _v_N: - ecefToNed - print_status - publish_ground_truth - generate_rover_ackermann_dynamics - equations_of_motion - parameters_updated, init_variables which are all not relevant for aerodynamics. * sih: wind review suggestions * sih wind: switch direction to global wind source direction convention * SIH: clean up variable declarations * SIH: rename variables for consistency * docs: SIH: document new wind parameters * Release notes: note for SIH wind settings --------- Co-authored-by: Matthias Grob <maetugr@gmail.com>
918 lines
29 KiB
C++
918 lines
29 KiB
C++
/****************************************************************************
|
|
*
|
|
* Copyright (c) 2019-2025 PX4 Development Team. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name PX4 nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/**
|
|
* @file sih.cpp
|
|
* Simulator in Hardware
|
|
*
|
|
* @author Romain Chiappinelli <romain.chiap@gmail.com>
|
|
*
|
|
* Coriolis g Corporation - January 2019
|
|
*/
|
|
|
|
#include "aero.hpp"
|
|
#include "sih.hpp"
|
|
|
|
#include <px4_platform_common/getopt.h>
|
|
#include <px4_platform_common/log.h>
|
|
|
|
#include <drivers/drv_pwm_output.h> // to get PWM flags
|
|
#include <lib/drivers/device/Device.hpp>
|
|
|
|
using namespace math;
|
|
using namespace matrix;
|
|
using namespace time_literals;
|
|
|
|
ModuleBase::Descriptor Sih::desc{task_spawn, custom_command, print_usage};
|
|
|
|
Sih::Sih() :
|
|
ModuleParams(nullptr)
|
|
{
|
|
srand(1234); // initialize the random seed once before calling generate_wgn()
|
|
}
|
|
|
|
Sih::~Sih()
|
|
{
|
|
perf_free(_loop_perf);
|
|
perf_free(_loop_interval_perf);
|
|
}
|
|
|
|
void Sih::run()
|
|
{
|
|
_px4_accel.set_temperature(T1_C);
|
|
_px4_gyro.set_temperature(T1_C);
|
|
|
|
parameters_updated();
|
|
|
|
const hrt_abstime task_start = hrt_absolute_time();
|
|
_last_run = task_start;
|
|
_airspeed_time = task_start;
|
|
_dist_snsr_time = task_start;
|
|
_vehicle = static_cast<VehicleType>(constrain(_sih_vtype.get(),
|
|
static_cast<int32_t>(VehicleType::First),
|
|
static_cast<int32_t>(VehicleType::Last)));
|
|
|
|
#if defined(ENABLE_LOCKSTEP_SCHEDULER)
|
|
lockstep_loop();
|
|
#else
|
|
realtime_loop();
|
|
#endif
|
|
exit_and_cleanup(desc);
|
|
}
|
|
|
|
#if defined(ENABLE_LOCKSTEP_SCHEDULER)
|
|
// Get current timestamp in microseconds
|
|
static uint64_t micros()
|
|
{
|
|
struct timeval t;
|
|
gettimeofday(&t, nullptr);
|
|
return t.tv_sec * ((uint64_t)1000000) + t.tv_usec;
|
|
}
|
|
|
|
void Sih::lockstep_loop()
|
|
{
|
|
int rate = math::min(_imu_gyro_ratemax.get(), _imu_integration_rate.get());
|
|
|
|
// default to 400Hz (2500 us interval)
|
|
if (rate <= 0) {
|
|
rate = 400;
|
|
}
|
|
|
|
// 200 - 2000 Hz
|
|
int sim_interval_us = math::constrain(int(roundf(1e6f / rate)), 500, 5000);
|
|
|
|
float speed_factor = 1.f;
|
|
const char *speedup = getenv("PX4_SIM_SPEED_FACTOR");
|
|
|
|
if (speedup) {
|
|
speed_factor = atof(speedup);
|
|
}
|
|
|
|
int rt_interval_us = int(roundf(sim_interval_us / speed_factor));
|
|
|
|
PX4_INFO("Simulation loop with %d Hz (%d us sim time interval)", rate, sim_interval_us);
|
|
PX4_INFO("Simulation with %.1fx speedup. Loop with (%d us wall time interval)", (double)speed_factor, rt_interval_us);
|
|
uint64_t pre_compute_wall_time_us;
|
|
|
|
while (!should_exit()) {
|
|
pre_compute_wall_time_us = micros();
|
|
perf_count(_loop_interval_perf);
|
|
|
|
_current_simulation_time_us += sim_interval_us;
|
|
struct timespec ts;
|
|
abstime_to_ts(&ts, _current_simulation_time_us);
|
|
px4_clock_settime(CLOCK_MONOTONIC, &ts);
|
|
|
|
perf_begin(_loop_perf);
|
|
sensor_step();
|
|
perf_end(_loop_perf);
|
|
|
|
// Only do lock-step once we received the first actuator output
|
|
int sleep_time;
|
|
uint64_t current_wall_time_us;
|
|
|
|
if (_last_actuator_output_time <= 0) {
|
|
PX4_DEBUG("SIH starting up - no lockstep yet");
|
|
current_wall_time_us = micros();
|
|
sleep_time = math::max(0, sim_interval_us - (int)(current_wall_time_us - pre_compute_wall_time_us));
|
|
|
|
} else {
|
|
px4_lockstep_wait_for_components();
|
|
current_wall_time_us = micros();
|
|
sleep_time = math::max(0, rt_interval_us - (int)(current_wall_time_us - pre_compute_wall_time_us));
|
|
}
|
|
|
|
_achieved_speedup = 0.99f * _achieved_speedup + 0.01f * ((float)sim_interval_us / (float)(
|
|
current_wall_time_us - pre_compute_wall_time_us + sleep_time));
|
|
usleep(sleep_time);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void timer_callback(void *sem)
|
|
{
|
|
px4_sem_post((px4_sem_t *)sem);
|
|
}
|
|
|
|
void Sih::realtime_loop()
|
|
{
|
|
int rate = _imu_gyro_ratemax.get();
|
|
|
|
// default to 250 Hz (4000 us interval)
|
|
if (rate <= 0) {
|
|
rate = 250;
|
|
}
|
|
|
|
// 200 - 2000 Hz
|
|
int interval_us = math::constrain(int(roundf(1e6f / rate)), 500, 5000);
|
|
|
|
px4_sem_init(&_data_semaphore, 0, 0);
|
|
hrt_call_every(&_timer_call, interval_us, interval_us, timer_callback, &_data_semaphore);
|
|
|
|
while (!should_exit()) {
|
|
px4_sem_wait(&_data_semaphore); // periodic real time wakeup
|
|
perf_begin(_loop_perf);
|
|
sensor_step();
|
|
perf_end(_loop_perf);
|
|
}
|
|
|
|
hrt_cancel(&_timer_call);
|
|
px4_sem_destroy(&_data_semaphore);
|
|
}
|
|
|
|
void Sih::sensor_step()
|
|
{
|
|
// check for parameter updates
|
|
if (_parameter_update_sub.updated()) {
|
|
// clear update
|
|
parameter_update_s pupdate;
|
|
_parameter_update_sub.copy(&pupdate);
|
|
|
|
// update parameters from storage
|
|
updateParams();
|
|
parameters_updated();
|
|
}
|
|
|
|
perf_begin(_loop_perf);
|
|
|
|
const hrt_abstime now = hrt_absolute_time();
|
|
const float dt = (now - _last_run) * 1e-6f;
|
|
_last_run = now;
|
|
|
|
read_motors(dt);
|
|
|
|
generate_force_and_torques(dt);
|
|
|
|
equations_of_motion(dt);
|
|
|
|
reconstruct_sensors_signals(now);
|
|
|
|
if ((_vehicle == VehicleType::FixedWing
|
|
|| _vehicle == VehicleType::TailsitterVTOL
|
|
|| _vehicle == VehicleType::StandardVTOL)
|
|
&& now - _airspeed_time >= 50_ms) {
|
|
_airspeed_time = now;
|
|
send_airspeed(now);
|
|
}
|
|
|
|
// distance sensor published at 50 Hz
|
|
if (now - _dist_snsr_time >= 20_ms
|
|
&& fabs(_distance_snsr_override) < 10000) {
|
|
_dist_snsr_time = now;
|
|
send_dist_snsr(now);
|
|
}
|
|
|
|
publish_ground_truth(now);
|
|
|
|
perf_end(_loop_perf);
|
|
}
|
|
|
|
void Sih::parameters_updated()
|
|
{
|
|
_T_MAX = _sih_t_max.get();
|
|
_Q_MAX = _sih_q_max.get();
|
|
_L_ROLL = _sih_l_roll.get();
|
|
_L_PITCH = _sih_l_pitch.get();
|
|
_KDV = _sih_kdv.get();
|
|
_KDW = _sih_kdw.get();
|
|
|
|
if (!_lpos_ref.isInitialized()
|
|
|| (fabsf(static_cast<float>(_lpos_ref.getProjectionReferenceLat()) - _sih_lat0.get()) > FLT_EPSILON)
|
|
|| (fabsf(static_cast<float>(_lpos_ref.getProjectionReferenceLon()) - _sih_lon0.get()) > FLT_EPSILON)
|
|
|| (fabsf(_lpos_ref_alt - _sih_h0.get()) > FLT_EPSILON)) {
|
|
_lpos_ref.initReference(static_cast<double>(_sih_lat0.get()), static_cast<double>(_sih_lon0.get()));
|
|
_lpos_ref_alt = _sih_h0.get();
|
|
|
|
// Reset earth position, velocity and attitude
|
|
_lla.setLatitudeDeg(static_cast<double>(_sih_lat0.get()));
|
|
_lla.setLongitudeDeg(static_cast<double>(_sih_lon0.get()));
|
|
_lla.setAltitude(_lpos_ref_alt);
|
|
_p_E = _lla.toEcef();
|
|
|
|
const Dcmf R_E2N = computeRotEcefToNed(_lla);
|
|
_R_N2E = R_E2N.transpose();
|
|
_v_E = _R_N2E * _v_N;
|
|
|
|
_q_E = Quatf(_R_N2E) * _q;
|
|
_q_E.normalize();
|
|
}
|
|
|
|
_MASS = _sih_mass.get();
|
|
|
|
_I = diag(Vector3f(_sih_ixx.get(), _sih_iyy.get(), _sih_izz.get()));
|
|
_I(0, 1) = _I(1, 0) = _sih_ixy.get();
|
|
_I(0, 2) = _I(2, 0) = _sih_ixz.get();
|
|
_I(1, 2) = _I(2, 1) = _sih_iyz.get();
|
|
|
|
// guards against too small determinants
|
|
_Im1 = 100.0f * inv(static_cast<typeof _I>(100.0f * _I));
|
|
|
|
_distance_snsr_min = _sih_distance_snsr_min.get();
|
|
_distance_snsr_max = _sih_distance_snsr_max.get();
|
|
_distance_snsr_override = _sih_distance_snsr_override.get();
|
|
|
|
_T_TAU = _sih_thrust_tau.get();
|
|
|
|
_v_wind_N = Vector3f(_sih_wind_n.get(), _sih_wind_e.get(), 0.f);
|
|
}
|
|
|
|
void Sih::read_motors(const float dt)
|
|
{
|
|
actuator_outputs_s actuators_out;
|
|
|
|
if (_actuator_out_sub.update(&actuators_out)) {
|
|
_last_actuator_output_time = actuators_out.timestamp;
|
|
|
|
for (int i = 0; i < NUM_ACTUATORS_MAX; i++) { // saturate the motor signals
|
|
if ((_vehicle == VehicleType::FixedWing && i < 3) || (_vehicle == VehicleType::TailsitterVTOL && i > 3)) {
|
|
_u[i] = actuators_out.output[i];
|
|
|
|
} else {
|
|
float u_sp = actuators_out.output[i];
|
|
_u[i] = _u[i] + dt / _T_TAU * (u_sp - _u[i]); // first order transfer function with time constant tau
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sih::generate_force_and_torques(const float dt)
|
|
{
|
|
if (_vehicle == VehicleType::Quadcopter) {
|
|
_T_B = Vector3f(0.0f, 0.0f, -_T_MAX * (+_u[0] + _u[1] + _u[2] + _u[3]));
|
|
_Mt_B = Vector3f(_L_ROLL * _T_MAX * (-_u[0] + _u[1] + _u[2] - _u[3]),
|
|
_L_PITCH * _T_MAX * (+_u[0] - _u[1] + _u[2] - _u[3]),
|
|
_Q_MAX * (+_u[0] + _u[1] - _u[2] - _u[3]));
|
|
|
|
_Fa_E = -_KDV * _R_N2E * _v_apparent_N; // first order drag to slow down the aircraft
|
|
_Ma_B = -_KDW * _w_B; // first order angular damper
|
|
|
|
} else if (_vehicle == VehicleType::Hexacopter) {
|
|
/* m5 m0 ┬
|
|
\ / √3/2
|
|
m4 -- + -- m1 ┴
|
|
/ \
|
|
m3 m2
|
|
├1/2┤
|
|
├ 1 ┤ */
|
|
_T_B = Vector3f(0.0f, 0.0f, -_T_MAX * (+_u[0] + _u[1] + _u[2] + _u[3] + _u[4] + _u[5]));
|
|
_Mt_B = Vector3f(_L_ROLL * _T_MAX * (-.5f * _u[0] - _u[1] - .5f * _u[2] + .5f * _u[3] + _u[4] + .5f * _u[5]),
|
|
_L_PITCH * _T_MAX * (M_SQRT3_F / 2.f) * (+_u[0] - _u[2] - _u[3] + _u[5]),
|
|
_Q_MAX * (+_u[0] - _u[1] + _u[2] - _u[3] + _u[4] - _u[5]));
|
|
|
|
_Fa_E = -_KDV * _R_N2E * _v_apparent_N; // first order drag to slow down the aircraft
|
|
_Ma_B = -_KDW * _w_B; // first order angular damper
|
|
|
|
} else if (_vehicle == VehicleType::FixedWing) {
|
|
_T_B = Vector3f(_T_MAX * _u[3], 0.0f, 0.0f); // forward thruster
|
|
// _Mt_B = Vector3f(_Q_MAX*_u[3], 0.0f,0.0f); // thruster torque
|
|
_Mt_B = Vector3f();
|
|
generate_fw_aerodynamics(_u[0], _u[1], _u[2], _u[3]);
|
|
|
|
} else if (_vehicle == VehicleType::TailsitterVTOL) {
|
|
_T_B = Vector3f(0.0f, 0.0f, -_T_MAX * (_u[0] + _u[1]));
|
|
_Mt_B = Vector3f(_L_ROLL * _T_MAX * (_u[1] - _u[0]), 0.0f, _Q_MAX * (_u[1] - _u[0]));
|
|
generate_ts_aerodynamics();
|
|
|
|
// _Fa_E = -_KDV * _R_N2E * _v_apparent_N; // first order drag to slow down the aircraft
|
|
// _Ma_B = -_KDW * _w_B; // first order angular damper
|
|
|
|
} else if (_vehicle == VehicleType::StandardVTOL) {
|
|
|
|
_T_B = Vector3f(_T_MAX * 2 * _u[7], 0.0f, -_T_MAX * (+_u[0] + _u[1] + _u[2] + _u[3]));
|
|
_Mt_B = Vector3f(_L_ROLL * _T_MAX * (-_u[0] + _u[1] + _u[2] - _u[3]),
|
|
_L_PITCH * _T_MAX * (+_u[0] - _u[1] + _u[2] - _u[3]),
|
|
_Q_MAX * (+_u[0] + _u[1] - _u[2] - _u[3]));
|
|
|
|
// thrust 0 because it is already contained in _T_B. in
|
|
// equations_of_motion they are all summed into sum_of_forces_E
|
|
generate_fw_aerodynamics(_u[4], _u[5], _u[6], 0);
|
|
|
|
} else if (_vehicle == VehicleType::RoverAckermann) {
|
|
generate_rover_ackermann_dynamics(_u[1], _u[0], dt);
|
|
}
|
|
}
|
|
|
|
void Sih::generate_fw_aerodynamics(const float roll_cmd, const float pitch_cmd, const float yaw_cmd,
|
|
const float throttle_cmd)
|
|
{
|
|
const Vector3f v_B = _q.rotateVectorInverse(_v_apparent_N);
|
|
const float &alt = _lla.altitude();
|
|
|
|
_wing_l.update_aero(v_B, _w_B, alt, roll_cmd * FLAP_MAX);
|
|
_wing_r.update_aero(v_B, _w_B, alt, -roll_cmd * FLAP_MAX);
|
|
|
|
_tailplane.update_aero(v_B, _w_B, alt, -pitch_cmd * FLAP_MAX, _T_MAX * throttle_cmd);
|
|
_fin.update_aero(v_B, _w_B, alt, yaw_cmd * FLAP_MAX, _T_MAX * throttle_cmd);
|
|
_fuselage.update_aero(v_B, _w_B, alt);
|
|
|
|
// sum of aerodynamic forces
|
|
const Vector3f Fa_B = _wing_l.get_Fa() + _wing_r.get_Fa() + _tailplane.get_Fa() + _fin.get_Fa() + _fuselage.get_Fa() -
|
|
_KDV * v_B;
|
|
_Fa_E = _q_E.rotateVector(Fa_B);
|
|
|
|
// aerodynamic moments
|
|
_Ma_B = _wing_l.get_Ma() + _wing_r.get_Ma() + _tailplane.get_Ma() + _fin.get_Ma() + _fuselage.get_Ma() - _KDW * _w_B;
|
|
}
|
|
|
|
void Sih::generate_ts_aerodynamics()
|
|
{
|
|
// velocity in body frame [m/s]
|
|
const Vector3f v_B = _q.rotateVectorInverse(_v_apparent_N);
|
|
|
|
// the aerodynamic is resolved in a frame like a standard aircraft (nose-right-belly)
|
|
Vector3f v_ts = _R_S2B.transpose() * v_B;
|
|
Vector3f w_ts = _R_S2B.transpose() * _w_B;
|
|
float altitude = _lpos_ref_alt - _lpos(2);
|
|
|
|
Vector3f Fa_ts{};
|
|
Vector3f Ma_ts{};
|
|
|
|
for (int i = 0; i < NB_TS_SEG; i++) {
|
|
if (i <= NB_TS_SEG / 2) {
|
|
_ts[i].update_aero(v_ts, w_ts, altitude, _u[5]*TS_DEF_MAX, _T_MAX * _u[1]);
|
|
|
|
} else {
|
|
_ts[i].update_aero(v_ts, w_ts, altitude, -_u[4]*TS_DEF_MAX, _T_MAX * _u[0]);
|
|
}
|
|
|
|
Fa_ts += _ts[i].get_Fa();
|
|
Ma_ts += _ts[i].get_Ma();
|
|
}
|
|
|
|
const Vector3f Fa_B = _R_S2B * Fa_ts - _KDV * v_B; // sum of aerodynamic forces
|
|
_Fa_E = _q_E.rotateVector(Fa_B);
|
|
_Ma_B = _R_S2B * Ma_ts - _KDW * _w_B; // aerodynamic moments
|
|
}
|
|
|
|
void Sih::generate_rover_ackermann_dynamics(const float throttle_cmd, const float steering_cmd, const float dt)
|
|
{
|
|
// --- Constants ---
|
|
static constexpr float MAX_THROTTLE_FORCE = 400.0f; // [N]
|
|
static constexpr float MAX_STEER_ANGLE = radians(30.f); // [rad]
|
|
static constexpr float WHEEL_BASE = 0.321f; // [m] Distance between front and rear axle
|
|
static constexpr float C = 500.f; // [N/rad] Cornering stiffness
|
|
static constexpr float MU_S = 0.5f; // [-] Static Coefficient of friction
|
|
static constexpr float MU_K = 0.4f; // [-] Kinetic Coefficient of friction
|
|
static constexpr float MU_R = 0.3f; // [-] Rolling Coefficient of friction
|
|
static constexpr float ROLLING_THRESHOLD = 0.05f; // [m/s] Threshold for rolling resistance
|
|
static constexpr float STATIC_THRESHOLD = 0.01f; // [m/s] Threshold for static resistance
|
|
|
|
matrix::Vector3f v_B = _q.rotateVectorInverse(_v_N); // Nav -> Body
|
|
|
|
// --- Compute inputs ---
|
|
const float delta = MAX_STEER_ANGLE * steering_cmd; // [rad] Steering angle
|
|
const float F_x = MAX_THROTTLE_FORCE * throttle_cmd; // [N] Throttle force
|
|
|
|
// --- Compute forces and moments ---
|
|
float F_y = 0.f; // [N] Lateral force
|
|
float M_z = 0.f; // [Nm] Yaw moment
|
|
|
|
if (fabsf(v_B(0)) > ROLLING_THRESHOLD) {
|
|
// Equations based on the lateral dynamics of the bicycle model from [1]
|
|
// [1] Sri Anumakonda, Everything you need to know about Self-Driving Cars in <30 minutes
|
|
// Link: https://srianumakonda.medium.com/everything-you-need-to-know-about-self-driving-in-30-minutes-b38d68bd3427
|
|
const float a_y = C * delta / _MASS - fabsf(v_B(0)) * _w_B(2)
|
|
- 2 * C * v_B(1) / (_MASS * fabsf(v_B(0))); // [m/s^2] Lateral acceleration
|
|
const float psi_dot_dot = WHEEL_BASE * C * delta / _sih_izz.get()
|
|
- C * WHEEL_BASE * WHEEL_BASE * _w_B(2) / (_sih_izz.get() * fabsf(v_B(0))); // [rad/s^2] Yaw acceleration
|
|
F_y = _MASS * a_y; // Lateral force [N]
|
|
M_z = _sih_izz.get() * psi_dot_dot; // [Nm] Yaw moment
|
|
}
|
|
|
|
_T_B = Vector3f(F_x, F_y, 0.f);
|
|
_Mt_B = Vector3f(0.f, 0.f, M_z);
|
|
|
|
// --- Compute drag/friction forces and moments ---
|
|
Vector3f F_f = Vector3f(0.f, 0.f, 0.f); // [N] Friction force
|
|
Vector3f F_a = Vector3f(0.f, 0.f, 0.f); // [N] Aerodynamic force (neglect until rover is rolling)
|
|
|
|
if (_v_E.norm() < STATIC_THRESHOLD) { // Static friction
|
|
Vector3f F_f_B = Vector3f(sign(F_x) * math::min(fabsf(F_x), MU_S * _MASS * 9.81f), sign(F_y) * math::min(fabsf(F_y),
|
|
MU_S * _MASS * 9.81f), 0.f);
|
|
F_f = _q_E.rotateVector(F_f_B);
|
|
|
|
} else if (_v_E.norm() < ROLLING_THRESHOLD) { // Kinetic friction
|
|
if (_T_B.norm() > FLT_EPSILON) {
|
|
F_f = _v_E.unit_or_zero() * MU_K * _MASS * 9.81f;
|
|
|
|
} else {
|
|
F_f = _v_E * _MASS / dt; // Stop the vehicle
|
|
}
|
|
|
|
} else { // Rolling friction
|
|
F_f = _v_E.unit_or_zero() * MU_R * _MASS * 9.81f;
|
|
Vector3f v_E_squared = Vector3f(sign(_v_E(0)) * _v_E(0) * _v_E(0), sign(_v_E(1)) * _v_E(1) * _v_E(1),
|
|
sign(_v_E(2)) * _v_E(2) * _v_E(2));
|
|
F_a = _KDV * v_E_squared; // [N] Second order drag
|
|
}
|
|
|
|
_Fa_E = -F_a - F_f; // [N] Second order drag and friction
|
|
_Ma_B = -_KDW * _w_B; // [Nm] First order angular damper
|
|
|
|
}
|
|
|
|
float Sih::computeGravity(const double lat)
|
|
{
|
|
// Somigliana formula for gravitational acceleration
|
|
const double sin_lat = sin(lat);
|
|
const double g = LatLonAlt::Wgs84::gravity_equator * (1.0 + 0.001931851353 * sin_lat * sin_lat) / sqrt(
|
|
1.0 - LatLonAlt::Wgs84::eccentricity2 * sin_lat * sin_lat);
|
|
return static_cast<float>(g);
|
|
}
|
|
|
|
void Sih::equations_of_motion(const float dt)
|
|
{
|
|
const Vector3f gravity_acceleration_E = Vector3f(_R_N2E.col(2)) * computeGravity(
|
|
_lla.latitude_rad()); // gravity along the Down axis
|
|
const Vector3f coriolis_acceleration_E = -2.f * Vector3f(0.f, 0.f, CONSTANTS_EARTH_SPIN_RATE).cross(_v_E);
|
|
|
|
const Vector3f weight_E = _MASS * gravity_acceleration_E;
|
|
Vector3f sum_of_forces_E = _Fa_E + _q_E.rotateVector(_T_B) + weight_E;
|
|
|
|
// fake ground, avoid free fall
|
|
const float force_down = Vector3f(_R_N2E.transpose() * sum_of_forces_E)(2);
|
|
Vector3f ground_force_E;
|
|
|
|
if ((_lla.altitude() - _lpos_ref_alt) < 0.f && force_down > 0.f) {
|
|
if (_vehicle == VehicleType::Quadcopter
|
|
|| _vehicle == VehicleType::Hexacopter
|
|
|| _vehicle == VehicleType::TailsitterVTOL
|
|
|| _vehicle == VehicleType::StandardVTOL) {
|
|
ground_force_E = -sum_of_forces_E;
|
|
|
|
if (!_grounded) {
|
|
// if we just hit the floor
|
|
// compute the force that will stop the vehicle in one time step
|
|
ground_force_E += -_v_E / dt * _MASS;
|
|
}
|
|
|
|
_grounded = true;
|
|
|
|
} else if (_vehicle == VehicleType::FixedWing
|
|
|| _vehicle == VehicleType::RoverAckermann) {
|
|
Vector3f down_u = _R_N2E.col(2);
|
|
ground_force_E = -down_u * sum_of_forces_E * down_u;
|
|
|
|
if (!_grounded) {
|
|
// if we just hit the floor
|
|
// compute the force that will stop the vehicle in one time step
|
|
ground_force_E += down_u * (-_v_N(2) / dt) * _MASS;
|
|
}
|
|
|
|
_grounded = true;
|
|
}
|
|
|
|
} else {
|
|
_grounded = false;
|
|
}
|
|
|
|
sum_of_forces_E += ground_force_E;
|
|
const Vector3f acceleration_E = sum_of_forces_E / _MASS;
|
|
_specific_force_E = acceleration_E - gravity_acceleration_E;
|
|
|
|
_v_E_dot = acceleration_E + coriolis_acceleration_E;
|
|
|
|
// add fictitious transport rate acceleration as the local navigation frame rotates
|
|
// to stay tangent to the ellipsoid
|
|
const Vector3f transport_rate = -_lla.computeAngularRateNavFrame(_v_N).cross(_v_N);
|
|
_v_N_dot = _R_N2E.transpose() * _v_E_dot + transport_rate;
|
|
|
|
// forward Euler velocity intergation
|
|
Vector3f v_E_prev = _v_E;
|
|
_v_E = _v_E + _v_E_dot * dt;
|
|
// trapezoidal position integration
|
|
_p_E = _p_E + Vector3d(_v_E + v_E_prev) * 0.5 * static_cast<double>(dt);
|
|
|
|
const Quatf dq(AxisAnglef(_w_B * dt));
|
|
|
|
_q_E = _q_E * dq;
|
|
_q_E.normalize();
|
|
|
|
const Vector3f w_B_dot = _Im1 * (_Mt_B + _Ma_B - _w_B.cross(_I * _w_B)); // conservation of angular momentum
|
|
_w_B = constrain(_w_B + w_B_dot * dt, -6.0f * M_PI_F, 6.0f * M_PI_F);
|
|
|
|
ecefToNed();
|
|
|
|
_lpos_ref.project(_lla.latitude_deg(), _lla.longitude_deg(), _lpos(0), _lpos(1));
|
|
_lpos(2) = -(_lla.altitude() - _lpos_ref_alt);
|
|
}
|
|
|
|
void Sih::ecefToNed()
|
|
{
|
|
_lla = LatLonAlt::fromEcef(_p_E);
|
|
|
|
const Dcmf C_SE = computeRotEcefToNed(_lla);
|
|
_R_N2E = C_SE.transpose();
|
|
|
|
// Transform velocity to NED frame
|
|
_v_N = C_SE * _v_E;
|
|
_v_apparent_N = _v_N + _v_wind_N;
|
|
|
|
_q = Quatf(C_SE) * _q_E;
|
|
_q.normalize();
|
|
}
|
|
|
|
Dcmf Sih::computeRotEcefToNed(const LatLonAlt &lla)
|
|
{
|
|
// Calculate the ECEF to NED coordinate transformation matrix
|
|
const double cos_lat = cos(lla.latitude_rad());
|
|
const double sin_lat = sin(lla.latitude_rad());
|
|
const double cos_lon = cos(lla.longitude_rad());
|
|
const double sin_lon = sin(lla.longitude_rad());
|
|
|
|
const float val[] = {(float)(-sin_lat * cos_lon), (float)(-sin_lat * sin_lon), (float)cos_lat,
|
|
(float) - sin_lon, (float)cos_lon, 0.f,
|
|
(float)(-cos_lat * cos_lon), (float)(-cos_lat * sin_lon), (float) - sin_lat
|
|
};
|
|
|
|
return Dcmf(val);
|
|
}
|
|
|
|
void Sih::reconstruct_sensors_signals(const hrt_abstime &time_now_us)
|
|
{
|
|
// The sensor signals reconstruction and noise levels are from [1]
|
|
// [1] Bulka, Eitan, and Meyer Nahon. "Autonomous fixed-wing aerobatics: from theory to flight."
|
|
// In 2018 IEEE International Conference on Robotics and Automation (ICRA), pp. 6573-6580. IEEE, 2018.
|
|
|
|
// IMU
|
|
const Dcmf R_E2B(_q_E.inversed());
|
|
Vector3f accel_noise;
|
|
Vector3f gyro_noise;
|
|
|
|
if (_T_B.longerThan(FLT_EPSILON)) {
|
|
accel_noise = noiseGauss3f(0.5f, 1.7f, 1.4f);
|
|
gyro_noise = noiseGauss3f(0.14f, 0.07f, 0.03f);
|
|
|
|
} else {
|
|
// Lower noise when not armed
|
|
accel_noise = noiseGauss3f(0.1f, 0.1f, 0.1f);
|
|
gyro_noise = noiseGauss3f(0.01f, 0.01f, 0.01f);
|
|
}
|
|
|
|
Vector3f specific_force_B = R_E2B * _specific_force_E;
|
|
Vector3f accel = specific_force_B + accel_noise;
|
|
|
|
const Vector3f earth_spin_rate_B = R_E2B * Vector3f(0.f, 0.f, CONSTANTS_EARTH_SPIN_RATE);
|
|
Vector3f gyro = _w_B + earth_spin_rate_B + gyro_noise;
|
|
|
|
// update IMU every iteration
|
|
_px4_accel.update(time_now_us, accel(0), accel(1), accel(2));
|
|
_px4_gyro.update(time_now_us, gyro(0), gyro(1), gyro(2));
|
|
}
|
|
|
|
void Sih::send_airspeed(const hrt_abstime &time_now_us)
|
|
{
|
|
// TODO: send differential pressure instead?
|
|
airspeed_s airspeed{};
|
|
airspeed.timestamp_sample = time_now_us;
|
|
|
|
// Assume the pitot tube always points against the wind to not have tailsitter edge cases
|
|
airspeed.true_airspeed_m_s = fmaxf(0.1f, _v_apparent_N.norm() + generate_wgn() * 0.2f);
|
|
airspeed.indicated_airspeed_m_s = airspeed.true_airspeed_m_s * sqrtf(_wing_l.get_rho() / RHO);
|
|
airspeed.confidence = 0.7f;
|
|
airspeed.timestamp = hrt_absolute_time();
|
|
_airspeed_pub.publish(airspeed);
|
|
}
|
|
|
|
void Sih::send_dist_snsr(const hrt_abstime &time_now_us)
|
|
{
|
|
device::Device::DeviceId device_id;
|
|
device_id.devid_s.bus_type = device::Device::DeviceBusType::DeviceBusType_SIMULATION;
|
|
device_id.devid_s.bus = 0;
|
|
device_id.devid_s.address = 0;
|
|
device_id.devid_s.devtype = DRV_DIST_DEVTYPE_SIM;
|
|
|
|
distance_sensor_s distance_sensor{};
|
|
// distance_sensor.timestamp_sample = time_now_us;
|
|
distance_sensor.device_id = device_id.devid;
|
|
distance_sensor.type = distance_sensor_s::MAV_DISTANCE_SENSOR_LASER;
|
|
distance_sensor.orientation = distance_sensor_s::ROTATION_DOWNWARD_FACING;
|
|
distance_sensor.min_distance = _distance_snsr_min;
|
|
distance_sensor.max_distance = _distance_snsr_max;
|
|
distance_sensor.signal_quality = -1;
|
|
|
|
if (_distance_snsr_override >= 0.f) {
|
|
distance_sensor.current_distance = _distance_snsr_override;
|
|
|
|
} else {
|
|
distance_sensor.current_distance = -_lpos(2) / _q.dcm_z()(2);
|
|
|
|
if (distance_sensor.current_distance > _distance_snsr_max) {
|
|
// this is based on lightware lw20 behaviour
|
|
distance_sensor.current_distance = UINT16_MAX / 100.f;
|
|
|
|
}
|
|
}
|
|
|
|
distance_sensor.timestamp = hrt_absolute_time();
|
|
_distance_snsr_pub.publish(distance_sensor);
|
|
}
|
|
|
|
void Sih::publish_ground_truth(const hrt_abstime &time_now_us)
|
|
{
|
|
{
|
|
// publish angular velocity groundtruth
|
|
vehicle_angular_velocity_s angular_velocity{};
|
|
angular_velocity.timestamp_sample = time_now_us;
|
|
angular_velocity.xyz[0] = _w_B(0); // rollspeed;
|
|
angular_velocity.xyz[1] = _w_B(1); // pitchspeed;
|
|
angular_velocity.xyz[2] = _w_B(2); // yawspeed;
|
|
angular_velocity.timestamp = hrt_absolute_time();
|
|
_angular_velocity_ground_truth_pub.publish(angular_velocity);
|
|
}
|
|
|
|
{
|
|
// publish attitude groundtruth
|
|
vehicle_attitude_s attitude{};
|
|
attitude.timestamp_sample = time_now_us;
|
|
_q.copyTo(attitude.q);
|
|
attitude.timestamp = hrt_absolute_time();
|
|
_attitude_ground_truth_pub.publish(attitude);
|
|
}
|
|
|
|
{
|
|
// publish local position groundtruth
|
|
vehicle_local_position_s local_position{};
|
|
local_position.timestamp_sample = time_now_us;
|
|
|
|
local_position.xy_valid = true;
|
|
local_position.z_valid = true;
|
|
local_position.v_xy_valid = true;
|
|
local_position.v_z_valid = true;
|
|
|
|
local_position.x = _lpos(0);
|
|
local_position.y = _lpos(1);
|
|
local_position.z = _lpos(2);
|
|
|
|
local_position.vx = _v_N(0);
|
|
local_position.vy = _v_N(1);
|
|
local_position.vz = _v_N(2);
|
|
|
|
local_position.z_deriv = _v_N(2);
|
|
|
|
local_position.ax = _v_N_dot(0);
|
|
local_position.ay = _v_N_dot(1);
|
|
local_position.az = _v_N_dot(2);
|
|
|
|
local_position.xy_global = true;
|
|
local_position.z_global = true;
|
|
local_position.ref_timestamp = _last_run;
|
|
local_position.ref_lat = _lpos_ref.getProjectionReferenceLat();
|
|
local_position.ref_lon = _lpos_ref.getProjectionReferenceLon();
|
|
local_position.ref_alt = _lpos_ref_alt;
|
|
|
|
local_position.heading = Eulerf(_q).psi();
|
|
local_position.heading_good_for_control = true;
|
|
local_position.unaided_heading = NAN;
|
|
|
|
local_position.timestamp = hrt_absolute_time();
|
|
_local_position_ground_truth_pub.publish(local_position);
|
|
}
|
|
|
|
{
|
|
// publish global position groundtruth
|
|
vehicle_global_position_s global_position{};
|
|
global_position.timestamp_sample = time_now_us;
|
|
global_position.lat = _lla.latitude_deg();
|
|
global_position.lon = _lla.longitude_deg();
|
|
global_position.alt = _lla.altitude();
|
|
global_position.alt_ellipsoid = global_position.alt;
|
|
global_position.terrain_alt = -_lpos(2);
|
|
global_position.timestamp = hrt_absolute_time();
|
|
_global_position_ground_truth_pub.publish(global_position);
|
|
}
|
|
}
|
|
|
|
float Sih::generate_wgn() // generate white Gaussian noise sample with std=1
|
|
{
|
|
// algorithm 1:
|
|
// float temp=((float)(rand()+1))/(((float)RAND_MAX+1.0f));
|
|
// return sqrtf(-2.0f*logf(temp))*cosf(2.0f*M_PI_F*rand()/RAND_MAX);
|
|
// algorithm 2: from BlockRandGauss.hpp
|
|
static float V1, V2, S;
|
|
static bool phase = true;
|
|
float X;
|
|
|
|
if (phase) {
|
|
do {
|
|
float U1 = (float)rand() / (float)RAND_MAX;
|
|
float U2 = (float)rand() / (float)RAND_MAX;
|
|
V1 = 2.0f * U1 - 1.0f;
|
|
V2 = 2.0f * U2 - 1.0f;
|
|
S = V1 * V1 + V2 * V2;
|
|
} while (S >= 1.0f || fabsf(S) < 1e-8f);
|
|
|
|
X = V1 * float(sqrtf(-2.0f * float(logf(S)) / S));
|
|
|
|
} else {
|
|
X = V2 * float(sqrtf(-2.0f * float(logf(S)) / S));
|
|
}
|
|
|
|
phase = !phase;
|
|
return X;
|
|
}
|
|
|
|
Vector3f Sih::noiseGauss3f(float stdx, float stdy, float stdz)
|
|
{
|
|
return Vector3f(generate_wgn() * stdx, generate_wgn() * stdy, generate_wgn() * stdz);
|
|
}
|
|
|
|
int Sih::print_status()
|
|
{
|
|
#if defined(ENABLE_LOCKSTEP_SCHEDULER)
|
|
PX4_INFO("Running in lockstep mode");
|
|
PX4_INFO("Achieved speedup: %.2fX", (double)_achieved_speedup);
|
|
#endif
|
|
|
|
if (_vehicle == VehicleType::Quadcopter) {
|
|
PX4_INFO("Quadcopter");
|
|
|
|
} else if (_vehicle == VehicleType::Hexacopter) {
|
|
PX4_INFO("Hexacopter");
|
|
|
|
} else if (_vehicle == VehicleType::FixedWing) {
|
|
PX4_INFO("Fixed-Wing");
|
|
|
|
} else if (_vehicle == VehicleType::TailsitterVTOL) {
|
|
PX4_INFO("TailSitter");
|
|
PX4_INFO("aoa [deg]: %d", (int)(degrees(_ts[4].get_aoa())));
|
|
PX4_INFO("v segment (m/s)");
|
|
_ts[4].get_vS().print();
|
|
|
|
} else if (_vehicle == VehicleType::StandardVTOL) {
|
|
PX4_INFO("Standard VTOL");
|
|
|
|
} else if (_vehicle == VehicleType::RoverAckermann) {
|
|
PX4_INFO("Rover Ackermann");
|
|
}
|
|
|
|
PX4_INFO("vehicle landed: %d", _grounded);
|
|
PX4_INFO("local position NED (m)");
|
|
_lpos.print();
|
|
PX4_INFO("local velocity NED (m/s)");
|
|
_v_N.print();
|
|
PX4_INFO("attitude roll-pitch-yaw (deg)");
|
|
(Eulerf(_q) * 180.0f / M_PI_F).print();
|
|
PX4_INFO("angular acceleration roll-pitch-yaw (deg/s)");
|
|
(_w_B * 180.0f / M_PI_F).print();
|
|
PX4_INFO("actuator signals");
|
|
Vector<float, 8> u = Vector<float, 8>(_u);
|
|
u.transpose().print();
|
|
PX4_INFO("Aerodynamic forces NED (N)");
|
|
(_R_N2E.transpose() * _Fa_E).print();
|
|
PX4_INFO("Aerodynamic moments body frame (Nm)");
|
|
_Ma_B.print();
|
|
PX4_INFO("Thruster moments in body frame (Nm)");
|
|
_Mt_B.print();
|
|
return 0;
|
|
}
|
|
|
|
int Sih::run_trampoline(int argc, char *argv[])
|
|
{
|
|
return ModuleBase::run_trampoline_impl(desc, [](int ac, char *av[]) -> ModuleBase * {
|
|
return Sih::instantiate(ac, av);
|
|
}, argc, argv);
|
|
}
|
|
|
|
int Sih::task_spawn(int argc, char *argv[])
|
|
{
|
|
desc.task_id = px4_task_spawn_cmd("sih",
|
|
SCHED_DEFAULT,
|
|
SCHED_PRIORITY_MAX,
|
|
1560,
|
|
(px4_main_t)&run_trampoline,
|
|
(char *const *)argv);
|
|
|
|
if (desc.task_id < 0) {
|
|
desc.task_id = -1;
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Sih *Sih::instantiate(int argc, char *argv[])
|
|
{
|
|
Sih *instance = new Sih();
|
|
|
|
if (instance == nullptr) {
|
|
PX4_ERR("alloc failed");
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
int Sih::custom_command(int argc, char *argv[])
|
|
{
|
|
return print_usage("unknown command");
|
|
}
|
|
|
|
int Sih::print_usage(const char *reason)
|
|
{
|
|
if (reason) {
|
|
PX4_WARN("%s\n", reason);
|
|
}
|
|
|
|
PRINT_MODULE_DESCRIPTION(
|
|
R"DESCR_STR(
|
|
### Description
|
|
This module provides a simulator for quadrotors and fixed-wings running fully
|
|
inside the hardware autopilot.
|
|
|
|
This simulator subscribes to "actuator_outputs" which are the actuator pwm
|
|
signals given by the control allocation module.
|
|
|
|
This simulator publishes the sensors signals corrupted with realistic noise
|
|
in order to incorporate the state estimator in the loop.
|
|
|
|
### Implementation
|
|
The simulator implements the equations of motion using matrix algebra.
|
|
Quaternion representation is used for the attitude.
|
|
Forward Euler is used for integration.
|
|
Most of the variables are declared global in the .hpp file to avoid stack overflow.
|
|
|
|
|
|
)DESCR_STR");
|
|
|
|
PRINT_MODULE_USAGE_NAME("simulator_sih", "simulation");
|
|
PRINT_MODULE_USAGE_COMMAND("start");
|
|
PRINT_MODULE_USAGE_DEFAULT_COMMANDS();
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" __EXPORT int simulator_sih_main(int argc, char *argv[])
|
|
{
|
|
return ModuleBase::main(Sih::desc, argc, argv);
|
|
}
|