16 * General Public License for more details. |
16 * General Public License for more details. |
17 * |
17 * |
18 * You should have received a copy of the GNU General Public License |
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 |
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. |
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 |
21 *****************************************************************************/ |
24 *****************************************************************************/ |
22 |
25 |
23 #include "brewco.h" |
26 #include "brewco.h" |
24 #include "pid.h" |
27 #include "pid.h" |
25 |
28 #include "util.h" |
26 |
29 |
27 void InitPID(pid_var *pid) |
30 |
28 { |
31 /* |
29 pid->Err = pid->ErrLast = pid->iState = 0.0; |
32 * The parameters specified here are those for for which we can't set up |
30 pid->Input = pid->OutP = pid->SetP = 0.0; |
33 * reliable defaults, so we need to have the user set them. |
31 pid->pGain = pid->iGain = pid->dGain = 0.0; |
34 */ |
32 pid->Mode = PID_MODE_NONE; |
35 void PID_init(pid_var *pid, double *Input, double *Output, double *Setpoint, double Kp, double Ki, double Kd, int ControllerDirection) |
33 pid->iMax = 100.0; |
36 { |
34 pid->Times = PID_TIMES; |
37 pid->myOutput = Output; |
35 } |
38 pid->myInput = Input; |
36 |
39 pid->mySetpoint = Setpoint; |
37 |
40 pid->inAuto = FALSE; |
38 |
41 PID_setOutputLimits(pid, 0, 255); |
39 void UpdatePID(pid_var *pid) |
42 pid->SampleTime = 100; |
40 { |
43 PID_setControllerDirection(pid, ControllerDirection); |
41 if (pid->Mode == PID_MODE_AUTO) { |
44 PID_setTunings(pid, Kp, Ki, Kd); |
42 |
45 pid->lastTime = millis() - pid->SampleTime; |
43 double pTerm, dTerm, iTerm; |
46 } |
44 |
47 |
45 pid->Err = pid->SetP - pid->Input; |
48 |
46 |
49 |
47 /* |
50 /* |
48 * Calculate the integral state with appopriate limiting. |
51 * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) |
49 * Use ErrLastLast as iState |
52 * when the transition from manual to auto occurs, the controller is |
50 */ |
53 * automatically initialized |
51 pid->iState += pid->Err; |
54 */ |
52 if (pid->iState > PID_WINDUP_GUARD) |
55 void PID_setMode(pid_var *pid, int Mode) |
53 pid->iState = PID_WINDUP_GUARD; |
56 { |
54 else if (pid->iState < -PID_WINDUP_GUARD) |
57 int newAuto = (Mode == P_AUTOMATIC); |
55 pid->iState = -PID_WINDUP_GUARD; |
58 |
56 |
59 if (newAuto != pid->inAuto) { |
57 pTerm = pid->pGain * pid->Err; |
60 /* |
58 iTerm = pid->iGain * pid->iState; |
61 * we just went from manual to auto |
59 dTerm = pid->dGain * (pid->Err - pid->ErrLast); |
62 */ |
60 |
63 pid->ITerm = *pid->myOutput; |
61 pid->OutP = pTerm + dTerm + iTerm; |
64 pid->lastInput = *pid->myInput; |
62 pid->ErrLast = pid->Err; |
65 if (pid->ITerm > pid->outMax) |
63 |
66 pid->ITerm = pid->outMax; |
64 } else if (pid->Mode == PID_MODE_BOO) { |
67 else if (pid->ITerm < pid->outMin) |
65 /* |
68 pid->ITerm = pid->outMin; |
66 * Mode Bang On Off |
69 } |
67 */ |
70 pid->inAuto = newAuto; |
68 pid->ErrLast = pid->Err; |
71 } |
69 pid->Err = pid->SetP - pid->Input; |
72 |
70 |
73 |
71 if (pid->OutP && (pid->Err <= 0.0)) |
74 |
72 pid->OutP = 0.0; |
75 /* |
73 else if ((pid->OutP == 0.0) && (pid->Err > 0.0)) |
76 * This function will be used far more often than SetInputLimits. while |
74 pid->OutP = pid->iMax; |
77 * the input to the controller will generally be in the 0-1023 range (which is |
75 |
78 * the default already,) the output will be a little different. maybe they'll |
76 pid->iState = 0.0; |
79 * be doing a time window and will need 0-8000 or something. or maybe they'll |
77 |
80 * want to clamp it from 0-125. who knows. at any rate, that can all be done |
78 } else { |
81 * here. |
79 /* |
82 */ |
80 * While in manual mode, stay ready for bumpless switch to |
83 void PID_setOutputLimits(pid_var *pid, double Min, double Max) |
81 * auto. |
84 { |
82 */ |
85 if (Min >= Max) |
83 pid->ErrLast = pid->Err = 0.0; |
86 return; |
84 pid->OutP = pid->iState = 0.0; |
87 |
85 } |
88 pid->outMin = Min; |
86 |
89 pid->outMax = Max; |
87 if (pid->OutP > pid->iMax) |
90 |
88 pid->OutP = pid->iMax; |
91 if (pid->inAuto == P_AUTOMATIC) { |
89 if (pid->OutP < 0.0) |
92 if (*pid->myOutput > pid->outMax) |
90 pid->OutP = 0.0; |
93 *pid->myOutput = pid->outMax; |
91 |
94 else if (*pid->myOutput < pid->outMin) |
92 } |
95 *pid->myOutput = pid->outMin; |
93 |
96 |
94 |
97 if (pid->ITerm > pid->outMax) |
|
98 pid->ITerm = pid->outMax; |
|
99 else if (pid->ITerm < pid->outMin) |
|
100 pid->ITerm = pid->outMin; |
|
101 } |
|
102 } |
|
103 |
|
104 |
|
105 |
|
106 /* |
|
107 * This function allows the controller's dynamic performance to be adjusted. |
|
108 * it's called automatically from the constructor, but tunings can also |
|
109 * be adjusted on the fly during normal operation. |
|
110 */ |
|
111 void PID_setTunings(pid_var *pid, double Kp, double Ki, double Kd) |
|
112 { |
|
113 if (Kp < 0 || Ki < 0 || Kd < 0) |
|
114 return; |
|
115 pid->dispKp = Kp; |
|
116 pid->dispKi = Ki; |
|
117 pid->dispKd = Kd; |
|
118 |
|
119 double SampleTimeInSec = ((double)pid->SampleTime) / 1000; |
|
120 pid->Kp = Kp; |
|
121 pid->Ki = Ki * SampleTimeInSec; |
|
122 pid->Kd = Kd / SampleTimeInSec; |
|
123 |
|
124 if (pid->Direction == P_REVERSE) { |
|
125 pid->Kp = (0 - pid->Kp); |
|
126 pid->Ki = (0 - pid->Ki); |
|
127 pid->Kd = (0 - pid->Kd); |
|
128 } |
|
129 } |
|
130 |
|
131 |
|
132 |
|
133 /* |
|
134 * The PID will either be connected to a DIRECT acting process (+Output leads |
|
135 * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to |
|
136 * know which one, because otherwise we may increase the output when we should |
|
137 * be decreasing. This is called from PID_init(). |
|
138 */ |
|
139 void PID_setControllerDirection(pid_var *pid, int Direction) |
|
140 { |
|
141 if (pid->inAuto && Direction != pid->Direction) { |
|
142 pid->Kp = (0 - pid->Kp); |
|
143 pid->Ki = (0 - pid->Ki); |
|
144 pid->Kd = (0 - pid->Kd); |
|
145 } |
|
146 pid->Direction = Direction; |
|
147 } |
|
148 |
|
149 |
|
150 |
|
151 /* |
|
152 * sets the period, in Milliseconds, at which the calculation is performed. |
|
153 */ |
|
154 void PID_setSampleTime(pid_var *pid, int NewSampleTime) |
|
155 { |
|
156 if (NewSampleTime > 0) { |
|
157 double ratio = (double)NewSampleTime / (double)pid->SampleTime; |
|
158 |
|
159 pid->Ki *= ratio; |
|
160 pid->Kd /= ratio; |
|
161 pid->SampleTime = NewSampleTime; |
|
162 } |
|
163 } |
|
164 |
|
165 |
|
166 |
|
167 /* |
|
168 * Just because you set the Kp=-1 doesn't mean it actually happened. these |
|
169 * functions query the internal state of the PID. they're here for display |
|
170 * purposes. this are the functions the PID Front-end uses for example |
|
171 */ |
|
172 double PID_getKp(pid_var *pid) |
|
173 { |
|
174 return pid->dispKp; |
|
175 } |
|
176 |
|
177 |
|
178 |
|
179 double PID_getKi(pid_var *pid) |
|
180 { |
|
181 return pid->dispKi; |
|
182 } |
|
183 |
|
184 |
|
185 |
|
186 double PID_getKd(pid_var *pid) |
|
187 { |
|
188 return pid->dispKd; |
|
189 } |
|
190 |
|
191 |
|
192 |
|
193 int PID_getMode(pid_var *pid) |
|
194 { |
|
195 return pid->inAuto ? P_AUTOMATIC : P_MANUAL; |
|
196 } |
|
197 |
|
198 |
|
199 |
|
200 int PID_getDirection(pid_var *pid) |
|
201 { |
|
202 return pid->Direction; |
|
203 } |
|
204 |
|
205 |
|
206 |
|
207 /* |
|
208 * This, as they say, is where the magic happens. this function should be called |
|
209 * every time "void loop()" executes. the function will decide for itself whether a new |
|
210 * pid Output needs to be computed. returns true when the output is computed, |
|
211 * false when nothing has been done. |
|
212 */ |
|
213 int PID_compute(pid_var *pid) |
|
214 { |
|
215 if (! pid->inAuto) |
|
216 return FALSE; |
|
217 |
|
218 long now = millis(); |
|
219 long timeChange = (now - pid->lastTime); |
|
220 |
|
221 if (timeChange >= pid->SampleTime) { |
|
222 /* |
|
223 * Compute all the working error variables |
|
224 */ |
|
225 double input = *pid->myInput; |
|
226 double error = *pid->mySetpoint - input; |
|
227 pid->ITerm += (pid->Ki * error); |
|
228 if (pid->ITerm > pid->outMax) |
|
229 pid->ITerm = pid->outMax; |
|
230 else if (pid->ITerm < pid->outMin) |
|
231 pid->ITerm = pid->outMin; |
|
232 double dInput = input - pid->lastInput; |
|
233 |
|
234 /* |
|
235 * Compute PID output |
|
236 */ |
|
237 double output = pid->Kp * error + pid->ITerm - pid->Kd * dInput; |
|
238 if (output > pid->outMax) |
|
239 output = pid->outMax; |
|
240 else if (output < pid->outMin) |
|
241 output = pid->outMin; |
|
242 |
|
243 /* |
|
244 * Remember some variables for the next time |
|
245 */ |
|
246 pid->lastInput = input; |
|
247 pid->lastTime = now; |
|
248 return TRUE; |
|
249 } |
|
250 |
|
251 return FALSE; |
|
252 } |
|
253 |
|
254 |