2013-08-29 02:34:34 -03:00
/*
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
2021-11-25 01:23:42 -04:00
2013-08-29 02:34:34 -03:00
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
2021-11-25 01:23:42 -04:00
2013-08-29 02:34:34 -03:00
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2012-08-21 23:08:14 -03:00
2013-04-23 08:02:18 -03:00
// Initial Code by Jon Challinger
// Modified by Paul Riseborough
2012-08-21 23:08:14 -03:00
2015-08-11 03:28:41 -03:00
# include <AP_HAL/AP_HAL.h>
2012-08-21 23:08:14 -03:00
# include "AP_PitchController.h"
2021-07-20 08:15:06 -03:00
# include <AP_AHRS/AP_AHRS.h>
2022-09-29 20:10:40 -03:00
# include <AP_Scheduler/AP_Scheduler.h>
# include <GCS_MAVLink/GCS.h>
2012-08-21 23:08:14 -03:00
2012-10-12 14:53:42 -03:00
extern const AP_HAL : : HAL & hal ;
2015-10-25 14:03:46 -03:00
const AP_Param : : GroupInfo AP_PitchController : : var_info [ ] = {
2013-05-05 03:07:05 -03:00
2020-09-18 21:29:41 -03:00
// @Param: 2SRV_TCONST
2021-11-25 01:23:42 -04:00
// @DisplayName: Pitch Time Constant
// @Description: Time constant in seconds from demanded to achieved pitch angle. Most models respond well to 0.5. May be reduced for faster responses, but setting lower than a model can achieve will not help.
// @Range: 0.4 1.0
// @Units: s
// @Increment: 0.1
// @User: Advanced
2020-09-18 21:29:41 -03:00
AP_GROUPINFO ( " 2SRV_TCONST " , 0 , AP_PitchController , gains . tau , 0.5f ) ,
2013-05-05 03:07:05 -03:00
2021-02-27 05:56:41 -04:00
// index 1 to 3 reserved for old PID values
2013-05-05 03:07:05 -03:00
2020-09-18 21:29:41 -03:00
// @Param: 2SRV_RMAX_UP
2021-11-25 01:23:42 -04:00
// @DisplayName: Pitch up max rate
2022-01-24 13:06:20 -04:00
// @Description: This sets the maximum nose up pitch rate that the attitude controller will demand (degrees/sec) in angle stabilized modes. Setting it to zero disables the limit.
2021-11-25 01:23:42 -04:00
// @Range: 0 100
// @Units: deg/s
// @Increment: 1
// @User: Advanced
2021-03-31 20:42:19 -03:00
AP_GROUPINFO ( " 2SRV_RMAX_UP " , 4 , AP_PitchController , gains . rmax_pos , 0.0f ) ,
2013-05-05 03:07:05 -03:00
2020-09-18 21:29:41 -03:00
// @Param: 2SRV_RMAX_DN
2021-11-25 01:23:42 -04:00
// @DisplayName: Pitch down max rate
2022-01-24 13:06:20 -04:00
// @Description: This sets the maximum nose down pitch rate that the attitude controller will demand (degrees/sec) in angle stabilized modes. Setting it to zero disables the limit.
2021-11-25 01:23:42 -04:00
// @Range: 0 100
// @Units: deg/s
// @Increment: 1
// @User: Advanced
2021-03-31 20:42:19 -03:00
AP_GROUPINFO ( " 2SRV_RMAX_DN " , 5 , AP_PitchController , gains . rmax_neg , 0.0f ) ,
2013-05-05 03:07:05 -03:00
2020-09-18 21:29:41 -03:00
// @Param: 2SRV_RLL
2021-11-25 01:23:42 -04:00
// @DisplayName: Roll compensation
// @Description: Gain added to pitch to keep aircraft from descending or ascending in turns. Increase in increments of 0.05 to reduce altitude loss. Decrease for altitude gain.
// @Range: 0.7 1.5
// @Increment: 0.05
2020-09-18 21:29:41 -03:00
// @User: Standard
AP_GROUPINFO ( " 2SRV_RLL " , 6 , AP_PitchController , _roll_ff , 1.0f ) ,
2013-05-05 03:07:05 -03:00
2021-03-31 20:42:19 -03:00
// index 7, 8 reserved for old IMAX, FF
2020-09-18 21:29:41 -03:00
// @Param: _RATE_P
// @DisplayName: Pitch axis rate controller P gain
2022-11-20 20:45:06 -04:00
// @Description: Pitch axis rate controller P gain. Corrects in proportion to the difference between the desired pitch rate vs actual pitch rate
2020-09-18 21:29:41 -03:00
// @Range: 0.08 0.35
// @Increment: 0.005
// @User: Standard
// @Param: _RATE_I
// @DisplayName: Pitch axis rate controller I gain
// @Description: Pitch axis rate controller I gain. Corrects long-term difference in desired roll rate vs actual roll rate
// @Range: 0.01 0.6
// @Increment: 0.01
// @User: Standard
// @Param: _RATE_IMAX
// @DisplayName: Pitch axis rate controller I gain maximum
2022-11-20 20:45:06 -04:00
// @Description: Pitch axis rate controller I gain maximum. Constrains the maximum that the I term will output
2020-09-18 21:29:41 -03:00
// @Range: 0 1
// @Increment: 0.01
// @User: Standard
// @Param: _RATE_D
// @DisplayName: Pitch axis rate controller D gain
// @Description: Pitch axis rate controller D gain. Compensates for short-term change in desired roll rate vs actual roll rate
// @Range: 0.001 0.03
// @Increment: 0.001
// @User: Standard
// @Param: _RATE_FF
// @DisplayName: Pitch axis rate controller feed forward
// @Description: Pitch axis rate controller feed forward
// @Range: 0 3.0
// @Increment: 0.001
// @User: Standard
// @Param: _RATE_FLTT
// @DisplayName: Pitch axis rate controller target frequency in Hz
// @Description: Pitch axis rate controller target frequency in Hz
// @Range: 2 50
// @Increment: 1
// @Units: Hz
// @User: Standard
// @Param: _RATE_FLTE
// @DisplayName: Pitch axis rate controller error frequency in Hz
// @Description: Pitch axis rate controller error frequency in Hz
// @Range: 2 50
// @Increment: 1
// @Units: Hz
// @User: Standard
// @Param: _RATE_FLTD
// @DisplayName: Pitch axis rate controller derivative frequency in Hz
// @Description: Pitch axis rate controller derivative frequency in Hz
// @Range: 0 50
// @Increment: 1
// @Units: Hz
// @User: Standard
// @Param: _RATE_SMAX
// @DisplayName: Pitch slew rate limit
// @Description: Sets an upper limit on the slew rate produced by the combined P and D gains. If the amplitude of the control action produced by the rate feedback exceeds this value, then the D+P gain is reduced to respect the limit. This limits the amplitude of high frequency oscillations caused by an excessive gain. The limit should be set to no more than 25% of the actuators maximum slew rate to allow for load effects. Note: The gain will not be reduced to less than 10% of the nominal value. A value of zero will disable this feature.
// @Range: 0 200
// @Increment: 0.5
// @User: Advanced
2023-09-18 22:46:29 -03:00
// @Param: _RATE_PDMX
// @DisplayName: Pitch axis rate controller PD sum maximum
// @Description: Pitch axis rate controller PD sum maximum. The maximum/minimum value that the sum of the P and D term can output
// @Range: 0 1
// @Increment: 0.01
2023-07-30 13:03:34 -03:00
// @Param: _RATE_D_FF
// @DisplayName: Pitch Derivative FeedForward Gain
// @Description: FF D Gain which produces an output that is proportional to the rate of change of the target
2023-10-01 14:21:01 -03:00
// @Range: 0 0.03
2023-07-30 13:03:34 -03:00
// @Increment: 0.001
// @User: Advanced
// @Param: _RATE_NTF
2023-10-01 14:21:01 -03:00
// @DisplayName: Pitch Target notch filter index
// @Description: Pitch Target notch filter index
// @Range: 1 8
2023-07-30 13:03:34 -03:00
// @User: Advanced
// @Param: _RATE_NEF
2023-10-01 14:21:01 -03:00
// @DisplayName: Pitch Error notch filter index
// @Description: Pitch Error notch filter index
// @Range: 1 8
2023-09-18 22:46:29 -03:00
// @User: Advanced
2020-09-18 21:29:41 -03:00
AP_SUBGROUPINFO ( rate_pid , " _RATE_ " , 11 , AP_PitchController , AC_PID ) ,
2021-11-25 01:23:42 -04:00
2020-05-10 04:11:11 -03:00
AP_GROUPEND
2012-08-21 23:08:14 -03:00
} ;
2022-09-29 20:10:40 -03:00
AP_PitchController : : AP_PitchController ( const AP_FixedWing & parms )
2021-03-31 20:42:19 -03:00
: aparm ( parms )
{
AP_Param : : setup_object_defaults ( this , var_info ) ;
rate_pid . set_slew_limit_scale ( 45 ) ;
}
2020-09-18 21:29:41 -03:00
/*
AC_PID based rate controller
*/
2021-10-29 07:37:15 -03:00
float AP_PitchController : : _get_rate_out ( float desired_rate , float scaler , bool disable_integrator , float aspeed , bool ground_mode )
2020-09-18 21:29:41 -03:00
{
const float dt = AP : : scheduler ( ) . get_loop_period_s ( ) ;
2021-07-20 08:15:06 -03:00
const AP_AHRS & _ahrs = AP : : ahrs ( ) ;
2020-09-18 21:29:41 -03:00
const float eas2tas = _ahrs . get_EAS2TAS ( ) ;
2021-03-31 20:42:19 -03:00
bool limit_I = fabsf ( _last_out ) > = 45 ;
2020-09-18 21:29:41 -03:00
float rate_y = _ahrs . get_gyro ( ) . y ;
float old_I = rate_pid . get_i ( ) ;
bool underspeed = aspeed < = 0.5 * float ( aparm . airspeed_min ) ;
if ( underspeed ) {
limit_I = true ;
}
// the P and I elements are scaled by sq(scaler). To use an
// unmodified AC_PID object we scale the inputs and calculate FF separately
//
// note that we run AC_PID in radians so that the normal scaling
// range for IMAX in AC_PID applies (usually an IMAX value less than 1.0)
2022-11-30 01:24:18 -04:00
rate_pid . update_all ( radians ( desired_rate ) * scaler * scaler , rate_y * scaler * scaler , dt , limit_I ) ;
2020-09-18 21:29:41 -03:00
if ( underspeed ) {
// when underspeed we lock the integrator
rate_pid . set_integrator ( old_I ) ;
}
2021-11-25 01:23:42 -04:00
2020-09-18 21:29:41 -03:00
// FF should be scaled by scaler/eas2tas, but since we have scaled
// the AC_PID target above by scaler*scaler we need to instead
// divide by scaler*eas2tas to get the right scaling
2023-07-03 15:50:53 -03:00
const float ff = degrees ( ff_scale * rate_pid . get_ff ( ) / ( scaler * eas2tas ) ) ;
ff_scale = 1.0 ;
2020-09-18 21:29:41 -03:00
if ( disable_integrator ) {
rate_pid . reset_I ( ) ;
}
// convert AC_PID info object to same scale as old controller
2021-03-12 02:05:00 -04:00
_pid_info = rate_pid . get_pid_info ( ) ;
auto & pinfo = _pid_info ;
2020-09-18 21:29:41 -03:00
const float deg_scale = degrees ( 1 ) ;
pinfo . FF = ff ;
pinfo . P * = deg_scale ;
pinfo . I * = deg_scale ;
pinfo . D * = deg_scale ;
2023-11-12 02:34:27 -04:00
pinfo . DFF * = deg_scale ;
2020-09-18 21:29:41 -03:00
// fix the logged target and actual values to not have the scalers applied
pinfo . target = desired_rate ;
pinfo . actual = degrees ( rate_y ) ;
// sum components
2023-11-12 02:34:27 -04:00
float out = pinfo . FF + pinfo . P + pinfo . I + pinfo . D + pinfo . DFF ;
2021-10-29 07:37:15 -03:00
if ( ground_mode ) {
// when on ground suppress D and half P term to prevent oscillations
out - = pinfo . D + 0.5 * pinfo . P ;
}
2020-09-18 21:29:41 -03:00
// remember the last output to trigger the I limit
2021-03-31 20:42:19 -03:00
_last_out = out ;
2020-09-18 21:29:41 -03:00
2021-03-31 20:42:19 -03:00
if ( autotune ! = nullptr & & autotune - > running & & aspeed > aparm . airspeed_min ) {
2021-11-25 01:23:42 -04:00
// let autotune have a go at the values
2021-04-12 23:37:37 -03:00
autotune - > update ( pinfo , scaler , angle_err_deg ) ;
2021-03-31 20:42:19 -03:00
}
2021-11-25 01:23:42 -04:00
2020-09-18 21:29:41 -03:00
// output is scaled to notional centidegrees of deflection
2021-09-18 14:57:51 -03:00
return constrain_float ( out * 100 , - 4500 , 4500 ) ;
2020-09-18 21:29:41 -03:00
}
2013-07-10 10:25:06 -03:00
/*
Function returns an equivalent elevator deflection in centi - degrees in the range from - 4500 to 4500
A positive demand is up
2021-11-25 01:23:42 -04:00
Inputs are :
2013-07-10 10:25:06 -03:00
1 ) demanded pitch rate in degrees / second
2 ) control gain scaler = scaling_speed / aspeed
3 ) boolean which is true when stabilise mode is active
4 ) minimum FBW airspeed ( metres / sec )
5 ) maximum FBW airspeed ( metres / sec )
*/
2021-09-18 14:57:51 -03:00
float AP_PitchController : : get_rate_out ( float desired_rate , float scaler )
2013-07-10 10:25:06 -03:00
{
2013-10-04 08:52:47 -03:00
float aspeed ;
2021-11-25 01:23:42 -04:00
if ( ! AP : : ahrs ( ) . airspeed_estimate ( aspeed ) ) {
// If no airspeed available use average of min and max
2016-08-07 21:48:25 -03:00
aspeed = 0.5f * ( float ( aparm . airspeed_min ) + float ( aparm . airspeed_max ) ) ;
2021-11-25 01:23:42 -04:00
}
2021-10-29 07:37:15 -03:00
return _get_rate_out ( desired_rate , scaler , false , aspeed , false ) ;
2013-07-10 10:25:06 -03:00
}
2013-05-13 08:10:55 -03:00
2013-07-17 22:57:11 -03:00
/*
get the rate offset in degrees / second needed for pitch in body frame
to maintain height in a coordinated turn .
2021-11-25 01:23:42 -04:00
2013-07-17 22:57:11 -03:00
Also returns the inverted flag and the estimated airspeed in m / s for
use by the rest of the pitch controller
*/
float AP_PitchController : : _get_coordination_rate_offset ( float & aspeed , bool & inverted ) const
2013-07-10 10:25:06 -03:00
{
2021-11-25 01:23:42 -04:00
float rate_offset ;
2024-01-12 08:40:22 -04:00
float bank_angle = AP : : ahrs ( ) . get_roll ( ) ;
2013-06-01 09:27:01 -03:00
2021-11-25 01:23:42 -04:00
// limit bank angle between +- 80 deg if right way up
if ( fabsf ( bank_angle ) < radians ( 90 ) ) {
bank_angle = constrain_float ( bank_angle , - radians ( 80 ) , radians ( 80 ) ) ;
2013-07-17 22:57:11 -03:00
inverted = false ;
2021-11-25 01:23:42 -04:00
} else {
inverted = true ;
if ( bank_angle > 0.0f ) {
bank_angle = constrain_float ( bank_angle , radians ( 100 ) , radians ( 180 ) ) ;
} else {
bank_angle = constrain_float ( bank_angle , - radians ( 180 ) , - radians ( 100 ) ) ;
}
}
2021-07-20 08:15:06 -03:00
const AP_AHRS & _ahrs = AP : : ahrs ( ) ;
2021-11-25 01:23:42 -04:00
if ( ! _ahrs . airspeed_estimate ( aspeed ) ) {
// If no airspeed available use average of min and max
2016-08-07 21:48:25 -03:00
aspeed = 0.5f * ( float ( aparm . airspeed_min ) + float ( aparm . airspeed_max ) ) ;
2021-11-25 01:23:42 -04:00
}
2013-08-14 01:56:18 -03:00
if ( abs ( _ahrs . pitch_sensor ) > 7000 ) {
2013-07-13 07:27:48 -03:00
// don't do turn coordination handling when at very high pitch angles
rate_offset = 0 ;
} else {
2024-01-12 08:40:22 -04:00
rate_offset = cosf ( _ahrs . get_pitch ( ) ) * fabsf ( ToDeg ( ( GRAVITY_MSS / MAX ( ( aspeed * _ahrs . get_EAS2TAS ( ) ) , MAX ( aparm . airspeed_min , 1 ) ) ) * tanf ( bank_angle ) * sinf ( bank_angle ) ) ) * _roll_ff ;
2021-11-25 01:23:42 -04:00
}
if ( inverted ) {
rate_offset = - rate_offset ;
2013-07-13 07:27:48 -03:00
}
2013-07-17 22:57:11 -03:00
return rate_offset ;
}
// Function returns an equivalent elevator deflection in centi-degrees in the range from -4500 to 4500
// A positive demand is up
2021-11-25 01:23:42 -04:00
// Inputs are:
2013-07-17 22:57:11 -03:00
// 1) demanded pitch angle in centi-degrees
// 2) control gain scaler = scaling_speed / aspeed
// 3) boolean which is true when stabilise mode is active
// 4) minimum FBW airspeed (metres/sec)
// 5) maximum FBW airspeed (metres/sec)
//
2021-10-29 07:37:15 -03:00
float AP_PitchController : : get_servo_out ( int32_t angle_err , float scaler , bool disable_integrator , bool ground_mode )
2013-07-17 22:57:11 -03:00
{
2021-11-25 01:23:42 -04:00
// Calculate offset to pitch rate demand required to maintain pitch angle whilst banking
// Calculate ideal turn rate from bank angle and airspeed assuming a level coordinated turn
// Pitch rate offset is the component of turn rate about the pitch axis
float aspeed ;
float rate_offset ;
bool inverted ;
2013-07-17 22:57:11 -03:00
2021-04-04 21:23:53 -03:00
if ( gains . tau < 0.05f ) {
gains . tau . set ( 0.05f ) ;
2013-07-17 22:57:11 -03:00
}
rate_offset = _get_coordination_rate_offset ( aspeed , inverted ) ;
2021-11-25 01:23:42 -04:00
// Calculate the desired pitch rate (deg/sec) from the angle error
2021-04-12 23:37:37 -03:00
angle_err_deg = angle_err * 0.01 ;
float desired_rate = angle_err_deg / gains . tau ;
2021-11-25 01:23:42 -04:00
// limit the maximum pitch rate demand. Don't apply when inverted
// as the rates will be tuned when upright, and it is common that
// much higher rates are needed inverted
if ( ! inverted ) {
2021-03-20 17:37:07 -03:00
desired_rate + = rate_offset ;
2021-03-31 20:42:19 -03:00
if ( gains . rmax_neg & & desired_rate < - gains . rmax_neg ) {
desired_rate = - gains . rmax_neg ;
} else if ( gains . rmax_pos & & desired_rate > gains . rmax_pos ) {
desired_rate = gains . rmax_pos ;
2021-11-25 01:23:42 -04:00
}
2021-03-20 17:37:07 -03:00
} else {
// Make sure not to invert the turn coordination offset
desired_rate = - desired_rate + rate_offset ;
2021-11-25 01:23:42 -04:00
}
2013-04-23 16:22:17 -03:00
2021-04-03 00:12:46 -03:00
/*
when we are past the users defined roll limit for the aircraft
our priority should be to bring the aircraft back within the
roll limit . Using elevator for pitch control at large roll
angles is ineffective , and can be counter productive as it
induces earth - frame yaw which can reduce the ability to roll . We
linearly reduce pitch demanded rate when beyond the configured
roll limit , reducing to zero at 90 degrees
*/
2021-07-20 08:15:06 -03:00
const AP_AHRS & _ahrs = AP : : ahrs ( ) ;
2021-04-03 00:12:46 -03:00
float roll_wrapped = labs ( _ahrs . roll_sensor ) ;
if ( roll_wrapped > 9000 ) {
roll_wrapped = 18000 - roll_wrapped ;
}
2024-01-18 21:58:28 -04:00
const float roll_limit_margin = MIN ( aparm . roll_limit * 100 + 500.0 , 8500.0 ) ;
2021-04-03 00:12:46 -03:00
if ( roll_wrapped > roll_limit_margin & & labs ( _ahrs . pitch_sensor ) < 7000 ) {
float roll_prop = ( roll_wrapped - roll_limit_margin ) / ( float ) ( 9000 - roll_limit_margin ) ;
desired_rate * = ( 1 - roll_prop ) ;
}
2021-10-29 07:37:15 -03:00
return _get_rate_out ( desired_rate , scaler , disable_integrator , aspeed , ground_mode ) ;
2012-08-21 23:08:14 -03:00
}
void AP_PitchController : : reset_I ( )
{
2020-09-18 21:29:41 -03:00
rate_pid . reset_I ( ) ;
}
/*
convert from old to new PIDs
this is a temporary conversion function during development
*/
void AP_PitchController : : convert_pid ( )
{
2021-02-27 05:56:41 -04:00
AP_Float & ff = rate_pid . ff ( ) ;
2022-05-11 19:04:36 -03:00
if ( ff . configured ( ) ) {
2020-09-18 21:29:41 -03:00
return ;
}
2021-02-27 05:56:41 -04:00
float old_ff = 0 , old_p = 1.0 , old_i = 0.3 , old_d = 0.08 ;
2021-03-31 20:42:19 -03:00
int16_t old_imax = 3000 ;
2021-02-27 05:56:41 -04:00
bool have_old = AP_Param : : get_param_by_index ( this , 1 , AP_PARAM_FLOAT , & old_p ) ;
have_old | = AP_Param : : get_param_by_index ( this , 3 , AP_PARAM_FLOAT , & old_i ) ;
have_old | = AP_Param : : get_param_by_index ( this , 2 , AP_PARAM_FLOAT , & old_d ) ;
have_old | = AP_Param : : get_param_by_index ( this , 8 , AP_PARAM_FLOAT , & old_ff ) ;
2021-03-31 20:42:19 -03:00
have_old | = AP_Param : : get_param_by_index ( this , 7 , AP_PARAM_FLOAT , & old_imax ) ;
2021-02-27 05:56:41 -04:00
if ( ! have_old ) {
// none of the old gains were set
2020-09-18 21:29:41 -03:00
return ;
}
2021-02-27 05:56:41 -04:00
const float kp_ff = MAX ( ( old_p - old_i * gains . tau ) * gains . tau - old_d , 0 ) ;
rate_pid . ff ( ) . set_and_save ( old_ff + kp_ff ) ;
rate_pid . kI ( ) . set_and_save_ifchanged ( old_i * gains . tau ) ;
rate_pid . kP ( ) . set_and_save_ifchanged ( old_d ) ;
2020-09-18 21:29:41 -03:00
rate_pid . kD ( ) . set_and_save_ifchanged ( 0 ) ;
2021-03-31 20:42:19 -03:00
rate_pid . kIMAX ( ) . set_and_save_ifchanged ( old_imax / 4500.0 ) ;
}
/*
start an autotune
*/
void AP_PitchController : : autotune_start ( void )
{
if ( autotune = = nullptr ) {
2024-05-26 22:24:13 -03:00
autotune = NEW_NOTHROW AP_AutoTune ( gains , AP_AutoTune : : AUTOTUNE_PITCH , aparm , rate_pid ) ;
2021-03-31 20:42:19 -03:00
if ( autotune = = nullptr ) {
if ( ! failed_autotune_alloc ) {
GCS_SEND_TEXT ( MAV_SEVERITY_ERROR , " AutoTune: failed pitch allocation " ) ;
}
failed_autotune_alloc = true ;
}
}
if ( autotune ! = nullptr ) {
autotune - > start ( ) ;
}
}
/*
restore autotune gains
*/
void AP_PitchController : : autotune_restore ( void )
{
if ( autotune ! = nullptr ) {
autotune - > stop ( ) ;
}
2019-01-27 01:42:16 -04:00
}