ardupilot/libraries/AP_Scripting/applets/rover-quicktune.lua

797 lines
27 KiB
Lua

--[[
Rover QuickTune tunes the steering (aka turn rate), speed and position controller velocity gains for rovers and boats
The script is designed to be used in Circle mode and updates the following parameters
ATC_STR_RAT_P
ATC_STR_RAT_I
ATC_STR_RAT_D
ATC_STR_RAT_FF
ATC_SPEED_P
ATC_SPEED_I
ATC_SPEED_D
CRUISE_SPEED
CRUISE_THROTTLE
PSC_VEL_P
PSC_VEL_I
PSC_VEL_D
See the accompanying rover-quiktune.md file for instructions on how to use
--]]
-- global definitions
local MAV_SEVERITY = {EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFO=6, DEBUG=7}
local PARAM_TABLE_KEY = 12
local PARAM_TABLE_PREFIX = "RTUN_"
-- bind a parameter to a variable
function bind_param(name)
local p = Parameter()
assert(p:init(name), string.format("RTun: could not find %s parameter", name))
return p
end
-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format("RTun: could not add param %s", name))
return bind_param(PARAM_TABLE_PREFIX .. name)
end
-- setup quicktune specific parameters
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 12), "RTun: could not add param table")
--[[
// @Param: RTUN_ENABLE
// @DisplayName: Rover Quicktune enable
// @Description: Enable quicktune system
// @Values: 0:Disabled,1:Enabled
// @User: Standard
--]]
local RTUN_ENABLE = bind_add_param('ENABLE', 1, 1)
--[[
// @Param: RTUN_AXES
// @DisplayName: Rover Quicktune axes
// @Description: axes to tune
// @Bitmask: 0:Steering,1:Speed,2:Velocity
// @User: Standard
--]]
local RTUN_AXES = bind_add_param('AXES', 2, 7)
--[[
// @Param: RTUN_DOUBLE_TIME
// @DisplayName: Rover Quicktune doubling time
// @Description: Time to double a tuning parameter. Raise this for a slower tune.
// @Range: 5 20
// @Units: s
// @User: Standard
--]]
local RTUN_DOUBLE_TIME = bind_add_param('DOUBLE_TIME', 3, 10)
--[[
// @Param: RTUN_PD_GAINMARG
// @DisplayName: Rover Quicktune P and D gain margin
// @Description: Reduction in P and D gain after oscillation detected. Raise this number to get a more conservative tune
// @Range: 20 90
// @Units: %
// @User: Standard
--]]
local RTUN_PD_GAINMARG = bind_add_param('PD_GAINMARG', 4, 80)
--[[
// @Param: RTUN_OSC_SMAX
// @DisplayName: Rover Quicktune oscillation rate threshold
// @Description: Threshold for oscillation detection. A lower value will lead to a more conservative tune.
// @Range: 1 10
// @User: Standard
--]]
local RTUN_OSC_SMAX = bind_add_param('OSC_SMAX', 5, 1)
--[[
// @Param: RTUN_SPD_P_MAX
// @DisplayName: Rover Quicktune Speed P max
// @Description: Maximum value for speed P gain
// @Range: 0.1 10
// @User: Standard
--]]
local RTUN_SPD_P_MAX = bind_add_param('SPD_P_MAX', 6, 5.0)
--[[
// @Param: RTUN_SPD_D_MAX
// @DisplayName: Rover Quicktune Speed D max
// @Description: Maximum value for speed D gain
// @Range: 0.001 1
// @User: Standard
--]]
local RTUN_SPD_D_MAX = bind_add_param('SPD_D_MAX', 7, 0.5)
--[[
// @Param: RTUN_PI_RATIO
// @DisplayName: Rover Quicktune PI ratio
// @Description: Ratio between P and I gains. Raise this to get a lower I gain, 0 to disable
// @Range: 0.5 1.0
// @User: Standard
--]]
local RTUN_PI_RATIO = bind_add_param('PI_RATIO', 8, 1.0)
--[[
// @Param: RTUN_AUTO_FILTER
// @DisplayName: Rover Quicktune auto filter enable
// @Description: When enabled the PID filter settings are automatically set based on INS_GYRO_FILTER
// @Values: 0:Disabled,1:Enabled
// @User: Standard
--]]
local RTUN_AUTO_FILTER = bind_add_param('AUTO_FILTER', 9, 1)
--[[
// @Param: RTUN_AUTO_SAVE
// @DisplayName: Rover Quicktune auto save
// @Description: Number of seconds after completion of tune to auto-save. This is useful when using a 2 position switch for quicktune
// @Units: s
// @User: Standard
--]]
local RTUN_AUTO_SAVE = bind_add_param('AUTO_SAVE', 10, 0)
--[[
// @Param: RTUN_RC_FUNC
// @DisplayName: Rover Quicktune RC function
// @Description: RCn_OPTION number to use to control tuning stop/start/save
// @Values: 300:Scripting1, 301:Scripting2, 302:Scripting3, 303:Scripting4, 304:Scripting5, 305:Scripting6, 306:Scripting7, 307:Scripting8
// @User: Standard
--]]
local RTUN_RC_FUNC = bind_add_param('RC_FUNC', 11, 300)
--[[
// @Param: RTUN_FF_GAINMARG
// @DisplayName: Rover Quicktune Steering Rate FeedForward gain margin
// @Description: Reduction in Steering Turn Rate FF gain from measured outputs and response. Raise this number to get a more conservative tune
// @Range: 0 80
// @Units: %
// @User: Standard
--]]
local RTUN_FF_GAINMARG = bind_add_param('FF_GAINMARG', 12, 10)
-- other vehicle parameters used by this script
local INS_GYRO_FILTER = bind_param("INS_GYRO_FILTER")
local GCS_PID_MASK = bind_param("GCS_PID_MASK")
local RCMAP_ROLL = bind_param("RCMAP_ROLL")
local RCMAP_THROTTLE = bind_param("RCMAP_THROTTLE")
local RCIN_ROLL = rc:get_channel(RCMAP_ROLL:get())
local RCIN_THROTTLE = rc:get_channel(RCMAP_THROTTLE:get())
-- definitions
local UPDATE_RATE_HZ = 40 -- this script updates at 40hz
local STAGE_DELAY = 4.0 -- gains increased every 4 seconds
local PILOT_INPUT_DELAY = 4.0 -- gains are not updated for 4 seconds after pilot releases sticks
local FLTD_MUL = 0.5 -- ATC_STR_RAT_FLTD set to 0.5 * INS_GYRO_FILTER
local FLTT_MUL = 0.5 -- ATC_STR_RAT_FLTT set to 0.5 * INS_GYRO_FILTER
local STR_RAT_FF_TURNRATE_MIN = math.rad(10) -- steering rate feedforward min vehicle turn rate (in radians/sec)
local STR_RAT_FF_STEERING_MIN = 0.10 -- steering rate feedforward min steering output (in the range 0 to 1)
local SPEED_FF_SPEED_MIN = 0.5 -- speed feedforward minimum vehicle speed (in m/s)
local SPEED_FF_THROTTLE_MIN = 0.20 -- speed feedforward requires throttle output (in the range 0 to 1)
-- get time in seconds since boot
function get_time()
return millis():tofloat() * 0.001
end
-- local variables
local axis_names = { "ATC_STR_RAT", "ATC_SPEED", "PSC_VEL" } -- table of axis that may be tuned
local param_suffixes = { "FF", "P", "I", "D", "FLTT", "FLTD", "FLTE" } -- table of parameters that may be tuned
local params_extra = {"CRUISE_SPEED", "CRUISE_THROTTLE"} -- table of extra parameters that may be changed
local params_skip = {"PSC_VEL_FLTT"} -- parameters that should be skipped from saving/restoring (because they do not exist)
local stages = { "D", "P", "FF" } -- table of tuning stages
local stage = stages[1] -- current tuning stage
local last_stage_change = get_time() -- time (in seconds) that stage last changed
local last_gain_report = get_time() -- time of last update to user on parameter gain actively being tuned
local last_pilot_input = get_time() -- time pilot last provided RC input
local tune_done_time = nil -- time that tuning completed (used for auto save feature)
local slew_parm = nil -- parameter name being slewed towards a target. params are sometimes slewed to reduce impact on controllers
local slew_delta = 0 -- gain change increment to be applied to parameter being actively tuned
local slew_steps = 0 -- max number of times to increment parameter being actively tuned
local axes_done = {} -- list of axes that have been tuned
local filters_done = {} -- table recording if filters have been set for each axis
local gcs_pid_mask_done = {} -- table recording if GCS_PID_MASK has been set for each axis
local gcs_pid_mask_orig -- GCS_PID_MASK value before tuning started
-- feed forward tuning related local variables
local ff_throttle_sum = 0 -- total throttle recorded during speed FF tuning (divided by count to calc average)
local ff_speed_sum = 0 -- total speed recorded during speed FF tuning (divided by count to calc average)
local ff_speed_count = 0 -- number of speed and throttle samples taken during FF tuning
local ff_steering_sum = 0 -- total steering input recorded during steering rate FF tuning (divided by count to calc average)
local ff_turn_rate_sum = 0 -- total turn rate recorded during steering rate FF tuning (divided by count to calc average)
local ff_turn_rate_count = 0 -- number of steering and turn rate samples taken during FF tuning
local ff_last_warning = 0 -- time of last warning to user
-- params dictionary indexed by name, such as "ATC_STR_RAT_P"
local params = {} -- table of all parameters that may be tuned
local params_axis = {} -- table of each parameter's axis (used for logging of the appropriate srate)
local param_saved = {} -- table holding backup of each parameter's value from before tuning
local param_changed = {} -- table holding whether each param's gain has been saved
local need_restore = false -- true if any param's gain has been changed
-- check for an item within a table
-- returns true if found, false if not found
function in_skip_table(pname)
for _, skip_item in ipairs(params_skip) do
if pname == skip_item then
return true
end
end
return false
end
-- initialise params, params_axis and param_changed tables
function init_params_tables()
-- add parameters to params dictionary
for _, axis in ipairs(axis_names) do
for _, suffix in ipairs(param_suffixes) do
local pname = axis .. "_" .. suffix
if not in_skip_table(pname) then
params[pname] = bind_param(pname)
params_axis[pname] = axis
param_changed[pname] = false
end
end
end
-- add extra parameters to param dictionary
for _, extra_param_name in ipairs(params_extra) do
params[extra_param_name] = bind_param(extra_param_name)
params_axis[extra_param_name] = "ATC_SPEED" -- axis hard-coded to always be ATC_SPEED
param_changed[extra_param_name] = false
end
end
-- initialise all state variables so we are ready to start another tune
function reset_axes_done()
for _, axis in ipairs(axis_names) do
axes_done[axis] = false
filters_done[axis] = false
gcs_pid_mask_done[axis] = false
end
tune_done_time = nil
stage = stages[1]
end
-- get all current param values into param_saved dictionary
function get_all_params()
for pname in pairs(params) do
param_saved[pname] = params[pname]:get()
end
end
-- restore all param values from param_saved dictionary
function restore_all_params()
for pname in pairs(params) do
if param_changed[pname] then
params[pname]:set(param_saved[pname])
param_changed[pname] = false
end
end
end
-- save all param values to storage
function save_all_params()
for pname in pairs(params) do
if param_changed[pname] then
params[pname]:set_and_save(params[pname]:get())
param_saved[pname] = params[pname]:get()
param_changed[pname] = false
end
end
gcs:send_text(MAV_SEVERITY.NOTICE, "RTun: tuning gains saved")
end
-- setup filter frequencies
function setup_filters(axis)
if RTUN_AUTO_FILTER:get() > 0 then
if axis == "ATC_STR_RAT" then
adjust_gain(axis .. "_FLTT", INS_GYRO_FILTER:get() * FLTT_MUL)
adjust_gain(axis .. "_FLTD", INS_GYRO_FILTER:get() * FLTD_MUL)
end
end
filters_done[axis] = true
end
-- backup GCS_PID_MASK to value before tuning
function save_gcs_pid_mask()
gcs_pid_mask_orig = GCS_PID_MASK:get()
end
-- restore GCS_PID_MASK to value before tuning started
function restore_gcs_pid_mask()
GCS_PID_MASK:set(gcs_pid_mask_orig)
end
-- setup GCS_PID_MASK to provide real-time PID info to GCS during tuning
function setup_gcs_pid_mask(axis)
if axis == "ATC_STR_RAT" then
GCS_PID_MASK:set(1)
elseif axis == "ATC_SPEED" then
GCS_PID_MASK:set(2)
elseif axis == "PSC_VEL" then
GCS_PID_MASK:set(64)
else
gcs:send_text(MAV_SEVERITY.CRITICAL, string.format("RTun: setup_gcs_pid_mask received unhandled aixs %s", axis))
end
gcs_pid_mask_done[axis] = true
end
-- check for pilot input to pause tune
function have_pilot_input()
if (math.abs(RCIN_ROLL:norm_input_dz()) > 0 or
math.abs(RCIN_THROTTLE:norm_input_dz()) > 0) then
return true
end
return false
end
-- get the axis name we are working on, or nil for all done
function get_current_axis()
local axes = RTUN_AXES:get()
for i = 1, #axis_names do
local mask = (1 << (i-1))
local axis_name = axis_names[i]
if (mask & axes) ~= 0 and axes_done[axis_name] == false then
return axis_names[i]
end
end
return nil
end
-- get slew rate for an axis
function get_slew_rate(axis)
local steering_srate, speed_srate = AR_AttitudeControl:get_srate()
if axis == "ATC_STR_RAT" then
return steering_srate
end
if axis == "ATC_SPEED" then
return speed_srate
end
if axis == "PSC_VEL" then
local velocity_srate = AR_PosControl:get_srate()
return velocity_srate
end
gcs:send_text(MAV_SEVERITY.CRITICAL, string.format("RTUN: get_slew_rate unsupported axis:%s", axis))
return 0.0
end
-- move to next stage of tune
function advance_stage(axis)
local now_sec = get_time()
if stage == "D" then
stage = "P"
elseif stage == "P" then
stage = "FF"
else
local prev_axis = get_current_axis()
axes_done[axis] = true
gcs:send_text(MAV_SEVERITY.NOTICE, string.format("RTun: %s tuning done", axis))
stage = "D"
-- check for tune completion
if prev_axis ~= nil and get_current_axis() == nil then
gcs:send_text(MAV_SEVERITY.NOTICE, string.format("RTun: Tuning DONE"))
tune_done_time = now_sec
end
end
last_stage_change = now_sec
end
-- change a gain
function adjust_gain(pname, value)
-- sanity check parameter shouldn't be skipped
if in_skip_table(pname) then
gcs:send_text(MAV_SEVERITY.CRITICAL, string.format("RTun: attempted to update skipped param %s", pname))
return
end
local P = params[pname]
need_restore = true
param_changed[pname] = true
P:set(value)
if string.sub(pname, -2) == "_P" then
-- if we are updating a P value we may also update I
local ffname = string.gsub(pname, "_P", "_FF")
local FF = params[ffname]
if FF:get() > 0 then
-- if we have any FF on an axis then we don't couple I to P,
-- usually we want I = FF for a one sectond time constant for trim
return
end
-- if PI_RATIO is non-zero then update I
local PI_ratio = RTUN_PI_RATIO:get()
if PI_ratio > 0 then
local iname = string.gsub(pname, "_P", "_I")
local I = params[iname]
new_I_gain = value/PI_ratio
I:set(new_I_gain)
param_changed[iname] = true
write_log(iname)
end
end
end
-- return gain multipler for one loop
function get_gain_mul()
return math.exp(math.log(2.0)/(UPDATE_RATE_HZ*RTUN_DOUBLE_TIME:get()))
end
-- setup parameter slewing. slewing some changes over 2 seconds reduces shock to controllers
function setup_slew_gain(pname, gain)
slew_parm = pname
slew_steps = UPDATE_RATE_HZ / 2
slew_delta = (gain - params[pname]:get()) / slew_steps
end
-- update parameter slewing. slewing some changes over 2 seconds reduces shock to controllers
function update_slew_gain()
if slew_parm ~= nil then
local P = params[slew_parm]
adjust_gain(slew_parm, P:get()+slew_delta)
write_log(slew_parm)
slew_steps = slew_steps - 1
-- check if slewing is complete
if slew_steps == 0 then
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: %s %.3f", slew_parm, P:get()))
slew_parm = nil
end
end
end
-- log parameter, current gain and current slew rate
function write_log(pname)
local param_gain = params[pname]:get()
local pname_axis = params_axis[pname]
local slew_rate = get_slew_rate(pname_axis)
logger:write("RTUN","SRate,Gain,Param", "ffN", slew_rate, param_gain, pname)
end
-- return gain limits on a parameter, or 0 for no limit
function gain_limit(pname)
if pname == "ATC_SPEED_P" then
return RTUN_SPD_P_MAX:get()
elseif pname == "ATC_SPEED_D" then
return RTUN_SPD_D_MAX:get()
end
return 0.0
end
-- check if parameter's gain has reached its limit
function reached_limit(pname, gain)
local limit = gain_limit(pname)
if limit > 0.0 and gain >= limit then
return true
end
return false
end
-- initialise steering ff tuning
function init_steering_ff()
ff_steering_sum = 0
ff_turn_rate_sum = 0
ff_turn_rate_count = 0
end
-- run steering turn rate controller feedforward calibration
function update_steering_ff(pname)
-- get steering, turn rate, throttle and speed
local steering_out, _ = vehicle:get_steering_and_throttle()
local turn_rate_rads = ahrs:get_gyro():z()
-- update user every 5 sec
local now_sec = get_time()
local update_user = false
if (now_sec > ff_last_warning + 5) then
update_user = true
ff_last_warning = now_sec
end
-- calculate percentage complete
local turn_rate_complete_pct = (ff_turn_rate_sum / math.pi * 2.0) * 100
local time_complete_pct = (ff_turn_rate_count / (10 * UPDATE_RATE_HZ)) * 100
local complete_pct = math.min(turn_rate_complete_pct, time_complete_pct)
-- check steering and turn rate and accumulate output and response
local steering_ok = steering_out >= STR_RAT_FF_STEERING_MIN
local turnrate_ok = math.abs(turn_rate_rads) > STR_RAT_FF_TURNRATE_MIN
if (steering_ok and turnrate_ok) then
ff_steering_sum = ff_steering_sum + steering_out
ff_turn_rate_sum = ff_turn_rate_sum + math.abs(turn_rate_rads)
ff_turn_rate_count = ff_turn_rate_count + 1
if (update_user) then
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: %s %.0f%% complete", pname, complete_pct))
end
else
if update_user then
if not steering_ok then
gcs:send_text(MAV_SEVERITY.WARNING, string.format("RTun: increase steering (%d%% < %d%%)", math.floor(steering_out * 100), math.floor(STR_RAT_FF_STEERING_MIN * 100)))
elseif not turnrate_ok then
gcs:send_text(MAV_SEVERITY.WARNING, string.format("RTun: increase turn rate (%d deg/s < %d)", math.floor(math.deg(math.abs(turn_rate_rads))), math.floor(math.deg(STR_RAT_FF_TURNRATE_MIN))))
end
end
end
-- check for completion of two rotations of turns data and 10 seconds
if complete_pct >= 100 then
local old_gain = params[pname]:get()
local new_gain = (ff_steering_sum / ff_turn_rate_sum) * (1.0-(RTUN_FF_GAINMARG:get()*0.01))
adjust_gain(pname, new_gain)
write_log(pname)
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusted %s %.3f -> %.3f", pname, old_gain, new_gain))
-- set I gain equal to FF
local iname = string.gsub(pname, "_FF", "_I")
local I = params[iname]
local I_old_gain = I:get()
I:set(new_gain)
param_changed[iname] = true
write_log(iname)
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusted %s %.3f -> %.3f", iname, I_old_gain, new_gain))
return true
end
return false
end
-- run speed controller feedforward calibration
function update_speed_ff(pname)
-- get steering, turn rate, throttle and speed
local _, throttle_out = vehicle:get_steering_and_throttle()
local velocity_ned = ahrs:get_velocity_NED()
if velocity_ned then
speed = ahrs:earth_to_body(velocity_ned):x()
end
-- update user every 5 sec
local now_sec = get_time()
local update_user = false
if (now_sec > ff_last_warning + 5) then
update_user = true
ff_last_warning = now_sec
end
-- calculate percentage complete
local complete_pct = (ff_speed_count / (10 * UPDATE_RATE_HZ)) * 100
-- check throttle and speed
local throttle_ok = throttle_out >= SPEED_FF_THROTTLE_MIN
local speed_ok = speed > SPEED_FF_SPEED_MIN
if (throttle_ok and speed_ok) then
ff_throttle_sum = ff_throttle_sum + throttle_out
ff_speed_sum = ff_speed_sum + speed
ff_speed_count = ff_speed_count + 1
if (update_user) then
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: %s %.0f%% complete", pname, complete_pct))
end
else
if update_user then
if not throttle_ok then
gcs:send_text(MAV_SEVERITY.WARNING, string.format("RTun: increase throttle (%d < %d)", math.floor(throttle_out * 100), math.floor(SPEED_FF_THROTTLE_MIN * 100)))
elseif not turnrate_ok then
gcs:send_text(MAV_SEVERITY.WARNING, string.format("RTun: increase speed (%3.1f < %3.1f)", speed, SPEED_FF_SPEED_MIN))
end
end
end
-- check for 10 seconds of data
if complete_pct >= 100 then
local cruise_speed_old = params["CRUISE_SPEED"]:get()
local cruise_speed_new = ff_speed_sum / ff_speed_count
local cruise_throttle_old = params["CRUISE_THROTTLE"]:get()
local cruise_throttle_new = (ff_throttle_sum / ff_speed_count) * 100
adjust_gain("CRUISE_SPEED", cruise_speed_new)
adjust_gain("CRUISE_THROTTLE", cruise_throttle_new)
write_log("CRUISE_SPEED")
write_log("CRUISE_THROTTLE")
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusted %s %.2f -> %.2f", "CRUISE_SPEED", cruise_speed_old, cruise_speed_new))
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusted %s %.1f -> %.1f", "CRUISE_THROTTLE", cruise_throttle_old, cruise_throttle_new))
-- set I gain equal to FF
local iname = string.gsub(pname, "_FF", "_I")
local I = params[iname]
local I_old_gain = I:get()
local I_new_gain = ff_throttle_sum / ff_speed_sum
I:set(I_new_gain)
param_changed[iname] = true
write_log(iname)
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusted %s %.3f -> %.3f", iname, I_old_gain, I_new_gain))
return true
end
return false
end
-- initialisation
init_params_tables()
reset_axes_done()
get_all_params()
save_gcs_pid_mask()
gcs:send_text(MAV_SEVERITY.INFO, "Rover quiktune loaded")
-- main update function
local last_warning = get_time()
function update()
-- exit immediately if not enabled
if RTUN_ENABLE:get() <= 0 then
return
end
if have_pilot_input() then
last_pilot_input = get_time()
end
local sw_pos = rc:get_aux_cached(RTUN_RC_FUNC:get())
if not sw_pos then
return
end
-- get output throttle
local _, throttle_out = vehicle:get_steering_and_throttle()
-- check switch position (0:low is stop, 1:middle is tune, 2:high is save gains
if sw_pos == 1 and (not arming:is_armed() or (throttle_out <= 0)) and get_time() > last_warning + 5 then
gcs:send_text(MAV_SEVERITY.CRITICAL, "RTun: must be armed and moving to tune")
last_warning = get_time()
return
end
if sw_pos == 0 or not arming:is_armed() then
-- abort, revert parameters
if need_restore then
need_restore = false
restore_all_params()
restore_gcs_pid_mask()
gcs:send_text(MAV_SEVERITY.CRITICAL, "RTun: gains reverted")
end
reset_axes_done()
return
end
if sw_pos == 2 then
-- save all params
if need_restore then
need_restore = false
save_all_params()
restore_gcs_pid_mask()
end
end
-- if we reach here we must be tuning
if sw_pos ~= 1 then
return
end
-- update param gains for params being slewed towards a target
update_slew_gain()
-- return if we have just changed stages to give time for oscillations to subside
if get_time() - last_stage_change < STAGE_DELAY then
return
end
-- get axis currently being tuned
axis = get_current_axis()
-- if no axis is being tuned we must be done
if axis == nil then
-- check if params should be auto saved
if tune_done_time ~= nil and RTUN_AUTO_SAVE:get() > 0 then
if get_time() - tune_done_time > RTUN_AUTO_SAVE:get() then
need_restore = false
save_all_params()
restore_gcs_pid_mask()
tune_done_time = nil
end
end
return
end
if not need_restore then
-- we are just starting tuning, get current values
get_all_params()
end
-- return immediately if pilot has provided input recently
if get_time() - last_pilot_input < PILOT_INPUT_DELAY then
return
end
-- check filters have been set for this axis
if not filters_done[axis] then
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: starting %s tune", axis))
setup_filters(axis)
end
-- check GCS_PID_MASK has been set for this axis
if not gcs_pid_mask_done[axis] then
setup_gcs_pid_mask(axis)
end
-- get parameter currently being tuned
local srate = get_slew_rate(axis)
local pname = axis .. "_" .. stage
local param = params[pname]
if stage == "FF" then
-- feedforward tuning
local ff_done
if axis == "ATC_STR_RAT" then
ff_done = update_steering_ff(pname)
elseif axis == "ATC_SPEED" then
ff_done = update_speed_ff(pname)
elseif axis == "PSC_VEL" then
-- position controller feed-forward is not tuned
ff_done = true
else
gcs:send_text(MAV_SEVERITY.CRITICAL, string.format("RTun: unsupported FF tuning %s", pname))
ff_done = true
end
if ff_done then
gcs:send_text(MAV_SEVERITY.NOTICE, string.format("RTun: %s tuning done", pname))
advance_stage(axis)
end
else
local oscillating = srate > RTUN_OSC_SMAX:get()
local limited = reached_limit(pname, param:get())
if limited or oscillating then
local reduction = (100.0-RTUN_PD_GAINMARG:get())*0.01
if not oscillating then
reduction = 1.0
end
local new_gain = param:get() * reduction
local limit = gain_limit(pname)
if limit > 0.0 and new_gain > limit then
gcs:send_text(MAV_SEVERITY.NOTICE, string.format("RTun: %s passed limit (>%.3f)", pname, limit))
new_gain = limit
end
local old_gain = param_saved[pname]
if new_gain < old_gain and string.sub(pname,-2) == '_D' then
-- we are lowering a D gain from the original gain. Also lower the P gain by the same amount
-- so that we don't trigger P oscillation. We don't drop P by more than a factor of 2
local ratio = math.max(new_gain / old_gain, 0.5)
local P_name = string.gsub(pname, "_D", "_P")
local old_P = params[P_name]:get()
local new_P = old_P * ratio
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: adjusting %s %.3f -> %.3f", P_name, old_P, new_P))
adjust_gain(P_name, new_P)
write_log(P_name)
end
-- slew gain change over 2 seconds to ease impact on controller
setup_slew_gain(pname, new_gain)
gcs:send_text(MAV_SEVERITY.NOTICE, string.format("RTun: %s tuning done %.3f", pname, new_gain))
advance_stage(axis)
else
local new_gain = param:get()*get_gain_mul()
if new_gain <= 0.0001 then
new_gain = 0.001
end
adjust_gain(pname, new_gain)
write_log(pname)
if get_time() - last_gain_report > 3 then
last_gain_report = get_time()
gcs:send_text(MAV_SEVERITY.INFO, string.format("RTun: %s %.3f sr:%.2f", pname, new_gain, srate))
end
end
end
end
-- wrapper around update(). This calls update() at 10Hz,
-- and if update faults then an error is displayed, but the script is not
-- stopped
function protected_wrapper()
local success, err = pcall(update)
if not success then
gcs:send_text(MAV_SEVERITY.CRITICAL, "RTun: Internal Error: " .. err)
-- when we fault we run the update function again after 1s, slowing it
-- down a bit so we don't flood the console with errors
--return protected_wrapper, 1000
return
end
return protected_wrapper, 1000/UPDATE_RATE_HZ
end
-- start running update loop
return protected_wrapper()