1 /***************************************************************************** |
|
2 * Copyright (C) 2015 |
|
3 * |
|
4 * Michiel Broek <mbroek at mbse dot eu> |
|
5 * |
|
6 * This file is part of the mbsePi-apps |
|
7 * |
|
8 * This is free software; you can redistribute it and/or modify it |
|
9 * under the terms of the GNU General Public License as published by the |
|
10 * Free Software Foundation; either version 2, or (at your option) any |
|
11 * later version. |
|
12 * |
|
13 * mbsePi-apps is distributed in the hope that it will be useful, but |
|
14 * WITHOUT ANY WARRANTY; without even the implied warranty of |
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
16 * General Public License for more details. |
|
17 * |
|
18 * You should have received a copy of the GNU General Public License |
|
19 * along with ThermFerm; see the file COPYING. If not, write to the Free |
|
20 * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
|
21 * |
|
22 * Based on the Arduino PID Library 1.1.1 by Brett Beauregard <br3ttb@gmail.com> |
|
23 * This Library is licensed under a GPLv3 License |
|
24 *****************************************************************************/ |
|
25 |
|
26 #include "brewco.h" |
|
27 #include "pid.h" |
|
28 #include "util.h" |
|
29 |
|
30 |
|
31 /* |
|
32 * The parameters specified here are those for for which we can't set up |
|
33 * reliable defaults, so we need to have the user set them. |
|
34 */ |
|
35 void PID_init(pid_var *pid, double *Input, double *Output, double *Setpoint, double Kp, double Ki, double Kd, int ControllerDirection) |
|
36 { |
|
37 pid->myOutput = Output; |
|
38 pid->myInput = Input; |
|
39 pid->mySetpoint = Setpoint; |
|
40 pid->inAuto = FALSE; |
|
41 PID_setOutputLimits(pid, 0, 255); |
|
42 pid->SampleTime = 100; |
|
43 PID_setDirection(pid, ControllerDirection); |
|
44 PID_setTunings(pid, Kp, Ki, Kd); |
|
45 pid->lastTime = millis() - pid->SampleTime; |
|
46 } |
|
47 |
|
48 |
|
49 |
|
50 /* |
|
51 * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) |
|
52 * when the transition from manual to auto occurs, the controller is |
|
53 * automatically initialized |
|
54 */ |
|
55 void PID_setMode(pid_var *pid, int Mode) |
|
56 { |
|
57 int newAuto = (Mode == P_AUTOMATIC); |
|
58 |
|
59 if (newAuto != pid->inAuto) { |
|
60 /* |
|
61 * we just went from manual to auto |
|
62 */ |
|
63 pid->ITerm = *pid->myOutput; |
|
64 pid->lastInput = *pid->myInput; |
|
65 if (pid->ITerm > pid->outMax) |
|
66 pid->ITerm = pid->outMax; |
|
67 else if (pid->ITerm < pid->outMin) |
|
68 pid->ITerm = pid->outMin; |
|
69 /* |
|
70 * If turned to manual, turn output off. |
|
71 */ |
|
72 if (Mode == P_MANUAL) |
|
73 *pid->myOutput = pid->outMin; |
|
74 } |
|
75 pid->inAuto = newAuto; |
|
76 } |
|
77 |
|
78 |
|
79 |
|
80 /* |
|
81 * This function will be used far more often than SetInputLimits. while |
|
82 * the input to the controller will generally be in the 0-1023 range (which is |
|
83 * the default already,) the output will be a little different. maybe they'll |
|
84 * be doing a time window and will need 0-8000 or something. or maybe they'll |
|
85 * want to clamp it from 0-125. who knows. at any rate, that can all be done |
|
86 * here. |
|
87 */ |
|
88 void PID_setOutputLimits(pid_var *pid, double Min, double Max) |
|
89 { |
|
90 if (Min >= Max) |
|
91 return; |
|
92 |
|
93 pid->outMin = Min; |
|
94 pid->outMax = Max; |
|
95 |
|
96 if (pid->inAuto == P_AUTOMATIC) { |
|
97 if (*pid->myOutput > pid->outMax) |
|
98 *pid->myOutput = pid->outMax; |
|
99 else if (*pid->myOutput < pid->outMin) |
|
100 *pid->myOutput = pid->outMin; |
|
101 |
|
102 if (pid->ITerm > pid->outMax) |
|
103 pid->ITerm = pid->outMax; |
|
104 else if (pid->ITerm < pid->outMin) |
|
105 pid->ITerm = pid->outMin; |
|
106 } |
|
107 } |
|
108 |
|
109 |
|
110 |
|
111 /* |
|
112 * This function allows the controller's dynamic performance to be adjusted. |
|
113 * it's called automatically from the constructor, but tunings can also |
|
114 * be adjusted on the fly during normal operation. |
|
115 */ |
|
116 void PID_setTunings(pid_var *pid, double Kp, double Ki, double Kd) |
|
117 { |
|
118 if (Kp < 0 || Ki < 0 || Kd < 0) |
|
119 return; |
|
120 pid->dispKp = Kp; |
|
121 pid->dispKi = Ki; |
|
122 pid->dispKd = Kd; |
|
123 |
|
124 double SampleTimeInSec = ((double)pid->SampleTime) / 1000; |
|
125 pid->Kp = Kp; |
|
126 pid->Ki = Ki * SampleTimeInSec; |
|
127 pid->Kd = Kd / SampleTimeInSec; |
|
128 |
|
129 if (pid->Direction == P_REVERSE) { |
|
130 pid->Kp = (0 - pid->Kp); |
|
131 pid->Ki = (0 - pid->Ki); |
|
132 pid->Kd = (0 - pid->Kd); |
|
133 } |
|
134 } |
|
135 |
|
136 |
|
137 |
|
138 /* |
|
139 * The PID will either be connected to a DIRECT acting process (+Output leads |
|
140 * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to |
|
141 * know which one, because otherwise we may increase the output when we should |
|
142 * be decreasing. This is called from PID_init(). |
|
143 */ |
|
144 void PID_setDirection(pid_var *pid, int Direction) |
|
145 { |
|
146 if (pid->inAuto && Direction != pid->Direction) { |
|
147 pid->Kp = (0 - pid->Kp); |
|
148 pid->Ki = (0 - pid->Ki); |
|
149 pid->Kd = (0 - pid->Kd); |
|
150 } |
|
151 pid->Direction = Direction; |
|
152 } |
|
153 |
|
154 |
|
155 |
|
156 /* |
|
157 * sets the period, in Milliseconds, at which the calculation is performed. |
|
158 */ |
|
159 void PID_setSampleTime(pid_var *pid, int NewSampleTime) |
|
160 { |
|
161 if (NewSampleTime > 0) { |
|
162 double ratio = (double)NewSampleTime / (double)pid->SampleTime; |
|
163 |
|
164 pid->Ki *= ratio; |
|
165 pid->Kd /= ratio; |
|
166 pid->SampleTime = NewSampleTime; |
|
167 } |
|
168 } |
|
169 |
|
170 |
|
171 |
|
172 /* |
|
173 * Just because you set the Kp=-1 doesn't mean it actually happened. these |
|
174 * functions query the internal state of the PID. they're here for display |
|
175 * purposes. this are the functions the PID Front-end uses for example |
|
176 */ |
|
177 double PID_getKp(pid_var *pid) |
|
178 { |
|
179 return pid->dispKp; |
|
180 } |
|
181 |
|
182 |
|
183 |
|
184 double PID_getKi(pid_var *pid) |
|
185 { |
|
186 return pid->dispKi; |
|
187 } |
|
188 |
|
189 |
|
190 |
|
191 double PID_getKd(pid_var *pid) |
|
192 { |
|
193 return pid->dispKd; |
|
194 } |
|
195 |
|
196 |
|
197 |
|
198 int PID_getMode(pid_var *pid) |
|
199 { |
|
200 return pid->inAuto ? P_AUTOMATIC : P_MANUAL; |
|
201 } |
|
202 |
|
203 |
|
204 |
|
205 int PID_getDirection(pid_var *pid) |
|
206 { |
|
207 return pid->Direction; |
|
208 } |
|
209 |
|
210 |
|
211 |
|
212 int PID_getSampleTime(pid_var *pid) |
|
213 { |
|
214 return pid->SampleTime; |
|
215 } |
|
216 |
|
217 |
|
218 |
|
219 /* |
|
220 * This, as they say, is where the magic happens. this function should be called |
|
221 * every time "void loop()" executes. the function will decide for itself whether a new |
|
222 * pid Output needs to be computed. returns true when the output is computed, |
|
223 * false when nothing has been done. |
|
224 */ |
|
225 int PID_compute(pid_var *pid) |
|
226 { |
|
227 if (! pid->inAuto) |
|
228 return FALSE; |
|
229 |
|
230 long now = millis(); |
|
231 long timeChange = (now - pid->lastTime); |
|
232 |
|
233 if (timeChange >= pid->SampleTime) { |
|
234 /* |
|
235 * Compute all the working error variables |
|
236 */ |
|
237 double input = *pid->myInput; |
|
238 double error = *pid->mySetpoint - input; |
|
239 pid->ITerm += (pid->Ki * error); |
|
240 if (pid->ITerm > pid->outMax) |
|
241 pid->ITerm = pid->outMax; |
|
242 else if (pid->ITerm < pid->outMin) |
|
243 pid->ITerm = pid->outMin; |
|
244 double dInput = input - pid->lastInput; |
|
245 |
|
246 /* |
|
247 * Compute PID output |
|
248 */ |
|
249 double output = pid->Kp * error + pid->ITerm - pid->Kd * dInput; |
|
250 if (output > pid->outMax) |
|
251 output = pid->outMax; |
|
252 else if (output < pid->outMin) |
|
253 output = pid->outMin; |
|
254 *pid->myOutput = output; |
|
255 |
|
256 /* |
|
257 * Remember some variables for the next time |
|
258 */ |
|
259 pid->lastInput = input; |
|
260 pid->lastTime = now; |
|
261 return TRUE; |
|
262 } |
|
263 |
|
264 return FALSE; |
|
265 } |
|
266 |
|
267 |
|