Files
ardupilot/libraries/AP_Scripting/modules/pid.lua
2025-10-07 11:48:50 +11:00

212 lines
6.4 KiB
Lua

--[[
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.
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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
PIDcontroller
A simple "PID" controller for airspeed. Copied from Andrew Tridgell's original
work on the ArduPilot Aerobatics Lua scripts.
Usage:
1. drop it in the scripts/modules directory
2. include in your own script using
local speedpi = requre("speedpid.lua")
3. create an instance - you may need to tune the gains
local speed_controller = speedpid.speed_controller(0.1, 0.1, 2.5, airspeed_min, airspeed_max)
4. call it's update() from your update() with the current airspeed and airspeed error
local airspeed_new = speed_controller.update(vehicle_airspeed, desired_airspeed - vehicle_airspeed)
5. Set the vehicle airspeed based on the airspeed_new value returned from speedpi
--]]
local PIDcontroller = {}
PIDcontroller.SCRIPT_VERSION = "4.7.0-001"
PIDcontroller.SCRIPT_NAME = "PID Controller"
PIDcontroller.SCRIPT_NAME_SHORT = "PID"
PIDcontroller.MAV_SEVERITY = {EMERGENCY=0, ALERT=1, CRITICAL=2, ERROR=3, WARNING=4, NOTICE=5, INFO=6, DEBUG=7}
-- constrain a value between limits
function PIDcontroller.constrain(v, vmin, vmax)
if v < vmin then
v = vmin
end
if v > vmax then
v = vmax
end
return v
end
function PIDcontroller.is_zero(x)
return math.abs(x) < 1.1920928955078125e-7
end
function PIDcontroller.PID_controller(kP,kI,kD,iMax,min,max)
-- the new instance. You can put public variables inside this self
-- declaration if you want to
local self = {}
-- private fields as locals
local _kP = kP or 0.0
local _kI = kI or 0.0
local _kD = kD or 0.0
local _iMax = iMax
local _min = min
local _max = max
local _last_t_us = nil
local _error = 0.0
local _derivative = 0.0
local _reset_filter = true
local _filt_E_hz = 0.01
local _filt_D_hz = 0.005
local _D = 0
local _I = 0
local _P = 0
local _total = 0
local _counter = 0
local _target = 0
local _current = 0
function self.calc_lowpass_alpha_dt(dt, cutoff_freq)
if (dt <= 0.0 or cutoff_freq <= 0.0) then
--INTERNAL_ERROR(AP_InternalError::error_t::invalid_arg_or_result);
return 1.0
end
if (PIDcontroller.is_zero(cutoff_freq)) then
return 1.0
end
if (PIDcontroller.is_zero(dt)) then
return 0.0
end
local rc = 1.0 / (math.pi * 2.0 * cutoff_freq)
return dt / (dt + rc);
end
function self.get_filt_E_alpha(dt)
return self.calc_lowpass_alpha_dt(dt, _filt_E_hz);
end
function self.get_filt_D_alpha(dt)
return self.calc_lowpass_alpha_dt(dt, _filt_D_hz);
end
-- update the controller.
function self.update(target, current)
local now_us = micros()
if not _last_t_us then
_last_t_us = now_us
end
local dt = (now_us - _last_t_us):tofloat() * 0.000001
_last_t_us = now_us
local err = target - current
_counter = _counter + 1
-- reset input filter to value received
if (_reset_filter) then
_reset_filter = false
_error = _target - current
_derivative = 0.0
else
local error_last = _error;
_error = _error + self.get_filt_E_alpha(dt) * ((_target - current) - _error);
-- calculate and filter derivative
if (dt >= 0) then
local derivative = (_error - error_last) / dt;
_derivative = _derivative + self.get_filt_D_alpha(dt) * (derivative - _derivative);
end
end
local D = _derivative * _kD
_D = D
local P = _kP * err
if ((_total < _max and _total > _min) or
(_total >= _max and err < 0) or
(_total <= _min and err > 0)) then
_I = _I + _kI * err * dt
end
if _iMax then
_I = PIDcontroller.constrain(_I, -_iMax, iMax)
end
local I = _I
local ret = target + P + I + D
_target = target
_current = current
_P = P
ret = PIDcontroller.constrain(ret, _min, _max)
_total = ret
return ret
end
-- reset integrator to an initial value
function self.reset(integrator)
_I = integrator
_reset_filter = true
end
function self.set_D(D)
_D = D
_D_error = 0
end
function self.set_I(I)
_kI = I
end
function self.set_P(P)
_kP = P
end
function self.set_Imax(Imax)
_iMax = Imax
end
-- log the controller internals
function self.log(name, add_total)
-- allow for an external addition to total
-- Targ = Current + error ( target airspeed )
-- Curr = Current airspeed input to the controller
-- P = calculated Proportional component
-- I = calculated Integral component
-- Total = calculated new Airspeed
-- Add - passed in as 0
logger:write(name,'Targ,Curr,P,I,D,Total,Add','fffffff',_target,_current,_P,_I,_D,_total,add_total)
end
-- return the instance
return self
end
function PIDcontroller.speed_controller(kP_param, kI_param, kD_param, iMax, sMin, sMax)
local self = {}
local speedpid = PIDcontroller.PID_controller(kP_param, kI_param, kD_param, iMax, sMin, sMax)
function self.update(spd_current, spd_error)
local adjustment = speedpid.update(spd_current + spd_error, spd_current)
speedpid.log("ZSPI", 0) -- Z = scripted, S = speed, PI = PI controller
return adjustment
end
function self.reset()
speedpid.reset(0)
end
return self
end
gcs:send_text(PIDcontroller.MAV_SEVERITY.INFO, string.format("%s %s module loaded", PIDcontroller.SCRIPT_NAME, PIDcontroller.SCRIPT_VERSION) )
return PIDcontroller