Files
ardupilot/ArduPlane/systemid.cpp
T

481 lines
17 KiB
C++

#include "systemid.h"
#if AP_PLANE_SYSTEMID_ENABLED
#include <AP_Math/control.h>
#include "Plane.h"
/*
handle systemid via an auxiliary switch
*/
const AP_Param::GroupInfo AP_SystemID::var_info[] = {
// @Param: _AXIS
// @DisplayName: System identification axis
// @Description: Controls which axis are being excited. Set to non-zero to see more parameters
// @User: Standard
// @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
AP_GROUPINFO_FLAGS("_AXIS", 1, AP_SystemID, axis, 0, AP_PARAM_FLAG_ENABLE),
// @Param: _MAGNITUDE
// @DisplayName: System identification Chirp Magnitude
// @Description: Magnitude of sweep in deg, deg/s and 0-1 for mixer outputs.
// @User: Standard
AP_GROUPINFO("_MAGNITUDE", 2, AP_SystemID, waveform_magnitude, 5),
// @Param: _F_START_HZ
// @DisplayName: System identification Start Frequency
// @Description: Frequency at the start of the sweep
// @Range: 0.01 100
// @Units: Hz
// @User: Standard
AP_GROUPINFO("_F_START_HZ", 3, AP_SystemID, frequency_start, 0.5f),
// @Param: _F_STOP_HZ
// @DisplayName: System identification Stop Frequency
// @Description: Frequency at the end of the sweep
// @Range: 0.01 100
// @Units: Hz
// @User: Standard
AP_GROUPINFO("_F_STOP_HZ", 4, AP_SystemID, frequency_stop, 15),
// @Param: _T_FADE_IN
// @DisplayName: System identification Fade in time
// @Description: Time to reach maximum amplitude of sweep
// @Range: 0 20
// @Units: s
// @User: Standard
AP_GROUPINFO("_T_FADE_IN", 5, AP_SystemID, time_fade_in, 5),
// @Param: _T_REC
// @DisplayName: System identification Total Sweep length
// @Description: Time taken to complete the sweep
// @Range: 0 255
// @Units: s
// @User: Standard
AP_GROUPINFO("_T_REC", 6, AP_SystemID, time_record, 70),
// @Param: _T_FADE_OUT
// @DisplayName: System identification Fade out time
// @Description: Time to reach zero amplitude at the end of the sweep
// @Range: 0 5
// @Units: s
// @User: Standard
AP_GROUPINFO("_T_FADE_OUT", 7, AP_SystemID, time_fade_out, 1),
// @Param: _XY_CTRL_MUL
// @DisplayName: System identification XY control multiplier
// @Description: A multiplier for the XY velocity and position controller when using systemID in VTOL modes that do horizontal position and velocity control
// @Range: 0.05 1.0
// @User: Standard
AP_GROUPINFO("_XY_CTRL_MUL", 8, AP_SystemID, xy_control_mul, 0.1),
AP_GROUPEND
};
AP_SystemID::AP_SystemID(void)
{
AP_Param::setup_object_defaults(this, var_info);
}
// @LoggerMessage: SIDS
// @Description: System ID settings
// @Field: TimeUS: Time since system startup
// @Field: Ax: The axis which is being excited
// @Field: Mag: Magnitude of the chirp waveform
// @Field: FSt: Frequency at the start of chirp
// @Field: FSp: Frequency at the end of chirp
// @Field: TFin: Time to reach maximum amplitude of chirp
// @Field: TC: Time at constant frequency before chirp starts
// @Field: TR: Time taken to complete chirp waveform
// @Field: TFout: Time to reach zero amplitude after chirp finishes
/*
start systemid
*/
void AP_SystemID::start()
{
start_axis = axis;
switch (start_axis) {
case AxisType::NONE:
// check if enabled
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: No axis selected");
return;
case AxisType::INPUT_ROLL:
case AxisType::INPUT_PITCH:
case AxisType::INPUT_YAW:
case AxisType::RECOVER_ROLL:
case AxisType::RECOVER_PITCH:
case AxisType::RECOVER_YAW:
case AxisType::RATE_ROLL:
case AxisType::RATE_PITCH:
case AxisType::RATE_YAW:
case AxisType::MIX_ROLL:
case AxisType::MIX_PITCH:
case AxisType::MIX_YAW:
case AxisType::MIX_THROTTLE:
// Exits if the current flight mode or phase does not support system ID axis.
if (!plane.control_mode->supports_vtol_systemid()) {
#if HAL_QUADPLANE_ENABLED
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight mode");
#else
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported in Plane");
#endif
return;
}
break;
case AxisType::FW_INPUT_ROLL:
case AxisType::FW_INPUT_PITCH:
case AxisType::FW_MIX_ROLL:
case AxisType::FW_MIX_PITCH:
// Exits if the currently flight mode or phase does not support system ID axis.
if (!plane.control_mode->supports_fw_systemid()) {
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight mode");
return;
}
if (!plane.control_mode->allow_fw_systemid()) {
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: Axis not supported for this flight phase");
return;
}
break;
}
if (!hal.util->get_soft_armed()) {
gcs().send_text(MAV_SEVERITY_WARNING, "SystemID: must be armed");
return;
}
attitude_offset_deg.zero();
throttle_offset = 0;
#if HAL_QUADPLANE_ENABLED
if (plane.quadplane.available()) {
restore.att_bf_feedforward = plane.quadplane.attitude_control->get_bf_feedforward();
}
#endif
waveform_time = 0;
time_const_freq = 2.0 / frequency_start; // Two full cycles at the starting frequency
chirp_input.init(time_record, frequency_start, frequency_stop, time_fade_in, time_fade_out, time_const_freq);
gcs().send_text(MAV_SEVERITY_INFO, "SystemID Starting: axis=%d", (unsigned)axis);
#if HAL_LOGGING_ENABLED
AP::logger().WriteStreaming("SIDS", "TimeUS,Ax,Mag,FSt,FSp,TFin,TC,TR,TFout",
"s--ssssss", "F--------", "QBfffffff",
AP_HAL::micros64(),
uint8_t(start_axis),
waveform_magnitude.get(),
frequency_start.get(),
frequency_stop.get(),
time_fade_in.get(),
time_const_freq,
time_record.get(),
time_fade_out.get());
#endif // HAL_LOGGING_ENABLED
running = true;
}
/*
stop systemid
*/
void AP_SystemID::stop()
{
if (running) {
running = false;
attitude_offset_deg.zero();
throttle_offset = 0;
#if HAL_QUADPLANE_ENABLED
if (plane.quadplane.available()) {
auto *attitude_control = plane.quadplane.attitude_control;
attitude_control->bf_feedforward(restore.att_bf_feedforward);
attitude_control->rate_bf_roll_sysid_rads(0);
attitude_control->rate_bf_pitch_sysid_rads(0);
attitude_control->rate_bf_yaw_sysid_rads(0);
plane.quadplane.pos_control->NE_set_control_scale_factor(1);
// re-initialise the XY controller so we take current position as target
plane.quadplane.pos_control->NE_init_controller();
}
#endif
gcs().send_text(MAV_SEVERITY_INFO, "SystemID stopped");
}
}
/*
update systemid - needs to be called at main loop rate
*/
void AP_SystemID::vtol_update()
{
#if HAL_QUADPLANE_ENABLED
if (!running) {
return;
}
if (chirp_input.completed()) {
stop();
return;
}
float const last_loop_time_s = AP::scheduler().get_last_loop_time_s();
waveform_time += last_loop_time_s;
waveform_sample = chirp_input.update(waveform_time, waveform_magnitude);
waveform_freq_rads = chirp_input.get_frequency_rads();
auto *attitude_control = plane.quadplane.attitude_control;
switch (start_axis) {
case AxisType::NONE:
// not possible, see start()
break;
case AxisType::INPUT_ROLL:
attitude_offset_deg.x = waveform_sample;
break;
case AxisType::INPUT_PITCH:
attitude_offset_deg.y = waveform_sample;
break;
case AxisType::INPUT_YAW:
attitude_offset_deg.z = waveform_sample;
break;
case AxisType::RECOVER_ROLL:
attitude_offset_deg.x = waveform_sample;
attitude_control->bf_feedforward(false);
break;
case AxisType::RECOVER_PITCH:
attitude_offset_deg.y = waveform_sample;
attitude_control->bf_feedforward(false);
break;
case AxisType::RECOVER_YAW:
attitude_offset_deg.z = waveform_sample;
attitude_control->bf_feedforward(false);
break;
case AxisType::RATE_ROLL:
attitude_control->rate_bf_roll_sysid_rads(radians(waveform_sample));
break;
case AxisType::RATE_PITCH:
attitude_control->rate_bf_pitch_sysid_rads(radians(waveform_sample));
break;
case AxisType::RATE_YAW:
attitude_control->rate_bf_yaw_sysid_rads(radians(waveform_sample));
break;
case AxisType::MIX_ROLL:
attitude_control->actuator_roll_sysid(waveform_sample);
break;
case AxisType::MIX_PITCH:
attitude_control->actuator_pitch_sysid(waveform_sample);
break;
case AxisType::MIX_YAW:
attitude_control->actuator_yaw_sysid(waveform_sample);
break;
case AxisType::MIX_THROTTLE:
throttle_offset = waveform_sample;
break;
default:
break;
}
// reduce control in NE axis when in position controlled modes
plane.quadplane.pos_control->NE_set_control_scale_factor(xy_control_mul);
if (log_subsample <= 0) {
log_data();
// log attitude controller at the same rate
plane.quadplane.Log_Write_AttRate();
if (plane.should_log(MASK_LOG_ATTITUDE_FAST) && plane.should_log(MASK_LOG_ATTITUDE_MED)) {
log_subsample = 1;
} else if (plane.should_log(MASK_LOG_ATTITUDE_FAST)) {
log_subsample = 2;
} else if (plane.should_log(MASK_LOG_ATTITUDE_MED)) {
log_subsample = 4;
} else {
log_subsample = 8;
}
}
log_subsample -= 1;
#endif
}
// Return true if a fixed wing system ID is currently running
bool AP_SystemID::is_running_fw() const
{
if (!is_running()) {
return false;
}
switch (start_axis) {
case AxisType::NONE:
case AxisType::INPUT_ROLL:
case AxisType::INPUT_PITCH:
case AxisType::INPUT_YAW:
case AxisType::RECOVER_ROLL:
case AxisType::RECOVER_PITCH:
case AxisType::RECOVER_YAW:
case AxisType::RATE_ROLL:
case AxisType::RATE_PITCH:
case AxisType::RATE_YAW:
case AxisType::MIX_ROLL:
case AxisType::MIX_PITCH:
case AxisType::MIX_YAW:
case AxisType::MIX_THROTTLE:
break;
case AxisType::FW_INPUT_ROLL:
case AxisType::FW_INPUT_PITCH:
case AxisType::FW_MIX_ROLL:
case AxisType::FW_MIX_PITCH:
return true;
}
return false;
}
/*
update systemid - needs to be called at main loop rate
*/
void AP_SystemID::fw_update()
{
if (!plane.control_mode->allow_fw_systemid() || chirp_input.completed()) {
// Control mode change means chirp should be stopped, or
// Chirp is complete
stop();
return;
}
float const last_loop_time_s = AP::scheduler().get_last_loop_time_s();
waveform_time += last_loop_time_s;
waveform_sample = chirp_input.update(waveform_time, waveform_magnitude);
waveform_freq_rads = chirp_input.get_frequency_rads();
switch (start_axis) {
case AxisType::NONE:
// not possible, see start()
break;
case AxisType::FW_INPUT_ROLL:
plane.nav_roll_cd += waveform_sample * 100.0f;
break;
case AxisType::FW_INPUT_PITCH:
plane.nav_pitch_cd += waveform_sample * 100.0f;
break;
case AxisType::FW_MIX_ROLL:
output_offset.x = waveform_sample;
break;
case AxisType::FW_MIX_PITCH:
output_offset.y = waveform_sample;
break;
default:
break;
}
if (log_subsample <= 0) {
log_data();
log_plane_data();
if (plane.should_log(MASK_LOG_ATTITUDE_FAST) && plane.should_log(MASK_LOG_ATTITUDE_MED)) {
log_subsample = 1;
} else if (plane.should_log(MASK_LOG_ATTITUDE_FAST)) {
log_subsample = 2;
} else if (plane.should_log(MASK_LOG_ATTITUDE_MED)) {
log_subsample = 4;
} else {
log_subsample = 8;
}
}
log_subsample -= 1;
}
// @LoggerMessage: SIDD
// @Description: System ID data
// @Field: TimeUS: Time since system startup
// @Field: Time: Time reference for waveform
// @Field: Targ: Current waveform sample
// @Field: F: Instantaneous waveform frequency
// @Field: Gx: Delta angle, X-Axis
// @Field: Gy: Delta angle, Y-Axis
// @Field: Gz: Delta angle, Z-Axis
// @Field: Ax: Delta velocity, X-Axis
// @Field: Ay: Delta velocity, Y-Axis
// @Field: Az: Delta velocity, Z-Axis
// log system id
void AP_SystemID::log_data() const
{
#if HAL_LOGGING_ENABLED
Vector3f delta_angle;
float delta_angle_dt;
plane.ins.get_delta_angle(delta_angle, delta_angle_dt);
Vector3f delta_velocity;
float delta_velocity_dt;
plane.ins.get_delta_velocity(delta_velocity, delta_velocity_dt);
if (is_positive(delta_angle_dt) && is_positive(delta_velocity_dt)) {
const float dt_ang_inv = 1.0 / delta_angle_dt;
const float dt_vel_inv = 1.0 / delta_velocity_dt;
AP::logger().WriteStreaming("SIDD", "TimeUS,Time,Targ,F,Gx,Gy,Gz,Ax,Ay,Az",
"ss-zkkkooo", "F---------", "Qfffffffff",
AP_HAL::micros64(),
waveform_time, waveform_sample, waveform_freq_rads / (2 * M_PI),
degrees(delta_angle.x * dt_ang_inv),
degrees(delta_angle.y * dt_ang_inv),
degrees(delta_angle.z * dt_ang_inv),
delta_velocity.x * dt_vel_inv,
delta_velocity.y * dt_vel_inv,
delta_velocity.z * dt_vel_inv);
}
#endif // HAL_LOGGING_ENABLED
}
// @LoggerMessage: SIDP
// @Description: System ID data for Plane
// @Field: TimeUS: Time since system startup
// @Field: DRll: Desired Roll Angle
// @Field: Rll: Roll Angle
// @Field: DPit: Desired Pitch Angle
// @Field: Pit: Pitch Angle
// @Field: rdes: Desired Roll Rate
// @Field: r: Measured Roll Rate
// @Field: pdes: Desired Pitch Rate
// @Field: p: Measured Pitch Rate
// @Field: Aile: Aileron
// @Field: Elev: Elevator
// @Field: aspd: Speed_Scalar
// @Field: eastas: EAS2TAS
void AP_SystemID::log_plane_data() const
{
#if HAL_LOGGING_ENABLED
// int16_t pitch = plane.ahrs.pitch_sensor - plane.g.pitch_trim * 100;
float speed_scaler = plane.get_speed_scaler();
const auto &pitch_pid_info = plane.pitchController.get_pid_info();
const auto &roll_pid_info = plane.rollController.get_pid_info();
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;
AP::logger().WriteStreaming("SIDP", "TimeUS,DRll,Rll,DPit,Pit,rdes,r,pdes,p,Aile,Elev,aspd,eastas",
"soooooooooooo", "F------------", "Qffffffffffff",
AP_HAL::micros64(),
plane.nav_roll_cd * 0.01f,
plane.ahrs.roll_sensor * 0.01f,
demanded_pitch * 0.01f,
plane.ahrs.pitch_sensor * 0.01f,
roll_pid_info.target,
degrees(plane.ahrs.get_gyro().x),
pitch_pid_info.target,
degrees(plane.ahrs.get_gyro().y),
SRV_Channels::get_output_scaled(SRV_Channel::k_aileron) * 0.01f,
SRV_Channels::get_output_scaled(SRV_Channel::k_elevator) * 0.01f,
speed_scaler,
plane.ahrs.get_EAS2TAS());
#endif // HAL_LOGGING_ENABLED
}
#endif // AP_PLANE_SYSTEMID_ENABLED