Allow TABS in config
[clinton/Smoothieware.git] / src / modules / tools / temperaturecontrol / PID_Autotuner.cpp
CommitLineData
3c308aeb 1#include "PID_Autotuner.h"
3c308aeb 2#include "Kernel.h"
43424972 3#include <cmath> // std::abs
61134a65
JM
4#include "SlowTicker.h"
5#include "Gcode.h"
43424972
JM
6
7#define DEBUG_PRINTF s->printf
3c308aeb
MM
8
9PID_Autotuner::PID_Autotuner()
10{
11 t = NULL;
12 s = NULL;
43424972
JM
13 lastInputs = NULL;
14 peaks = NULL;
15 tick = false;
16 tickCnt= 0;
3c308aeb
MM
17}
18
19void PID_Autotuner::on_module_loaded()
20{
f2fa9ba2 21 tick = false;
314ab8f7 22 THEKERNEL->slow_ticker->attach(20, this, &PID_Autotuner::on_tick );
f2fa9ba2 23 register_for_event(ON_IDLE);
92f737d7 24 register_for_event(ON_GCODE_RECEIVED);
3c308aeb
MM
25}
26
1ad23cd3 27void PID_Autotuner::begin(TemperatureControl *temp, float target, StreamOutput *stream, int ncycles)
3c308aeb 28{
43424972
JM
29 noiseBand = 0.5;
30 oStep = temp->heater_pin.max_pwm(); // use max pwm to cycle temp
31 nLookBack = 5 * 20; // 5 seconds of lookback
32 lookBackCnt= 0;
33 tickCnt= 0;
34
35 if (lastInputs != NULL) delete[] lastInputs;
36 lastInputs = new float[nLookBack+1];
3c308aeb 37 t = temp;
43424972 38 s = stream;
3c308aeb 39
43424972 40 t->heater_pin.set(0);
827a49ca 41 t->target_temperature = 0.0;
3c308aeb 42
827a49ca 43 target_temperature = target;
43424972 44 requested_cycles = ncycles;
3c308aeb 45
43424972
JM
46 if (peaks != NULL) delete[] peaks;
47 peaks = new float[ncycles];
48
49 for (int i = 0; i < ncycles; i++) {
50 peaks[i] = 0.0;
3c308aeb 51 }
3c308aeb 52
43424972
JM
53 peakType = 0;
54 peakCount = 0;
55 justchanged = false;
3c308aeb 56
1ad23cd3 57 float refVal = t->get_temperature();
43424972
JM
58 absMax = refVal;
59 absMin = refVal;
60 output= oStep;
61 t->heater_pin.pwm(oStep); // turn on to start heating
827a49ca 62
43424972 63 s->printf("%s: Starting PID Autotune, %d max cycles, M304 aborts\n", t->designator.c_str(), ncycles);
3c308aeb
MM
64}
65
92f737d7
MM
66void PID_Autotuner::abort()
67{
68 if (!t)
69 return;
70
71 t->target_temperature = 0;
72 t->heater_pin.set(0);
73 t = NULL;
74
75 if (s)
76 s->printf("PID Autotune Aborted\n");
77 s = NULL;
43424972
JM
78
79 if (peaks != NULL)
80 delete[] peaks;
81 peaks = NULL;
82 if (lastInputs != NULL)
83 delete[] lastInputs;
84 lastInputs = NULL;
92f737d7
MM
85}
86
43424972 87void PID_Autotuner::on_gcode_received(void *argument)
92f737d7 88{
43424972 89 Gcode *gcode = static_cast<Gcode *>(argument);
92f737d7
MM
90
91 if ((gcode->has_m) && (gcode->m == 304))
92 abort();
93}
94
3c308aeb
MM
95uint32_t PID_Autotuner::on_tick(uint32_t dummy)
96{
f2fa9ba2
MM
97 if (t)
98 tick = true;
43424972 99 tickCnt += 1000/20; // millisecond tick count
f2fa9ba2
MM
100 return 0;
101}
102
43424972
JM
103/**
104 * this autopid is based on https://github.com/br3ttb/Arduino-PID-AutoTune-Library/blob/master/PID_AutoTune_v0/PID_AutoTune_v0.cpp
105 */
106void PID_Autotuner::on_idle(void *)
f2fa9ba2
MM
107{
108 if (!tick)
109 return;
110
111 tick = false;
112
3c308aeb 113 if (t == NULL)
f2fa9ba2 114 return;
3c308aeb 115
43424972
JM
116 if(peakCount >= requested_cycles) {
117 finishUp();
118 return;
119 }
3c308aeb 120
1ad23cd3 121 float refVal = t->get_temperature();
43424972
JM
122
123 if (refVal > absMax) absMax = refVal;
124 if (refVal < absMin) absMin = refVal;
125
126 // oscillate the output base on the input's relation to the setpoint
127 if (refVal > target_temperature + noiseBand){
128 output= 0;
129 //t->heater_pin.pwm(output);
130 t->heater_pin.set(0);
131 } else if (refVal < target_temperature - noiseBand) {
132 output= oStep;
133 t->heater_pin.pwm(output);
3c308aeb
MM
134 }
135
43424972 136 bool isMax = true, isMin = true;
3c308aeb 137
43424972
JM
138 // id peaks
139 for (int i = nLookBack - 1; i >= 0; i--) {
140 float val = lastInputs[i];
141 if (isMax) isMax = refVal > val;
142 if (isMin) isMin = refVal < val;
143 lastInputs[i + 1] = lastInputs[i];
827a49ca 144 }
43424972
JM
145
146 lastInputs[0] = refVal;
147
148 if (lookBackCnt < nLookBack) {
149 lookBackCnt++; // count number of times we have filled lastInputs
150 //we don't want to trust the maxes or mins until the inputs array has been filled
151 return;
827a49ca 152 }
3c308aeb 153
43424972
JM
154 if (isMax) {
155 if (peakType == 0) peakType = 1;
156 if (peakType == -1) {
157 peakType = 1;
158 justchanged = true;
159 peak2 = peak1;
160 }
161 peak1 = tickCnt;
162 peaks[peakCount] = refVal;
163
164 } else if (isMin) {
165 if (peakType == 0) peakType = -1;
166 if (peakType == 1) {
167 peakType = -1;
168 peakCount++;
169 justchanged = true;
170 }
171
172 if (peakCount < requested_cycles) peaks[peakCount] = refVal;
173 }
174
175 // we need to ignore the first cycle warming up from room temp
176
177 if (justchanged && peakCount > 2) {
178 if(peakCount == 3) { // reset min to new min
179 absMin= refVal;
180 }
181 //we've transitioned. check if we can autotune based on the last peaks
182 float avgSeparation = (std::abs(peaks[peakCount - 1] - peaks[peakCount - 2]) + std::abs(peaks[peakCount - 2] - peaks[peakCount - 3])) / 2;
183 s->printf("Cycle %d: max: %g, min: %g, avg separation: %g\n", peakCount, absMax, absMin, avgSeparation);
184 if (peakCount > 3 && avgSeparation < 0.05 * (absMax - absMin)) {
185 DEBUG_PRINTF("Stabilized\n");
186 finishUp();
187 return;
188 }
189 }
190
191 justchanged = false;
192
193 if ((tickCnt % 1000) == 0) {
194 s->printf("%s: %5.1f/%5.1f @%d %d/%d\n", t->designator.c_str(), t->get_temperature(), target_temperature, output, peakCount, requested_cycles);
195 DEBUG_PRINTF("lookBackCnt= %d, peakCount= %d, absmax= %g, absmin= %g, peak1= %lu, peak2= %lu\n", lookBackCnt, peakCount, absMax, absMin, peak1, peak2);
3c308aeb 196 }
43424972
JM
197}
198
3c308aeb 199
43424972
JM
200void PID_Autotuner::finishUp()
201{
202 //we can generate tuning parameters!
1ad23cd3
MM
203 float Ku = 4*(2*oStep)/((absMax-absMin)*3.14159);
204 float Pu = (float)(peak1-peak2) / 1000;
43424972
JM
205 s->printf("\tKu: %g, Pu: %g\n", Ku, Pu);
206
1ad23cd3
MM
207 float kp = 0.6 * Ku;
208 float ki = 1.2 * Ku / Pu;
209 float kd = Ku * Pu * 0.075;
43424972
JM
210
211 s->printf("\tTrying:\n\tKp: %5.1f\n\tKi: %5.3f\n\tKd: %5.0f\n", kp, ki, kd);
212
213 t->setPIDp(kp);
214 t->setPIDi(ki);
215 t->setPIDd(kd);
216
217 s->printf("PID Autotune Complete! The settings above have been loaded into memory, but not written to your config file.\n");
218
219
220 // and clean up
221 t->target_temperature = 0;
222 t->heater_pin.set(0);
223 t = NULL;
224 s = NULL;
3c308aeb 225
43424972
JM
226 if (peaks != NULL)
227 delete[] peaks;
228 peaks = NULL;
3c308aeb 229
43424972
JM
230 if (lastInputs != NULL)
231 delete[] lastInputs;
232 lastInputs = NULL;
3c308aeb 233}