2022-04-30 06:26:24 -03:00
|
|
|
--[[
|
|
|
|
fast tuning of VTOL gains for multirotors and quadplanes
|
|
|
|
|
|
|
|
This should be used in QLOITER mode for quadplanes and LOITER mode
|
|
|
|
for copters, although it will work in other VTOL modes
|
|
|
|
|
|
|
|
--]]
|
2023-02-13 22:44:36 -04:00
|
|
|
|
2024-05-03 15:22:25 -03:00
|
|
|
---@diagnostic disable: param-type-mismatch
|
|
|
|
---@diagnostic disable: need-check-nil
|
|
|
|
---@diagnostic disable: missing-parameter
|
2022-04-30 06:26:24 -03:00
|
|
|
|
2022-05-06 02:07:41 -03:00
|
|
|
--[[
|
|
|
|
- TODO:
|
|
|
|
- disable weathervaning while tuning?
|
|
|
|
- possibly lower P XY gains during tuning?
|
|
|
|
- bail out on a large angle error?
|
|
|
|
--]]
|
|
|
|
|
2022-06-04 09:55:09 -03:00
|
|
|
local MAV_SEVERITY_INFO = 6
|
|
|
|
local MAV_SEVERITY_NOTICE = 5
|
|
|
|
local MAV_SEVERITY_EMERGENCY = 0
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
local PARAM_TABLE_KEY = 8
|
|
|
|
local PARAM_TABLE_PREFIX = "QUIK_"
|
|
|
|
|
|
|
|
local atc_prefix = "ATC"
|
|
|
|
|
|
|
|
-- bind a parameter to a variable
|
|
|
|
function bind_param(name)
|
|
|
|
local p = Parameter()
|
|
|
|
assert(p:init(name), string.format('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('could not add param %s', name))
|
|
|
|
return bind_param(PARAM_TABLE_PREFIX .. name)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- setup quicktune specific parameters
|
2023-10-18 19:21:34 -03:00
|
|
|
assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 14), 'could not add param table')
|
2022-04-30 06:26:24 -03:00
|
|
|
|
2023-02-08 20:25:24 -04:00
|
|
|
--[[
|
|
|
|
// @Param: QUIK_ENABLE
|
|
|
|
// @DisplayName: Quicktune enable
|
|
|
|
// @Description: Enable quicktune system
|
|
|
|
// @Values: 0:Disabled,1:Enabled
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-06-04 09:55:09 -03:00
|
|
|
local QUIK_ENABLE = bind_add_param('ENABLE', 1, 0)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_AXES
|
|
|
|
// @DisplayName: Quicktune axes
|
|
|
|
// @Description: axes to tune
|
|
|
|
// @Bitmask: 0:Roll,1:Pitch,2:Yaw
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-04-30 06:26:24 -03:00
|
|
|
local QUIK_AXES = bind_add_param('AXES', 2, 7)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_DOUBLE_TIME
|
|
|
|
// @DisplayName: Quicktune doubling time
|
|
|
|
// @Description: Time to double a tuning parameter. Raise this for a slower tune.
|
|
|
|
// @Range: 5 20
|
|
|
|
// @Units: s
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-04-30 06:26:24 -03:00
|
|
|
local QUIK_DOUBLE_TIME = bind_add_param('DOUBLE_TIME', 3, 10)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_GAIN_MARGIN
|
|
|
|
// @DisplayName: Quicktune gain margin
|
|
|
|
// @Description: Reduction in gain after oscillation detected. Raise this number to get a more conservative tune
|
|
|
|
// @Range: 20 80
|
|
|
|
// @Units: %
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-05-06 02:07:41 -03:00
|
|
|
local QUIK_GAIN_MARGIN = bind_add_param('GAIN_MARGIN', 4, 60)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_OSC_SMAX
|
|
|
|
// @DisplayName: Quicktune oscillation rate threshold
|
|
|
|
// @Description: Threshold for oscillation detection. A lower value will lead to a more conservative tune.
|
|
|
|
// @Range: 1 10
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-04-30 06:26:24 -03:00
|
|
|
local QUIK_OSC_SMAX = bind_add_param('OSC_SMAX', 5, 5)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_YAW_P_MAX
|
|
|
|
// @DisplayName: Quicktune Yaw P max
|
|
|
|
// @Description: Maximum value for yaw P gain
|
|
|
|
// @Range: 0.1 3
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-04-30 06:26:24 -03:00
|
|
|
local QUIK_YAW_P_MAX = bind_add_param('YAW_P_MAX', 6, 0.5)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_YAW_D_MAX
|
|
|
|
// @DisplayName: Quicktune Yaw D max
|
|
|
|
// @Description: Maximum value for yaw D gain
|
|
|
|
// @Range: 0.001 1
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-04-30 06:26:24 -03:00
|
|
|
local QUIK_YAW_D_MAX = bind_add_param('YAW_D_MAX', 7, 0.01)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_RP_PI_RATIO
|
|
|
|
// @DisplayName: Quicktune roll/pitch PI ratio
|
|
|
|
// @Description: Ratio between P and I gains for roll and pitch. Raise this to get a lower I gain
|
|
|
|
// @Range: 0.5 1.0
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-05-06 02:07:41 -03:00
|
|
|
local QUIK_RP_PI_RATIO = bind_add_param('RP_PI_RATIO', 8, 1.0)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_Y_PI_RATIO
|
|
|
|
// @DisplayName: Quicktune Yaw PI ratio
|
|
|
|
// @Description: Ratio between P and I gains for yaw. Raise this to get a lower I gain
|
|
|
|
// @Range: 0.5 20
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-05-06 02:07:41 -03:00
|
|
|
local QUIK_Y_PI_RATIO = bind_add_param('Y_PI_RATIO', 9, 10)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_AUTO_FILTER
|
|
|
|
// @DisplayName: 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
|
|
|
|
--]]
|
2022-05-06 02:07:41 -03:00
|
|
|
local QUIK_AUTO_FILTER = bind_add_param('AUTO_FILTER', 10, 1)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_AUTO_SAVE
|
|
|
|
// @DisplayName: 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
|
|
|
|
--]]
|
2022-06-08 03:36:22 -03:00
|
|
|
local QUIK_AUTO_SAVE = bind_add_param('AUTO_SAVE', 11, 0)
|
2023-02-08 20:25:24 -04:00
|
|
|
|
|
|
|
--[[
|
|
|
|
// @Param: QUIK_RC_FUNC
|
|
|
|
// @DisplayName: Quicktune RC function
|
|
|
|
// @Description: RCn_OPTION number to use to control tuning stop/start/save
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
2022-08-26 01:45:57 -03:00
|
|
|
local QUIK_RC_FUNC = bind_add_param('RC_FUNC', 12, 300)
|
2022-04-30 06:26:24 -03:00
|
|
|
|
2023-07-02 19:06:38 -03:00
|
|
|
--[[
|
|
|
|
// @Param: QUIK_MAX_REDUCE
|
|
|
|
// @DisplayName: Quicktune maximum gain reduction
|
|
|
|
// @Description: This controls how much quicktune is allowed to lower gains from the original gains. If the vehicle already has a reasonable tune and is not oscillating then you can set this to zero to prevent gain reductions. The default of 20% is reasonable for most vehicles. Using a maximum gain reduction lowers the chance of an angle P oscillation happening if quicktune gets a false positive oscillation at a low gain, which can result in very low rate gains and a dangerous angle P oscillation.
|
|
|
|
// @Units: %
|
|
|
|
// @Range: 0 100
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
|
|
|
local QUIK_MAX_REDUCE = bind_add_param('MAX_REDUCE', 13, 20)
|
|
|
|
|
2023-10-18 19:21:34 -03:00
|
|
|
--[[
|
|
|
|
// @Param: QUIK_OPTIONS
|
|
|
|
// @DisplayName: Quicktune options
|
|
|
|
// @Description: Additional options. When the Two Position Switch option is enabled then a high switch position will start the tune, low will disable the tune. you should also set a QUIK_AUTO_SAVE time so that you will be able to save the tune.
|
|
|
|
// @Bitmask: 0:UseTwoPositionSwitch
|
|
|
|
// @User: Standard
|
|
|
|
--]]
|
|
|
|
local QUIK_OPTIONS = bind_add_param('OPTIONS', 14, 0)
|
|
|
|
|
|
|
|
local OPTIONS_TWO_POSITION = (1<<0)
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
local INS_GYRO_FILTER = bind_param("INS_GYRO_FILTER")
|
|
|
|
|
2022-05-03 08:53:37 -03:00
|
|
|
local RCMAP_ROLL = bind_param("RCMAP_ROLL")
|
|
|
|
local RCMAP_PITCH = bind_param("RCMAP_PITCH")
|
|
|
|
local RCMAP_YAW = bind_param("RCMAP_YAW")
|
|
|
|
|
|
|
|
local RCIN_ROLL = rc:get_channel(RCMAP_ROLL:get())
|
|
|
|
local RCIN_PITCH = rc:get_channel(RCMAP_PITCH:get())
|
|
|
|
local RCIN_YAW = rc:get_channel(RCMAP_YAW:get())
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
local UPDATE_RATE_HZ = 40
|
|
|
|
local STAGE_DELAY = 4.0
|
2022-05-03 08:53:37 -03:00
|
|
|
local PILOT_INPUT_DELAY = 4.0
|
2022-04-30 06:26:24 -03:00
|
|
|
|
2023-09-09 23:30:55 -03:00
|
|
|
local YAW_FLTE_MAX = 8.0
|
2022-04-30 06:26:24 -03:00
|
|
|
local FLTD_MUL = 0.5
|
|
|
|
local FLTT_MUL = 0.5
|
|
|
|
|
|
|
|
-- SMAX to set if none set at start
|
|
|
|
local DEFAULT_SMAX = 50.0
|
|
|
|
|
|
|
|
-- work out vehicle type
|
|
|
|
if param:get("Q_A_RAT_RLL_SMAX") then
|
|
|
|
is_quadplane = true
|
|
|
|
atc_prefix = "Q_A"
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, "Quicktune for quadplane loaded")
|
2022-04-30 06:26:24 -03:00
|
|
|
elseif param:get("ATC_RAT_RLL_SMAX") then
|
|
|
|
is_quadplane = false
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, "Quicktune for multicopter loaded")
|
2022-04-30 06:26:24 -03:00
|
|
|
else
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, "Quicktune unknown vehicle")
|
2022-04-30 06:26:24 -03:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2022-06-04 09:55:09 -03:00
|
|
|
-- get time in seconds since boot
|
2022-04-30 06:26:24 -03:00
|
|
|
function get_time()
|
|
|
|
return millis():tofloat() * 0.001
|
|
|
|
end
|
|
|
|
|
|
|
|
local axis_names = { "RLL", "PIT", "YAW" }
|
|
|
|
local param_suffixes = { "FF", "P", "I", "D", "SMAX", "FLTT", "FLTD", "FLTE" }
|
|
|
|
local stages = { "D", "P" }
|
|
|
|
local stage = stages[1]
|
|
|
|
local last_stage_change = get_time()
|
|
|
|
local last_gain_report = get_time()
|
2022-05-03 08:53:37 -03:00
|
|
|
local last_pilot_input = get_time()
|
2022-06-08 03:36:22 -03:00
|
|
|
local tune_done_time = nil
|
2022-04-30 06:26:24 -03:00
|
|
|
local slew_parm = nil
|
|
|
|
local slew_target = 0
|
|
|
|
local slew_delta = 0
|
|
|
|
|
|
|
|
local axes_done = {}
|
2022-06-08 03:48:26 -03:00
|
|
|
local filters_done = {}
|
2022-04-30 06:26:24 -03:00
|
|
|
|
|
|
|
|
|
|
|
-- create params dictionary indexed by name, such as "RLL_P"
|
|
|
|
local params = {}
|
|
|
|
local param_saved = {}
|
|
|
|
local param_changed = {}
|
|
|
|
local need_restore = false
|
|
|
|
|
2023-07-02 19:17:02 -03:00
|
|
|
for _, axis in ipairs(axis_names) do
|
|
|
|
for _, suffix in ipairs(param_suffixes) do
|
2022-04-30 06:26:24 -03:00
|
|
|
local pname = axis .. "_" .. suffix
|
|
|
|
params[pname] = bind_param(atc_prefix .. "_" .. "RAT_" .. pname)
|
|
|
|
param_changed[pname] = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- reset to start
|
|
|
|
function reset_axes_done()
|
2023-07-02 19:17:02 -03:00
|
|
|
for _, axis in ipairs(axis_names) do
|
2022-04-30 06:26:24 -03:00
|
|
|
axes_done[axis] = false
|
2022-06-08 03:48:26 -03:00
|
|
|
filters_done[axis] = false
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
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
|
|
|
|
end
|
|
|
|
|
|
|
|
-- setup a default SMAX if zero
|
|
|
|
function setup_SMAX()
|
2023-07-02 19:17:02 -03:00
|
|
|
for _, axis in ipairs(axis_names) do
|
2022-04-30 06:26:24 -03:00
|
|
|
local smax = axis .. "_SMAX"
|
|
|
|
if params[smax]:get() <= 0 then
|
|
|
|
adjust_gain(smax, DEFAULT_SMAX)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- setup filter frequencies
|
2022-06-08 03:48:26 -03:00
|
|
|
function setup_filters(axis)
|
2022-08-25 03:45:33 -03:00
|
|
|
if QUIK_AUTO_FILTER:get() <= 0 then
|
2024-03-11 22:25:47 -03:00
|
|
|
filters_done[axis] = true
|
2022-08-25 03:45:33 -03:00
|
|
|
return
|
|
|
|
end
|
2022-06-08 03:48:26 -03:00
|
|
|
local fltd = axis .. "_FLTD"
|
|
|
|
local fltt = axis .. "_FLTT"
|
|
|
|
local flte = axis .. "_FLTE"
|
|
|
|
adjust_gain(fltt, INS_GYRO_FILTER:get() * FLTT_MUL)
|
|
|
|
adjust_gain(fltd, INS_GYRO_FILTER:get() * FLTD_MUL)
|
|
|
|
if axis == "YAW" then
|
|
|
|
local FLTE = params[flte]
|
2024-04-02 12:56:22 -03:00
|
|
|
if FLTE:get() < 0.0 or FLTE:get() > YAW_FLTE_MAX then
|
2022-06-08 03:48:26 -03:00
|
|
|
adjust_gain(flte, YAW_FLTE_MAX)
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
end
|
2022-06-08 03:48:26 -03:00
|
|
|
filters_done[axis] = true
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
|
2022-05-03 08:53:37 -03:00
|
|
|
-- check for pilot input to pause tune
|
|
|
|
function have_pilot_input()
|
|
|
|
if (math.abs(RCIN_ROLL:norm_input_dz()) > 0 or
|
|
|
|
math.abs(RCIN_PITCH:norm_input_dz()) > 0 or
|
|
|
|
math.abs(RCIN_YAW:norm_input_dz()) > 0) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
reset_axes_done()
|
|
|
|
get_all_params()
|
|
|
|
|
2022-05-06 02:07:41 -03:00
|
|
|
function axis_enabled(axis)
|
|
|
|
local axes = QUIK_AXES:get()
|
|
|
|
for i = 1, #axis_names do
|
|
|
|
local mask = (1 << (i-1))
|
|
|
|
local axis_name = axis_names[i]
|
|
|
|
if axis == axis_name and (mask & axes) ~= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
-- get the axis name we are working on, or nil for all done
|
|
|
|
function get_current_axis()
|
|
|
|
local axes = QUIK_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 roll_srate, pitch_srate, yaw_srate = AC_AttitudeControl:get_rpy_srate()
|
|
|
|
if axis == "RLL" then
|
|
|
|
return roll_srate
|
|
|
|
end
|
|
|
|
if axis == "PIT" then
|
|
|
|
return pitch_srate
|
|
|
|
end
|
|
|
|
if axis == "YAW" then
|
|
|
|
return yaw_srate
|
|
|
|
end
|
|
|
|
return 0.0
|
|
|
|
end
|
|
|
|
|
|
|
|
-- move to next stage of tune
|
|
|
|
function advance_stage(axis)
|
|
|
|
if stage == "D" then
|
|
|
|
stage = "P"
|
|
|
|
else
|
|
|
|
axes_done[axis] = true
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(5, string.format("Tuning: %s done", axis))
|
2022-04-30 06:26:24 -03:00
|
|
|
stage = "D"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get param name, such as RLL_P, used as index into param dictionaries
|
2023-07-02 19:17:02 -03:00
|
|
|
function get_pname(axis, tstage)
|
|
|
|
return axis .. "_" .. tstage
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
|
2022-05-06 02:07:41 -03:00
|
|
|
-- get axis name from parameter name
|
|
|
|
function param_axis(pname)
|
|
|
|
return string.sub(pname, 1, 3)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get parameter suffix from parameter name
|
|
|
|
function param_suffix(pname)
|
|
|
|
return string.sub(pname, 4)
|
|
|
|
end
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
-- change a gain
|
|
|
|
function adjust_gain(pname, value)
|
|
|
|
local P = params[pname]
|
|
|
|
need_restore = true
|
|
|
|
param_changed[pname] = true
|
|
|
|
P:set(value)
|
|
|
|
if string.sub(pname, -2) == "_P" then
|
|
|
|
-- also change I gain
|
|
|
|
local iname = string.gsub(pname, "_P", "_I")
|
|
|
|
local ffname = string.gsub(pname, "_P", "_FF")
|
|
|
|
local I = params[iname]
|
|
|
|
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,
|
2024-06-20 16:08:12 -03:00
|
|
|
-- usually we want I = FF for a one second time constant for trim
|
2022-04-30 06:26:24 -03:00
|
|
|
return
|
|
|
|
end
|
|
|
|
param_changed[iname] = true
|
2022-05-06 02:07:41 -03:00
|
|
|
|
|
|
|
-- work out ratio of P to I that we want
|
|
|
|
local PI_ratio = QUIK_RP_PI_RATIO:get()
|
|
|
|
if param_axis(pname) == "YAW" then
|
|
|
|
PI_ratio = QUIK_Y_PI_RATIO:get()
|
|
|
|
end
|
|
|
|
if PI_ratio >= 1 then
|
|
|
|
I:set(value/PI_ratio)
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-07-02 19:06:38 -03:00
|
|
|
-- limit a gain change to QUIK_MAX_REDUCE
|
|
|
|
function limit_gain(pname, value)
|
|
|
|
local saved_value = param_saved[pname]
|
|
|
|
local max_reduction = QUIK_MAX_REDUCE:get()
|
|
|
|
if max_reduction >= 0 and max_reduction < 100 and saved_value > 0 then
|
|
|
|
-- check if we exceeded gain reduction
|
|
|
|
local reduction_pct = 100.0 * (saved_value - value) / saved_value
|
|
|
|
if reduction_pct > max_reduction then
|
|
|
|
local new_value = saved_value * (100 - max_reduction) * 0.01
|
|
|
|
gcs:send_text(MAV_SEVERITY_INFO, string.format("limiting %s %.3f -> %.3f", pname, value, new_value))
|
|
|
|
value = new_value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
|
|
|
|
-- change a gain, limiting to QUIK_MAX_REDUCE
|
|
|
|
function adjust_gain_limited(pname, value)
|
|
|
|
adjust_gain(pname, limit_gain(pname, value))
|
|
|
|
end
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
-- return gain multipler for one loop
|
|
|
|
function get_gain_mul()
|
|
|
|
return math.exp(math.log(2.0)/(UPDATE_RATE_HZ*QUIK_DOUBLE_TIME:get()))
|
|
|
|
end
|
|
|
|
|
|
|
|
function setup_slew_gain(pname, gain)
|
|
|
|
slew_parm = pname
|
2023-07-02 19:06:38 -03:00
|
|
|
slew_target = limit_gain(pname, gain)
|
2022-04-30 06:26:24 -03:00
|
|
|
slew_steps = UPDATE_RATE_HZ/2
|
2023-07-02 19:06:38 -03:00
|
|
|
slew_delta = (slew_target - params[pname]:get()) / slew_steps
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
|
|
|
|
function update_slew_gain()
|
|
|
|
if slew_parm ~= nil then
|
|
|
|
local P = params[slew_parm]
|
2022-05-06 02:07:41 -03:00
|
|
|
local axis = param_axis(slew_parm)
|
2022-04-30 06:26:24 -03:00
|
|
|
local ax_stage = string.sub(slew_parm, -1)
|
|
|
|
adjust_gain(slew_parm, P:get()+slew_delta)
|
|
|
|
slew_steps = slew_steps - 1
|
2022-09-12 20:40:54 -03:00
|
|
|
logger.write('QUIK','SRate,Gain,Param', 'ffn', get_slew_rate(axis), P:get(), axis .. ax_stage)
|
2022-04-30 06:26:24 -03:00
|
|
|
if slew_steps == 0 then
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_INFO, string.format("%s %.4f", slew_parm, P:get()))
|
2022-04-30 06:26:24 -03:00
|
|
|
slew_parm = nil
|
2022-06-04 09:55:09 -03:00
|
|
|
if get_current_axis() == nil then
|
2022-06-08 03:36:22 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_NOTICE, string.format("Tuning: DONE"))
|
|
|
|
tune_done_time = get_time()
|
2022-06-04 09:55:09 -03:00
|
|
|
end
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- return gain limits on a parameter, or 0 for no limit
|
|
|
|
function gain_limit(pname)
|
2022-05-06 02:07:41 -03:00
|
|
|
if param_axis(pname) == "YAW" then
|
|
|
|
local suffix = param_suffix(pname)
|
2022-04-30 06:26:24 -03:00
|
|
|
if suffix == "_P" then
|
|
|
|
return QUIK_YAW_P_MAX:get()
|
|
|
|
end
|
|
|
|
if suffix == "_D" then
|
|
|
|
return QUIK_YAW_D_MAX:get()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return 0.0
|
|
|
|
end
|
|
|
|
|
2022-05-03 19:29:48 -03:00
|
|
|
function reached_limit(pname, gain)
|
2022-04-30 06:26:24 -03:00
|
|
|
local limit = gain_limit(pname)
|
|
|
|
if limit > 0.0 and gain >= limit then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- main update function
|
2022-06-04 09:55:09 -03:00
|
|
|
local last_warning = get_time()
|
2022-04-30 06:26:24 -03:00
|
|
|
function update()
|
2023-07-02 19:10:45 -03:00
|
|
|
if QUIK_ENABLE:get() < 1 then
|
|
|
|
return
|
|
|
|
end
|
2022-05-03 08:53:37 -03:00
|
|
|
if have_pilot_input() then
|
|
|
|
last_pilot_input = get_time()
|
|
|
|
end
|
|
|
|
|
2023-02-23 06:15:00 -04:00
|
|
|
local sw_pos = rc:get_aux_cached(QUIK_RC_FUNC:get())
|
|
|
|
if not sw_pos then
|
|
|
|
return
|
|
|
|
end
|
2023-10-18 19:21:34 -03:00
|
|
|
local sw_pos_tune = 1
|
|
|
|
local sw_pos_save = 2
|
|
|
|
if (QUIK_OPTIONS:get() & OPTIONS_TWO_POSITION) ~= 0 then
|
|
|
|
sw_pos_tune = 2
|
|
|
|
sw_pos_save = -1
|
|
|
|
end
|
|
|
|
if sw_pos == sw_pos_tune and (not arming:is_armed() or not vehicle:get_likely_flying()) and get_time() > last_warning + 5 then
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, string.format("Tuning: Must be flying to tune"))
|
|
|
|
last_warning = get_time()
|
|
|
|
return
|
|
|
|
end
|
2022-04-30 06:26:24 -03:00
|
|
|
if sw_pos == 0 or not arming:is_armed() or not vehicle:get_likely_flying() then
|
|
|
|
-- abort, revert parameters
|
|
|
|
if need_restore then
|
|
|
|
need_restore = false
|
|
|
|
restore_all_params()
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, string.format("Tuning: reverted"))
|
2022-06-08 03:36:22 -03:00
|
|
|
tune_done_time = nil
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
reset_axes_done()
|
|
|
|
return
|
|
|
|
end
|
2023-10-18 19:21:34 -03:00
|
|
|
if sw_pos == sw_pos_save then
|
2022-04-30 06:26:24 -03:00
|
|
|
-- save all params
|
|
|
|
if need_restore then
|
|
|
|
need_restore = false
|
|
|
|
save_all_params()
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_NOTICE, string.format("Tuning: saved"))
|
2022-04-30 06:26:24 -03:00
|
|
|
end
|
|
|
|
end
|
2023-10-18 19:21:34 -03:00
|
|
|
if sw_pos ~= sw_pos_tune then
|
2022-04-30 06:26:24 -03:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if get_time() - last_stage_change < STAGE_DELAY then
|
|
|
|
update_slew_gain()
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
axis = get_current_axis()
|
|
|
|
if axis == nil then
|
2022-06-08 03:36:22 -03:00
|
|
|
-- nothing left to do, check autosave time
|
|
|
|
if tune_done_time ~= nil and QUIK_AUTO_SAVE:get() > 0 then
|
|
|
|
if get_time() - tune_done_time > QUIK_AUTO_SAVE:get() then
|
|
|
|
need_restore = false
|
|
|
|
save_all_params()
|
|
|
|
gcs:send_text(MAV_SEVERITY_NOTICE, string.format("Tuning: saved"))
|
|
|
|
tune_done_time = nil
|
|
|
|
end
|
|
|
|
end
|
2022-04-30 06:26:24 -03:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if not need_restore then
|
|
|
|
-- we are just starting tuning, get current values
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_NOTICE, string.format("Tuning: starting tune"))
|
2022-04-30 06:26:24 -03:00
|
|
|
get_all_params()
|
|
|
|
setup_SMAX()
|
|
|
|
end
|
|
|
|
|
2022-05-03 08:53:37 -03:00
|
|
|
if get_time() - last_pilot_input < PILOT_INPUT_DELAY then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2022-06-08 03:48:26 -03:00
|
|
|
if not filters_done[axis] then
|
|
|
|
gcs:send_text(MAV_SEVERITY_INFO, string.format("Starting %s tune", axis))
|
|
|
|
setup_filters(axis)
|
|
|
|
end
|
|
|
|
|
2022-04-30 06:26:24 -03:00
|
|
|
local srate = get_slew_rate(axis)
|
|
|
|
local pname = get_pname(axis, stage)
|
|
|
|
local P = params[pname]
|
2022-05-03 19:29:48 -03:00
|
|
|
local oscillating = srate > QUIK_OSC_SMAX:get()
|
|
|
|
local limited = reached_limit(pname, P:get())
|
|
|
|
if limited or oscillating then
|
2022-04-30 06:26:24 -03:00
|
|
|
local reduction = (100.0-QUIK_GAIN_MARGIN:get())*0.01
|
2022-05-03 19:29:48 -03:00
|
|
|
if not oscillating then
|
|
|
|
reduction = 1.0
|
|
|
|
end
|
2022-04-30 06:26:24 -03:00
|
|
|
local new_gain = P:get() * reduction
|
|
|
|
local limit = gain_limit(pname)
|
|
|
|
if limit > 0.0 and new_gain > limit then
|
|
|
|
new_gain = limit
|
|
|
|
end
|
2022-05-06 02:07:41 -03:00
|
|
|
local old_gain = param_saved[pname]
|
|
|
|
if new_gain < old_gain and string.sub(pname,-2) == '_D' and param_axis(pname) ~= 'YAW' 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
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_INFO, string.format("adjusting %s %.3f -> %.3f", P_name, old_P, new_P))
|
2023-07-02 19:06:38 -03:00
|
|
|
adjust_gain_limited(P_name, new_P)
|
2022-05-06 02:07:41 -03:00
|
|
|
end
|
2022-04-30 06:26:24 -03:00
|
|
|
setup_slew_gain(pname, new_gain)
|
2022-09-12 20:40:54 -03:00
|
|
|
logger.write('QUIK','SRate,Gain,Param', 'ffn', srate, P:get(), axis .. stage)
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(6, string.format("Tuning: %s done", pname))
|
2022-04-30 06:26:24 -03:00
|
|
|
advance_stage(axis)
|
|
|
|
last_stage_change = get_time()
|
|
|
|
else
|
|
|
|
local new_gain = P:get()*get_gain_mul()
|
|
|
|
if new_gain <= 0.0001 then
|
|
|
|
new_gain = 0.001
|
|
|
|
end
|
2023-07-02 19:06:38 -03:00
|
|
|
adjust_gain_limited(pname, new_gain)
|
2022-09-12 20:40:54 -03:00
|
|
|
logger.write('QUIK','SRate,Gain,Param', 'ffn', srate, P:get(), axis .. stage)
|
2022-04-30 06:26:24 -03:00
|
|
|
if get_time() - last_gain_report > 3 then
|
|
|
|
last_gain_report = get_time()
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_INFO, string.format("%s %.4f sr:%.2f", pname, new_gain, srate))
|
2022-04-30 06:26:24 -03:00
|
|
|
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
|
2022-06-04 09:55:09 -03:00
|
|
|
gcs:send_text(MAV_SEVERITY_EMERGENCY, "Internal Error: " .. err)
|
2022-04-30 06:26:24 -03:00
|
|
|
-- 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()
|