LCOV - code coverage report
Current view: top level - SITL - SIM_Blimp.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 160 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 4 0.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             :   Blimp simulator class
      17             : */
      18             : 
      19             : #include "SIM_Blimp.h"
      20             : #include <AP_Logger/AP_Logger.h>
      21             : 
      22             : #include <stdio.h>
      23             : 
      24             : using namespace SITL;
      25             : 
      26             : extern const AP_HAL::HAL& hal;
      27             : 
      28           0 : Blimp::Blimp(const char *frame_str) :
      29           0 :     Aircraft(frame_str)
      30             : {
      31           0 :     mass = 0.07;   //i.e. 70g
      32           0 :     radius = 0.25; // i.e. 25 cm
      33           0 :     moment_of_inertia = {0.004375, 0.004375, 0.004375}; //m*r^2 for hoop...
      34           0 :     cog = {0, 0, 0.1}; //10 cm down from center (i.e. center of buoyancy), for now
      35           0 :     k_tan = 0.6e-7; //Tangential (thrust) and normal force multipliers for the fins
      36           0 :     k_nor = 0;
      37           0 :     k_m = 0.15; //Thrust multiplier for motors
      38           0 :     gondolawidth = 0.1; //10 cm
      39           0 :     drag_constant = 0.04;
      40           0 :     drag_gyr_constant = 0.15;
      41             :     // linear drag dominates at low speed/rate; without it quadratic-only
      42             :     // drag decays as 1/t and the vehicle never quite stops moving/yawing
      43           0 :     drag_lin_constant = 0.008;
      44           0 :     drag_gyr_lin_constant = 0.035;
      45             : 
      46           0 :     lock_step_scheduled = true;
      47             : 
      48           0 :     constexpr float default_battery_resistance_ohm = 0.01f;
      49           0 :     battery.setup(sitl->batt_capacity_ah,
      50             :                   default_battery_resistance_ohm,
      51           0 :                   sitl->batt_voltage,
      52             :                   ambient_outside_temperature_degC());
      53             : 
      54           0 :     ::printf("Starting Blimp model\n");
      55             : 
      56           0 :     if (strstr(frame_str, "motor")) {
      57           0 :       motorblimp = true;
      58           0 :       ::printf("Running motorblimp frame.\n");
      59             :     }
      60           0 : }
      61             : 
      62             : // calculate rotational and linear accelerations
      63           0 : void Blimp::calculate_forces(const struct sitl_input &input, Vector3f &body_acc, Vector3f &rot_accel)
      64             : {
      65           0 :   float delta_time = frame_time_us * 1.0e-6f;
      66             : 
      67           0 :   if (!hal.scheduler->is_system_initialized()) {
      68           0 :       return;
      69             :   }
      70             : 
      71           0 :   if (!motorblimp) { //Finned blimp
      72             :     //all fin setup
      73           0 :     for (uint8_t i=0; i<4; i++) {
      74           0 :       fin[i].last_angle = fin[i].angle;
      75           0 :       if (!battery_is_empty()) {
      76           0 :           if (input.servos[i] == 0) {
      77           0 :               fin[i].angle = 0;
      78           0 :               fin[i].servo_angle = 0;
      79             :           } else {
      80             :               // filtered_servo_angle() normalises PWM against a fixed 1500 +/- 500
      81             :               // range, but blimp.parm uses SERVOn_MIN 500 / TRIM 1350 / MAX 2200;
      82             :               // the +13.5 degree offset recentres that asymmetric range to a
      83             :               // symmetric -76.5..+76.5 degrees with 0 degrees at servo trim
      84           0 :               fin[i].angle = filtered_servo_angle(input, i)*radians(45.0f)+radians(13.5);
      85           0 :               fin[i].servo_angle = filtered_servo_angle(input, i);
      86             :           }
      87             :       }
      88             : 
      89           0 :       if (fin[i].angle < fin[i].last_angle) fin[i].dir = 0; //thus 0 = "angle is reducing"
      90           0 :       else fin[i].dir = 1;
      91             : 
      92           0 :       fin[i].vel = degrees(fin[i].angle - fin[i].last_angle)/delta_time; //Could also do multi-point derivative filter - DerivativeFilter.cpp
      93             :       //deg/s (should really be rad/s, but that would require modifying k_tan, k_nor)
      94             :       //all other angles should be in radians.
      95           0 :       fin[i].vel = constrain_float(fin[i].vel, -450, 450);
      96           0 :       fin[i].T = sq(fin[i].vel) * k_tan;
      97           0 :       fin[i].N = sq(fin[i].vel) * k_nor;
      98           0 :       if (fin[i].dir == 0) fin[i].N = -fin[i].N; //normal force flips when fin changes direction
      99             : 
     100           0 :       fin[i].Fx = 0;
     101           0 :       fin[i].Fy = 0;
     102           0 :       fin[i].Fz = 0;
     103             :     }
     104             : 
     105             :     //Back fin
     106           0 :     fin[0].Fx =  fin[0].T*cos(fin[0].angle);// + fin[0].N*sin(fin[0].angle); //causes forward movement
     107           0 :     fin[0].Fz =  fin[0].T*sin(fin[0].angle);// - fin[0].N*cos(fin[0].angle); //causes height & wobble in y
     108             : 
     109             :     //Front fin
     110           0 :     fin[1].Fx = -fin[1].T*cos(fin[1].angle);// - fin[1].N*sin(fin[1].angle); //causes backward movement
     111           0 :     fin[1].Fz =  fin[1].T*sin(fin[1].angle);// - fin[1].N*cos(fin[1].angle); //causes height & wobble in y
     112             : 
     113             :     //Right fin
     114           0 :     fin[2].Fy = -fin[2].T*cos(fin[2].angle);// - fin[2].N*sin(fin[2].angle); //causes left movement
     115           0 :     fin[2].Fx =  fin[2].T*sin(fin[2].angle);// - fin[2].N*cos(fin[2].angle); //cause yaw & wobble in z
     116             : 
     117             :     //Left fin
     118           0 :     fin[3].Fy =  fin[3].T*cos(fin[3].angle);// + fin[3].N*sin(fin[3].angle); //causes right movement
     119           0 :     fin[3].Fx =  fin[3].T*sin(fin[3].angle);// + fin[3].N*cos(fin[3].angle); //causes yaw & wobble in z
     120             : 
     121           0 :     Vector3f F_BF{0,0,0};
     122           0 :     for (uint8_t i=0; i<4; i++) {
     123           0 :       F_BF.x = F_BF.x + fin[i].Fx;
     124           0 :       F_BF.y = F_BF.y + fin[i].Fy;
     125           0 :       F_BF.z = F_BF.z + fin[i].Fz;
     126             :     }
     127             : 
     128           0 :     body_acc.x = F_BF.x/mass; //mass in kg, thus accel in m/s/s
     129           0 :     body_acc.y = F_BF.y/mass;
     130           0 :     body_acc.z = F_BF.z/mass;
     131             : 
     132           0 :     Vector3f rot_T{0,0,0};
     133             : 
     134             : #if HAL_LOGGING_ENABLED
     135             :     // @LoggerMessage: SFT
     136             :     // @Description: Simulated Blimp Fin Thrust
     137             :     // @Field: TimeUS: Time since system startup
     138             :     // @Field: f0: Fin 0 tangential thrust
     139             :     // @Field: f1: Fin 1 tangential thrust
     140             :     // @Field: f2: Fin 2 tangential thrust
     141             :     // @Field: f3: Fin 3 tangential thrust
     142           0 :     AP::logger().WriteStreaming("SFT", "TimeUS,f0,f1,f2,f3",
     143             :                                 "Qffff",
     144             :                                 AP_HAL::micros64(),
     145           0 :                                 fin[0].T, fin[1].T, fin[2].T, fin[3].T);
     146             :     // @LoggerMessage: SFN
     147             :     // @Description: Simulated Blimp Fin Forces
     148             :     // @Field: TimeUS: Time since system startup
     149             :     // @Field: n0: Fin 0 normal force
     150             :     // @Field: n1: Fin 1 normal force
     151             :     // @Field: n2: Fin 2 normal force
     152             :     // @Field: n3: Fin 3 normal force
     153           0 :     AP::logger().WriteStreaming("SFN", "TimeUS,n0,n1,n2,n3",
     154             :                                 "Qffff",
     155             :                                 AP_HAL::micros64(),
     156           0 :                                 fin[0].N, fin[1].N, fin[2].N, fin[3].N);
     157             :     // @LoggerMessage: SBA1
     158             :     // @Description: Simulated Blimp Body-Frame accelerations
     159             :     // @Field: TimeUS: Time since system startup
     160             :     // @Field: ax: x-axis acceleration
     161             :     // @Field: ay: y-axis acceleration
     162             :     // @Field: az: z-axis acceleration
     163           0 :     AP::logger().WriteStreaming("SBA1", "TimeUS,ax,ay,az",
     164             :                                 "Qfff",
     165             :                                 AP_HAL::micros64(),
     166           0 :                                 body_acc.x, body_acc.y, body_acc.z);
     167             :     // @LoggerMessage: SFA1
     168             :     // @Description: Simulated Blimp Fin Angles
     169             :     // @Field: TimeUS: Time since system startup
     170             :     // @Field: f0: fin 0 angle
     171             :     // @Field: f1: fin 1 angle
     172             :     // @Field: f2: fin 2 angle
     173             :     // @Field: f3: fin 3 angle
     174           0 :     AP::logger().WriteStreaming("SFA1", "TimeUS,f0,f1,f2,f3",
     175             :                                 "Qffff",
     176             :                                 AP_HAL::micros64(),
     177           0 :                                 fin[0].angle, fin[1].angle, fin[2].angle, fin[3].angle);
     178             :     // @LoggerMessage: SFAN
     179             :     // @Description: Simulated Blimp Servo Angles
     180             :     // @Field: TimeUS: Time since system startup
     181             :     // @Field: f0: fin 0 servo angle
     182             :     // @Field: f1: fin 1 servo angle
     183             :     // @Field: f2: fin 2 servo angle
     184             :     // @Field: f3: fin 3 servo angle
     185           0 :     AP::logger().WriteStreaming("SFAN", "TimeUS,f0,f1,f2,f3",
     186             :                                 "Qffff",
     187             :                                 AP_HAL::micros64(),
     188           0 :                                 fin[0].servo_angle, fin[1].servo_angle, fin[2].servo_angle, fin[3].servo_angle);
     189             :     // @LoggerMessage: SSAN
     190             :     // @Description: Simulated Blimp Servo Inputs
     191             :     // @Field: TimeUS: Time since system startup
     192             :     // @Field: f0: fin 0 servo angle input
     193             :     // @Field: f1: fin 1 servo angle input
     194             :     // @Field: f2: fin 2 servo angle input
     195             :     // @Field: f3: fin 3 servo angle input
     196           0 :     AP::logger().WriteStreaming("SSAN", "TimeUS,f0,f1,f2,f3",
     197             :                                 "QHHHH",
     198             :                                 AP_HAL::micros64(),
     199           0 :                                 input.servos[0], input.servos[1], input.servos[2], input.servos[3]);
     200             :     // @LoggerMessage: SFV1
     201             :     // @Description: Simulated Blimp Fin Velocities
     202             :     // @Field: TimeUS: Time since system startup
     203             :     // @Field: f0: fin 0 velocity
     204             :     // @Field: f1: fin 1 velocity
     205             :     // @Field: f2: fin 2 velocity
     206             :     // @Field: f3: fin 3 velocity
     207           0 :     AP::logger().WriteStreaming("SFV1", "TimeUS,f0,f1,f2,f3",
     208             :                                 "Qffff",
     209             :                                 AP_HAL::micros64(),
     210           0 :                                 fin[0].vel, fin[1].vel, fin[2].vel, fin[3].vel);
     211             :     // @LoggerMessage: SRT1
     212             :     // @Description: Simulated Blimp Rotational forces
     213             :     // @Field: TimeUS: Time since system startup
     214             :     // @Field: rtx: zero
     215             :     // @Field: rty: zero
     216             :     // @Field: rtz: zero
     217           0 :     AP::logger().WriteStreaming("SRT1", "TimeUS,rtx,rty,rtz",
     218             :                                 "Qfff",
     219             :                                 AP_HAL::micros64(),
     220           0 :                                 rot_T.x, rot_T.y, rot_T.z);
     221             : #endif  // HAL_LOGGING_ENABLED
     222             : 
     223             : #if 0 //"Wobble" attempt
     224             :     rot_T.y = fin[0].Fz * radius + fin[1].Fz * radius;
     225             :     // @LoggerMessage: SRT2
     226             :     // @Description: Transformed Simulated Blimp Rotational forces
     227             :     // @Field: TimeUS: Time since system startup
     228             :     // @Field: rtx: x-axis wobble rotational force
     229             :     // @Field: rty: y-axis wobble rotational force
     230             :     // @Field: rtz: z-axis wobble rotational force
     231             :     AP::logger().WriteStreaming("SRT2", "TimeUS,rtx,rty,rtz",
     232             :                                 "Qfff",
     233             :                                 AP_HAL::micros64(),
     234             :                                 rot_T.x, rot_T.y, rot_T.z);
     235             :     // the blimp has pendulum stability due to the centre of gravity being lower than the centre of buoyancy
     236             :     Vector3f ang; //x,y,z correspond to roll, pitch, yaw.
     237             :     dcm.to_euler(&ang.x, &ang.y, &ang.z); //rpy in radians
     238             :     Vector3f ang_ef = dcm * ang;
     239             :     rot_T.x -= mass*GRAVITY_MSS*sinf(M_PI-ang_ef.x)/cog.z;
     240             :     rot_T.y -= mass*GRAVITY_MSS*sinf(M_PI-ang_ef.y)/cog.z;
     241             :     // @LoggerMessage: SRT3
     242             :     // @Description: Simulated Blimp Torques
     243             :     // @Field: TimeUS: Time since system startup
     244             :     // @Field: rtx: torque around x axis
     245             :     // @Field: rty: torque around y axis
     246             :     // @Field: rtz: torque around z axis
     247             :     AP::logger().WriteStreaming("SRT3", "TimeUS,rtx,rty,rtz",
     248             :                                 "Qfff",
     249             :                                 AP_HAL::micros64(),
     250             :                                 rot_T.x, rot_T.y, rot_T.z);
     251             :     // @LoggerMessage: SAN1
     252             :     // @Description: Simulated Blimp Angles
     253             :     // @Field: TimeUS: Time since system startup
     254             :     // @Field: anx: x angle
     255             :     // @Field: any: y angle
     256             :     // @Field: anz: z angle
     257             :     AP::logger().WriteStreaming("SAN1", "TimeUS,anx,any,anz",
     258             :                                 "Qfff",
     259             :                                 AP_HAL::micros64(),
     260             :                                 ang.x, ang.y, ang.z);
     261             :     // @LoggerMessage: SAN2
     262             :     // @Description: Simulated Blimp Angles
     263             :     // @Field: TimeUS: Time since system startup
     264             :     // @Field: anx: x earth-frame angle
     265             :     // @Field: any: y earth-frame angle
     266             :     // @Field: anz: z earth-frame angle
     267             :     AP::logger().WriteStreaming("SAN2", "TimeUS,anx,any,anz",
     268             :                                 "Qfff",
     269             :                                 AP_HAL::micros64(),
     270             :                                 ang_ef.x, ang_ef.y, ang_ef.z);
     271             :     // @LoggerMessage: SAF1
     272             :     // @Description: Simulated Blimp Sin-Angles
     273             :     // @Field: TimeUS: Time since system startup
     274             :     // @Field: afx: sin(x angle)
     275             :     // @Field: afy: sin(y angle)
     276             :     // @Field: afz: sin(z angle)
     277             :     AP::logger().WriteStreaming("SAF1", "TimeUS,afx,afy,afz",
     278             :                                 "Qfff",
     279             :                                 AP_HAL::micros64(),
     280             :                                 sinf(ang.x), sinf(ang.y), sinf(ang.z));
     281             :     // @LoggerMessage: SMGC
     282             :     // @Description: Simulated Blimp Mass and COG
     283             :     // @Field: TimeUS: Time since system startup
     284             :     // @Field: m: mass
     285             :     // @Field: g: gravity
     286             :     // @Field: cz: centre-of-gravity, z-axis
     287             :     AP::logger().WriteStreaming("SMGC", "TimeUS,m,g,cz",
     288             :                                 "Qfff",
     289             :                                 AP_HAL::micros64(),
     290             :                                 mass, GRAVITY_MSS, cog.z);
     291             : #endif
     292             : 
     293             :     //Blimp yaw
     294           0 :     rot_T.z = fin[2].Fx * radius - fin[3].Fx * radius;//in N*m (Torque = force * lever arm)
     295             :     //rot accel = torque / moment of inertia
     296             :     //Torque = moment force.
     297           0 :     rot_accel.x = rot_T.x / moment_of_inertia.x;
     298           0 :     rot_accel.y = rot_T.y / moment_of_inertia.y;
     299           0 :     rot_accel.z = rot_T.z / moment_of_inertia.z;
     300             :     
     301             :   } else { //MotorBlimp
     302           0 :     for (uint8_t i=0; i<4; i++) {
     303           0 :       if (battery_is_empty() || input.servos[i] == 0) {
     304           0 :           mot[i].throttle = 0;
     305             :       } else {
     306           0 :         mot[i].throttle = filtered_servo_angle(input, i);
     307             :       }
     308           0 :       mot[i].thrust = mot[i].throttle * k_m;
     309             : 
     310           0 :       mot[i].Fx = 0;
     311           0 :       mot[i].Fy = 0;
     312           0 :       mot[i].Fz = 0;
     313             :     }
     314             : 
     315             :     //FrontLeft motor
     316           0 :     mot[0].Fx = mot[0].thrust;
     317             :     //FrontRight motor
     318           0 :     mot[1].Fx = mot[1].thrust;
     319             :     //Up motor
     320           0 :     mot[2].Fz = -mot[2].thrust;
     321             :     //Right motor
     322           0 :     mot[3].Fy = mot[3].thrust;
     323             :   
     324           0 :     Vector3f F_BF{0,0,0};
     325           0 :     for (uint8_t i=0; i<4; i++) {
     326           0 :       F_BF.x = F_BF.x + mot[i].Fx;
     327           0 :       F_BF.y = F_BF.y + mot[i].Fy;
     328           0 :       F_BF.z = F_BF.z + mot[i].Fz;
     329             :     }
     330           0 :     body_acc.x = F_BF.x/mass; //mass in kg, thus accel in m/s/s
     331           0 :     body_acc.y = F_BF.y/mass;
     332           0 :     body_acc.z = F_BF.z/mass;
     333             : 
     334           0 :     Vector3f rot_T{0,0,0};
     335           0 :     rot_T.z = mot[0].thrust * gondolawidth - mot[1].thrust * gondolawidth; //in N*m (Torque = force * lever arm)
     336             :     //skipping swing effect generated by the motors.
     337           0 :     rot_accel.x = rot_T.x / moment_of_inertia.x;
     338           0 :     rot_accel.y = rot_T.y / moment_of_inertia.y;
     339           0 :     rot_accel.z = rot_T.z / moment_of_inertia.z;
     340             :   }
     341             : }
     342             : 
     343             : /*
     344             :   update the blimp simulation by one time step
     345             :  */
     346           0 : void Blimp::update(const struct sitl_input &input)
     347             : {
     348           0 :   float delta_time = frame_time_us * 1.0e-6f;
     349             : 
     350           0 :   Vector3f rot_accel = Vector3f(0,0,0);
     351           0 :   calculate_forces(input, accel_body, rot_accel);
     352             : 
     353           0 :   if (hal.scheduler->is_system_initialized()) {
     354             :     // quadratic rotational drag, dominant at higher rates
     355           0 :     float gyr_sq = gyro.length_squared();
     356           0 :     if (is_positive(gyr_sq)) {
     357           0 :         Vector3f force_gyr = (gyro.normalized() * drag_gyr_constant * gyr_sq);
     358           0 :         Vector3f ef_drag_accel_gyr = -force_gyr / mass;
     359           0 :         Vector3f bf_drag_accel_gyr = dcm.transposed() * ef_drag_accel_gyr;
     360           0 :         rot_accel += bf_drag_accel_gyr;
     361             :     }
     362             :     // linear rotational drag so residual rates decay to zero
     363           0 :     rot_accel -= gyro * (drag_gyr_lin_constant / mass);
     364             :   }
     365             : 
     366             :   // update rotational rates in body frame
     367           0 :   gyro += rot_accel * delta_time;
     368             : 
     369           0 :   gyro.x = constrain_float(gyro.x, -radians(2000.0f), radians(2000.0f));
     370           0 :   gyro.y = constrain_float(gyro.y, -radians(2000.0f), radians(2000.0f));
     371           0 :   gyro.z = constrain_float(gyro.z, -radians(2000.0f), radians(2000.0f));
     372             : 
     373             :   // Vector3f ang; //x,y,z correspond to roll, pitch, yaw.
     374             :   // dcm.to_euler(&ang.x, &ang.y, &ang.z); //rpy in radians
     375             :   // dcm.from_euler(0.0f, 0.0f, ang.z);
     376             :   // update attitude
     377           0 :   dcm.rotate(gyro * delta_time);
     378           0 :   dcm.normalize();
     379             : 
     380           0 :   if (hal.scheduler->is_system_initialized()) {
     381             :     // quadratic translational drag, dominant at higher speeds
     382           0 :     float speed_sq = velocity_ef.length_squared();
     383           0 :     if (is_positive(speed_sq)) {
     384           0 :         Vector3f force = (velocity_ef.normalized() * drag_constant * speed_sq);
     385           0 :         Vector3f ef_drag_accel = -force / mass;
     386           0 :         Vector3f bf_drag_accel = dcm.transposed() * ef_drag_accel;
     387           0 :         accel_body += bf_drag_accel;
     388             :     }
     389             :     // linear translational drag so residual velocity decays to zero
     390           0 :     accel_body -= (dcm.transposed() * velocity_ef) * (drag_lin_constant / mass);
     391             : 
     392             :     // add lifting force exactly equal to gravity, for neutral buoyancy (buoyancy in ef)
     393           0 :     accel_body += dcm.transposed() * Vector3f(0,0,-GRAVITY_MSS);
     394             :   }
     395             : 
     396           0 :   Vector3f accel_earth = dcm * accel_body;
     397           0 :   accel_earth += Vector3f(0.0f, 0.0f, GRAVITY_MSS); //add gravity
     398           0 :   velocity_ef += accel_earth * delta_time;
     399           0 :   position += (velocity_ef * delta_time).todouble(); //update position vector
     400             : 
     401           0 :   update_position(); //updates the position from the Vector3f position
     402           0 :   time_advance();
     403           0 :   update_mag_field_bf();
     404           0 :   rate_hz = sitl->loop_rate_hz;
     405             : 
     406           0 :   update_battery();
     407           0 : }
     408             : 
     409           0 : void Blimp::update_battery()
     410             : {
     411           0 :   battery.maybe_reset(sitl->batt_voltage, sitl->batt_capacity_ah);
     412           0 :   battery_current = 0.0f;
     413             : 
     414           0 :   if (!battery_is_empty()) {
     415           0 :     if (motorblimp) {
     416           0 :       constexpr float motor_current_scaler_amps = 2.0f;
     417           0 :       for (uint8_t i=0; i<4; i++) {
     418           0 :         battery_current += fabsf(mot[i].throttle) * motor_current_scaler_amps;
     419             :       }
     420             :     } else {
     421           0 :       constexpr float fin_current_scaler_amps = 0.01f;
     422           0 :       for (uint8_t i=0; i<4; i++) {
     423           0 :         battery_current += fabsf(fin[i].vel) * fin_current_scaler_amps;
     424             :       }
     425             :     }
     426             :   }
     427             : 
     428           0 :   battery.consume_energy(battery_current, AP_HAL::micros64());
     429           0 :   battery_voltage = battery.get_voltage();
     430           0 :   battery_temperature_degC = battery.get_temperature_degC();
     431           0 : }

Generated by: LCOV version 1.14