LCOV - code coverage report
Current view: top level - SITL - SIM_Helicopter.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 284 365 77.8 %
Date: 2026-09-09 11:02:08 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   helicopter simulator class
      17             : */
      18             : 
      19             : #include "SIM_Helicopter.h"
      20             : 
      21             : #include <stdio.h>
      22             : #include <GCS_MAVLink/GCS.h>
      23             : 
      24             : namespace SITL {
      25             : 
      26          80 : Helicopter::Helicopter(const char *frame_str) :
      27          80 :     Aircraft(frame_str)
      28             : {
      29          80 :     mass = 4.54f;
      30             : 
      31          80 :     if (strstr(frame_str, "-dual")) {
      32           0 :         frame_type = HELI_FRAME_DUAL;
      33           0 :         _time_delay = 30;
      34           0 :         nominal_rpm = 1300;
      35           0 :         mass = 9.08f;
      36           0 :         iyy = 5.0f;
      37          80 :     } else if (strstr(frame_str, "-compound")) {
      38           0 :         frame_type = HELI_FRAME_COMPOUND;
      39           0 :         _time_delay = 50;
      40           0 :         nominal_rpm = 1500;
      41          80 :     } else if (strstr(frame_str, "-blade360")) {
      42           1 :         frame_type = HELI_FRAME_BLADE360;
      43           1 :         _time_delay = 40;
      44           1 :         nominal_rpm = 2100;
      45          79 :     } else if (strstr(frame_str, "-ddvptail")) {
      46           1 :         frame_type = HELI_FRAME_CONVENTIONAL_DDVPTAIL;
      47           1 :         _time_delay = 50;
      48           1 :         nominal_rpm = 1500;
      49          78 :     } else if (strstr(frame_str, "-ddfptail")) {
      50           2 :         frame_type = HELI_FRAME_CONVENTIONAL_DDFPTAIL;
      51           2 :         _time_delay = 50;
      52           2 :         nominal_rpm = 1500;
      53             :     } else {
      54          76 :         frame_type = HELI_FRAME_CONVENTIONAL;
      55          76 :         _time_delay = 50;
      56          76 :         nominal_rpm = 1500;
      57             :     }
      58             : 
      59             :     /*
      60             :        For conventional and compound
      61             :        scaling from motor power to Newtons. Allows the copter
      62             :        to hover against gravity when the motor is at hover_throttle
      63             :        normalized to hover at 1500RPM at 5 deg collective.
      64             :     */
      65          80 :     thrust_scale = (mass * GRAVITY_MSS) / (hover_coll * sq(nominal_rpm * 2.0f * M_PI / 60.0f));
      66             : 
      67             :     // torque with zero collective pitch. Percentage of total +hover torque is based on full scale helicopters.
      68          80 :     torque_mpog = 1.08f / sq(nominal_rpm * 2.0f * M_PI / 60.0f);  // based on separate calculation
      69             : 
      70             :     // torque_max based on power being weight^3/2.  Assuming thrust is linear with collective, it can be 
      71             :     // determined that max torque would be 3 times hover torque.  Then dividing by 1-MPOG_torq to get total torque.
      72          80 :     torque_max = ((mass * GRAVITY_MSS * sinf(radians(hover_lean)) * tr_dist - torque_mpog * sq(nominal_rpm * 2.0f * M_PI / 60.0f)) * powf(2.0f,1.5f) + torque_mpog * sq(nominal_rpm * 2.0f * M_PI / 60.0f)) / sq(nominal_rpm * 2.0f * M_PI / 60.0f);
      73             : 
      74             :     // calculates tail rotor thrust to overcome rotor torque using the lean angle in a hover
      75          80 :     torque_scale = (torque_max - torque_mpog) / powf(10.0f,1.5f);
      76             : 
      77          80 :     frame_height = 0.1;
      78             : 
      79          80 :     gas_heli = (strstr(frame_str, "-gas") != nullptr);
      80             : 
      81          80 :     ground_behavior = GROUND_BEHAVIOR_NO_MOVEMENT;
      82          80 :     lock_step_scheduled = true;
      83             : 
      84          80 :     motor_mask |= (1U<<0);
      85             : 
      86         160 :     battery.setup(sitl->batt_capacity_ah,
      87             :                   default_battery_resistance_ohm,
      88          80 :                   sitl->batt_voltage,
      89             :                   ambient_outside_temperature_degC());
      90             : 
      91          80 : }
      92             : 
      93             : /*
      94             :   update the helicopter simulation by one time step
      95             :  */
      96     9413166 : void Helicopter::update(const struct sitl_input &input)
      97             : {
      98     9413166 :     const float dt = frame_time_us * 1.0e-6f;
      99             : 
     100             :     // get wind vector setup
     101     9413166 :     update_wind(input);
     102             : 
     103     9413166 :     motor_interlock = input.servos[7] > 1400;
     104             : 
     105     9413166 :     float rsc = constrain_float((input.servos[7]-1000) / 1000.0f, 0, 1);
     106             : 
     107     9413166 :     float thrust = 0;
     108     9413166 :     float thrust_1 = 0;
     109     9413166 :     float thrust_2 = 0;
     110     9413166 :     float eng_torque = 0;
     111     9413166 :     float lateral_x_thrust = 0;
     112     9413166 :     float lateral_y_thrust = 0;
     113             : 
     114     9413166 :     if (gas_heli) {
     115      337532 :         power_consumption_watts = 1.0f;
     116             :     } else {
     117     9075634 :         power_consumption_watts = 1000.0f;
     118             :     }
     119             : 
     120     9413166 :     update_battery();
     121             : 
     122     9413166 :     if (_time_delay == 0) {
     123           0 :         for (uint8_t i = 0; i < 6; i++) {
     124           0 :             _servos_delayed[i] = input.servos[i];
     125             :         }
     126     9413166 :     } else if (servos_stored_buffer == nullptr) {
     127          80 :         uint16_t buffer_size = constrain_int16(_time_delay, 1, 100) * 0.001f / dt;
     128          80 :         servos_stored_buffer = NEW_NOTHROW ObjectBuffer<servos_stored>(buffer_size);
     129        4868 :         while (servos_stored_buffer->space() != 0) {
     130        4788 :             push_to_buffer(input.servos);
     131             :         }
     132         560 :         for (uint8_t i = 0; i < 6; i++) {
     133         480 :             _servos_delayed[i] = input.servos[i];
     134             :         }
     135             :     } else {
     136     9413086 :         pull_from_buffer(_servos_delayed);
     137     9413086 :         push_to_buffer(input.servos);
     138             :     }
     139             : 
     140     9413166 :     float swash1 = (_servos_delayed[0]-1000) / 1000.0f;
     141     9413166 :     float swash2 = (_servos_delayed[1]-1000) / 1000.0f;
     142     9413166 :     float swash3 = (_servos_delayed[2]-1000) / 1000.0f;
     143             : 
     144     9413166 :     Vector3f rot_accel;
     145             : 
     146     9413166 :     switch (frame_type) {
     147     8665485 :     case HELI_FRAME_CONVENTIONAL: {
     148             :         // simulate a traditional helicopter
     149             : 
     150     8665485 :         float Ma1s = 617.5f;
     151     8665485 :         float Lb1s = 3588.6f;
     152     8665485 :         float Mu = 0.003f;
     153     8665485 :         float Lv = -0.006;
     154     8665485 :         float Xu = -0.125;
     155     8665485 :         float Yv = -0.375;
     156     8665485 :         float Zw = -2.25;
     157             : 
     158     8665485 :         float tail_rotor = (_servos_delayed[3]-1000) / 1000.0f;
     159             : 
     160             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     161     8665485 :         float coll = 50.0f * (swash1+swash2+swash3) / 3.0f - 25.0f;
     162     8665485 :         thrust = (rpm[0] / nominal_rpm) * thrust_scale * sq(nominal_rpm * 0.104667f) * coll;
     163             : 
     164             :         // determine RPM
     165     8665485 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, coll, dt);
     166             : 
     167             :         // Calculate rotor tip path plane angle
     168     8665485 :         float roll_cyclic = 1.283 * (swash1 - swash2) / cyclic_scalar;
     169     8665485 :         float pitch_cyclic = 1.48 * ((swash1+swash2) / 2.0f - swash3) / cyclic_scalar;
     170     8665485 :         Vector2f ctrl_pos = Vector2f(roll_cyclic, pitch_cyclic);
     171     8665485 :         update_rotor_dynamics(gyro, ctrl_pos, _tpp_angle, dt);
     172             : 
     173     8665485 :         float yaw_cmd = 2.0f * tail_rotor - 1.0f; // convert range to -1 to 1
     174     8665485 :         float tail_rotor_torque = (21.6f * 2.96f * yaw_cmd - 2.96f * gyro.z) * sq(rpm[0]/nominal_rpm);
     175     8665485 :         float tail_rotor_thrust =  -1.0f * tail_rotor_torque * izz / tr_dist;  //right pedal produces left body accel
     176             : 
     177             :         // rotational acceleration, in rad/s/s, in body frame
     178     8665485 :         rot_accel.x = _tpp_angle.x * Lb1s + Lv * velocity_air_bf.y;
     179     8665485 :         rot_accel.y = _tpp_angle.y * Ma1s + Mu * velocity_air_bf.x;
     180     8665485 :         rot_accel.z = tail_rotor_torque - eng_torque;
     181             : 
     182     8665485 :         lateral_y_thrust = tail_rotor_thrust / mass + GRAVITY_MSS * _tpp_angle.x + Yv * velocity_air_bf.y;
     183     8665485 :         lateral_x_thrust = -1.0f * GRAVITY_MSS * _tpp_angle.y + Xu * velocity_air_bf.x;
     184     8665485 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, -thrust / mass + velocity_air_bf.z * Zw);
     185             : 
     186     8665485 :         break;
     187             :     }
     188             : 
     189      146519 :     case HELI_FRAME_CONVENTIONAL_DDVPTAIL: {
     190             :         // simulate a traditional helicopter with DDVP tail.  Simulates the tail rotor RSC by just applying a tailrsc multiplier to the tail rotor torque and thrust.
     191             :         // If the code is not configured correctly, the tail rotor will not produce any torque or thrust.  The spool up is assumed to be the same as the main rotor.
     192             :         // the H_TAIL_SPEED parameter must be set to 100 for the tail rotor to operate correctly. The tail RSC is hard coded to SERVO channel 7.
     193             : 
     194      146519 :         float Ma1s = 617.5f;
     195      146519 :         float Lb1s = 3588.6f;
     196      146519 :         float Mu = 0.003f;
     197      146519 :         float Lv = -0.006;
     198      146519 :         float Xu = -0.125;
     199      146519 :         float Yv = -0.375;
     200      146519 :         float Zw = -2.25;
     201             : 
     202      146519 :         float tailrsc = constrain_float((input.servos[6]-1000) / 1000.0f, 0, 1);
     203             : 
     204      146519 :         float tail_rotor = (_servos_delayed[3]-1000) / 1000.0f;
     205             : 
     206             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     207      146519 :         float coll = 50.0f * (swash1+swash2+swash3) / 3.0f - 25.0f;
     208      146519 :         thrust = (rpm[0] / nominal_rpm) * thrust_scale * sq(nominal_rpm * 0.104667f) * coll;
     209             : 
     210             :         // determine RPM
     211      146519 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, coll, dt);
     212             : 
     213             :         // Calculate rotor tip path plane angle
     214      146519 :         float roll_cyclic = 1.283 * (swash1 - swash2) / cyclic_scalar;
     215      146519 :         float pitch_cyclic = 1.48 * ((swash1+swash2) / 2.0f - swash3) / cyclic_scalar;
     216      146519 :         Vector2f ctrl_pos = Vector2f(roll_cyclic, pitch_cyclic);
     217      146519 :         update_rotor_dynamics(gyro, ctrl_pos, _tpp_angle, dt);
     218             : 
     219      146519 :         float yaw_cmd = 2.0f * tail_rotor - 1.0f; // convert range to -1 to 1
     220      146519 :         float tail_rotor_torque = (21.6f * 2.96f * yaw_cmd - 2.96f * gyro.z) * tailrsc * sq(rpm[0]/nominal_rpm);
     221      146519 :         float tail_rotor_thrust =  -1.0f * tail_rotor_torque * izz / tr_dist;  //right pedal produces left body accel
     222             : 
     223             :         // rotational acceleration, in rad/s/s, in body frame
     224      146519 :         rot_accel.x = _tpp_angle.x * Lb1s + Lv * velocity_air_bf.y;
     225      146519 :         rot_accel.y = _tpp_angle.y * Ma1s + Mu * velocity_air_bf.x;
     226      146519 :         rot_accel.z = tail_rotor_torque - eng_torque;
     227             : 
     228      146519 :         lateral_y_thrust = tail_rotor_thrust / mass + GRAVITY_MSS * _tpp_angle.x + Yv * velocity_air_bf.y;
     229      146519 :         lateral_x_thrust = -1.0f * GRAVITY_MSS * _tpp_angle.y + Xu * velocity_air_bf.x;
     230      146519 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, -thrust / mass + velocity_air_bf.z * Zw);
     231             : 
     232      146519 :         break;
     233             :     }
     234             : 
     235      458692 :     case HELI_FRAME_CONVENTIONAL_DDFPTAIL: {
     236             :         // simulate a traditional helicopter with DDFP tail.  Simulates the tail rotor by just applying a multiplier to the tail rotor command.
     237             :         // If the code is not configured correctly, the tail rotor will not produce any torque or thrust.  
     238             : 
     239      458692 :         float Ma1s = 617.5f;
     240      458692 :         float Lb1s = 3588.6f;
     241      458692 :         float Mu = 0.003f;
     242      458692 :         float Lv = -0.006;
     243      458692 :         float Xu = -0.125;
     244      458692 :         float Yv = -0.375;
     245      458692 :         float Zw = -2.25;
     246             : 
     247             :         // float tailrsc = constrain_float((input.servos[6]-1000) / 1000.0f, 0, 1);
     248      458692 :         float tail_rotor = (input.servos[6]-1000) / 1000.0f;
     249             : 
     250             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     251      458692 :         float coll = 50.0f * (swash1+swash2+swash3) / 3.0f - 25.0f;
     252      458692 :         thrust = (rpm[0] / nominal_rpm) * thrust_scale * sq(nominal_rpm * 0.104667f) * coll;
     253             : 
     254             :         // determine RPM
     255      458692 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, coll, dt);
     256             : 
     257             :         // Calculate rotor tip path plane angle
     258      458692 :         float roll_cyclic = 1.283 * (swash1 - swash2) / cyclic_scalar;
     259      458692 :         float pitch_cyclic = 1.48 * ((swash1+swash2) / 2.0f - swash3) / cyclic_scalar;
     260      458692 :         Vector2f ctrl_pos = Vector2f(roll_cyclic, pitch_cyclic);
     261      458692 :         update_rotor_dynamics(gyro, ctrl_pos, _tpp_angle, dt);
     262             : 
     263             :         // tail rotor output is modified by the ratio of current battery voltage to max battery voltage
     264      458692 :         float tail_rotor_torque = (21.6f * 2.96f - 2.96f * gyro.z) * sq(tail_rotor * battery_voltage / sitl->batt_voltage);
     265      458692 :         float tail_rotor_thrust =  -1.0f * tail_rotor_torque * izz / tr_dist;  //right pedal produces left body accel
     266             : 
     267             :         // rotational acceleration, in rad/s/s, in body frame
     268      458692 :         rot_accel.x = _tpp_angle.x * Lb1s + Lv * velocity_air_bf.y;
     269      458692 :         rot_accel.y = _tpp_angle.y * Ma1s + Mu * velocity_air_bf.x;
     270      458692 :         rot_accel.z = tail_rotor_torque - eng_torque;
     271             : 
     272      458692 :         lateral_y_thrust = tail_rotor_thrust / mass + GRAVITY_MSS * _tpp_angle.x + Yv * velocity_air_bf.y;
     273      458692 :         lateral_x_thrust = -1.0f * GRAVITY_MSS * _tpp_angle.y + Xu * velocity_air_bf.x;
     274      458692 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, -thrust / mass + velocity_air_bf.z * Zw);
     275             : 
     276      458692 :         break;
     277             :     }
     278             : 
     279      142470 :     case HELI_FRAME_BLADE360: {
     280             :         // simulate a Blade 360 helicopter.  This model was taken from the following reference.
     281             :         // Walker, J, Tishler, M, "Identification and Control Design of a Sub-Scale Flybarless Helicopter",
     282             :         // Vertical Flight Society’s 77th Annual Forum & Technology Display, Virtual, May 10-14, 2021.
     283             : 
     284      142470 :         float Ma1s = 796.7f;
     285      142470 :         float Lb1s = 5115.2f;
     286      142470 :         float Mu = 2.7501f;
     287      142470 :         float Mv = -2.3039f;
     288      142470 :         float Lu = -28.7796f;
     289      142470 :         float Lv = -5.5376f;
     290      142470 :         float Xu = -0.2270f;
     291      142470 :         float Yv = -0.1852f;
     292      142470 :         float Yp = 0.2303f;
     293      142470 :         float Zw = -0.5910f;
     294      142470 :         float Nr = -2.0131f;
     295      142470 :         float Nw = 5.7574f;
     296      142470 :         float Nv = 1.7258f;
     297      142470 :         float Ncol = -32.4616f;
     298      142470 :         float Nped = 63.0040f;
     299      142470 :         float Zcol = -22.3239f;
     300             : 
     301      142470 :         float tail_rotor = (_servos_delayed[3]-1000) / 1000.0f;
     302             : 
     303             :         // collective adjusted for coll_min(1460) to coll_max(1740) as 0 to 1 with 1500 being zero thrust
     304      142470 :         float coll = 3.51 * ((swash1+swash2+swash3) / 3.0f - 0.5f);
     305             : 
     306             :         // determine RPM
     307      142470 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, coll, dt);
     308             : 
     309             :         // Calculate rotor tip path plane angle
     310      142470 :         float roll_cyclic = 1.283f * (swash1 - swash2);
     311      142470 :         float pitch_cyclic = 1.48f * ((swash1+swash2) / 2.0f - swash3);
     312      142470 :         Vector2f ctrl_pos = Vector2f(roll_cyclic, pitch_cyclic);
     313      142470 :         update_rotor_dynamics(gyro, ctrl_pos, _tpp_angle, dt);
     314             : 
     315      142470 :         float yaw_cmd = 1.45f * (2.0f * tail_rotor - 1.0f); // convert range to -1 to 1
     316             : 
     317             :         // rotational acceleration, in rad/s/s, in body frame
     318      142470 :         rot_accel.x = _tpp_angle.x * Lb1s + Lu * velocity_air_bf.x + Lv * velocity_air_bf.y;
     319      142470 :         rot_accel.y = _tpp_angle.y * Ma1s + Mu * velocity_air_bf.x + Mv * velocity_air_bf.y;
     320      142470 :         rot_accel.z = Nv * velocity_air_bf.y + Nr * gyro.z + sq(rpm[0]/nominal_rpm) * Nped * yaw_cmd + Nw * velocity_air_bf.z + sq(rpm[0]/nominal_rpm) * Ncol * (coll - 0.5f);
     321             : 
     322      142470 :         lateral_y_thrust = GRAVITY_MSS * _tpp_angle.x + Yv * velocity_air_bf.y + Yp * gyro.x - 3.2 * 0.01745 * GRAVITY_MSS;
     323      142470 :         lateral_x_thrust = -1.0f * GRAVITY_MSS * _tpp_angle.y + Xu * velocity_air_bf.x;
     324      142470 :         float vertical_thrust = Zcol * coll * sq(rpm[0]/nominal_rpm) + velocity_air_bf.z * Zw;
     325      142470 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, vertical_thrust);
     326             : 
     327      142470 :         break;
     328             :     }
     329             : 
     330           0 :     case HELI_FRAME_DUAL: {
     331             : 
     332           0 :         float Ma1s = 617.5f/5.0f;
     333           0 :         float Lb1s = 3588.6f;
     334           0 :         float Mu = 0.003f;
     335           0 :         float Lv = -0.006f;
     336           0 :         float Xu = -0.125f;
     337           0 :         float Yv = -0.375f;
     338           0 :         float Zw = -2.25f;
     339           0 :         float hub_dist = 1.8f; //meters
     340             : 
     341           0 :         float swash4 = (_servos_delayed[3]-1000) / 1000.0f;
     342           0 :         float swash5 = (_servos_delayed[4]-1000) / 1000.0f;
     343           0 :         float swash6 = (_servos_delayed[5]-1000) / 1000.0f;
     344             : 
     345             :         // Forward rotor is number 1
     346             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     347           0 :         float coll_1 = 50.0f * (swash1+swash2+swash3) / 3.0f - 25.0f;
     348             : 
     349             :         // Calculate rotor tip path plane angle
     350           0 :         float roll_cyclic_1 = 1.283 * (swash1 - swash2) / cyclic_scalar;
     351           0 :         float pitch_cyclic_1 = 1.48 * ((swash1+swash2) / 2.0f - swash3) / cyclic_scalar;
     352           0 :         Vector2f ctrl_pos_1 = Vector2f(roll_cyclic_1, pitch_cyclic_1);
     353           0 :         update_rotor_dynamics(gyro, ctrl_pos_1, _tpp_angle_1, dt);
     354             : 
     355             :         // Aft rotor is number 2
     356             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     357           0 :         float coll_2 = 50.0f * (swash4+swash5+swash6) / 3.0f - 25.0f;
     358             : 
     359             :         // Calculate rotor tip path plane angle
     360           0 :         float roll_cyclic_2 = 1.283 * (swash4 - swash5) / cyclic_scalar;
     361           0 :         float pitch_cyclic_2 = 1.48 * ((swash4+swash5) / 2.0f - swash6) / cyclic_scalar;
     362           0 :         Vector2f ctrl_pos_2 = Vector2f(roll_cyclic_2, pitch_cyclic_2);
     363           0 :         update_rotor_dynamics(gyro, ctrl_pos_2, _tpp_angle_2, dt);
     364             : 
     365             :         // determine RPM
     366           0 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, (coll_1 + coll_2) * 0.5f, dt);
     367             : 
     368           0 :         thrust_1 = 0.5f * thrust_scale * sq(rpm[0] * 0.104667f) * coll_1;
     369           0 :         thrust_2 = 0.5f * thrust_scale * sq(rpm[0] * 0.104667f) * coll_2;
     370             : 
     371             :         // rotational acceleration, in rad/s/s, in body frame
     372           0 :         rot_accel.x = (_tpp_angle_1.x + _tpp_angle_2.x) * Lb1s + Lv * velocity_air_bf.y;
     373           0 :         rot_accel.y = (_tpp_angle_1.y + _tpp_angle_2.y) * Ma1s + (thrust_1 - thrust_2) * hub_dist / iyy + Mu * velocity_air_bf.x;
     374           0 :         rot_accel.z = (_tpp_angle_1.x * thrust_1 - _tpp_angle_2.x * thrust_2) * hub_dist / (iyy * 2.0f) - 0.5f * gyro.z;
     375             : 
     376           0 :         lateral_y_thrust = GRAVITY_MSS * (_tpp_angle_1.x + _tpp_angle_2.x) + Yv * velocity_air_bf.y;
     377           0 :         lateral_x_thrust = -1.0f * GRAVITY_MSS * (_tpp_angle_1.y + _tpp_angle_2.y) + Xu * velocity_air_bf.x;
     378           0 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, -(thrust_1 + thrust_2) / mass + velocity_air_bf.z * Zw);
     379             : 
     380             : 
     381           0 :         break;
     382             :     }
     383             : 
     384           0 :     case HELI_FRAME_COMPOUND: {
     385             :         // simulate a compound helicopter
     386             : 
     387           0 :         float Ma1s = 617.5f;
     388           0 :         float Lb1s = 3588.6f;
     389           0 :         float Mu = 0.003f;
     390           0 :         float Lv = -0.006;
     391           0 :         float Xu = -0.125;
     392           0 :         float Yv = -0.375;
     393           0 :         float Zw = -2.25;
     394             : 
     395             :         // thrust calculated based on 5 deg hover collective for 10lb aircraft at 1500RPM
     396           0 :         float coll = 50.0f * (swash1+swash2+swash3) / 3.0f - 25.0f;
     397           0 :         thrust = thrust_scale * sq(rpm[0] * 0.104667f) * coll;
     398             : 
     399             :         // determine RPM
     400           0 :         rpm[0] = update_rpm(rpm[0], rsc, eng_torque, coll, dt);
     401             : 
     402             :          // Calculate rotor tip path plane angle
     403           0 :         float roll_cyclic = 1.283 * (swash1 - swash2) / cyclic_scalar;
     404           0 :         float pitch_cyclic = 1.48 * ((swash1+swash2) / 2.0f - swash3) / cyclic_scalar;
     405           0 :         Vector2f ctrl_pos = Vector2f(roll_cyclic, pitch_cyclic);
     406           0 :         update_rotor_dynamics(gyro, ctrl_pos, _tpp_angle, dt);
     407             : 
     408             :         // Calculate thruster yaw and forward thrust effects
     409             :         // Thruster command range -1 to 1.  Positive is forward thrust for both
     410           0 :         float right_thruster_cmd = 2.0f * (_servos_delayed[3]-1000) / 1000.0f - 1.0f;
     411           0 :         float left_thruster_cmd = 2.0f * (_servos_delayed[4]-1000) / 1000.0f - 1.0f;
     412             : 
     413             :         // assume torque from each thruster only half of normal tailrotor since thrusters 1/2 distance from cg
     414           0 :         float right_thruster_torque = (-0.5f * 21.6f * 2.96f * right_thruster_cmd - 2.96f * gyro.z) * sq(rpm[0] / nominal_rpm);
     415           0 :         float left_thruster_torque = (0.5f * 21.6f * 2.96f * left_thruster_cmd - 2.96f * gyro.z) * sq(rpm[0] / nominal_rpm);
     416             : 
     417           0 :         float right_thruster_force = -1.0f * right_thruster_torque * izz / (0.5f * tr_dist);
     418           0 :         float left_thruster_force = left_thruster_torque * izz / (0.5f * tr_dist);
     419             : 
     420             :         // rotational acceleration, in rad/s/s, in body frame
     421           0 :         rot_accel.x = _tpp_angle.x * Lb1s + Lv * velocity_air_bf.y;
     422           0 :         rot_accel.y = _tpp_angle.y * Ma1s + Mu * velocity_air_bf.x;
     423           0 :         rot_accel.z = right_thruster_torque + left_thruster_torque - eng_torque;
     424             : 
     425           0 :         lateral_y_thrust = GRAVITY_MSS * _tpp_angle.x + Yv * velocity_air_bf.y;
     426           0 :         lateral_x_thrust = (right_thruster_force + left_thruster_force) / mass - GRAVITY_MSS * _tpp_angle.y + Xu * velocity_air_bf.x;
     427           0 :         accel_body = Vector3f(lateral_x_thrust, lateral_y_thrust, -thrust / mass + velocity_air_bf.z * Zw);
     428             : 
     429           0 :         break;
     430             :     }
     431             :     }
     432             : 
     433             : 
     434     9413166 :     update_dynamics(rot_accel);
     435     9413166 :     update_external_payload(input);
     436             : 
     437             :     // update lat/lon/altitude
     438     9413166 :     update_position();
     439     9413166 :     time_advance();
     440             : 
     441             :     // update magnetic field
     442     9413166 :     update_mag_field_bf();
     443             : 
     444     9413166 : }
     445             : 
     446     9413166 : void Helicopter::update_rotor_dynamics(Vector3f gyros, Vector2f ctrl_pos, Vector2f &tpp_angle, float dt)
     447             : {
     448             : 
     449             :     float tf_inv;
     450             :     float Lfa1s;
     451             :     float Mfb1s;
     452             :     float Lflt;
     453             :     float Lflg;
     454             :     float Mflt;
     455             :     float Mflg;
     456             : 
     457     9413166 :     if (frame_type == HELI_FRAME_BLADE360) {
     458      142470 :         tf_inv = 1.0f / 0.0353f;
     459      142470 :         Lfa1s = 1.0477f;
     460      142470 :         Mfb1s = -1.0057f;
     461      142470 :         Lflt = 0.2375f;
     462      142470 :         Lflg = -0.0286f;
     463      142470 :         Mflt = 0.0344f;
     464      142470 :         Mflg = 0.2292f;
     465     9270696 :     } else if (frame_type == HELI_FRAME_DUAL) { // remove coupling in rotor
     466           0 :         tf_inv = 1.0f / 0.068232f;
     467           0 :         Lfa1s = 0.0f;
     468           0 :         Mfb1s = 0.0f;
     469           0 :         Lflt = 1.7635f;
     470           0 :         Lflg = 0.0f;
     471           0 :         Mflt = 0.0f;
     472           0 :         Mflg = 1.9432f;
     473             :     } else {
     474     9270696 :         tf_inv = 1.0f / 0.068232f;
     475     9270696 :         Lfa1s = 1.2963f;
     476     9270696 :         Mfb1s = -1.3402f;
     477     9270696 :         Lflt = 1.7635f;
     478     9270696 :         Lflg = -0.61171f;
     479     9270696 :         Mflt = 0.52454f;
     480     9270696 :         Mflg = 1.9432f;
     481             :     }
     482             : 
     483     9413166 :     float b1s_dot = -1 * gyro.x - tf_inv * tpp_angle.x + tf_inv * (Lfa1s * tpp_angle.y + Lflt * ctrl_pos.x + Lflg * ctrl_pos.y);
     484     9413166 :     float a1s_dot = -1 * gyro.y - tf_inv * tpp_angle.y + tf_inv * (Mfb1s * tpp_angle.x + Mflt * ctrl_pos.x + Mflg * ctrl_pos.y);
     485             : 
     486     9413166 :     tpp_angle.x += b1s_dot * dt;
     487     9413166 :     tpp_angle.y += a1s_dot * dt;
     488             : 
     489     9413166 : }
     490             : 
     491     9413166 : float Helicopter::update_rpm(float curr_rpm, float throttle, float &engine_torque, float collective, float dt)
     492             : {
     493             :     static float rotor_runup_output;
     494             :     static uint8_t motor_status;
     495             :     float accel_scale;
     496             :     float auto_ss_torque;
     497             :     float descent_torque;
     498             :     float rotor_torque;
     499             :     float rpm_dot;
     500             :     float rpm_engine;
     501             : 
     502             :     //use this to make rpm model more realistic
     503     9413166 :     accel_scale = 100.0f;
     504             : 
     505             :     // calculate aerodynamic rotor drag torque
     506     9413166 :     rotor_torque = (sq(curr_rpm * 0.104667f) * (torque_mpog + torque_scale * powf(fabsf(collective),1.5f))) / izz;
     507             : 
     508             :     // Calculate autorotation effect on rotor
     509     9413166 :     auto_ss_torque = sq(nominal_rpm * 0.104667f) * (torque_mpog + torque_scale * powf(fabsf(-1.0f),1.5f)) / izz;
     510     9413166 :     if (is_positive(velocity_air_bf.z)) {
     511     2224619 :         descent_torque = (velocity_air_bf.z - 5.3) * auto_ss_torque / 5.3f + auto_ss_torque;
     512             :     } else {
     513     7188547 :         descent_torque = 0.0f;
     514             :     }
     515             : 
     516             :     // calculate max engine torque
     517     9413166 :     float engine_torque_max = sq(nominal_rpm * 0.104667f) * torque_max / izz;
     518             : 
     519     9413166 :     if (gas_heli) {
     520             : 
     521             :         // calculate engine RPM.  Based off of throttle and it matches rotor RPM at 30% throttle
     522      337532 :         rpm_engine = nominal_rpm * throttle / 0.3f;
     523             : 
     524             :         // calculate engine torque.  extra 20% given to have a little extra power
     525      337532 :         engine_torque = 1.20f * throttle * engine_torque_max;
     526             : 
     527             :         // model clutch on gas heli 
     528             :         float input_torque;
     529      337532 :         if (throttle >= 0.15f && rpm_engine > curr_rpm) {
     530      162849 :             input_torque = engine_torque;
     531             :         } else {
     532      174683 :             input_torque = 0.0f;
     533             :         }
     534             : 
     535             :         // help spool down quickly go to zero
     536      337532 :         if (throttle <= 0.15f && curr_rpm < 300) {
     537      143310 :             rpm_dot = - 40.0f;
     538      143310 :             if (curr_rpm <= 0.0f) {
     539      125304 :                 rpm_dot = 0.0f;
     540      125304 :                 curr_rpm = 0.0f;
     541             :             }
     542             :         } else {
     543      194222 :             rpm_dot = accel_scale * (input_torque + descent_torque - rotor_torque);
     544      194222 :             if (curr_rpm <= 0.0 && !is_positive(rpm_dot)) {
     545           0 :                 rpm_dot = 0.0f;
     546           0 :                 curr_rpm = 0.0f;
     547             :             }
     548             :         }
     549             :     } else {
     550     9075634 :         if (throttle > 0.25) {
     551     5668643 :             motor_status = 3; // throttle unlimited
     552     3406991 :         } else if (motor_status == 3 && throttle <= 0.25 && throttle > 0.15) {
     553           1 :             motor_status = 2; // autorotational window
     554     3406990 :         } else if (throttle <= 0.15) {
     555     3366230 :             motor_status = 1; // idle
     556             :         }
     557             : 
     558     9075634 :         if (battery_is_empty()) {
     559           0 :             motor_status = 1; // battery empty and motor shuts down prematurely
     560             :         }
     561             : 
     562     9075634 :         float runup_time = 8.0f;
     563     9075634 :         if (motor_status == 2) {
     564        6003 :             runup_time = 2.0f;
     565             :         }
     566             : 
     567             :         // ramp speed estimate towards control out
     568     9075634 :         float runup_increment = dt / runup_time;
     569     9075634 :         if (motor_status > 2) {
     570     5668643 :             if (rotor_runup_output < 1.0f) {
     571      328283 :                 rotor_runup_output += runup_increment;
     572             :             } else {
     573     5340360 :                 rotor_runup_output = 1.0f;
     574             :             }
     575     5668643 :             if (curr_rpm < nominal_rpm - 25.0f) {
     576      339914 :                 accel_scale = 2000.0f / runup_time;
     577             :             }
     578             :         }else{
     579     3406991 :             if (rotor_runup_output > 0.0f) {
     580       32526 :                 rotor_runup_output -= runup_increment * 10.0f; // make ramp down 10 times faster
     581             :             } else {
     582     3374465 :                 rotor_runup_output = 0.0f;
     583             :             }
     584             :         }
     585             : 
     586             :         // calculate engine torque just for start up and shutdown of rotor
     587     9075634 :         engine_torque = 0.333f * rotor_runup_output * engine_torque_max;
     588             : 
     589             :         // manage input torque so descent torque combined with engine torque doesn't allow rotor to overspeed
     590             :         float input_torque;
     591     9075634 :         if (rotor_runup_output >= 1.0f && curr_rpm > nominal_rpm - 100.0f) {
     592             :             // want the rpm to seek the nominal rpm so set the input torque to only be that for nominal RPM
     593     5331852 :             input_torque = rotor_torque * sq(nominal_rpm / curr_rpm);
     594     3743782 :         } else if (rotor_runup_output <= 0.0f) {
     595     3374503 :             input_torque = descent_torque;
     596             :         } else {
     597      369279 :             input_torque = engine_torque + descent_torque;
     598             :         }
     599             : 
     600             :         // Help spool down quickly got to zero 
     601     9075634 :         if (rotor_runup_output <= 0.0f && curr_rpm < 300) {
     602     2993459 :             rpm_dot = - 40.0f;
     603     2993459 :             if (curr_rpm <= 0.0f) {
     604     2791909 :                 rpm_dot = 0.0f;
     605     2791909 :                 curr_rpm = 0.0f;
     606             :             }
     607             :         } else {
     608     6082175 :             rpm_dot = accel_scale * (input_torque - rotor_torque);
     609             :         }
     610     9075634 :         engine_torque = input_torque;
     611             :     }
     612             : 
     613     9413166 :     curr_rpm += rpm_dot * dt;
     614             : 
     615     9413166 :     return curr_rpm;
     616             : 
     617             : }
     618             : 
     619             : // push servo input to buffer
     620     9417874 : void Helicopter::push_to_buffer(const uint16_t servos_input[16])
     621             : {
     622             :     servos_stored sample;
     623     9417874 :     sample.servo1 = servos_input[0];
     624     9417874 :     sample.servo2 = servos_input[1];
     625     9417874 :     sample.servo3 = servos_input[2];
     626     9417874 :     sample.servo4 = servos_input[3];
     627     9417874 :     sample.servo5 = servos_input[4];
     628     9417874 :     sample.servo6 = servos_input[5];
     629     9417874 :     servos_stored_buffer->push(sample);
     630             : 
     631     9417874 : }
     632             : 
     633             : // pull servo delay from buffer
     634     9413086 : void Helicopter::pull_from_buffer(uint16_t servos_delayed[6])
     635             : {
     636             :     servos_stored sample;
     637     9413086 :     if (!servos_stored_buffer->pop(sample)) {
     638             :         // no sample
     639           0 :         return;
     640             :     }
     641     9413086 :     servos_delayed[0] = sample.servo1;
     642     9413086 :     servos_delayed[1] = sample.servo2; 
     643     9413086 :     servos_delayed[2] = sample.servo3;
     644     9413086 :     servos_delayed[3] = sample.servo4;
     645     9413086 :     servos_delayed[4] = sample.servo5;
     646     9413086 :     servos_delayed[5] = sample.servo6;
     647             : 
     648             : }
     649             : 
     650     9413166 : void Helicopter::update_battery()
     651             : {
     652     9413166 :     battery.maybe_reset(sitl->batt_voltage, sitl->batt_capacity_ah);
     653     9413166 :     battery_voltage = battery.get_voltage();
     654             : 
     655             :     // initially set power consumption for just running the servos and electronics.
     656     9413166 :     float power_watt = 1.0f;
     657     9413166 :     if (motor_interlock) {
     658             :         // set the power consumption for the rotors turning at operational speed.
     659     5702010 :         power_watt = power_consumption_watts;
     660             :     }
     661             :     // calculate current drawn from battery based on power and voltage.  Use a minimum voltage of 0.1 to avoid divide by zero.
     662     9413166 :     battery_current = power_watt / MAX(battery_voltage, 0.1);
     663             : 
     664     9413166 :     const uint64_t now_us = AP_HAL::micros64();
     665     9413166 :     battery.consume_energy(battery_current, now_us);
     666     9413166 : }
     667             : 
     668             : } // namespace SITL

Generated by: LCOV version 1.14