1 #include "PID_Autotuner.h"
3 #include "SlowTicker.h"
5 #include "TemperatureControl.h"
6 #include "libs/StreamOutput.h"
7 #include "TemperatureControlPublicAccess.h"
8 #include "PublicDataRequest.h"
9 #include "PublicData.h"
11 #include <cmath> // std::abs
13 //#define DEBUG_PRINTF s->printf
14 #define DEBUG_PRINTF(...)
16 PID_Autotuner::PID_Autotuner()
26 void PID_Autotuner::on_module_loaded()
29 THEKERNEL
->slow_ticker
->attach(20, this, &PID_Autotuner::on_tick
);
30 register_for_event(ON_IDLE
);
31 register_for_event(ON_GCODE_RECEIVED
);
34 void PID_Autotuner::begin(float target
, StreamOutput
*stream
, int ncycles
)
37 oStep
= temp_control
->heater_pin
.max_pwm(); // use max pwm to cycle temp
38 nLookBack
= 5 * 20; // 5 seconds of lookback
42 if (lastInputs
!= NULL
) delete[] lastInputs
;
43 lastInputs
= new float[nLookBack
+ 1];
47 temp_control
->heater_pin
.set(0);
48 temp_control
->target_temperature
= 0.0;
50 target_temperature
= target
;
51 requested_cycles
= ncycles
;
53 if (peaks
!= NULL
) delete[] peaks
;
54 peaks
= new float[ncycles
];
56 for (int i
= 0; i
< ncycles
; i
++) {
64 float refVal
= temp_control
->get_temperature();
68 temp_control
->heater_pin
.pwm(oStep
); // turn on to start heating
70 s
->printf("%s: Starting PID Autotune, %d max cycles, M304 aborts\n", temp_control
->designator
.c_str(), ncycles
);
73 void PID_Autotuner::abort()
75 if (temp_control
== NULL
)
78 temp_control
->target_temperature
= 0;
79 temp_control
->heater_pin
.set(0);
83 s
->printf("PID Autotune Aborted\n");
89 if (lastInputs
!= NULL
)
94 void PID_Autotuner::on_gcode_received(void *argument
)
96 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
100 gcode
->mark_as_taken();
103 } else if (gcode
->m
== 303 && gcode
->has_letter('E')) {
104 gcode
->mark_as_taken();
105 int pool_index
= gcode
->get_value('E');
107 // get the temperature control instance with this pool index
109 bool ok
= PublicData::get_value( temperature_control_checksum
, pool_index_checksum
, pool_index
, &returned_data
);
112 this->temp_control
= *static_cast<TemperatureControl
**>(returned_data
);
115 gcode
->stream
->printf("No temperature control with index %d found\r\n", pool_index
);
119 float target
= 150.0;
120 if (gcode
->has_letter('S')) {
121 target
= gcode
->get_value('S');
122 gcode
->stream
->printf("Target: %5.1f\n", target
);
125 if (gcode
->has_letter('C')) {
126 ncycles
= gcode
->get_value('C');
128 gcode
->stream
->printf("Start PID tune for index E%d, designator: %s\n", pool_index
, this->temp_control
->designator
.c_str());
129 this->begin(target
, gcode
->stream
, ncycles
);
134 uint32_t PID_Autotuner::on_tick(uint32_t dummy
)
136 if (temp_control
!= NULL
)
139 tickCnt
+= 1000 / 20; // millisecond tick count
144 * this autopid is based on https://github.com/br3ttb/Arduino-PID-AutoTune-Library/blob/master/PID_AutoTune_v0/PID_AutoTune_v0.cpp
146 void PID_Autotuner::on_idle(void *)
153 if (temp_control
== NULL
)
156 if(peakCount
>= requested_cycles
) {
161 float refVal
= temp_control
->get_temperature();
163 if (refVal
> absMax
) absMax
= refVal
;
164 if (refVal
< absMin
) absMin
= refVal
;
166 // oscillate the output base on the input's relation to the setpoint
167 if (refVal
> target_temperature
+ noiseBand
) {
169 //temp_control->heater_pin.pwm(output);
170 temp_control
->heater_pin
.set(0);
171 } else if (refVal
< target_temperature
- noiseBand
) {
173 temp_control
->heater_pin
.pwm(output
);
176 bool isMax
= true, isMin
= true;
179 for (int i
= nLookBack
- 1; i
>= 0; i
--) {
180 float val
= lastInputs
[i
];
181 if (isMax
) isMax
= refVal
> val
;
182 if (isMin
) isMin
= refVal
< val
;
183 lastInputs
[i
+ 1] = lastInputs
[i
];
186 lastInputs
[0] = refVal
;
188 if (lookBackCnt
< nLookBack
) {
189 lookBackCnt
++; // count number of times we have filled lastInputs
190 //we don't want to trust the maxes or mins until the inputs array has been filled
195 if (peakType
== 0) peakType
= 1;
196 if (peakType
== -1) {
202 peaks
[peakCount
] = refVal
;
205 if (peakType
== 0) peakType
= -1;
212 if (peakCount
< requested_cycles
) peaks
[peakCount
] = refVal
;
215 // we need to ignore the first cycle warming up from room temp
217 if (justchanged
&& peakCount
> 2) {
218 if(peakCount
== 3) { // reset min to new min
221 //we've transitioned. check if we can autotune based on the last peaks
222 float avgSeparation
= (std::abs(peaks
[peakCount
- 1] - peaks
[peakCount
- 2]) + std::abs(peaks
[peakCount
- 2] - peaks
[peakCount
- 3])) / 2;
223 s
->printf("Cycle %d: max: %g, min: %g, avg separation: %g\n", peakCount
, absMax
, absMin
, avgSeparation
);
224 if (peakCount
> 3 && avgSeparation
< 0.05 * (absMax
- absMin
)) {
225 DEBUG_PRINTF("Stabilized\n");
233 if ((tickCnt
% 1000) == 0) {
234 s
->printf("%s: %5.1f/%5.1f @%d %d/%d\n", temp_control
->designator
.c_str(), temp_control
->get_temperature(), target_temperature
, output
, peakCount
, requested_cycles
);
235 DEBUG_PRINTF("lookBackCnt= %d, peakCount= %d, absmax= %g, absmin= %g, peak1= %lu, peak2= %lu\n", lookBackCnt
, peakCount
, absMax
, absMin
, peak1
, peak2
);
240 void PID_Autotuner::finishUp()
242 //we can generate tuning parameters!
243 float Ku
= 4 * (2 * oStep
) / ((absMax
- absMin
) * 3.14159);
244 float Pu
= (float)(peak1
- peak2
) / 1000;
245 s
->printf("\tKu: %g, Pu: %g\n", Ku
, Pu
);
248 float ki
= 1.2 * Ku
/ Pu
;
249 float kd
= Ku
* Pu
* 0.075;
251 s
->printf("\tTrying:\n\tKp: %5.1f\n\tKi: %5.3f\n\tKd: %5.0f\n", kp
, ki
, kd
);
253 temp_control
->setPIDp(kp
);
254 temp_control
->setPIDi(ki
);
255 temp_control
->setPIDd(kd
);
257 s
->printf("PID Autotune Complete! The settings above have been loaded into memory, but not written to your config file.\n");
261 temp_control
->target_temperature
= 0;
262 temp_control
->heater_pin
.set(0);
270 if (lastInputs
!= NULL
)