├── .gitignore ├── LICENSE ├── PID_Controller ├── PID.cs ├── PID_Controller.csproj ├── PID_Controller.sln └── Properties │ └── AssemblyInfo.cs ├── README.md └── assets ├── LaTeX_breadcrumbs.txt ├── Simulink.PNG ├── matlab Outputs.PNG ├── render 1.svg ├── render 10.svg ├── render 2.svg ├── render 3.svg ├── render 4.svg ├── render 5.svg ├── render 6.svg ├── render 7.svg ├── render 8.svg ├── render 9.svg └── render.svg /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /PID_Controller/.vs/PID_Controller/v14 6 | /PID_Controller/obj/Debug 7 | /PID_Controller/bin/Debug 8 | /.vs 9 | /PID_Controller/obj/Release 10 | /PID_Controller/.vs/PID_Controller/v16 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tommallama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PID_Controller/PID.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PID_Controller 4 | { 5 | public class PID 6 | { 7 | private double Ts; // Sample period in seconds 8 | private double K; // Rollup parameter 9 | private double b0, b1, b2; // Rollup parameters 10 | private double a0, a1, a2; // Rollup parameters 11 | private double y0 = 0; // Current output 12 | private double y1 = 0; // Output one iteration old 13 | private double y2 = 0; // Output two iterations old 14 | private double e0 = 0; // Current error 15 | private double e1 = 0; // Error one iteration old 16 | private double e2 = 0; // Error two iterations old 17 | 18 | /// 19 | /// PID Constructor 20 | /// 21 | /// Proportional Gain 22 | /// Integral Gain 23 | /// Derivative Gain 24 | /// Derivative Filter Coefficient 25 | /// Controller Upper Output Limit 26 | /// Controller Lower Output Limit 27 | public PID(double Kp, double Ki, double Kd, double N, double OutputUpperLimit, double OutputLowerLimit) 28 | { 29 | this.Kp = Kp; 30 | this.Ki = Ki; 31 | this.Kd = Kd; 32 | this.N = N; 33 | this.OutputUpperLimit = OutputUpperLimit; 34 | this.OutputLowerLimit = OutputLowerLimit; 35 | } 36 | 37 | /// 38 | /// PID iterator, call this function every sample period to get the current controller output. 39 | /// setpoint and processValue should use the same units. 40 | /// 41 | /// Current Desired Setpoint 42 | /// Current Process Value 43 | /// Timespan Since Last Iteration, Use Default Sample Period for First Call 44 | /// Current Controller Output 45 | public double PID_iterate(double setPoint, double processValue, TimeSpan ts) 46 | { 47 | // Ensure the timespan is not too small or zero. 48 | Ts = (ts.TotalSeconds >= TsMin) ? ts.TotalSeconds : TsMin; 49 | 50 | // Calculate rollup parameters 51 | K = 2 / Ts; 52 | b0 = Math.Pow(K, 2) * Kp + K * Ki + Ki * N + K * Kp * N + Math.Pow(K, 2) * Kd * N; 53 | b1 = 2 * Ki * N - 2 * Math.Pow(K, 2) * Kp - 2 * Math.Pow(K, 2) * Kd * N; 54 | b2 = Math.Pow(K, 2) * Kp - K * Ki + Ki * N - K * Kp * N + Math.Pow(K, 2) * Kd * N; 55 | a0 = Math.Pow(K, 2) + N * K; 56 | a1 = -2 * Math.Pow(K, 2); 57 | a2 = Math.Pow(K, 2) - K * N; 58 | 59 | // Age errors and output history 60 | e2 = e1; // Age errors one iteration 61 | e1 = e0; // Age errors one iteration 62 | e0 = setPoint - processValue; // Compute new error 63 | y2 = y1; // Age outputs one iteration 64 | y1 = y0; // Age outputs one iteration 65 | y0 = -a1 / a0 * y1 - a2 / a0 * y2 + b0 / a0 * e0 + b1 / a0 * e1 + b2 / a0 * e2; // Calculate current output 66 | 67 | // Clamp output if needed 68 | if (y0 > OutputUpperLimit) 69 | { 70 | y0 = OutputUpperLimit; 71 | } 72 | else if (y0 < OutputLowerLimit) 73 | { 74 | y0 = OutputLowerLimit; 75 | } 76 | 77 | return y0; 78 | } 79 | 80 | /// 81 | /// Reset controller history effectively resetting the controller. 82 | /// 83 | public void ResetController() 84 | { 85 | e2 = 0; 86 | e1 = 0; 87 | e0 = 0; 88 | y2 = 0; 89 | y1 = 0; 90 | y0 = 0; 91 | } 92 | 93 | /// 94 | /// Proportional Gain, consider resetting controller if this parameter is drastically changed. 95 | /// 96 | public double Kp { get; set; } 97 | 98 | /// 99 | /// Integral Gain, consider resetting controller if this parameter is drastically changed. 100 | /// 101 | public double Ki { get; set; } 102 | 103 | /// 104 | /// Derivative Gain, consider resetting controller if this parameter is drastically changed. 105 | /// 106 | public double Kd { get; set; } 107 | 108 | /// 109 | /// Derivative filter coefficient. 110 | /// A smaller N for more filtering. 111 | /// A larger N for less filtering. 112 | /// Consider resetting controller if this parameter is drastically changed. 113 | /// 114 | public double N { get; set; } 115 | 116 | /// 117 | /// Minimum allowed sample period to avoid dividing by zero! 118 | /// The Ts value can be mistakenly set to too low of a value or zero on the first iteration. 119 | /// TsMin by default is set to 1 millisecond. 120 | /// 121 | public double TsMin { get; set; } = 0.001; 122 | 123 | /// 124 | /// Upper output limit of the controller. 125 | /// This should obviously be a numerically greater value than the lower output limit. 126 | /// 127 | public double OutputUpperLimit { get; set; } 128 | 129 | /// 130 | /// Lower output limit of the controller 131 | /// This should obviously be a numerically lesser value than the upper output limit. 132 | /// 133 | public double OutputLowerLimit { get; set; } 134 | } 135 | } -------------------------------------------------------------------------------- /PID_Controller/PID_Controller.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FE4A51D7-1612-4684-BA60-C785E129BC09} 8 | Library 9 | Properties 10 | PID_Controller 11 | PID_Controller 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /PID_Controller/PID_Controller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PID_Controller", "PID_Controller.csproj", "{FE4A51D7-1612-4684-BA60-C785E129BC09}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FE4A51D7-1612-4684-BA60-C785E129BC09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FE4A51D7-1612-4684-BA60-C785E129BC09}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FE4A51D7-1612-4684-BA60-C785E129BC09}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FE4A51D7-1612-4684-BA60-C785E129BC09}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /PID_Controller/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PID_Controller")] 9 | [assembly: AssemblyDescription("A C# PID controller implementation")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Tommallama")] 12 | [assembly: AssemblyProduct("PID_Controller")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fe4a51d7-1612-4684-ba60-c785e129bc09")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSharp-PID 2 | A Proportional Integral Derivative (PID) controller, written in C#. 3 | Use this project as a guide for implementing a PID controller. 4 | 5 | ## Limitation 6 | Implementing anti-windup in the final discrete form would prove quite the effort. 7 | Consider another form if you need an aggressive integrator and/or if windup will be an issue. 8 | 9 | ## Constructor 10 | ```cs 11 | public PID(double Kp, double Ki, double Kd, double N, double OutputUpperLimit, double OutputLowerLimit) 12 | ``` 13 | * ```Kp``` Proportional Gain 14 | * ```Ki``` Integral Gain 15 | * ```Kd``` Derivative Gain 16 | * ```N``` Derivative FIlter Coefficient 17 | * ```OutputUpperLimit``` Controller Upper Output Limit 18 | * ```OutputLowerLimit``` Controller Lower Output Limit 19 | 20 | ## Methods 21 | ```cs 22 | public double PID_iterate(double setPoint, double processValue, TimeSpan ts) 23 | ``` 24 | The **PID_iterate** method is called once a sample period to run the controller. 25 | * **setPoint** is the desired (target) setpoint for the system to reach. 26 | * **processValue** is the current process value which is being controlled. 27 | * **ts** is the TimeSpan since the last time **PID_iterate** was called. 28 | * The returned value is the controller output (input to what is being controlled). 29 | 30 | ```cs 31 | public void ResetController() 32 | ``` 33 | **ResetController** resets the controller history effectively resetting the controller. 34 | * This should be called when the system is idle if gains have been drastically modified. 35 | 36 | ## Properties 37 | | Property | Type | Access | Description | 38 | |:-------------------:|:-----------:|:-------:|---------------------------------------------| 39 | | Kp | ```double``` | get/set | The proportional gain | 40 | | Ki | ```double``` | get/set | The integral gain | 41 | | Kd | ```double``` | get/set | The derivative gain | 42 | | N | ```double``` | get/set | The derivative filter coefficient | 43 | | TsMin | ```double``` | get/set | The minimum sample time allowed | 44 | | OutputUpperLimit | ```double``` | get/set | The maximum value the controller return | 45 | | OutputLowerLimit | ```double``` | get/set | The minimum value the controller return | 46 | 47 | ### Notes on Properties 48 | * The **N** property is the filter coefficient on the derivative terms low pass filter. A higher value of **N** equates to less filtering and a lower value of **N** equates to more filtering. See the **Controller Derivation** section below for technical details. 49 | * The **TsMin** property is by default **1 millisecond**. **TsMin** should be left alone so long the intended sample period is greater than 1 millisecond. This property mostly exists to keep the controller from being passed a zero timespan and causing a division by zero. 50 | 51 | ## Controller Derivation 52 | This class implements a Proportional-Integral-Derivative (PID) controller with a fixed sample period. 53 | This readme assumes the reader has some understanding of a PID controller. Here I offer a bit of the logic used to derive the algorithms utilized. 54 | 55 | We begin with the parallel form of a PID controller: 56 | 57 |

58 | 59 | Hit this up with some Laplace transform magic: 60 | 61 |

62 | 63 | While this form is great in the ideal realm, taking the derivative of an error will prove a hot mess and therefore it is much more common to modify the derivative portion with a low pass filter thusly: 64 | 65 |

66 | 67 | Where N is the filter coefficient. 68 | 69 | Now things get a bit interesting with some bilinear transformation. We begin by making the following substitution: 70 | 71 |

72 |

73 | 74 | This is where things get ugly… Making the substitution and rearranging things we now have this: 75 | 76 |

77 | Before proceeding, this hot mess has to be made causal by having the numerator and denominator divided by the highest order of z. Leaving us with the slightly modified: 78 | 79 |

80 | 81 | For sanity's sake we make some substitutions: 82 | 83 |

84 | 85 | Now we rearrange some terms: 86 | 87 |

88 | 89 | Then we take the inverse z-transform: 90 | 91 |

92 | 93 | Moving right along to solve for y[n]: 94 | 95 |

96 | 97 | This equation now defines how we can calculate the current output, *__y[n]__*, based on previous outputs, *__y[n-k]__*, the current error, *__e[n]__*, and previous errors, *__e[n-k]__*. 98 | -------------------------------------------------------------------------------- /assets/LaTeX_breadcrumbs.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tommallama/CSharp-PID/6a32ee097d2c9e87413fff342988a30c22db19f1/assets/LaTeX_breadcrumbs.txt -------------------------------------------------------------------------------- /assets/Simulink.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tommallama/CSharp-PID/6a32ee097d2c9e87413fff342988a30c22db19f1/assets/Simulink.PNG -------------------------------------------------------------------------------- /assets/matlab Outputs.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tommallama/CSharp-PID/6a32ee097d2c9e87413fff342988a30c22db19f1/assets/matlab Outputs.PNG -------------------------------------------------------------------------------- /assets/render 1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render 2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render 3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render 4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render 8.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render 9.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/render.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------