Path: blob/master/libraries/AC_Autorotation/AC_Autorotation.cpp
9420 views
#include "AC_Autorotation.h"1#include <AP_Logger/AP_Logger.h>2#include <AP_RPM/AP_RPM.h>3#include <AP_AHRS/AP_AHRS.h>4#include <GCS_MAVLink/GCS.h>56extern const AP_HAL::HAL& hal;78// Autorotation controller defaults9#define HEAD_SPEED_TARGET_RATIO 1.0 // Normalised target main rotor head speed1011const AP_Param::GroupInfo AC_Autorotation::var_info[] = {1213// @Param: ENABLE14// @DisplayName: Enable settings for RSC Setpoint15// @Description: Allows you to enable (1) or disable (0) the autonomous autorotation capability.16// @Values: 0:Disabled,1:Enabled17// @User: Standard18AP_GROUPINFO_FLAGS("ENABLE", 1, AC_Autorotation, _param_enable, 0, AP_PARAM_FLAG_ENABLE),1920// @Param: HS_P21// @DisplayName: P gain for head speed controller22// @Description: Increase value to increase sensitivity of head speed controller during autonomous autorotation.23// @Range: 0.3 124// @Increment: 0.0125// @User: Standard26AP_SUBGROUPINFO(_p_hs, "HS_", 2, AC_Autorotation, AC_P),2728// @Param: HS_SET_PT29// @DisplayName: Target Head Speed30// @Description: The target head speed in RPM during autorotation. Start by setting to desired hover speed and tune from there as necessary.31// @Units: RPM32// @Range: 1000 280033// @Increment: 134// @User: Standard35AP_GROUPINFO("HS_SET_PT", 3, AC_Autorotation, _param_head_speed_set_point, 1500),3637// @Param: FWD_SP_TARG38// @DisplayName: Target Glide Body Frame Forward Speed39// @Description: Target ground speed in cm/s for the autorotation controller to try and achieve/ maintain during the glide phase.40// @Units: m/s41// @Range: 8 2042// @Increment: 0.543// @User: Standard44AP_GROUPINFO("FWD_SP_TARG", 4, AC_Autorotation, _param_target_speed_ms, 11),4546// @Param: COL_FILT_E47// @DisplayName: Entry Phase Collective Filter48// @Description: Cut-off frequency for collective low pass filter. For the entry phase. Acts as a following trim. Must be higher than AROT_COL_FILT_G.49// @Units: Hz50// @Range: 0.2 0.551// @Increment: 0.0152// @User: Standard53AP_GROUPINFO("COL_FILT_E", 5, AC_Autorotation, _param_col_entry_cutoff_freq, 0.7),5455// @Param: COL_FILT_G56// @DisplayName: Glide Phase Collective Filter57// @Description: Cut-off frequency for collective low pass filter. For the glide phase. Acts as a following trim. Must be lower than AROT_COL_FILT_E.58// @Units: Hz59// @Range: 0.03 0.1560// @Increment: 0.0161// @User: Standard62AP_GROUPINFO("COL_FILT_G", 6, AC_Autorotation, _param_col_glide_cutoff_freq, 0.1),6364// @Param: XY_ACC_MAX65// @DisplayName: Body Frame XY Acceleration Limit66// @Description: Maximum body frame acceleration allowed in the in speed controller. This limit defines a circular constraint in accel. Minimum used is 0.5 m/s/s.67// @Units: m/s/s68// @Range: 0.5 8.069// @Increment: 0.170// @User: Standard71AP_GROUPINFO("XY_ACC_MAX", 7, AC_Autorotation, _param_accel_max_mss, 2.0),7273// @Param: HS_SENSOR74// @DisplayName: Main Rotor RPM Sensor75// @Description: Allocate the RPM sensor instance to use for measuring head speed. RPM1 = 0. RPM2 = 1.76// @Units: s77// @Range: 0.5 378// @Increment: 0.179// @User: Standard80AP_GROUPINFO("HS_SENSOR", 8, AC_Autorotation, _param_rpm_instance, 0),8182// @Param: FWD_P83// @DisplayName: Forward Speed Controller P Gain84// @Description: Converts the difference between desired forward speed and actual speed into an acceleration target that is passed to the pitch angle controller.85// @Range: 1.000 8.00086// @User: Standard8788// @Param: FWD_I89// @DisplayName: Forward Speed Controller I Gain90// @Description: Corrects long-term difference in desired velocity to a target acceleration.91// @Range: 0.02 1.0092// @Increment: 0.0193// @User: Advanced9495// @Param: FWD_IMAX96// @DisplayName: Forward Speed Controller I Gain Maximum97// @Description: Constrains the target acceleration that the I gain will output.98// @Range: 1.000 8.00099// @User: Standard100101// @Param: FWD_D102// @DisplayName: Forward Speed Controller D Gain103// @Description: Provides damping to velocity controller.104// @Range: 0.00 1.00105// @Increment: 0.001106// @User: Advanced107108// @Param: FWD_FF109// @DisplayName: Forward Speed Controller Feed Forward Gain110// @Description: Produces an output that is proportional to the magnitude of the target.111// @Range: 0 1112// @Increment: 0.01113// @User: Advanced114115// @Param: FWD_FLTE116// @DisplayName: Forward Speed Controller Error Filter117// @Description: This filter low pass filter is applied to the input for P and I terms.118// @Range: 0 100119// @Units: Hz120// @User: Advanced121122// @Param: FWD_FLTD123// @DisplayName: Forward Speed Controller input filter for D term124// @Description: This filter low pass filter is applied to the input for D terms.125// @Range: 0 100126// @Units: Hz127// @User: Advanced128AP_SUBGROUPINFO(_fwd_speed_pid, "FWD_", 9, AC_Autorotation, AC_PID_Basic),129130AP_GROUPEND131};132133// Constructor134AC_Autorotation::AC_Autorotation(AP_MotorsHeli*& motors, AC_AttitudeControl*& att_crtl) :135_motors_heli(motors),136_attitude_control(att_crtl)137{138AP_Param::setup_object_defaults(this, var_info);139}140141void AC_Autorotation::init(void)142{143// Initialisation of head speed controller144// Set initial collective position to be the current collective position for smooth init145const float collective_out = _motors_heli->get_throttle_out();146147// Reset feed forward filter148col_trim_lpf.reset(collective_out);149150// Protect against divide by zero TODO: move this to an accessor function151_param_head_speed_set_point.set(MAX(_param_head_speed_set_point, 500.0));152153// Reset the landed reason154_landed_reason.min_speed = false;155_landed_reason.land_col = false;156_landed_reason.is_still = false;157}158159// Functions and config that are only to be done once at the beginning of the entry160void AC_Autorotation::init_entry(void)161{162GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AROT: Entry Phase");163164// Target head speed is set to rpm at initiation to prevent steps in controller165if (!get_norm_head_speed(_target_head_speed)) {166// Cannot get a valid RPM sensor reading so we default to not slewing the head speed target167_target_head_speed = HEAD_SPEED_TARGET_RATIO;168}169170// The rate to move the head speed from the current measurement to the target171_hs_accel = (HEAD_SPEED_TARGET_RATIO - _target_head_speed) / (float(entry_time_ms)*1e-3);172173// Set collective following trim low pass filter cut off frequency174col_trim_lpf.set_cutoff_frequency(_param_col_entry_cutoff_freq.get());175176// Set collective low-pass cut off filter at 2 Hz177_motors_heli->set_throttle_filter_cutoff(2.0);178179// Set speed target to maintain the current speed whilst we enter the autorotation180_desired_vel_ms = _param_target_speed_ms.get();181_target_vel_ms = get_speed_forward_ms();182183// Reset I term of velocity PID184_fwd_speed_pid.reset_filter();185_fwd_speed_pid.set_integrator(0.0);186}187188// The entry controller just a special case of the glide controller with head speed target slewing189void AC_Autorotation::run_entry(float pilot_accel_norm)190{191// Slowly change the target head speed until the target head speed matches the parameter defined value192float head_speed_norm;193if (!get_norm_head_speed(head_speed_norm)) {194// RPM sensor is bad, so we don't attempt to slew the head speed target as we do not know what head speed actually is195// The collective output handling of the rpm sensor failure is handled later in the head speed controller196head_speed_norm = HEAD_SPEED_TARGET_RATIO;197}198199// Slew the head speed target from the initial condition to the target head speed ratio for the glide200const float max_change = _hs_accel * _dt;201_target_head_speed = constrain_float(HEAD_SPEED_TARGET_RATIO, _target_head_speed - max_change, _target_head_speed + max_change);202203run_glide(pilot_accel_norm);204}205206// Functions and config that are only to be done once at the beginning of the glide207void AC_Autorotation::init_glide(void)208{209GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AROT: Glide Phase");210211// Set collective following trim low pass filter cut off frequency212col_trim_lpf.set_cutoff_frequency(_param_col_glide_cutoff_freq.get());213214// Ensure target head speed is set to setpoint, in case it didn't reach the target during entry215_target_head_speed = HEAD_SPEED_TARGET_RATIO;216217// Ensure desired forward speed target is set to param value218_desired_vel_ms = _param_target_speed_ms.get();219}220221// Maintain head speed and forward speed as we glide to the ground222void AC_Autorotation::run_glide(float pilot_accel_norm)223{224update_headspeed_controller();225226update_forward_speed_controller(pilot_accel_norm);227}228229void AC_Autorotation::update_headspeed_controller(void)230{231// Get current rpm and update healthy signal counters232float head_speed_norm;233if (!get_norm_head_speed(head_speed_norm)) {234// RPM sensor is bad, set collective to angle of -2 deg and hope for the best235_motors_heli->set_coll_from_ang(-2.0);236return;237}238239// Calculate the head speed error.240_head_speed_error = head_speed_norm - _target_head_speed;241242_p_term_hs = _p_hs.get_p(_head_speed_error);243244// Adjusting collective trim using feed forward (not yet been updated, so this value is the previous time steps collective position)245_ff_term_hs = col_trim_lpf.apply(_motors_heli->get_throttle(), _dt);246247// Calculate collective position to be set248const float collective_out = constrain_value((_p_term_hs + _ff_term_hs), 0.0f, 1.0f);249250// Send collective to setting to motors output library251_motors_heli->set_throttle(collective_out);252253#if HAL_LOGGING_ENABLED254// @LoggerMessage: ARHS255// @Vehicles: Copter256// @Description: helicopter AutoRotation Head Speed (ARHS) controller information257// @Field: TimeUS: Time since system startup258// @Field: Tar: Normalised target head speed259// @Field: Act: Normalised measured head speed260// @Field: Err: Head speed controller error261// @Field: P: P-term for head speed controller response262// @Field: FF: FF-term for head speed controller response263264// Write to data flash log265AP::logger().WriteStreaming("ARHS",266"TimeUS,Tar,Act,Err,P,FF",267"s-----",268"F00000",269"Qfffff",270AP_HAL::micros64(),271_target_head_speed,272head_speed_norm,273_head_speed_error,274_p_term_hs,275_ff_term_hs);276#endif277}278279// Get measured head speed and normalise by head speed set point. Returns false if a valid rpm measurement cannot be obtained280bool AC_Autorotation::get_norm_head_speed(float& norm_rpm) const281{282// Assuming zero rpm is safer as it will drive collective in the direction of increasing head speed283float current_rpm = 0.0;284285#if AP_RPM_ENABLED286// Get singleton for RPM library287const AP_RPM *rpm = AP_RPM::get_singleton();288289// Checking to ensure no nullptr, we do have a pre-arm check for this so it will be very bad if RPM has gone away290if (rpm == nullptr) {291return false;292}293294// Check RPM sensor is returning a healthy status295if (!rpm->get_rpm(_param_rpm_instance.get(), current_rpm)) {296return false;297}298#endif299300// Protect against div by zeros later in the code301float head_speed_set_point = MAX(1.0, _param_head_speed_set_point.get());302303// Normalize the RPM by the setpoint304norm_rpm = current_rpm/head_speed_set_point;305return true;306}307308// Update speed controller309void AC_Autorotation::update_forward_speed_controller(float pilot_accel_norm)310{311// Limiting the desired velocity based on the max acceleration limit to get an update target312const float min_vel_ms = _target_vel_ms - get_accel_max_mss() * _dt;313const float max_vel_ms = _target_vel_ms + get_accel_max_mss() * _dt;314_target_vel_ms = constrain_float(_desired_vel_ms, min_vel_ms, max_vel_ms); // (m/s)315316// Calculate acceleration target317const float fwd_accel_target_mss = _fwd_speed_pid.update_all(_target_vel_ms, get_speed_forward_ms(), _dt, _limit_accel); // (m/s/s)318319// Build the body frame XY accel vector.320// Pilot can request as much as 1/2 of the max accel laterally to perform a turn.321// We only allow up to half as we need to prioritize building/maintaining airspeed.322Vector2f bf_accel_target_mss = {fwd_accel_target_mss, pilot_accel_norm * get_accel_max_mss() * 0.5};323324// Ensure we do not exceed the accel limit325_limit_accel = bf_accel_target_mss.limit_length(get_accel_max_mss());326327// Calculate roll and pitch targets from angles, negative accel for negative pitch (pitch forward)328Vector2f angle_target_rad = { accel_mss_to_angle_rad(-bf_accel_target_mss.x), // Pitch329accel_mss_to_angle_rad(bf_accel_target_mss.y)}; // Roll330331// Ensure that the requested angles do not exceed angle max332_limit_accel |= angle_target_rad.limit_length(_attitude_control->lean_angle_max_rad());333334// we may have scaled the lateral accel in the angle limit scaling, so we need to335// back calculate the resulting accel from this constrained angle for the yaw rate calc336const float bf_lat_accel_target_mss = angle_rad_to_accel_mss(angle_target_rad.y);337338// Calc yaw rate from desired body-frame accels339// this seems suspiciously simple, but it is correct340// accel = r * w^2, r = radius and w = angular rate341// radius can be calculated as the distance traveled in the time it takes to do 360 deg342// One rotation takes: (2*pi)/w seconds343// Distance traveled in that time: (vel*2*pi)/w344// radius for that distance: ((vel*2*pi)/w) / (2*pi)345// r = vel / w346// accel = (vel / w) * w^2347// accel = vel * w348// w = accel / vel349float yaw_rate_rads = 0.0;350if (!is_zero(_target_vel_ms)) {351yaw_rate_rads = bf_lat_accel_target_mss / _target_vel_ms;352}353354// Output to attitude controller355_attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_rad(angle_target_rad.y, angle_target_rad.x, yaw_rate_rads);356357#if HAL_LOGGING_ENABLED358// @LoggerMessage: ARSC359// @Vehicles: Copter360// @Description: Helicopter AutoRotation Speed Controller (ARSC) information361// @Field: TimeUS: Time since system startup362// @Field: Des: Desired forward velocity363// @Field: Tar: Target forward velocity364// @Field: Act: Measured forward velocity365// @Field: P: Velocity to acceleration P-term component366// @Field: I: Velocity to acceleration I-term component367// @Field: D: Velocity to acceleration D-term component368// @Field: FF: Velocity to acceleration feed forward component369// @Field: Lim: Accel limit flag370// @Field: FA: Forward acceleration target371// @Field: LA: Lateral acceleration target372373const AP_PIDInfo& pid_info = _fwd_speed_pid.get_pid_info();374AP::logger().WriteStreaming("ARSC",375"TimeUS,Des,Tar,Act,P,I,D,FF,Lim,FA,LA",376"snnn-----oo",377"F0000000-00",378"QfffffffBff",379AP_HAL::micros64(),380_desired_vel_ms,381pid_info.target,382pid_info.actual,383pid_info.P,384pid_info.I,385pid_info.D,386pid_info.FF,387uint8_t(_limit_accel),388bf_accel_target_mss.x,389bf_accel_target_mss.y);390#endif391}392393// smoothly zero velocity and accel394void AC_Autorotation::run_landed(void)395{396_desired_vel_ms *= 0.95;397update_forward_speed_controller(0.0);398}399400// Determine the body frame forward speed401float AC_Autorotation::get_speed_forward_ms(void) const402{403Vector3f vel_NED = {0,0,0};404const AP_AHRS &ahrs = AP::ahrs();405if (ahrs.get_velocity_NED(vel_NED)) {406vel_NED = ahrs.earth_to_body(vel_NED);407}408// TODO: need to improve the handling of the velocity NED not ok case409return vel_NED.x;410}411412#if HAL_LOGGING_ENABLED413// Logging of lower rate autorotation specific variables. This is meant for stuff that414// doesn't need a high rate, e.g. controller variables that are need for tuning.415void AC_Autorotation::log_write_autorotation(void) const416{417// enum class for bitmask documentation in logging418enum class AC_Autorotation_Landed_Reason : uint8_t {419LOW_SPEED = 1<<0, // true if below 1 m/s420LAND_COL = 1<<1, // true if collective below land col min421IS_STILL = 1<<2, // passes inertial nav is_still() check422};423424uint8_t reason = 0;425if (_landed_reason.min_speed) {426reason |= uint8_t(AC_Autorotation_Landed_Reason::LOW_SPEED);427}428if (_landed_reason.land_col) {429reason |= uint8_t(AC_Autorotation_Landed_Reason::LAND_COL);430}431if (_landed_reason.is_still) {432reason |= uint8_t(AC_Autorotation_Landed_Reason::IS_STILL);433}434435// @LoggerMessage: AROT436// @Vehicles: Copter437// @Description: Helicopter AutoROTation (AROT) information438// @Field: TimeUS: Time since system startup439// @Field: LR: Landed Reason state flags440// @FieldBitmaskEnum: LR: AC_Autorotation_Landed_Reason441442// Write to data flash log443AP::logger().WriteStreaming("AROT",444"TimeUS,LR",445"s-",446"F-",447"QB",448AP_HAL::micros64(),449reason);450}451#endif // HAL_LOGGING_ENABLED452453// Arming checks for autorotation, mostly checking for miss-configurations454bool AC_Autorotation::arming_checks(size_t buflen, char *buffer) const455{456if (!enabled()) {457// Don't run arming checks if not enabled458return true;459}460461// Check for correct RPM sensor config462#if AP_RPM_ENABLED463// Get singleton for RPM library464const AP_RPM *rpm = AP_RPM::get_singleton();465466// Get current rpm, checking to ensure no nullptr467if (rpm == nullptr) {468hal.util->snprintf(buffer, buflen, "Can't access RPM");469return false;470}471472// Sanity check that the designated rpm sensor instance is there473if (_param_rpm_instance.get() < 0) {474hal.util->snprintf(buffer, buflen, "RPM instance <0");475return false;476}477478if (!rpm->enabled(_param_rpm_instance.get())) {479hal.util->snprintf(buffer, buflen, "RPM%i not enabled", _param_rpm_instance.get()+1);480return false;481}482#endif483484// Check that heli motors is configured for autorotation485if (!_motors_heli->rsc_autorotation_enabled()) {486hal.util->snprintf(buffer, buflen, "H_RSC_AROT_* not configured");487return false;488}489490return true;491}492493// Check if we believe we have landed. We need the landed state to zero all494// controls and make sure that the copter landing detector will trip495bool AC_Autorotation::check_landed(void)496{497// minimum speed (m/s) used for "is moving" check498const float min_moving_speed = 1.0;499500Vector3f velocity;501const AP_AHRS &ahrs = AP::ahrs();502_landed_reason.min_speed = ahrs.get_velocity_NED(velocity) && (velocity.length() < min_moving_speed);503_landed_reason.land_col = _motors_heli->get_below_land_min_coll();504_landed_reason.is_still = AP::ins().is_still();505506return _landed_reason.min_speed && _landed_reason.land_col && _landed_reason.is_still;507}508509// Dynamically update time step used in autorotation controllers510void AC_Autorotation::set_dt(float delta_sec)511{512if (is_positive(delta_sec)) {513_dt = delta_sec;514return;515}516_dt = 2.5e-3; // Assume 400 Hz517}518519520