2021-07-27 16:51:19 -03:00
|
|
|
#include "Copter.h"
|
|
|
|
|
2024-09-04 00:48:37 -03:00
|
|
|
#if MODE_TURTLE_ENABLED
|
2021-07-27 16:51:19 -03:00
|
|
|
|
|
|
|
#define CRASH_FLIP_EXPO 35.0f
|
|
|
|
#define CRASH_FLIP_STICK_MINF 0.15f
|
2021-09-04 11:35:41 -03:00
|
|
|
#define power3(x) ((x) * (x) * (x))
|
2021-07-27 16:51:19 -03:00
|
|
|
|
|
|
|
bool ModeTurtle::init(bool ignore_checks)
|
|
|
|
{
|
|
|
|
// do not enter the mode when already armed or when flying
|
|
|
|
if (motors->armed() || SRV_Channels::get_dshot_esc_type() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// perform minimal arming checks
|
|
|
|
if (!copter.mavlink_motor_control_check(*gcs().chan(0), true, "Turtle Mode")) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-20 15:34:13 -03:00
|
|
|
// do not enter the mode if sticks are not centered or throttle is not at zero
|
2021-07-27 16:51:19 -03:00
|
|
|
if (!is_zero(channel_pitch->norm_input_dz())
|
|
|
|
|| !is_zero(channel_roll->norm_input_dz())
|
2022-10-20 15:34:13 -03:00
|
|
|
|| !is_zero(channel_yaw->norm_input_dz())
|
|
|
|
|| !is_zero(channel_throttle->norm_input_dz())) {
|
2021-09-04 11:35:41 -03:00
|
|
|
return false;
|
2021-07-27 16:51:19 -03:00
|
|
|
}
|
2022-10-20 15:34:13 -03:00
|
|
|
|
|
|
|
// turn on notify leds
|
|
|
|
AP_Notify::flags.esc_calibration = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModeTurtle::arm_motors()
|
|
|
|
{
|
|
|
|
if (hal.util->get_soft_armed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop the spoolup block activating
|
|
|
|
motors->set_spoolup_block(false);
|
|
|
|
|
2021-07-27 16:51:19 -03:00
|
|
|
// reverse the motors
|
|
|
|
hal.rcout->disable_channel_mask_updates();
|
|
|
|
change_motor_direction(true);
|
|
|
|
|
|
|
|
// disable throttle and gps failsafe
|
2022-07-05 00:08:57 -03:00
|
|
|
g.failsafe_throttle.set(FS_THR_DISABLED);
|
|
|
|
g.failsafe_gcs.set(FS_GCS_DISABLED);
|
|
|
|
g.fs_ekf_action.set(0);
|
2021-07-27 16:51:19 -03:00
|
|
|
|
|
|
|
// arm
|
|
|
|
motors->armed(true);
|
|
|
|
hal.util->set_soft_armed(true);
|
|
|
|
}
|
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
bool ModeTurtle::allows_arming(AP_Arming::Method method) const
|
2021-07-27 16:51:19 -03:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModeTurtle::exit()
|
|
|
|
{
|
2022-10-20 15:34:13 -03:00
|
|
|
disarm_motors();
|
|
|
|
|
|
|
|
// turn off notify leds
|
|
|
|
AP_Notify::flags.esc_calibration = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModeTurtle::disarm_motors()
|
|
|
|
{
|
|
|
|
if (!hal.util->get_soft_armed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-27 16:51:19 -03:00
|
|
|
// disarm
|
|
|
|
motors->armed(false);
|
|
|
|
hal.util->set_soft_armed(false);
|
|
|
|
|
|
|
|
// un-reverse the motors
|
|
|
|
change_motor_direction(false);
|
|
|
|
hal.rcout->enable_channel_mask_updates();
|
|
|
|
|
|
|
|
// re-enable failsafes
|
|
|
|
g.failsafe_throttle.load();
|
|
|
|
g.failsafe_gcs.load();
|
|
|
|
g.fs_ekf_action.load();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModeTurtle::change_motor_direction(bool reverse)
|
|
|
|
{
|
|
|
|
AP_HAL::RCOutput::BLHeliDshotCommand direction = reverse ? AP_HAL::RCOutput::DSHOT_REVERSE : AP_HAL::RCOutput::DSHOT_NORMAL;
|
|
|
|
AP_HAL::RCOutput::BLHeliDshotCommand inverse_direction = reverse ? AP_HAL::RCOutput::DSHOT_NORMAL : AP_HAL::RCOutput::DSHOT_REVERSE;
|
|
|
|
|
|
|
|
if (!hal.rcout->get_reversed_mask()) {
|
|
|
|
hal.rcout->send_dshot_command(direction, AP_HAL::RCOutput::ALL_CHANNELS, 0, 10, true);
|
|
|
|
} else {
|
|
|
|
for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; ++i) {
|
|
|
|
if (!motors->is_motor_enabled(i)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
if ((hal.rcout->get_reversed_mask() & (1U << i)) == 0) {
|
2021-07-27 16:51:19 -03:00
|
|
|
hal.rcout->send_dshot_command(direction, i, 0, 10, true);
|
|
|
|
} else {
|
|
|
|
hal.rcout->send_dshot_command(inverse_direction, i, 0, 10, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ModeTurtle::run()
|
|
|
|
{
|
2022-03-11 13:29:59 -04:00
|
|
|
const float flip_power_factor = 1.0f - CRASH_FLIP_EXPO * 0.01f;
|
2024-02-07 00:14:28 -04:00
|
|
|
const bool norc = copter.failsafe.radio || !rc().has_ever_seen_rc_input();
|
2021-09-04 11:35:41 -03:00
|
|
|
const float stick_deflection_pitch = norc ? 0.0f : channel_pitch->norm_input_dz();
|
|
|
|
const float stick_deflection_roll = norc ? 0.0f : channel_roll->norm_input_dz();
|
|
|
|
const float stick_deflection_yaw = norc ? 0.0f : channel_yaw->norm_input_dz();
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
const float stick_deflection_pitch_abs = fabsf(stick_deflection_pitch);
|
|
|
|
const float stick_deflection_roll_abs = fabsf(stick_deflection_roll);
|
|
|
|
const float stick_deflection_yaw_abs = fabsf(stick_deflection_yaw);
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
const float stick_deflection_pitch_expo = flip_power_factor * stick_deflection_pitch_abs + power3(stick_deflection_pitch_abs) * (1 - flip_power_factor);
|
|
|
|
const float stick_deflection_roll_expo = flip_power_factor * stick_deflection_roll_abs + power3(stick_deflection_roll_abs) * (1 - flip_power_factor);
|
|
|
|
const float stick_deflection_yaw_expo = flip_power_factor * stick_deflection_yaw_abs + power3(stick_deflection_yaw_abs) * (1 - flip_power_factor);
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
float sign_pitch = stick_deflection_pitch < 0 ? -1 : 1;
|
|
|
|
float sign_roll = stick_deflection_roll < 0 ? 1 : -1;
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
float stick_deflection_length = sqrtf(sq(stick_deflection_pitch_abs) + sq(stick_deflection_roll_abs));
|
|
|
|
float stick_deflection_expo_length = sqrtf(sq(stick_deflection_pitch_expo) + sq(stick_deflection_roll_expo));
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
if (stick_deflection_yaw_abs > MAX(stick_deflection_pitch_abs, stick_deflection_roll_abs)) {
|
2021-07-27 16:51:19 -03:00
|
|
|
// If yaw is the dominant, disable pitch and roll
|
2021-09-04 11:35:41 -03:00
|
|
|
stick_deflection_length = stick_deflection_yaw_abs;
|
|
|
|
stick_deflection_expo_length = stick_deflection_yaw_expo;
|
|
|
|
sign_roll = 0;
|
|
|
|
sign_pitch = 0;
|
2021-07-27 16:51:19 -03:00
|
|
|
}
|
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
const float cos_phi = (stick_deflection_length > 0) ? (stick_deflection_pitch_abs + stick_deflection_roll_abs) / (sqrtf(2.0f) * stick_deflection_length) : 0;
|
|
|
|
const float cos_threshold = sqrtf(3.0f) / 2.0f; // cos(PI/6.0f)
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
if (cos_phi < cos_threshold) {
|
2021-07-27 16:51:19 -03:00
|
|
|
// Enforce either roll or pitch exclusively, if not on diagonal
|
2021-09-04 11:35:41 -03:00
|
|
|
if (stick_deflection_roll_abs > stick_deflection_pitch_abs) {
|
|
|
|
sign_pitch = 0;
|
2021-07-27 16:51:19 -03:00
|
|
|
} else {
|
2021-09-04 11:35:41 -03:00
|
|
|
sign_roll = 0;
|
2021-07-27 16:51:19 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply a reasonable amount of stick deadband
|
2021-09-04 11:35:41 -03:00
|
|
|
const float crash_flip_stick_min_expo = flip_power_factor * CRASH_FLIP_STICK_MINF + power3(CRASH_FLIP_STICK_MINF) * (1 - flip_power_factor);
|
|
|
|
const float flip_stick_range = 1.0f - crash_flip_stick_min_expo;
|
|
|
|
const float flip_power = MAX(0.0f, stick_deflection_expo_length - crash_flip_stick_min_expo) / flip_stick_range;
|
2021-07-27 16:51:19 -03:00
|
|
|
|
|
|
|
// at this point we have a power value in the range 0..1
|
|
|
|
|
2022-11-19 13:33:16 -04:00
|
|
|
// normalise the roll and pitch input to match the motors
|
2021-09-04 11:35:41 -03:00
|
|
|
Vector2f input{sign_roll, sign_pitch};
|
2021-08-14 06:12:18 -03:00
|
|
|
motors_input = input.normalized() * 0.5;
|
2021-07-27 16:51:19 -03:00
|
|
|
// we bypass spin min and friends in the deadzone because we only want spin up when the sticks are moved
|
2023-01-30 21:54:34 -04:00
|
|
|
motors_output = !is_zero(flip_power) ? motors->thr_lin.thrust_to_actuator(flip_power) : 0.0f;
|
2021-08-14 06:12:18 -03:00
|
|
|
}
|
2021-07-27 16:51:19 -03:00
|
|
|
|
2021-08-14 06:12:18 -03:00
|
|
|
// actually write values to the motors
|
|
|
|
void ModeTurtle::output_to_motors()
|
|
|
|
{
|
2022-10-20 15:34:13 -03:00
|
|
|
// throttle needs to be raised
|
|
|
|
if (is_zero(channel_throttle->norm_input_dz())) {
|
2022-11-19 13:33:16 -04:00
|
|
|
const uint32_t now = AP_HAL::millis();
|
|
|
|
if (now - last_throttle_warning_output_ms > 5000) {
|
|
|
|
gcs().send_text(MAV_SEVERITY_WARNING, "Turtle: raise throttle to arm");
|
|
|
|
last_throttle_warning_output_ms = now;
|
|
|
|
}
|
|
|
|
|
2022-10-20 15:34:13 -03:00
|
|
|
disarm_motors();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
arm_motors();
|
|
|
|
|
2022-01-27 11:36:21 -04:00
|
|
|
// check if motor are allowed to spin
|
|
|
|
const bool allow_output = motors->armed() && motors->get_interlock();
|
|
|
|
|
2021-07-27 16:51:19 -03:00
|
|
|
for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; ++i) {
|
|
|
|
if (!motors->is_motor_enabled(i)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
const Vector2f output{motors->get_roll_factor(i), motors->get_pitch_factor(i)};
|
2021-07-27 16:51:19 -03:00
|
|
|
// if output aligns with input then use this motor
|
2022-01-27 11:36:21 -04:00
|
|
|
if (!allow_output || (motors_input - output).length() > 0.5) {
|
2021-07-27 16:51:19 -03:00
|
|
|
motors->rc_write(i, motors->get_pwm_output_min());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-04 11:35:41 -03:00
|
|
|
int16_t pwm = motors->get_pwm_output_min() + (motors->get_pwm_output_max() - motors->get_pwm_output_min()) * motors_output;
|
2021-07-27 16:51:19 -03:00
|
|
|
|
|
|
|
motors->rc_write(i, pwm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|