|
| 1 | +/******************************************************************************** |
| 2 | + sTune QuickPID Adaptive Control Example |
| 3 | + This sketch does on-the-fly tunning and PID SSR control of a PTC heater |
| 4 | + using the ZN_PID tuning method. When sTune completes, we need the input |
| 5 | + to approach setpoint for the first time. As the input gets closer, the gains |
| 6 | + (tunings) are gradually applied. This helps keep the integral term to a |
| 7 | + minimum to help reduce initial overshoot. Final overshoot correction is made |
| 8 | + at the first crossover from setpoint. Here, a full AC cyle is dropped from |
| 9 | + the output and 100% PID control resumes with ½ AC cycle optimized SSR control. |
| 10 | +
|
| 11 | + Open the serial plotter to view the graphical results. |
| 12 | + Reference: https://github.com/Dlloydev/sTune/wiki/Examples_MAX31856_PTC_SSR |
| 13 | + *******************************************************************************/ |
| 14 | + |
| 15 | +#include <Adafruit_MAX31856.h> |
| 16 | +#include <sTune.h> |
| 17 | +#include <QuickPID.h> |
| 18 | + |
| 19 | +// pins |
| 20 | +const uint8_t inputPin = 0; |
| 21 | +const uint8_t relayPin = 3; |
| 22 | +const uint8_t drdyPin = 5; |
| 23 | + |
| 24 | +// user settings |
| 25 | +uint32_t settleTimeSec = 10; |
| 26 | +uint32_t testTimeSec = 500; |
| 27 | +const uint16_t samples = 500; |
| 28 | +const float inputSpan = 200; |
| 29 | +const float outputSpan = 2000; |
| 30 | +float outputStart = 0; |
| 31 | +float outputStep = 60; |
| 32 | +float tempLimit = 75; |
| 33 | +uint8_t debounce = 1; |
| 34 | +uint8_t startup = 0; |
| 35 | +// variables |
| 36 | +float Input, Output, Setpoint = 50, Kp, Ki, Kd; |
| 37 | + |
| 38 | +Adafruit_MAX31856 maxthermo = Adafruit_MAX31856(10); //SPI |
| 39 | +sTune tuner = sTune(&Input, &Output, tuner.ZN_PID, tuner.directIP, tuner.printOFF); |
| 40 | +QuickPID myPID(&Input, &Output, &Setpoint); |
| 41 | + |
| 42 | +void setup() { |
| 43 | + pinMode(drdyPin, INPUT); |
| 44 | + pinMode(relayPin, OUTPUT); |
| 45 | + Serial.begin(115200); |
| 46 | + delay(3000); |
| 47 | + Output = 0; |
| 48 | + if (!maxthermo.begin()) { |
| 49 | + Serial.println("Could not initialize thermocouple."); |
| 50 | + while (1) delay(10); |
| 51 | + } |
| 52 | + maxthermo.setThermocoupleType(MAX31856_TCTYPE_K); |
| 53 | + maxthermo.setConversionMode(MAX31856_CONTINUOUS); |
| 54 | + tuner.Configure(inputSpan, outputSpan, outputStart, outputStep, testTimeSec, settleTimeSec, samples); |
| 55 | + tuner.SetEmergencyStop(tempLimit); |
| 56 | +} |
| 57 | + |
| 58 | +void loop() { |
| 59 | + float optimumOutput = tuner.softPwm(relayPin, Input, Output, Setpoint, outputSpan, debounce); |
| 60 | + |
| 61 | + switch (tuner.Run()) { |
| 62 | + case tuner.sample: // active once per sample during test |
| 63 | + if (!digitalRead(drdyPin)) Input = maxthermo.readThermocoupleTemperature(); |
| 64 | + tuner.plotter(Input, Output * 0.5, Setpoint, 1, 3); |
| 65 | + break; |
| 66 | + |
| 67 | + case tuner.tunings: // active just once when sTune is done |
| 68 | + tuner.GetAutoTunings(&Kp, &Ki, &Kd); // sketch variables updated by sTune |
| 69 | + myPID.SetOutputLimits(0, outputSpan * 0.1); |
| 70 | + myPID.SetSampleTimeUs(outputSpan * 1000 * 0.2); |
| 71 | + debounce = 0; // switch to SSR optimum cycle mode |
| 72 | + myPID.SetMode(myPID.Control::automatic); // the PID is turned on |
| 73 | + myPID.SetProportionalMode(myPID.pMode::pOnMeas); |
| 74 | + myPID.SetAntiWindupMode(myPID.iAwMode::iAwClamp); |
| 75 | + myPID.SetTunings(Kp, 0, 0); // update PID with the new Kp (P-controller) |
| 76 | + break; |
| 77 | + |
| 78 | + case tuner.runPid: // active once per sample after tunings |
| 79 | + if (!digitalRead(drdyPin)) Input = maxthermo.readThermocoupleTemperature(); |
| 80 | + if (Input > Setpoint && startup == 6) { |
| 81 | + Output -= 17; // drop full AC cycle |
| 82 | + myPID.SetMode(myPID.Control::manual); // toggle PID control mode |
| 83 | + myPID.SetMode(myPID.Control::automatic); // PID uses new output value |
| 84 | + startup++; |
| 85 | + } else if (Input > Setpoint - 3 && startup == 5) { |
| 86 | + myPID.SetTunings(Kp, Ki, Kd); // 100% of gains |
| 87 | + startup++; |
| 88 | + } else if (Input > Setpoint - 6 && startup == 4) { |
| 89 | + myPID.SetTunings(Kp, Ki * 0.8, Kd * 0.8); // 80% of gains |
| 90 | + startup++; |
| 91 | + } else if (Input > Setpoint - 9 && startup == 3) { |
| 92 | + myPID.SetTunings(Kp, Ki * 0.6, Kd * 0.6); // 60% of gains |
| 93 | + startup++; |
| 94 | + } else if (Input > Setpoint - 12 && startup == 2) { |
| 95 | + myPID.SetTunings(Kp, Ki * 0.4, Kd * 0.4); // 40% of gains |
| 96 | + startup++; |
| 97 | + } else if (Input > Setpoint - 15 && startup == 1) { |
| 98 | + myPID.SetTunings(Kp, Ki * 0.2, Kd * 0.2); // 20% of gains |
| 99 | + startup++; |
| 100 | + } else if (Input > Setpoint - 18 && startup == 0) { |
| 101 | + myPID.SetTunings(Kp, Ki * 0.1, Kd * 0.1); // 10% of gains |
| 102 | + startup++; |
| 103 | + } |
| 104 | + myPID.Compute(); |
| 105 | + tuner.plotter(Input, optimumOutput * 0.5, Setpoint, 1, 3); |
| 106 | + break; |
| 107 | + } |
| 108 | +} |
0 commit comments