├── .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 |
--------------------------------------------------------------------------------