170 | | if (now < nextOff) |
171 | | digitalWrite(HEATER, true); |
172 | | else |
173 | | digitalWrite(HEATER, false); |
174 | | } else { |
175 | | /* |
176 | | * Check |
177 | | */ |
178 | | nextCheck += 1000; // 1 second |
179 | | temperature = sensorValue(); |
| 202 | digitalWrite(HEATER, (now < nextOff)); |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | /* |
| 207 | * Check once a second |
| 208 | */ |
| 209 | ++timer; |
| 210 | nextCheck += 1000; // 1 second |
| 211 | double temperature = sensorValue(); |
| 212 | if (temperature == TEMP_ERROR) |
| 213 | temperature = previous; |
| 214 | |
| 215 | /* Check if the state should be changed. */ |
| 216 | switch (state) { |
| 217 | case 0: |
| 218 | state = 1; |
| 219 | integral = 0; |
| 220 | target = temperature; |
| 221 | slope = Rstart; |
| 222 | destination = Ts_min; |
| 223 | break; |
| 224 | case 1: |
| 225 | // ramp-up to 150, slope rate between 1.0/sec and 3.0/sec. |
| 226 | if (temperature >= Ts_min) { |
| 227 | // preheat to 190, for 60 and 120 seconds. |
| 228 | state = 2; |
| 229 | integral = 0; |
| 230 | target = temperature; |
| 231 | slope = (Ts_max - Ts_min) / ts; |
| 232 | destination = Ts_max; |
| 233 | } |
| 234 | break; |
| 235 | case 2: |
| 236 | // preheat to 190, for 60 and 120 seconds. |
| 237 | if (temperature >= Ts_max) { |
| 238 | // heat to 232 |
| 239 | state = 3; |
| 240 | integral = 0; |
| 241 | target = temperature; |
| 242 | slope = Rup; |
| 243 | destination = Tpeak; |
| 244 | } |
| 245 | break; |
| 246 | case 3: |
| 247 | // heat to 232 |
| 248 | if (temperature >= TL) { |
| 249 | // keep 232 for 30 to 60 seconds. |
| 250 | state = 4; |
| 251 | integral = 0; |
| 252 | target = destination = temperature; |
| 253 | slope = 0; |
| 254 | meltCount = 0; |
| 255 | } |
| 256 | break; |
| 257 | case 4: |
| 258 | // keep 232 for 30 to 60 seconds. |
| 259 | if (++meltCount > tL) { |
| 260 | // cool down to 190 |
| 261 | state = 5; |
| 262 | integral = 0; |
| 263 | target = temperature; |
| 264 | slope = Rdown; |
| 265 | destination = Ts_max; |
| 266 | } |
| 267 | break; |
| 268 | case 5: |
| 269 | // cool down to 190 |
| 270 | if (temperature <= Ts_max) { |
| 271 | // cool down to under 50 |
| 272 | state = 6; |
| 273 | integral = 0; |
| 274 | target = destination = -273.0; |
| 275 | slope = 0; |
| 276 | } |
| 277 | break; |
| 278 | case 6: |
| 279 | // cool down to under 50 |
| 280 | if (temperature <= Tend) { |
| 281 | // done |
| 282 | state = 0; |
| 283 | } |
| 284 | break; |
| 285 | } |
| 286 | |
| 287 | /* Next target */ |
| 288 | target += slope; |
| 289 | if (slope > 0 ? (target > destination) : (target < destination)) |
| 290 | target = destination; |
| 291 | |
| 292 | /* Heater control value */ |
| 293 | double error = target - temperature; |
| 294 | integral += error; |
| 295 | long MV = Kp * error + Ki * integral + Kd * (temperature - previous - slope); |
| 296 | MV = min(max(0, MV), 1000); |
| 297 | nextOff = now + MV; |
| 298 | previous = temperature; // backup for the next calcuration. |
| 299 | |
| 300 | /* LCD display */ |
| 301 | lcd.clear(); |
| 302 | lcd.print(messages[state]); |
| 303 | lcd.setCursor(0, 1); |
| 304 | lcd.print(timer); |
| 305 | lcd.print(' '); |
| 306 | lcd.print(MV); |
| 307 | if (temperature != TEMP_ERROR) { |
| 308 | lcd.print(' '); |
| 309 | lcd.print(temperature); |
| 310 | lcd.print(0xdf, BYTE); |
| 311 | lcd.print('C'); |
| 312 | } |
181 | | if (temperature == TEMP_ERROR) |
182 | | state = 6; |
183 | | |
184 | | /* Check if the state changes. */ |
185 | | switch (state) { |
186 | | case 9: |
187 | | state = 1; |
188 | | pid.Reset(); |
189 | | target = temperature; |
190 | | slope = Rstart; |
191 | | destination = Ts_min; |
192 | | break; |
193 | | case 1: |
194 | | if (temperature >= Ts_min) { |
195 | | state = 2; |
196 | | slope = (Ts_max - Ts_min) / ts; |
197 | | destination = Ts_max; |
198 | | } |
199 | | break; |
200 | | case 2: |
201 | | if (temperature >= Ts_max) { |
202 | | state = 3; |
203 | | slope = Rup; |
204 | | destination = Tpeak; |
205 | | meltCount = 0; |
206 | | } |
207 | | break; |
208 | | case 3: |
209 | | if (temperature >= TL) { |
210 | | state = 4; |
211 | | meltCount = 0; |
212 | | } |
213 | | break; |
214 | | case 4: |
215 | | if (++meltCount > tL) { |
216 | | state = 5; |
217 | | slope = Rdown; |
218 | | destination = -273.0; |
219 | | } |
220 | | break; |
221 | | case 5: |
222 | | if (temperature <= Tend) |
223 | | state = 0; |
224 | | break; |
225 | | } |
226 | | |
227 | | /* Next target */ |
228 | | switch (state) { |
229 | | case 1: |
230 | | case 2: |
231 | | case 3: |
232 | | target += slope; |
233 | | if (target > destination) |
234 | | target = destination; |
235 | | break; |
236 | | case 5: |
237 | | target -= slope; |
238 | | if (target < destination) |
239 | | target = destination; |
240 | | break; |
241 | | } |
242 | | |
243 | | /* Heater control value */ |
244 | | switch (state) { |
245 | | case 1: |
246 | | case 2: |
247 | | case 3: |
248 | | case 4: |
249 | | case 5: |
250 | | pid.Compute(); |
251 | | nextOff = now + output; |
252 | | break; |
253 | | |
254 | | case 0: |
255 | | case 6: |
256 | | default: |
257 | | nextOff = 0; |
258 | | } |
259 | | |
260 | | /* LCD display */ |
261 | | lcd.clear(); |
262 | | switch (state) { |
263 | | case 0: |
264 | | lcd.print("Press to start"); |
265 | | break; |
266 | | case 1: |
267 | | lcd.print("Ramp up"); |
268 | | break; |
269 | | case 2: |
270 | | lcd.print("Pre-heat"); |
271 | | break; |
272 | | case 3: |
273 | | lcd.print("Heat up"); |
274 | | break; |
275 | | case 4: |
276 | | lcd.print("Melted"); |
277 | | break; |
278 | | case 5: |
279 | | lcd.print("Cool down"); |
280 | | break; |
281 | | case 6: |
282 | | lcd.print("Fail"); |
283 | | break; |
284 | | } |
285 | | lcd.setCursor(0, 1); |
286 | | lcd.print(output); |
287 | | lcd.print(' '); |
288 | | if (temperature != TEMP_ERROR) |
289 | | lcd.print(temperature); |
290 | | |
291 | | SerialReceive(); |
292 | | SerialSend(); |
293 | | } |
294 | | } |
295 | | |
296 | | |
297 | | |
298 | | /******************************************** |
299 | | * Serial Communication functions / helpers |
300 | | ********************************************/ |
301 | | union { // This Data structure lets |
302 | | byte asBytes[24]; // us take the byte array |
303 | | float asFloat[6]; // sent from processing and |
304 | | } // easily convert it to a |
305 | | foo; // float array |
306 | | |
307 | | // getting float values from processing into the arduino |
308 | | // was no small task. the way this program does it is |
309 | | // as follows: |
310 | | // * a float takes up 4 bytes. in processing, convert |
311 | | // the array of floats we want to send, into an array |
312 | | // of bytes. |
313 | | // * send the bytes to the arduino |
314 | | // * use a data structure known as a union to convert |
315 | | // the array of bytes back into an array of floats |
316 | | |
317 | | // the bytes coming from the arduino follow the following |
318 | | // format: |
319 | | // 0: 0=Manual, 1=Auto, else = ? error ? |
320 | | // 1-4: float setpoint |
321 | | // 5-8: float input |
322 | | // 9-12: float output |
323 | | // 13-16: float P_Param |
324 | | // 17-20: float I_Param |
325 | | // 21-24: float D_Param |
326 | | void SerialReceive() |
327 | | { |
328 | | // read the bytes sent from Processing |
329 | | int index = 0; |
330 | | byte Auto_Man = -1; |
331 | | while (Serial.available() && index < 25) { |
332 | | if (index == 0) |
333 | | Auto_Man = Serial.read(); |
334 | | else |
335 | | foo.asBytes[index-1] = Serial.read(); |
336 | | index++; |
337 | | } |
338 | | |
339 | | // if the information we got was in the correct format, |
340 | | // read it into the system |
341 | | if (index == 25 && (Auto_Man == 0 || Auto_Man == 1)) { |
342 | | target = double(foo.asFloat[0]); |
343 | | if (Auto_Man == 0) // * only change the output if we are in |
344 | | { // manual mode. otherwise we'll get an |
345 | | output = double(foo.asFloat[2]); // output blip, then the controller will |
346 | | } // overwrite. |
347 | | |
348 | | double p, i, d; // * read in and set the controller tunings |
349 | | p = double(foo.asFloat[3]); // |
350 | | i = double(foo.asFloat[4]); // |
351 | | d = double(foo.asFloat[5]); // |
352 | | pid.SetTunings(p, i, d); // |
353 | | |
354 | | if(Auto_Man==0) |
355 | | pid.SetMode(MANUAL);// * set the controller mode |
356 | | else |
357 | | pid.SetMode(AUTO); // |
358 | | } |
359 | | Serial.flush(); // * clear any random data from the serial buffer |
360 | | } |
361 | | |
362 | | // unlike our tiny microprocessor, the processing ap |
363 | | // has no problem converting strings into floats, so |
364 | | // we can just send strings. much easier than getting |
365 | | // floats from processing to here no? |
366 | | void SerialSend() |
367 | | { |
368 | | Serial.print("PID "); |
369 | | Serial.print(target); |
370 | | Serial.print(" "); |
371 | | Serial.print(temperature); |
372 | | Serial.print(" "); |
373 | | Serial.print(output); |
374 | | Serial.print(" "); |
375 | | Serial.print(pid.GetP_Param()); |
376 | | Serial.print(" "); |
377 | | Serial.print(pid.GetI_Param()); |
378 | | Serial.print(" "); |
379 | | Serial.print(pid.GetD_Param()); |
380 | | Serial.print(" "); |
381 | | if (pid.GetMode() == AUTO) |
382 | | Serial.println("Automatic"); |
383 | | else |
384 | | Serial.println("Manual"); |
385 | | } |
| 314 | /* Report to PC */ |
| 315 | Serial.print(millis() / 1000); |
| 316 | Serial.print(","); |
| 317 | Serial.print((int)(target * 100)); |
| 318 | Serial.print(","); |
| 319 | Serial.print((int)(temperature * 100)); |
| 320 | Serial.println(","); |
| 321 | } |
| 322 | |