brewco/pid.c

changeset 446
78e9d5234d15
parent 435
4b1ed6897d80
child 448
7fe45f6e4f48
equal deleted inserted replaced
445:3ec477cda546 446:78e9d5234d15
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

mercurial