Commit | Line | Data |
---|---|---|
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 | |
9 | PID_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 | ||
19 | void 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 | 27 | void 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 |
66 | void 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 | 87 | void 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 |
95 | uint32_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 | */ | |
106 | void 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 |
200 | void 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 | } |