rename wait_for_empty_queue to wait for idle
[clinton/Smoothieware.git] / src / modules / tools / temperaturecontrol / TemperatureControl.cpp
CommitLineData
df27a6a3 1/*
cd011f58
AW
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
df27a6a3 5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
cd011f58
AW
6*/
7
7b49793d 8// TODO : THIS FILE IS LAME, MUST BE MADE MUCH BETTER
cd011f58 9
ded56b35
AW
10#include "libs/Module.h"
11#include "libs/Kernel.h"
12#include <math.h>
13#include "TemperatureControl.h"
3c308aeb 14#include "TemperatureControlPool.h"
3c132bd0 15#include "libs/Pin.h"
3c4f2dd8 16#include "modules/robot/Conveyor.h"
8293d443 17#include "PublicDataRequest.h"
2fa50ca0 18
38b9f24a
L
19#include "PublicData.h"
20#include "ToolManagerPublicAccess.h"
61134a65
JM
21#include "StreamOutputPool.h"
22#include "Config.h"
23#include "checksumm.h"
24#include "Gcode.h"
61134a65 25#include "SlowTicker.h"
8d54c34c 26#include "ConfigValue.h"
66383b80 27#include "PID_Autotuner.h"
1f8dab1a
JM
28#include "SerialMessage.h"
29#include "utils.h"
ded56b35 30
9d955060 31// Temp sensor implementations:
32#include "Thermistor.h"
4710532a 33#include "max31855.h"
073d88ec 34#include "AD8495.h"
9d955060 35
8f91e4e6
MM
36#include "MRI_Hooks.h"
37
85eabc50
JM
38#define UNDEFINED -1
39
9d955060 40#define sensor_checksum CHECKSUM("sensor")
41
85eabc50
JM
42#define readings_per_second_checksum CHECKSUM("readings_per_second")
43#define max_pwm_checksum CHECKSUM("max_pwm")
44#define pwm_frequency_checksum CHECKSUM("pwm_frequency")
989d0e94
JM
45#define bang_bang_checksum CHECKSUM("bang_bang")
46#define hysteresis_checksum CHECKSUM("hysteresis")
85eabc50 47#define heater_pin_checksum CHECKSUM("heater_pin")
bb02929e 48#define max_temp_checksum CHECKSUM("max_temp")
3a014cbb 49#define min_temp_checksum CHECKSUM("min_temp")
85eabc50
JM
50
51#define get_m_code_checksum CHECKSUM("get_m_code")
52#define set_m_code_checksum CHECKSUM("set_m_code")
53#define set_and_wait_m_code_checksum CHECKSUM("set_and_wait_m_code")
54
55#define designator_checksum CHECKSUM("designator")
56
57#define p_factor_checksum CHECKSUM("p_factor")
58#define i_factor_checksum CHECKSUM("i_factor")
59#define d_factor_checksum CHECKSUM("d_factor")
60
61#define i_max_checksum CHECKSUM("i_max")
08f89868 62#define windup_checksum CHECKSUM("windup")
85eabc50
JM
63
64#define preset1_checksum CHECKSUM("preset1")
65#define preset2_checksum CHECKSUM("preset2")
66
8e8b938e 67TemperatureControl::TemperatureControl(uint16_t name, int index)
d8baddd9 68{
8e8b938e
JM
69 name_checksum= name;
70 pool_index= index;
71 waiting= false;
01004e36 72 temp_violated= false;
8e8b938e 73 sensor= nullptr;
71cc73eb 74 readonly= false;
d8baddd9 75}
ded56b35 76
d8baddd9 77TemperatureControl::~TemperatureControl()
78{
79 delete sensor;
80}
4710532a
JM
81
82void TemperatureControl::on_module_loaded()
83{
907d5e8a 84
81b547a1 85 // We start not desiring any temp
907d5e8a 86 this->target_temperature = UNDEFINED;
f1e38d95 87 this->sensor_settings= false; // set to true if sensor settings have been overriden
ded56b35
AW
88
89 // Settings
71cc73eb 90 this->load_config();
ded56b35 91
ded56b35 92 // Register for events
b0be67b5 93 this->register_for_event(ON_GCODE_RECEIVED);
8293d443 94 this->register_for_event(ON_GET_PUBLIC_DATA);
71cc73eb
JM
95
96 if(!this->readonly) {
71cc73eb
JM
97 this->register_for_event(ON_SECOND_TICK);
98 this->register_for_event(ON_MAIN_LOOP);
99 this->register_for_event(ON_SET_PUBLIC_DATA);
100 this->register_for_event(ON_HALT);
101 }
3d1a4519
JM
102}
103
104void TemperatureControl::on_halt(void *arg)
105{
728477c4
JM
106 if(arg == nullptr) {
107 // turn off heater
108 this->o = 0;
109 this->heater_pin.set(0);
110 this->target_temperature = UNDEFINED;
111 }
ded56b35
AW
112}
113
4710532a
JM
114void TemperatureControl::on_main_loop(void *argument)
115{
01004e36
JM
116 if (this->temp_violated) {
117 this->temp_violated = false;
f0433db4 118 THEKERNEL->streams->printf("Error: MINTEMP or MAXTEMP triggered on %s. Check your temperature sensors!\n", designator.c_str());
01004e36
JM
119 THEKERNEL->streams->printf("HALT asserted - reset or M999 required\n");
120 THEKERNEL->call_event(ON_HALT, nullptr);
a7f12bed 121 }
7e57bc2c 122}
7dd8133c
AW
123
124// Get configuration from the config file
71cc73eb 125void TemperatureControl::load_config()
4710532a 126{
7dd8133c 127
1306ba99 128 // General config
314ab8f7
MM
129 this->set_m_code = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, set_m_code_checksum)->by_default(104)->as_number();
130 this->set_and_wait_m_code = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, set_and_wait_m_code_checksum)->by_default(109)->as_number();
131 this->get_m_code = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, get_m_code_checksum)->by_default(105)->as_number();
132 this->readings_per_second = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, readings_per_second_checksum)->by_default(20)->as_number();
7dee00e4 133
314ab8f7 134 this->designator = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, designator_checksum)->by_default(string("T"))->as_string();
b0be67b5 135
3a014cbb 136 // Max and min temperatures we are not allowed to get over (Safety)
01004e36 137 this->max_temp = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, max_temp_checksum)->by_default(300)->as_number();
3a014cbb 138 this->min_temp = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, min_temp_checksum)->by_default(0)->as_number();
7d4baeee 139
71cc73eb
JM
140 // Heater pin
141 this->heater_pin.from_string( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, heater_pin_checksum)->by_default("nc")->as_string());
142 if(this->heater_pin.connected()){
143 this->readonly= false;
144 this->heater_pin.as_output();
145
146 } else {
147 this->readonly= true;
148 }
149
d8baddd9 150 // For backward compatibility, default to a thermistor sensor.
151 std::string sensor_type = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, sensor_checksum)->by_default("thermistor")->as_string();
152
153 // Instantiate correct sensor (TBD: TempSensor factory?)
154 delete sensor;
155 sensor = nullptr; // In case we fail to create a new sensor.
4710532a 156 if(sensor_type.compare("thermistor") == 0) {
d8baddd9 157 sensor = new Thermistor();
4710532a 158 } else if(sensor_type.compare("max31855") == 0) {
d8baddd9 159 sensor = new Max31855();
073d88ec
E
160 } else if(sensor_type.compare("ad8495") == 0) {
161 sensor = new AD8495();
4710532a 162 } else {
d8baddd9 163 sensor = new TempSensor(); // A dummy implementation
164 }
165 sensor->UpdateConfig(temperature_control_checksum, this->name_checksum);
4710532a 166
71cc73eb
JM
167 this->preset1 = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, preset1_checksum)->by_default(0)->as_number();
168 this->preset2 = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, preset2_checksum)->by_default(0)->as_number();
f4bd4fc3 169
907d5e8a 170
c4f4cf73 171 // sigma-delta output modulation
b35ac71e 172 this->o = 0;
3c132bd0 173
71cc73eb
JM
174 if(!this->readonly) {
175 // used to enable bang bang control of heater
176 this->use_bangbang = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, bang_bang_checksum)->by_default(false)->as_bool();
177 this->hysteresis = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, hysteresis_checksum)->by_default(2)->as_number();
08f89868 178 this->windup = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, windup_checksum)->by_default(false)->as_bool();
71cc73eb
JM
179 this->heater_pin.max_pwm( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, max_pwm_checksum)->by_default(255)->as_number() );
180 this->heater_pin.set(0);
181 set_low_on_debug(heater_pin.port_number, heater_pin.pin);
182 // activate SD-DAC timer
183 THEKERNEL->slow_ticker->attach( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, pwm_frequency_checksum)->by_default(2000)->as_number(), &heater_pin, &Pwm::on_tick);
184 }
8f91e4e6 185
907d5e8a 186
f39da6fe 187 // reading tick
314ab8f7 188 THEKERNEL->slow_ticker->attach( this->readings_per_second, this, &TemperatureControl::thermistor_read_tick );
4710532a 189 this->PIDdt = 1.0 / this->readings_per_second;
f39da6fe 190
907d5e8a 191 // PID
314ab8f7 192 setPIDp( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, p_factor_checksum)->by_default(10 )->as_number() );
1ad23cd3 193 setPIDi( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, i_factor_checksum)->by_default(0.3f)->as_number() );
314ab8f7 194 setPIDd( THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, d_factor_checksum)->by_default(200)->as_number() );
71cc73eb
JM
195
196 if(!this->readonly) {
197 // set to the same as max_pwm by default
198 this->i_max = THEKERNEL->config->value(temperature_control_checksum, this->name_checksum, i_max_checksum )->by_default(this->heater_pin.max_pwm())->as_number();
199 }
200
10e66797 201 this->iTerm = 0.0;
4710532a 202 this->lastInput = -1.0;
907d5e8a 203 this->last_reading = 0.0;
7dd8133c
AW
204}
205
4710532a
JM
206void TemperatureControl::on_gcode_received(void *argument)
207{
208 Gcode *gcode = static_cast<Gcode *>(argument);
8dfbc976 209 if (gcode->has_m) {
ee4711d1 210
4710532a 211 if( gcode->m == this->get_m_code ) {
93284f6f 212 char buf[32]; // should be big enough for any status
3517edce 213 int n = snprintf(buf, sizeof(buf), "%s:%3.1f /%3.1f @%d ", this->designator.c_str(), this->get_temperature(), ((target_temperature <= 0) ? 0.0 : target_temperature), this->o);
93284f6f 214 gcode->txt_after_ok.append(buf, n);
71cc73eb
JM
215 return;
216 }
8dfbc976 217
76f53dc6 218 if (gcode->m == 305) { // set or get sensor settings
76f53dc6 219 if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index)) {
1f8dab1a 220 TempSensor::sensor_options_t args= gcode->get_args();
d22755f7
JM
221 args.erase('S'); // don't include the S
222 if(args.size() > 0) {
76f53dc6 223 // set the new options
d22755f7
JM
224 if(sensor->set_optional(args)) {
225 this->sensor_settings= true;
226 }else{
227 gcode->stream->printf("Unable to properly set sensor settings, make sure you specify all required values\n");
228 }
229 }else{
230 // don't override
231 this->sensor_settings= false;
76f53dc6
JM
232 }
233
234 }else if(!gcode->has_letter('S')) {
d22755f7 235 gcode->stream->printf("%s(S%d): using %s\n", this->designator.c_str(), this->pool_index, this->readonly?"Readonly" : this->use_bangbang?"Bangbang":"PID");
76f53dc6
JM
236 sensor->get_raw();
237 TempSensor::sensor_options_t options;
238 if(sensor->get_optional(options)) {
239 for(auto &i : options) {
240 // foreach optional value
a2e5f877 241 gcode->stream->printf("%s(S%d): %c %1.18f\n", this->designator.c_str(), this->pool_index, i.first, i.second);
76f53dc6
JM
242 }
243 }
244 }
245
246 return;
247 }
248
71cc73eb
JM
249 // readonly sensors don't handle the rest
250 if(this->readonly) return;
251
01004e36
JM
252 if (gcode->m == 143) {
253 if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index)) {
254 if(gcode->has_letter('P')) {
255 max_temp= gcode->get_value('P');
256
257 } else {
258 gcode->stream->printf("Nothing set NOTE Usage is M143 S0 P300 where <S> is the hotend index and <P> is the maximum temp to set\n");
259 }
260
261 }else if(gcode->get_num_args() == 0) {
262 gcode->stream->printf("Maximum temperature for %s(%d) is %f°C\n", this->designator.c_str(), this->pool_index, max_temp);
263 }
264
265 } else if (gcode->m == 301) {
4710532a 266 if (gcode->has_letter('S') && (gcode->get_value('S') == this->pool_index)) {
827a49ca 267 if (gcode->has_letter('P'))
201e6dcf 268 setPIDp( gcode->get_value('P') );
827a49ca 269 if (gcode->has_letter('I'))
201e6dcf 270 setPIDi( gcode->get_value('I') );
827a49ca 271 if (gcode->has_letter('D'))
201e6dcf 272 setPIDd( gcode->get_value('D') );
827a49ca 273 if (gcode->has_letter('X'))
f1e38d95
JM
274 this->i_max = gcode->get_value('X');
275 if (gcode->has_letter('Y'))
276 this->heater_pin.max_pwm(gcode->get_value('Y'));
277
278 }else if(!gcode->has_letter('S')) {
279 gcode->stream->printf("%s(S%d): Pf:%g If:%g Df:%g X(I_max):%g max pwm: %d O:%d\n", this->designator.c_str(), this->pool_index, this->p_factor, this->i_factor / this->PIDdt, this->d_factor * this->PIDdt, this->i_max, this->heater_pin.max_pwm(), o);
280 }
281
4710532a 282 } else if (gcode->m == 500 || gcode->m == 503) { // M500 saves some volatile settings to config override file, M503 just prints the settings
f1e38d95
JM
283 gcode->stream->printf(";PID settings:\nM301 S%d P%1.4f I%1.4f D%1.4f X%1.4f Y%d\n", this->pool_index, this->p_factor, this->i_factor / this->PIDdt, this->d_factor * this->PIDdt, this->i_max, this->heater_pin.max_pwm());
284
01004e36
JM
285 gcode->stream->printf(";Max temperature setting:\nM143 S%d P%1.4f\n", this->pool_index, this->max_temp);
286
f1e38d95
JM
287 if(this->sensor_settings) {
288 // get or save any sensor specific optional values
289 TempSensor::sensor_options_t options;
290 if(sensor->get_optional(options) && !options.empty()) {
291 gcode->stream->printf(";Optional temp sensor specific settings:\nM305 S%d", this->pool_index);
292 for(auto &i : options) {
7d678d16 293 gcode->stream->printf(" %c%1.18f", i.first, i.second);
f1e38d95
JM
294 }
295 gcode->stream->printf("\n");
296 }
297 }
33e4cc02 298
8e8b938e 299 } else if( ( gcode->m == this->set_m_code || gcode->m == this->set_and_wait_m_code ) && gcode->has_letter('S')) {
42cb1b30 300 // this only gets handled if it is not controlled by the tool manager or is active in the toolmanager
8e8b938e
JM
301 this->active = true;
302
303 // this is safe as old configs as well as single extruder configs the toolmanager will not be running so will return false
304 // this will also ignore anything that the tool manager is not controlling and return false, otherwise it returns the active tool
305 void *returned_data;
75e6428d 306 bool ok = PublicData::get_value( tool_manager_checksum, is_active_tool_checksum, this->name_checksum, &returned_data );
8e8b938e
JM
307 if (ok) {
308 uint16_t active_tool_name = *static_cast<uint16_t *>(returned_data);
309 this->active = (active_tool_name == this->name_checksum);
310 }
311
312 if(this->active) {
42cb1b30 313 // required so temp change happens in order
04782655 314 THEKERNEL->conveyor->wait_for_idle();
2134bcf2 315
42cb1b30 316 float v = gcode->get_value('S');
db453125 317
42cb1b30
JM
318 if (v == 0.0) {
319 this->target_temperature = UNDEFINED;
320 this->heater_pin.set((this->o = 0));
321 } else {
322 this->set_desired_temperature(v);
ed52bf31
JM
323 // wait for temp to be reached, no more gcodes will be fetched until this is complete
324 if( gcode->m == this->set_and_wait_m_code) {
487976f7 325 if(isinf(get_temperature()) && isinf(sensor->get_temperature())) {
f0433db4 326 THEKERNEL->streams->printf("Temperature reading is unreliable on %s HALT asserted - reset or M999 required\n", designator.c_str());
487976f7
JM
327 THEKERNEL->call_event(ON_HALT, nullptr);
328 return;
329 }
330
ed52bf31
JM
331 this->waiting = true; // on_second_tick will announce temps
332 while ( get_temperature() < target_temperature ) {
333 THEKERNEL->call_event(ON_IDLE, this);
798295c1 334 // check if ON_HALT was called (usually by kill button)
aa896868
JM
335 if(THEKERNEL->is_halted() || this->target_temperature == UNDEFINED) {
336 THEKERNEL->streams->printf("Wait on temperature aborted by kill\n");
337 break;
338 }
ed52bf31
JM
339 }
340 this->waiting = false;
42cb1b30 341 }
cf1a7632 342 }
907d5e8a 343 }
df27a6a3 344 }
df27a6a3 345 }
ded56b35
AW
346}
347
4710532a
JM
348void TemperatureControl::on_get_public_data(void *argument)
349{
350 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
201e6dcf 351
b19aa09d
JM
352 if(!pdr->starts_with(temperature_control_checksum)) return;
353
8e8b938e
JM
354 if(pdr->second_element_is(pool_index_checksum)) {
355 // asking for our instance pointer if we have this pool_index
356 if(pdr->third_element_is(this->pool_index)) {
357 static void *return_data;
358 return_data = this;
359 pdr->set_data_ptr(&return_data);
360 pdr->set_taken();
361 }
8e8b938e 362
56a6c8c1 363 }else if(pdr->second_element_is(poll_controls_checksum)) {
bab4e1bd 364 // polling for all temperature controls
3bfb2639
JM
365 // add our data to the list which is passed in via the data_ptr
366
367 std::vector<struct pad_temperature> *v= static_cast<std::vector<pad_temperature>*>(pdr->get_data_ptr());
bab4e1bd 368
3bfb2639 369 struct pad_temperature t;
bab4e1bd 370 // setup data
3bfb2639
JM
371 t.current_temperature = this->get_temperature();
372 t.target_temperature = (target_temperature <= 0) ? 0 : this->target_temperature;
373 t.pwm = this->o;
374 t.designator= this->designator;
375 t.id= this->name_checksum;
376 v->push_back(t);
377 pdr->set_taken();
b19aa09d 378
56a6c8c1
JM
379 }else if(pdr->second_element_is(current_temperature_checksum)) {
380 // if targeted at us
381 if(pdr->third_element_is(this->name_checksum)) {
564cf1f0 382 // ok this is targeted at us, so set the requ3sted data in the pointer passed into us
56a6c8c1
JM
383 struct pad_temperature *t= static_cast<pad_temperature*>(pdr->get_data_ptr());
384 t->current_temperature = this->get_temperature();
385 t->target_temperature = (target_temperature <= 0) ? 0 : this->target_temperature;
386 t->pwm = this->o;
387 t->designator= this->designator;
388 t->id= this->name_checksum;
389 pdr->set_taken();
56a6c8c1 390 }
b19aa09d 391 }
8e8b938e 392
8293d443 393}
db453125 394
4710532a
JM
395void TemperatureControl::on_set_public_data(void *argument)
396{
397 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
77047e76 398
991d98cc 399 if(!pdr->starts_with(temperature_control_checksum)) return;
77047e76 400
8e8b938e 401 if(!pdr->second_element_is(this->name_checksum)) return;
77047e76 402
991d98cc 403 // ok this is targeted at us, so set the temp
ed52bf31 404 // NOTE unlike the M code this will set the temp now not when the queue is empty
4710532a 405 float t = *static_cast<float *>(pdr->get_data_ptr());
991d98cc
JM
406 this->set_desired_temperature(t);
407 pdr->set_taken();
77047e76
JM
408}
409
1ad23cd3 410void TemperatureControl::set_desired_temperature(float desired_temperature)
cf1a7632 411{
7d4baeee
AW
412 // Never go over the configured max temperature
413 if( desired_temperature > this->max_temp ){
414 desired_temperature = this->max_temp;
415 }
416
08f89868 417 if (desired_temperature == 1.0F)
cf1a7632 418 desired_temperature = preset1;
08f89868 419 else if (desired_temperature == 2.0F)
cf1a7632
MM
420 desired_temperature = preset2;
421
08f89868 422 float last_target_temperature= target_temperature;
907d5e8a 423 target_temperature = desired_temperature;
3517edce 424 if (desired_temperature <= 0.0F){
08f89868 425 // turning it off
b35ac71e 426 heater_pin.set((this->o = 0));
08f89868 427
3517edce 428 }else if(last_target_temperature <= 0.0F) {
08f89868
JM
429 // if it was off and we are now turning it on we need to initialize
430 this->lastInput= last_reading;
431 // set to whatever the output currently is See http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-initialization/
432 this->iTerm= this->o;
433 if (this->iTerm > this->i_max) this->iTerm = this->i_max;
434 else if (this->iTerm < 0.0) this->iTerm = 0.0;
435 }
ded56b35
AW
436}
437
4710532a
JM
438float TemperatureControl::get_temperature()
439{
907d5e8a 440 return last_reading;
ded56b35
AW
441}
442
4710532a
JM
443uint32_t TemperatureControl::thermistor_read_tick(uint32_t dummy)
444{
9d955060 445 float temperature = sensor->get_temperature();
3517edce 446 if(!this->readonly && target_temperature > 2) {
01004e36
JM
447 if (isinf(temperature) || temperature < min_temp || temperature > max_temp) {
448 this->temp_violated = true;
907d5e8a 449 target_temperature = UNDEFINED;
4710532a
JM
450 heater_pin.set((this->o = 0));
451 } else {
907d5e8a 452 pid_process(temperature);
ded56b35 453 }
959dc7db 454 }
3517edce 455
907d5e8a 456 last_reading = temperature;
281967e4 457 return 0;
ded56b35
AW
458}
459
10e66797
JM
460/**
461 * Based on https://github.com/br3ttb/Arduino-PID-Library
462 */
1ad23cd3 463void TemperatureControl::pid_process(float temperature)
907d5e8a 464{
989d0e94 465 if(use_bangbang) {
b35ac71e 466 // bang bang is very simple, if temp is < target - hysteresis turn on full else if temp is > target + hysteresis turn heater off
989d0e94 467 // good for relays
4710532a 468 if(temperature > (target_temperature + hysteresis) && this->o > 0) {
989d0e94 469 heater_pin.set(false);
4710532a 470 this->o = 0; // for display purposes only
989d0e94 471
4710532a 472 } else if(temperature < (target_temperature - hysteresis) && this->o <= 0) {
989d0e94
JM
473 if(heater_pin.max_pwm() >= 255) {
474 // turn on full
475 this->heater_pin.set(true);
4710532a
JM
476 this->o = 255; // for display purposes only
477 } else {
989d0e94
JM
478 // only to whatever max pwm is configured
479 this->heater_pin.pwm(heater_pin.max_pwm());
4710532a 480 this->o = heater_pin.max_pwm(); // for display purposes only
989d0e94 481 }
989d0e94
JM
482 }
483 return;
484 }
ded56b35 485
989d0e94
JM
486 // regular PID control
487 float error = target_temperature - temperature;
08f89868 488
0d84905d
PA
489 float new_I = this->iTerm + (error * this->i_factor);
490 if (new_I > this->i_max) new_I = this->i_max;
491 else if (new_I < 0.0) new_I = 0.0;
08f89868 492 if(!this->windup) this->iTerm= new_I;
ded56b35 493
4710532a 494 float d = (temperature - this->lastInput);
ded56b35 495
10e66797
JM
496 // calculate the PID output
497 // TODO does this need to be scaled by max_pwm/256? I think not as p_factor already does that
0d84905d 498 this->o = (this->p_factor * error) + new_I - (this->d_factor * d);
8cdae54c 499
7dee00e4 500 if (this->o >= heater_pin.max_pwm())
7dee00e4 501 this->o = heater_pin.max_pwm();
27aecda6 502 else if (this->o < 0)
907d5e8a 503 this->o = 0;
08f89868 504 else if(this->windup)
0d84905d 505 this->iTerm = new_I; // Only update I term when output is not saturated.
907d5e8a 506
27aecda6 507 this->heater_pin.pwm(this->o);
4710532a 508 this->lastInput = temperature;
907d5e8a 509}
ded56b35 510
4710532a 511void TemperatureControl::on_second_tick(void *argument)
8ccab7cf
MM
512{
513 if (waiting)
3517edce 514 THEKERNEL->streams->printf("%s:%3.1f /%3.1f @%d\n", designator.c_str(), get_temperature(), ((target_temperature <= 0) ? 0.0 : target_temperature), o);
8ccab7cf 515}
201e6dcf 516
4710532a
JM
517void TemperatureControl::setPIDp(float p)
518{
519 this->p_factor = p;
201e6dcf
JM
520}
521
4710532a
JM
522void TemperatureControl::setPIDi(float i)
523{
524 this->i_factor = i * this->PIDdt;
201e6dcf
JM
525}
526
4710532a
JM
527void TemperatureControl::setPIDd(float d)
528{
529 this->d_factor = d / this->PIDdt;
201e6dcf 530}