Skip to content

Commit c165906

Browse files
committed
Update
Minor update and new examples using ZN_PID tuning method and adaptive control to reduce overshoot.
1 parent d26fd3d commit c165906

5 files changed

Lines changed: 222 additions & 10 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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_MAX6675_PTC_SSR
13+
*******************************************************************************/
14+
15+
#include <max6675.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+
const uint8_t SO = 12;
24+
const uint8_t CS = 10;
25+
const uint8_t sck = 13;
26+
27+
// user settings
28+
uint32_t settleTimeSec = 10;
29+
uint32_t testTimeSec = 500;
30+
const uint16_t samples = 500;
31+
const float inputSpan = 200;
32+
const float outputSpan = 2000;
33+
float outputStart = 0;
34+
float outputStep = 60;
35+
float tempLimit = 75;
36+
uint8_t debounce = 1;
37+
uint8_t startup = 0;
38+
// variables
39+
float Input, Output, Setpoint = 50, Kp, Ki, Kd;
40+
41+
MAX6675 module(sck, CS, SO); //SPI
42+
sTune tuner = sTune(&Input, &Output, tuner.ZN_PID, tuner.directIP, tuner.printOFF);
43+
QuickPID myPID(&Input, &Output, &Setpoint);
44+
45+
void setup() {
46+
pinMode(drdyPin, INPUT);
47+
pinMode(relayPin, OUTPUT);
48+
Serial.begin(115200);
49+
while (!Serial) delay(10);
50+
delay(3000);
51+
Output = 0;
52+
tuner.Configure(inputSpan, outputSpan, outputStart, outputStep, testTimeSec, settleTimeSec, samples);
53+
tuner.SetEmergencyStop(tempLimit);
54+
}
55+
56+
void loop() {
57+
float optimumOutput = tuner.softPwm(relayPin, Input, Output, Setpoint, outputSpan, debounce);
58+
59+
switch (tuner.Run()) {
60+
case tuner.sample: // active once per sample during test
61+
Input = module.readCelsius();
62+
tuner.plotter(Input, Output * 0.5, Setpoint, 1, 3);
63+
break;
64+
65+
case tuner.tunings: // active just once when sTune is done
66+
tuner.GetAutoTunings(&Kp, &Ki, &Kd); // sketch variables updated by sTune
67+
myPID.SetOutputLimits(0, outputSpan * 0.1);
68+
myPID.SetSampleTimeUs(outputSpan * 1000 * 0.2);
69+
debounce = 0; // switch to SSR optimum cycle mode
70+
myPID.SetMode(myPID.Control::automatic); // the PID is turned on
71+
myPID.SetProportionalMode(myPID.pMode::pOnMeas);
72+
myPID.SetAntiWindupMode(myPID.iAwMode::iAwClamp);
73+
myPID.SetTunings(Kp, 0, 0); // update PID with the new Kp (P-controller)
74+
break;
75+
76+
case tuner.runPid: // active once per sample after tunings
77+
Input = module.readCelsius();
78+
if (Input > Setpoint && startup == 6) {
79+
Output -= 17; // drop full AC cycle
80+
myPID.SetMode(myPID.Control::manual); // toggle PID control mode
81+
myPID.SetMode(myPID.Control::automatic); // PID uses new output value
82+
startup++;
83+
} else if (Input > Setpoint - 3 && startup == 5) {
84+
myPID.SetTunings(Kp, Ki, Kd); // 100% of gains
85+
startup++;
86+
} else if (Input > Setpoint - 6 && startup == 4) {
87+
myPID.SetTunings(Kp, Ki * 0.8, Kd * 0.8); // 80% of gains
88+
startup++;
89+
} else if (Input > Setpoint - 9 && startup == 3) {
90+
myPID.SetTunings(Kp, Ki * 0.6, Kd * 0.6); // 60% of gains
91+
startup++;
92+
} else if (Input > Setpoint - 12 && startup == 2) {
93+
myPID.SetTunings(Kp, Ki * 0.4, Kd * 0.4); // 40% of gains
94+
startup++;
95+
} else if (Input > Setpoint - 15 && startup == 1) {
96+
myPID.SetTunings(Kp, Ki * 0.2, Kd * 0.2); // 20% of gains
97+
startup++;
98+
} else if (Input > Setpoint - 18 && startup == 0) {
99+
myPID.SetTunings(Kp, Ki * 0.1, Kd * 0.1); // 10% of gains
100+
startup++;
101+
}
102+
myPID.Compute();
103+
tuner.plotter(Input, optimumOutput * 0.5, Setpoint, 1, 3);
104+
break;
105+
}
106+
}

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sTune",
3-
"version": "2.3.1",
3+
"version": "2.3.2",
44
"description": "Open loop PID autotuner using a novel s-curve inflection point test method. Tuning parameters are determined in about ½Tau on a first-order system with time delay. Full 5Tau testing and multiple serial output options are provided.",
55
"keywords": "PID, autotune, autotuner, tuner, tune, stune, inflection, step",
66
"repository":

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=sTune
2-
version=2.3.1
2+
version=2.3.2
33
author=David Lloyd
44
maintainer=David Lloyd <[email protected]>
55
sentence=Open loop PID autotuner using a novel s-curve inflection point test method.

src/sTune.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************************
2-
sTune Library for Arduino - Version 2.3.1
2+
sTune Library for Arduino - Version 2.3.2
33
by dlloydev https://github.com/Dlloydev/sTune
44
Licensed under the MIT License.
55
@@ -113,8 +113,8 @@ uint8_t sTune::Run() {
113113
float lastPvAvg = pvAvg;
114114
pvInst = *_input;
115115
pvAvg = tangent.avgVal(pvInst);
116-
float pvInstResolution = abs(pvInst - lastPvInst);
117-
float pvAvgResolution = abs(pvAvg - lastPvAvg);
116+
float pvInstResolution = fabs(pvInst - lastPvInst);
117+
float pvAvgResolution = fabs(pvAvg - lastPvAvg);
118118
if (pvInstResolution > epsilon && pvInstResolution < pvInstRes) pvInstRes = pvInstResolution;
119119
if (pvAvgResolution > epsilon && pvAvgResolution < pvAvgRes) pvAvgRes = pvAvgResolution;
120120

@@ -192,7 +192,7 @@ uint8_t sTune::Run() {
192192
_TuMin = _Tu * 0.1667; // units in minutes
193193
_tdMin = _td * 0.1667; // units in minutes
194194
_R = _tdMin / _TuMin;
195-
_Ku = abs(((pvMax - pvStart) / _inputSpan) / ((_outputStep - _outputStart) / _outputSpan)); // process gain
195+
_Ku = fabs(((pvMax - pvStart) / _inputSpan) / ((_outputStep - _outputStart) / _outputSpan)); // process gain
196196
_Ko = ((_outputStep - _outputStart) / pvMax) * (_TuMin / _tdMin); // process gain
197197

198198
_kp = sTune::GetKp();
@@ -507,11 +507,9 @@ float sTune::softPwm(const uint8_t relayPin, float input, float output, float se
507507
if (msNow - windowStartTime >= windowSize) {
508508
windowStartTime = msNow;
509509
}
510-
511-
// SSR optimum cycle controller (AC half-cycle control)
510+
// SSR optimum AC half-cycle controller
512511
static float optimumOutput;
513-
if (!debounce && setpoint > 0 && input > setpoint) optimumOutput = output - 8;
514-
else if (!debounce && setpoint > 0 && input < setpoint) optimumOutput = output + 8;
512+
if (!debounce && setpoint > 0 && input > setpoint) optimumOutput = output - 9;
515513
else optimumOutput = output;
516514
if (optimumOutput < 0) optimumOutput = 0;
517515

0 commit comments

Comments
 (0)