#include "Copter.h" #if MODE_THROW_ENABLED == ENABLED // throw_init - initialise throw controller bool ModeThrow::init(bool ignore_checks) { #if FRAME_CONFIG == HELI_FRAME // do not allow helis to use throw to start return false; #endif // do not enter the mode when already armed or when flying if (motors->armed()) { return false; } // init state stage = Throw_Disarmed; nextmode_attempted = false; // initialise pos controller speed and acceleration pos_control->set_max_speed_accel_xy(wp_nav->get_default_speed_xy(), BRAKE_MODE_DECEL_RATE); pos_control->set_correction_speed_accel_xy(wp_nav->get_default_speed_xy(), BRAKE_MODE_DECEL_RATE); // set vertical speed and acceleration limits pos_control->set_max_speed_accel_z(BRAKE_MODE_SPEED_Z, BRAKE_MODE_SPEED_Z, BRAKE_MODE_DECEL_RATE); pos_control->set_correction_speed_accel_z(BRAKE_MODE_SPEED_Z, BRAKE_MODE_SPEED_Z, BRAKE_MODE_DECEL_RATE); return true; } // runs the throw to start controller // should be called at 100hz or more void ModeThrow::run() { /* Throw State Machine Throw_Disarmed - motors are off Throw_Detecting - motors are on and we are waiting for the throw Throw_Uprighting - the throw has been detected and the copter is being uprighted Throw_HgtStabilise - the copter is kept level and height is stabilised about the target height Throw_PosHold - the copter is kept at a constant position and height */ if (!motors->armed()) { // state machine entry is always from a disarmed state stage = Throw_Disarmed; } else if (stage == Throw_Disarmed && motors->armed()) { gcs().send_text(MAV_SEVERITY_INFO,"waiting for throw"); stage = Throw_Detecting; } else if (stage == Throw_Detecting && throw_detected()){ gcs().send_text(MAV_SEVERITY_INFO,"throw detected - spooling motors"); copter.set_land_complete(false); stage = Throw_Wait_Throttle_Unlimited; // Cancel the waiting for throw tone sequence AP_Notify::flags.waiting_for_throw = false; } else if (stage == Throw_Wait_Throttle_Unlimited && motors->get_spool_state() == AP_Motors::SpoolState::THROTTLE_UNLIMITED) { gcs().send_text(MAV_SEVERITY_INFO,"throttle is unlimited - uprighting"); stage = Throw_Uprighting; } else if (stage == Throw_Uprighting && throw_attitude_good()) { gcs().send_text(MAV_SEVERITY_INFO,"uprighted - controlling height"); stage = Throw_HgtStabilise; // initialise the z controller pos_control->init_z_controller_no_descent(); // initialise the demanded height to 3m above the throw height // we want to rapidly clear surrounding obstacles if (g2.throw_type == ThrowType::Drop) { pos_control->set_pos_target_z_cm(inertial_nav.get_position_z_up_cm() - 100); } else { pos_control->set_pos_target_z_cm(inertial_nav.get_position_z_up_cm() + 300); } // Set the auto_arm status to true to avoid a possible automatic disarm caused by selection of an auto mode with throttle at minimum copter.set_auto_armed(true); } else if (stage == Throw_HgtStabilise && throw_height_good()) { gcs().send_text(MAV_SEVERITY_INFO,"height achieved - controlling position"); stage = Throw_PosHold; // initialise position controller pos_control->init_xy_controller(); // Set the auto_arm status to true to avoid a possible automatic disarm caused by selection of an auto mode with throttle at minimum copter.set_auto_armed(true); } else if (stage == Throw_PosHold && throw_position_good()) { if (!nextmode_attempted) { switch ((Mode::Number)g2.throw_nextmode.get()) { case Mode::Number::AUTO: case Mode::Number::GUIDED: case Mode::Number::RTL: case Mode::Number::LAND: case Mode::Number::BRAKE: case Mode::Number::LOITER: set_mode((Mode::Number)g2.throw_nextmode.get(), ModeReason::THROW_COMPLETE); break; default: // do nothing break; } nextmode_attempted = true; } } // Throw State Processing switch (stage) { case Throw_Disarmed: // prevent motors from rotating before the throw is detected unless enabled by the user if (g.throw_motor_start == PreThrowMotorState::RUNNING) { motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE); } else { motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN); } // demand zero throttle (motors will be stopped anyway) and continually reset the attitude controller attitude_control->reset_yaw_target_and_rate(); attitude_control->reset_rate_controller_I_terms(); attitude_control->set_throttle_out(0,true,g.throttle_filt); break; case Throw_Detecting: // prevent motors from rotating before the throw is detected unless enabled by the user if (g.throw_motor_start == PreThrowMotorState::RUNNING) { motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::GROUND_IDLE); } else { motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::SHUT_DOWN); } // Hold throttle at zero during the throw and continually reset the attitude controller attitude_control->reset_yaw_target_and_rate(); attitude_control->reset_rate_controller_I_terms(); attitude_control->set_throttle_out(0,true,g.throttle_filt); // Play the waiting for throw tone sequence to alert the user AP_Notify::flags.waiting_for_throw = true; break; case Throw_Wait_Throttle_Unlimited: // set motors to full range motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); break; case Throw_Uprighting: // set motors to full range motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); // demand a level roll/pitch attitude with zero yaw rate attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw(0.0f, 0.0f, 0.0f); // output 50% throttle and turn off angle boost to maximise righting moment attitude_control->set_throttle_out(0.5f, false, g.throttle_filt); break; case Throw_HgtStabilise: // set motors to full range motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); // call attitude controller attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw(0.0f, 0.0f, 0.0f); // call height controller pos_control->set_pos_target_z_from_climb_rate_cm(0.0f); pos_control->update_z_controller(); break; case Throw_PosHold: // set motors to full range motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); // use position controller to stop Vector2f vel; Vector2f accel; pos_control->input_vel_accel_xy(vel, accel); pos_control->update_xy_controller(); // call attitude controller attitude_control->input_thrust_vector_rate_heading(pos_control->get_thrust_vector(), 0.0f); // call height controller pos_control->set_pos_target_z_from_climb_rate_cm(0.0f); pos_control->update_z_controller(); break; } // log at 10hz or if stage changes uint32_t now = AP_HAL::millis(); if ((stage != prev_stage) || (now - last_log_ms) > 100) { prev_stage = stage; last_log_ms = now; const float velocity = inertial_nav.get_velocity_neu_cms().length(); const float velocity_z = inertial_nav.get_velocity_z_up_cms(); const float accel = copter.ins.get_accel().length(); const float ef_accel_z = ahrs.get_accel_ef().z; const bool throw_detect = (stage > Throw_Detecting) || throw_detected(); const bool attitude_ok = (stage > Throw_Uprighting) || throw_attitude_good(); const bool height_ok = (stage > Throw_HgtStabilise) || throw_height_good(); const bool pos_ok = (stage > Throw_PosHold) || throw_position_good(); // @LoggerMessage: THRO // @Description: Throw Mode messages // @URL: https://ardupilot.org/copter/docs/throw-mode.html // @Field: TimeUS: Time since system startup // @Field: Stage: Current stage of the Throw Mode // @Field: Vel: Magnitude of the velocity vector // @Field: VelZ: Vertical Velocity // @Field: Acc: Magnitude of the vector of the current acceleration // @Field: AccEfZ: Vertical earth frame accelerometer value // @Field: Throw: True if a throw has been detected since entering this mode // @Field: AttOk: True if the vehicle is upright // @Field: HgtOk: True if the vehicle is within 50cm of the demanded height // @Field: PosOk: True if the vehicle is within 50cm of the demanded horizontal position AP::logger().WriteStreaming( "THRO", "TimeUS,Stage,Vel,VelZ,Acc,AccEfZ,Throw,AttOk,HgtOk,PosOk", "s-nnoo----", "F-0000----", "QBffffbbbb", AP_HAL::micros64(), (uint8_t)stage, (double)velocity, (double)velocity_z, (double)accel, (double)ef_accel_z, throw_detect, attitude_ok, height_ok, pos_ok); } } bool ModeThrow::throw_detected() { // Check that we have a valid navigation solution nav_filter_status filt_status = inertial_nav.get_filter_status(); if (!filt_status.flags.attitude || !filt_status.flags.horiz_pos_abs || !filt_status.flags.vert_pos) { return false; } // Check for high speed (>500 cm/s) bool high_speed = inertial_nav.get_velocity_neu_cms().length_squared() > (THROW_HIGH_SPEED * THROW_HIGH_SPEED); // check for upwards or downwards trajectory (airdrop) of 50cm/s bool changing_height; if (g2.throw_type == ThrowType::Drop) { changing_height = inertial_nav.get_velocity_z_up_cms() < -THROW_VERTICAL_SPEED; } else { changing_height = inertial_nav.get_velocity_z_up_cms() > THROW_VERTICAL_SPEED; } // Check the vertical acceleraton is greater than 0.25g bool free_falling = ahrs.get_accel_ef().z > -0.25 * GRAVITY_MSS; // Check if the accel length is < 1.0g indicating that any throw action is complete and the copter has been released bool no_throw_action = copter.ins.get_accel().length() < 1.0f * GRAVITY_MSS; // fetch the altitude above home float altitude_above_home; // Use altitude above home if it is set, otherwise relative to EKF origin if (ahrs.home_is_set()) { ahrs.get_relative_position_D_home(altitude_above_home); altitude_above_home = -altitude_above_home; // altitude above home is returned as negative } else { altitude_above_home = inertial_nav.get_position_z_up_cm() * 0.01f; // centimeters to meters } // Check that the altitude is within user defined limits const bool height_within_params = (g.throw_altitude_min == 0 || altitude_above_home > g.throw_altitude_min) && (g.throw_altitude_max == 0 || (altitude_above_home < g.throw_altitude_max)); // High velocity or free-fall combined with increasing height indicate a possible air-drop or throw release bool possible_throw_detected = (free_falling || high_speed) && changing_height && no_throw_action && height_within_params; // Record time and vertical velocity when we detect the possible throw if (possible_throw_detected && ((AP_HAL::millis() - free_fall_start_ms) > 500)) { free_fall_start_ms = AP_HAL::millis(); free_fall_start_velz = inertial_nav.get_velocity_z_up_cms(); } // Once a possible throw condition has been detected, we check for 2.5 m/s of downwards velocity change in less than 0.5 seconds to confirm bool throw_condition_confirmed = ((AP_HAL::millis() - free_fall_start_ms < 500) && ((inertial_nav.get_velocity_z_up_cms() - free_fall_start_velz) < -250.0f)); // start motors and enter the control mode if we are in continuous freefall return throw_condition_confirmed; } bool ModeThrow::throw_attitude_good() const { // Check that we have uprighted the copter const Matrix3f &rotMat = ahrs.get_rotation_body_to_ned(); return (rotMat.c.z > 0.866f); // is_upright } bool ModeThrow::throw_height_good() const { // Check that we are within 0.5m of the demanded height return (pos_control->get_pos_error_z_cm() < 50.0f); } bool ModeThrow::throw_position_good() const { // check that our horizontal position error is within 50cm return (pos_control->get_pos_error_xy_cm() < 50.0f); } #endif