LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - systemid.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 12 208 5.8 %
Date: 2026-09-09 11:02:08 Functions: 4 8 50.0 %

          Line data    Source code
       1             : #include "systemid.h"
       2             : 
       3             : #if AP_PLANE_SYSTEMID_ENABLED
       4             : 
       5             : #include <AP_Math/control.h>
       6             : #include "Plane.h"
       7             : 
       8             : /*
       9             :   handle systemid via an auxiliary switch
      10             :  */
      11             : 
      12             : const AP_Param::GroupInfo AP_SystemID::var_info[] = {
      13             : 
      14             :     // @Param: _AXIS
      15             :     // @DisplayName: System identification axis
      16             :     // @Description: Controls which axis are being excited.  Set to non-zero to see more parameters
      17             :     // @User: Standard
      18             :     // @Values: 0:None, 1:VTOL Input Roll Angle, 2:VTOL Input Pitch Angle, 3:VTOL Input Yaw Angle, 4:VTOL Recovery Roll Angle, 5:VTOL Recovery Pitch Angle, 6:VTOL Recovery Yaw Angle, 7:VTOL Rate Roll, 8:VTOL Rate Pitch, 9:VTOL Rate Yaw, 10:VTOL Mixer Roll, 11:VTOL Mixer Pitch, 12:VTOL Mixer Yaw, 13:VTOL Mixer Thrust, 20:FW Input Roll Angle, 21:FW Input Pitch Angle, 22:FW Mixer Roll, 23:FW Mixer Pitch
      19             :     AP_GROUPINFO_FLAGS("_AXIS", 1, AP_SystemID, axis, 0, AP_PARAM_FLAG_ENABLE),
      20             : 
      21             :     // @Param: _MAGNITUDE
      22             :     // @DisplayName: System identification Chirp Magnitude
      23             :     // @Description: Magnitude of sweep in deg, deg/s and 0-1 for mixer outputs.
      24             :     // @User: Standard
      25             :     AP_GROUPINFO("_MAGNITUDE", 2, AP_SystemID, waveform_magnitude, 5),
      26             : 
      27             :     // @Param: _F_START_HZ
      28             :     // @DisplayName: System identification Start Frequency
      29             :     // @Description: Frequency at the start of the sweep
      30             :     // @Range: 0.01 100
      31             :     // @Units: Hz
      32             :     // @User: Standard
      33             :     AP_GROUPINFO("_F_START_HZ", 3, AP_SystemID, frequency_start, 0.5f),
      34             : 
      35             :     // @Param: _F_STOP_HZ
      36             :     // @DisplayName: System identification Stop Frequency
      37             :     // @Description: Frequency at the end of the sweep
      38             :     // @Range: 0.01 100
      39             :     // @Units: Hz
      40             :     // @User: Standard
      41             :     AP_GROUPINFO("_F_STOP_HZ", 4, AP_SystemID, frequency_stop, 15),
      42             : 
      43             :     // @Param: _T_FADE_IN
      44             :     // @DisplayName: System identification Fade in time
      45             :     // @Description: Time to reach maximum amplitude of sweep
      46             :     // @Range: 0 20
      47             :     // @Units: s
      48             :     // @User: Standard
      49             :     AP_GROUPINFO("_T_FADE_IN", 5, AP_SystemID, time_fade_in, 5),
      50             : 
      51             :     // @Param: _T_REC
      52             :     // @DisplayName: System identification Total Sweep length
      53             :     // @Description: Time taken to complete the sweep
      54             :     // @Range: 0 255
      55             :     // @Units: s
      56             :     // @User: Standard
      57             :     AP_GROUPINFO("_T_REC", 6, AP_SystemID, time_record, 70),
      58             : 
      59             :     // @Param: _T_FADE_OUT
      60             :     // @DisplayName: System identification Fade out time
      61             :     // @Description: Time to reach zero amplitude at the end of the sweep
      62             :     // @Range: 0 5
      63             :     // @Units: s
      64             :     // @User: Standard
      65             :     AP_GROUPINFO("_T_FADE_OUT", 7, AP_SystemID, time_fade_out, 1),
      66             : 
      67             :     // @Param: _XY_CTRL_MUL
      68             :     // @DisplayName: System identification XY control multiplier
      69             :     // @Description: A multiplier for the XY velocity and position controller when using systemID in VTOL modes that do horizontal position and velocity control
      70             :     // @Range: 0.05 1.0
      71             :     // @User: Standard
      72             :     AP_GROUPINFO("_XY_CTRL_MUL", 8, AP_SystemID, xy_control_mul, 0.1),
      73             :     
      74             :     AP_GROUPEND
      75             : };
      76             : 
      77         692 : AP_SystemID::AP_SystemID(void)
      78             : {
      79         692 :     AP_Param::setup_object_defaults(this, var_info);
      80         692 : }
      81             : 
      82             : // @LoggerMessage: SIDS
      83             : // @Description: System ID settings
      84             : // @Field: TimeUS: Time since system startup
      85             : // @Field: Ax: The axis which is being excited
      86             : // @Field: Mag: Magnitude of the chirp waveform
      87             : // @Field: FSt: Frequency at the start of chirp
      88             : // @Field: FSp: Frequency at the end of chirp
      89             : // @Field: TFin: Time to reach maximum amplitude of chirp
      90             : // @Field: TC: Time at constant frequency before chirp starts
      91             : // @Field: TR: Time taken to complete chirp waveform
      92             : // @Field: TFout: Time to reach zero amplitude after chirp finishes
      93             : 
      94             : /*
      95             :   start systemid
      96             :  */
      97           0 : void AP_SystemID::start()
      98             : {
      99           0 :     start_axis = axis;
     100             : 
     101           0 :     switch (start_axis) {
     102           0 :         case AxisType::NONE:
     103             :             // check if enabled
     104           0 :             gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: No axis selected");
     105           0 :             return;
     106           0 :         case AxisType::INPUT_ROLL:
     107             :         case AxisType::INPUT_PITCH:
     108             :         case AxisType::INPUT_YAW:
     109             :         case AxisType::RECOVER_ROLL:
     110             :         case AxisType::RECOVER_PITCH:
     111             :         case AxisType::RECOVER_YAW:
     112             :         case AxisType::RATE_ROLL:
     113             :         case AxisType::RATE_PITCH:
     114             :         case AxisType::RATE_YAW:
     115             :         case AxisType::MIX_ROLL:
     116             :         case AxisType::MIX_PITCH:
     117             :         case AxisType::MIX_YAW:
     118             :         case AxisType::MIX_THROTTLE:
     119             :             // Exits if the current flight mode or phase does not support system ID axis.
     120           0 :             if (!plane.control_mode->supports_vtol_systemid()) {
     121             : #if HAL_QUADPLANE_ENABLED
     122           0 :                 gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight mode");
     123             : #else
     124             :                 gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported in Plane");
     125             : #endif
     126           0 :                 return;
     127             :             }
     128           0 :             break;
     129           0 :         case AxisType::FW_INPUT_ROLL:
     130             :         case AxisType::FW_INPUT_PITCH:
     131             :         case AxisType::FW_MIX_ROLL:
     132             :         case AxisType::FW_MIX_PITCH:
     133             :             // Exits if the currently flight mode or phase does not support system ID axis.
     134           0 :             if (!plane.control_mode->supports_fw_systemid()) {
     135           0 :                 gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight mode");
     136           0 :                 return;
     137             :             }
     138           0 :             if (!plane.control_mode->allow_fw_systemid()) {
     139           0 :                 gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight phase");
     140           0 :                 return;
     141             :             }
     142           0 :             break;
     143             :     }
     144             : 
     145           0 :     if (!hal.util->get_soft_armed()) {
     146           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: must be armed");
     147           0 :         return;
     148             :     }
     149             : 
     150           0 :     attitude_offset_deg.zero();
     151           0 :     throttle_offset = 0;
     152             : 
     153             : #if HAL_QUADPLANE_ENABLED
     154           0 :     if (plane.quadplane.available()) {
     155           0 :         restore.att_bf_feedforward = plane.quadplane.attitude_control->get_bf_feedforward();
     156             :     }
     157             : #endif
     158             : 
     159           0 :     waveform_time = 0;
     160           0 :     time_const_freq = 2.0 / frequency_start; // Two full cycles at the starting frequency
     161             : 
     162           0 :     chirp_input.init(time_record, frequency_start, frequency_stop, time_fade_in, time_fade_out, time_const_freq);
     163             : 
     164           0 :     gcs().send_text(MAV_SEVERITY_INFO, "SystemID Starting: axis=%d", (unsigned)axis);
     165             : 
     166             : #if HAL_LOGGING_ENABLED
     167           0 :     AP::logger().WriteStreaming("SIDS", "TimeUS,Ax,Mag,FSt,FSp,TFin,TC,TR,TFout",
     168             :                                 "s--ssssss", "F--------", "QBfffffff",
     169             :                                 AP_HAL::micros64(),
     170           0 :                                 uint8_t(start_axis),
     171           0 :                                 waveform_magnitude.get(),
     172           0 :                                 frequency_start.get(),
     173           0 :                                 frequency_stop.get(),
     174           0 :                                 time_fade_in.get(),
     175           0 :                                 time_const_freq,
     176           0 :                                 time_record.get(),
     177           0 :                                 time_fade_out.get());
     178             : #endif // HAL_LOGGING_ENABLED
     179             : 
     180           0 :     running = true;
     181             : }
     182             : 
     183             : /*
     184             :   stop systemid
     185             :  */
     186        1930 : void AP_SystemID::stop()
     187             : {
     188        1930 :     if (running) {
     189           0 :         running = false;
     190           0 :         attitude_offset_deg.zero();
     191           0 :         throttle_offset = 0;
     192             : 
     193             : #if HAL_QUADPLANE_ENABLED
     194           0 :         if (plane.quadplane.available()) {
     195           0 :             auto *attitude_control = plane.quadplane.attitude_control;
     196           0 :             attitude_control->bf_feedforward(restore.att_bf_feedforward);
     197           0 :             attitude_control->rate_bf_roll_sysid_rads(0);
     198           0 :             attitude_control->rate_bf_pitch_sysid_rads(0);
     199           0 :             attitude_control->rate_bf_yaw_sysid_rads(0);
     200           0 :             plane.quadplane.pos_control->NE_set_control_scale_factor(1);
     201             : 
     202             :             // re-initialise the XY controller so we take current position as target
     203           0 :             plane.quadplane.pos_control->NE_init_controller();
     204             :         }
     205             : #endif
     206           0 :         gcs().send_text(MAV_SEVERITY_INFO, "SystemID stopped");
     207             :     }
     208        1930 : }
     209             : 
     210             : /*
     211             :   update systemid - needs to be called at main loop rate
     212             :  */
     213      810697 : void AP_SystemID::vtol_update()
     214             : {
     215             : #if HAL_QUADPLANE_ENABLED
     216      810697 :     if (!running) {
     217      810697 :         return;
     218             :     }
     219           0 :     if (chirp_input.completed()) {
     220           0 :         stop();
     221           0 :         return;
     222             :     }
     223             : 
     224           0 :     float const last_loop_time_s = AP::scheduler().get_last_loop_time_s();
     225             : 
     226           0 :     waveform_time += last_loop_time_s;
     227           0 :     waveform_sample = chirp_input.update(waveform_time, waveform_magnitude);
     228           0 :     waveform_freq_rads = chirp_input.get_frequency_rads();
     229             : 
     230           0 :     auto *attitude_control = plane.quadplane.attitude_control;
     231             : 
     232           0 :     switch (start_axis) {
     233           0 :         case AxisType::NONE:
     234             :             // not possible, see start()
     235           0 :             break;
     236           0 :         case AxisType::INPUT_ROLL:
     237           0 :             attitude_offset_deg.x = waveform_sample;
     238           0 :             break;
     239           0 :         case AxisType::INPUT_PITCH:
     240           0 :             attitude_offset_deg.y = waveform_sample;
     241           0 :             break;
     242           0 :         case AxisType::INPUT_YAW:
     243           0 :             attitude_offset_deg.z = waveform_sample;
     244           0 :             break;
     245           0 :         case AxisType::RECOVER_ROLL:
     246           0 :             attitude_offset_deg.x = waveform_sample;
     247           0 :             attitude_control->bf_feedforward(false);
     248           0 :             break;
     249           0 :         case AxisType::RECOVER_PITCH:
     250           0 :             attitude_offset_deg.y = waveform_sample;
     251           0 :             attitude_control->bf_feedforward(false);
     252           0 :             break;
     253           0 :         case AxisType::RECOVER_YAW:
     254           0 :             attitude_offset_deg.z = waveform_sample;
     255           0 :             attitude_control->bf_feedforward(false);
     256           0 :             break;
     257           0 :         case AxisType::RATE_ROLL:
     258           0 :             attitude_control->rate_bf_roll_sysid_rads(radians(waveform_sample));
     259           0 :             break;
     260           0 :         case AxisType::RATE_PITCH:
     261           0 :             attitude_control->rate_bf_pitch_sysid_rads(radians(waveform_sample));
     262           0 :             break;
     263           0 :         case AxisType::RATE_YAW:
     264           0 :             attitude_control->rate_bf_yaw_sysid_rads(radians(waveform_sample));
     265           0 :             break;
     266           0 :         case AxisType::MIX_ROLL:
     267           0 :             attitude_control->actuator_roll_sysid(waveform_sample);
     268           0 :             break;
     269           0 :         case AxisType::MIX_PITCH:
     270           0 :             attitude_control->actuator_pitch_sysid(waveform_sample);
     271           0 :             break;
     272           0 :         case AxisType::MIX_YAW:
     273           0 :             attitude_control->actuator_yaw_sysid(waveform_sample);
     274           0 :             break;
     275           0 :         case AxisType::MIX_THROTTLE:
     276           0 :             throttle_offset = waveform_sample;
     277           0 :             break;
     278           0 :         default:
     279           0 :             break;
     280             :     }
     281             : 
     282             :     // reduce control in NE axis when in position controlled modes
     283           0 :     plane.quadplane.pos_control->NE_set_control_scale_factor(xy_control_mul);
     284             : 
     285           0 :     if (log_subsample <= 0) {
     286           0 :         log_data();
     287             :         // log attitude controller at the same rate
     288           0 :         plane.quadplane.Log_Write_AttRate();
     289             : 
     290           0 :         if (plane.should_log(MASK_LOG_ATTITUDE_FAST) && plane.should_log(MASK_LOG_ATTITUDE_MED)) {
     291           0 :             log_subsample = 1;
     292           0 :         } else if (plane.should_log(MASK_LOG_ATTITUDE_FAST)) {
     293           0 :             log_subsample = 2;
     294           0 :         } else if (plane.should_log(MASK_LOG_ATTITUDE_MED)) {
     295           0 :             log_subsample = 4;
     296             :         } else {
     297           0 :             log_subsample = 8;
     298             :         }
     299             :     }
     300           0 :     log_subsample -= 1;
     301             : 
     302             : #endif
     303             : }
     304             : 
     305             : // Return true if a fixed wing system ID is currently running
     306    23460105 : bool AP_SystemID::is_running_fw() const
     307             : {
     308    23460105 :     if (!is_running()) {
     309    23460105 :         return false;
     310             :     }
     311             : 
     312           0 :     switch (start_axis) {
     313           0 :         case AxisType::NONE:
     314             :         case AxisType::INPUT_ROLL:
     315             :         case AxisType::INPUT_PITCH:
     316             :         case AxisType::INPUT_YAW:
     317             :         case AxisType::RECOVER_ROLL:
     318             :         case AxisType::RECOVER_PITCH:
     319             :         case AxisType::RECOVER_YAW:
     320             :         case AxisType::RATE_ROLL:
     321             :         case AxisType::RATE_PITCH:
     322             :         case AxisType::RATE_YAW:
     323             :         case AxisType::MIX_ROLL:
     324             :         case AxisType::MIX_PITCH:
     325             :         case AxisType::MIX_YAW:
     326             :         case AxisType::MIX_THROTTLE:
     327           0 :             break;
     328             : 
     329           0 :         case AxisType::FW_INPUT_ROLL:
     330             :         case AxisType::FW_INPUT_PITCH:
     331             :         case AxisType::FW_MIX_ROLL:
     332             :         case AxisType::FW_MIX_PITCH:
     333           0 :             return true;
     334             :     }
     335             : 
     336           0 :     return false;
     337             : }
     338             : 
     339             : /*
     340             :   update systemid - needs to be called at main loop rate
     341             :  */
     342           0 : void AP_SystemID::fw_update()
     343             : {
     344           0 :     if (!plane.control_mode->allow_fw_systemid() || chirp_input.completed()) {
     345             :         // Control mode change means chirp should be stopped, or
     346             :         // Chirp is complete
     347           0 :         stop();
     348           0 :         return;
     349             :     }
     350             : 
     351           0 :     float const last_loop_time_s = AP::scheduler().get_last_loop_time_s();
     352             : 
     353           0 :     waveform_time += last_loop_time_s;
     354           0 :     waveform_sample = chirp_input.update(waveform_time, waveform_magnitude);
     355           0 :     waveform_freq_rads = chirp_input.get_frequency_rads();
     356             : 
     357           0 :     switch (start_axis) {
     358           0 :         case AxisType::NONE:
     359             :             // not possible, see start()
     360           0 :             break;
     361           0 :         case AxisType::FW_INPUT_ROLL:
     362           0 :             plane.nav_roll_cd += waveform_sample * 100.0f;
     363           0 :             break;
     364           0 :         case AxisType::FW_INPUT_PITCH:
     365           0 :             plane.nav_pitch_cd += waveform_sample * 100.0f;
     366           0 :             break;
     367           0 :         case AxisType::FW_MIX_ROLL:
     368           0 :             output_offset.x = waveform_sample;
     369           0 :             break;
     370           0 :         case AxisType::FW_MIX_PITCH:
     371           0 :             output_offset.y = waveform_sample;
     372           0 :             break;
     373           0 :         default:
     374           0 :             break;
     375             :     }
     376             : 
     377           0 :     if (log_subsample <= 0) {
     378           0 :         log_data();
     379           0 :         log_plane_data();
     380             : 
     381           0 :         if (plane.should_log(MASK_LOG_ATTITUDE_FAST) && plane.should_log(MASK_LOG_ATTITUDE_MED)) {
     382           0 :             log_subsample = 1;
     383           0 :         } else if (plane.should_log(MASK_LOG_ATTITUDE_FAST)) {
     384           0 :             log_subsample = 2;
     385           0 :         } else if (plane.should_log(MASK_LOG_ATTITUDE_MED)) {
     386           0 :             log_subsample = 4;
     387             :         } else {
     388           0 :             log_subsample = 8;
     389             :         }
     390             :     }
     391           0 :     log_subsample -= 1;
     392             : }
     393             : 
     394             : // @LoggerMessage: SIDD
     395             : // @Description: System ID data
     396             : // @Field: TimeUS: Time since system startup
     397             : // @Field: Time: Time reference for waveform
     398             : // @Field: Targ: Current waveform sample
     399             : // @Field: F: Instantaneous waveform frequency
     400             : // @Field: Gx: Delta angle, X-Axis
     401             : // @Field: Gy: Delta angle, Y-Axis
     402             : // @Field: Gz: Delta angle, Z-Axis
     403             : // @Field: Ax: Delta velocity, X-Axis
     404             : // @Field: Ay: Delta velocity, Y-Axis
     405             : // @Field: Az: Delta velocity, Z-Axis
     406             : 
     407             : // log system id
     408           0 : void AP_SystemID::log_data() const
     409             : {
     410             : #if HAL_LOGGING_ENABLED
     411           0 :     Vector3f delta_angle;
     412             :     float delta_angle_dt;
     413           0 :     plane.ins.get_delta_angle(delta_angle, delta_angle_dt);
     414             : 
     415           0 :     Vector3f delta_velocity;
     416             :     float delta_velocity_dt;
     417           0 :     plane.ins.get_delta_velocity(delta_velocity, delta_velocity_dt);
     418             : 
     419           0 :     if (is_positive(delta_angle_dt) && is_positive(delta_velocity_dt)) {
     420           0 :         const float dt_ang_inv = 1.0 / delta_angle_dt;
     421           0 :         const float dt_vel_inv = 1.0 / delta_velocity_dt;
     422           0 :         AP::logger().WriteStreaming("SIDD", "TimeUS,Time,Targ,F,Gx,Gy,Gz,Ax,Ay,Az",
     423             :                                     "ss-zkkkooo", "F---------", "Qfffffffff",
     424             :                                     AP_HAL::micros64(),
     425           0 :                                     waveform_time, waveform_sample, waveform_freq_rads / (2 * M_PI),
     426           0 :                                     degrees(delta_angle.x * dt_ang_inv),
     427           0 :                                     degrees(delta_angle.y * dt_ang_inv),
     428           0 :                                     degrees(delta_angle.z * dt_ang_inv),
     429           0 :                                     delta_velocity.x * dt_vel_inv,
     430           0 :                                     delta_velocity.y * dt_vel_inv,
     431           0 :                                     delta_velocity.z * dt_vel_inv);
     432             :     }
     433             : #endif // HAL_LOGGING_ENABLED
     434           0 : }
     435             : 
     436             : // @LoggerMessage: SIDP
     437             : // @Description: System ID data for Plane
     438             : // @Field: TimeUS: Time since system startup
     439             : // @Field: DRll: Desired Roll Angle
     440             : // @Field: Rll: Roll Angle
     441             : // @Field: DPit: Desired Pitch Angle
     442             : // @Field: Pit: Pitch Angle
     443             : // @Field: rdes: Desired Roll Rate
     444             : // @Field: r: Measured Roll Rate
     445             : // @Field: pdes: Desired Pitch Rate
     446             : // @Field: p: Measured Pitch Rate
     447             : // @Field: Aile: Aileron
     448             : // @Field: Elev: Elevator
     449             : // @Field: aspd: Speed_Scalar
     450             : // @Field: eastas: EAS2TAS
     451             : 
     452           0 : void AP_SystemID::log_plane_data() const
     453             : {
     454             : #if HAL_LOGGING_ENABLED
     455             :    // int16_t pitch = plane.ahrs.pitch_sensor - plane.g.pitch_trim * 100;
     456           0 :     float speed_scaler = plane.get_speed_scaler();
     457           0 :     const auto &pitch_pid_info = plane.pitchController.get_pid_info();
     458           0 :     const auto &roll_pid_info = plane.rollController.get_pid_info();
     459             : 
     460           0 :     int16_t demanded_pitch = plane.nav_pitch_cd + int32_t(plane.g.pitch_trim * 100.0) + SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) * plane.g.kff_throttle_to_pitch;
     461           0 :     AP::logger().WriteStreaming("SIDP", "TimeUS,DRll,Rll,DPit,Pit,rdes,r,pdes,p,Aile,Elev,aspd,eastas",
     462             :                                 "soooooooooooo", "F------------", "Qffffffffffff",
     463             :                                 AP_HAL::micros64(),
     464           0 :                                 plane.nav_roll_cd * 0.01f,
     465           0 :                                 plane.ahrs.roll_sensor * 0.01f,
     466           0 :                                 demanded_pitch * 0.01f,
     467           0 :                                 plane.ahrs.pitch_sensor * 0.01f,
     468           0 :                                 roll_pid_info.target,
     469           0 :                                 degrees(plane.ahrs.get_gyro().x),
     470           0 :                                 pitch_pid_info.target,
     471           0 :                                 degrees(plane.ahrs.get_gyro().y),
     472           0 :                                 SRV_Channels::get_output_scaled(SRV_Channel::k_aileron) * 0.01f,
     473           0 :                                 SRV_Channels::get_output_scaled(SRV_Channel::k_elevator) * 0.01f,
     474             :                                 speed_scaler,
     475           0 :                                 plane.ahrs.get_EAS2TAS());
     476             : 
     477             : #endif // HAL_LOGGING_ENABLED
     478           0 : }
     479             : #endif // AP_PLANE_SYSTEMID_ENABLED
     480             : 

Generated by: LCOV version 1.14