diff --git a/README.md b/README.md index cff549c4..31b32696 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | [Log](https://sleitnick.github.io/RbxUtil/api/Log) | `Log = "sleitnick/log@0.1.1"` | Log class for logging to PlayFab | | [Net](https://sleitnick.github.io/RbxUtil/api/Net) | `Net = "sleitnick/net@0.2.0"` | Static networking module | | [Option](https://sleitnick.github.io/RbxUtil/api/Option) | `Option = "sleitnick/option@1.0.5"` | Represent optional values in Lua | -| [PID](https://sleitnick.github.io/RbxUtil/api/PID) | `PID = "sleitnick/pid@1.2.1"` | PID Controller class | +| [PID](https://sleitnick.github.io/RbxUtil/api/PID) | `PID = "sleitnick/pid@2.0.0"` | PID Controller class | | [Quaternion](https://sleitnick.github.io/RbxUtil/api/Quaternion) | `Quaternion = "sleitnick/quaternion@0.2.3"` | Quaternion class | | [Sequent](https://sleitnick.github.io/RbxUtil/api/Sequent) | `Sequent = "sleitnick/sequent@0.1.0"` | Sequent class | | [Ser](https://sleitnick.github.io/RbxUtil/api/Ser) | `Ser = "sleitnick/ser@1.0.5"` | Ser class for serialization and deserialization | diff --git a/modules/pid/index.d.ts b/modules/pid/index.d.ts index 25fa9218..97852802 100644 --- a/modules/pid/index.d.ts +++ b/modules/pid/index.d.ts @@ -14,28 +14,15 @@ declare namespace PID { } interface PID { - /** - * POnE stands for "Proportional on Error". - * - * Set to `true` by default. - * - * - `true`: The PID applies the proportional calculation on the _error_. - * - `false`: The PID applies the proportional calculation on the _measurement_. - * - * Setting this value to `false` may help the PID move smoother and help - * eliminate overshoot. - */ - POnE: boolean; - /** * Calculates the new output based on the setpoint and input. * * @param setpoint The goal for the PID. - * @param input The current input. + * @param processVariable The measured value of the system to compare against the setpoint. * @param deltaTime Delta time. * @returns The updated output. */ - Calculate(setpoint: number, input: number, deltaTime: number): number; + Calculate(setpoint: number, processVariable: number, deltaTime: number): number; /** * Resets the PID. diff --git a/modules/pid/init.lua b/modules/pid/init.lua index 6c041356..0ea668e1 100644 --- a/modules/pid/init.lua +++ b/modules/pid/init.lua @@ -1,7 +1,6 @@ --!native export type PID = { - POnE: boolean, Reset: (self: PID) -> (), Calculate: (self: PID, setpoint: number, input: number, deltaTime: number) -> number, Debug: (self: PID, name: string, parent: Instance?) -> (), @@ -14,42 +13,20 @@ export type PID = { for _proportional, integral, derivative_. PIDs are input feedback loops that try to reach a specific goal by measuring the difference between the input and the desired value, and then returning a new desired input. - + A common example is a car's cruise control, which would give a PID the current speed and the desired speed, and the PID controller would return the desired throttle input to reach the desired speed. - - Original code based upon the [Arduino PID Library](https://github.com/br3ttb/Arduino-PID-Library). ]=] local PID = {} PID.__index = PID ---[=[ - @within PID - @prop POnE boolean - - POnE stands for "Proportional on Error". - - Set to `true` by default. - - - `true`: The PID applies the proportional calculation on the _error_. - - `false`: The PID applies the proportional calculation on the _measurement_. - - Setting this value to `false` may help the PID move smoother and help - eliminate overshoot. - - ```lua - local pid = PID.new(...) - pid.POnE = true|false - ``` -]=] - --[=[ @param min number -- Minimum value the PID can output @param max number -- Maximum value the PID can output - @param kp number -- Proportional coefficient - @param ki number -- Integral coefficient - @param kd number -- Derivative coefficient + @param kp number -- Proportional coefficient (P) + @param ki number -- Integral coefficient (I) + @param kd number -- Derivative coefficient (D) @return PID Constructs a new PID. @@ -60,19 +37,13 @@ PID.__index = PID ]=] function PID.new(min: number, max: number, kp: number, ki: number, kd: number): PID local self = setmetatable({}, PID) - self._min = min self._max = max - self._kp = kp self._ki = ki self._kd = kd - - self._lastInput = 0 - self._outputSum = 0 - - self.POnE = true - + self._lastError = 0 -- Store the last error for derivative calculation + self._integralSum = 0 -- Store the sum Σ of errors for integral calculation return self end @@ -81,13 +52,13 @@ end ]=] function PID:Reset() self._lastInput = 0 - self._outputSum = 0 + self._integralSum = 0 end --[=[ - @param setpoint number -- The desired point to reach - @param input number -- The current inputted value - @param deltaTime number -- Delta time + @param setpoint number -- The desired point to reach + @param processVariable number -- The measured value of the system to compare against the setpoint + @param deltaTime number -- Delta time. This is the time between each PID calculation @return output: number Calculates the new output based on the setpoint and input. For example, @@ -98,36 +69,35 @@ end local cruisePID = PID.new(0, 1, ...) local desiredSpeed = 50 - RunService.Heartbeat:Connect(function() - local throttle = cruisePID:Calculate(desiredSpeed, car.CurrentSpeed) + RunService.Heartbeat:Connect(function(dt) + local throttle = cruisePID:Calculate(desiredSpeed, car.CurrentSpeed, dt) car:SetThrottle(throttle) end) ``` ]=] -function PID:Calculate(setpoint: number, input: number, deltaTime: number) - local ki = self._ki * deltaTime - local kd = self._kd * deltaTime +function PID:Calculate(setpoint: number, processVariable: number, deltaTime: number) + -- Calculate the error e(t) = SP - PV(t) + local err = setpoint - processVariable - local err = setpoint - input - local dInput = input - self._lastInput - self._outputSum += ki * err + -- Proportional term + local pOut = self._kp * err - if not self.POnE then - self._outputSum -= self._kp * dInput - end + -- Integral term + self._integralSum = self._integralSum + err * deltaTime + local iOut = self._ki * self._integralSum - self._outputSum = math.clamp(self._outputSum, self._min, self._max) + -- Derivative term + local derivative = (err - self._lastError) / deltaTime + local dOut = self._kd * derivative - local output = 0 - if self.POnE then - output = self._kp * err - end + -- Combine terms + local output = pOut + iOut + dOut - output += self._outputSum - kd * dInput + -- Clamp output to min/max output = math.clamp(output, self._min, self._max) - self._lastInput = input - + -- Save the current error for the next derivative calculation + self._lastError = err return output end diff --git a/modules/pid/package.json b/modules/pid/package.json index 0959d400..2588aea5 100644 --- a/modules/pid/package.json +++ b/modules/pid/package.json @@ -1,6 +1,6 @@ { "name": "@rbxutil/pid", - "version": "1.2.1", + "version": "2.0.0", "main": "init.lua", "repository": "github:Sleitnick/RbxUtil", "license": "MIT", diff --git a/modules/pid/wally.toml b/modules/pid/wally.toml index 32991217..000a6d6e 100644 --- a/modules/pid/wally.toml +++ b/modules/pid/wally.toml @@ -1,7 +1,7 @@ [package] name = "sleitnick/pid" description = "PID Controller class" -version = "1.2.1" +version = "2.0.0" license = "MIT" authors = ["Stephen Leitnick"] registry = "https://github.com/UpliftGames/wally-index"