Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2019-07-26 16:28:10 +0300
committerDavid Crocker <dcrocker@eschertech.com>2019-07-26 16:28:10 +0300
commite8b3883006c7757a33f67160b64c87bbb28a5bcd (patch)
treeae112caeae63c75896fdce5ef636f6a34c43b005 /src/Heating
parentdf3e8f2c766fe2cf8ea0b671cf044cb4d837c3d0 (diff)
Major refactoring of heater management and driver IDs
Renamed class PID to LocalHeater and moved some functions to new base class Heater Added class RemoteHeater Heaters are no longer created by default Replaced driver numbers by class DriverId with both board# and local driver# on Duet 3 Refectored stepper driver management to handle DriverId
Diffstat (limited to 'src/Heating')
-rw-r--r--src/Heating/Heat.cpp542
-rw-r--r--src/Heating/Heat.h78
-rw-r--r--src/Heating/Heater.cpp175
-rw-r--r--src/Heating/Heater.h120
-rw-r--r--src/Heating/LocalHeater.cpp (renamed from src/Heating/Pid.cpp)368
-rw-r--r--src/Heating/LocalHeater.h96
-rw-r--r--src/Heating/Pid.h203
-rw-r--r--src/Heating/RemoteHeater.cpp97
-rw-r--r--src/Heating/RemoteHeater.h43
9 files changed, 1046 insertions, 676 deletions
diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp
index 8efb7d95..3590e34a 100644
--- a/src/Heating/Heat.cpp
+++ b/src/Heating/Heat.cpp
@@ -19,17 +19,22 @@ Licence: GPL
****************************************************************************************************/
#include "Heat.h"
+#include "LocalHeater.h"
#include "HeaterProtection.h"
#include "Platform.h"
#include "RepRap.h"
#include "Sensors/TemperatureSensor.h"
#include "GCodes/GCodeBuffer/GCodeBuffer.h"
+#include "Tasks.h"
#if SUPPORT_DHT_SENSOR
# include "Sensors/DhtSensor.h"
#endif
-#include "Tasks.h"
+#if SUPPORT_CAN_EXPANSION
+# include "CanId.h"
+# include "RemoteHeater.h"
+#endif
constexpr uint32_t HeaterTaskStackWords = 400; // task stack size in dwords, must be large enough for auto tuning
static Task<HeaterTaskStackWords> heaterTask;
@@ -50,10 +55,138 @@ Heat::Heat()
heaterProtections[index] = new HeaterProtection(index);
}
- for (size_t heater : ARRAY_INDICES(pids))
+ for (Heater*& h : heaters)
{
- pids[heater] = new PID(heater);
+ h = nullptr;
}
+
+ // Then set up the real heaters and the corresponding PIDs
+ for (const Tool*& t : lastStandbyTools)
+ {
+ t = nullptr;
+ }
+
+}
+
+Heater *Heat::FindHeater(int heater) const
+{
+ return (heater < 0 || heater >= (int)MaxHeaters) ? nullptr : heaters[heater];
+}
+
+GCodeResult Heat::SetOrReportHeaterModel(GCodeBuffer& gb, const StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ const unsigned int heater = gb.GetUIValue();
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
+ {
+ const FopDt& model = h->GetModel();
+ bool seen = false;
+ float gain = model.GetGain(),
+ tc = model.GetTimeConstant(),
+ td = model.GetDeadTime(),
+ maxPwm = model.GetMaxPwm(),
+ voltage = model.GetVoltage();
+ int32_t dontUsePid = model.UsePid() ? 0 : 1;
+ int32_t inversionParameter = 0;
+
+ gb.TryGetFValue('A', gain, seen);
+ gb.TryGetFValue('C', tc, seen);
+ gb.TryGetFValue('D', td, seen);
+ gb.TryGetIValue('B', dontUsePid, seen);
+ gb.TryGetFValue('S', maxPwm, seen);
+ gb.TryGetFValue('V', voltage, seen);
+ gb.TryGetIValue('I', inversionParameter, seen);
+
+ if (seen)
+ {
+ const bool inverseTemperatureControl = (inversionParameter == 1 || inversionParameter == 3);
+ const GCodeResult rslt = h->SetModel(gain, tc, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply);
+ if (rslt != GCodeResult::ok)
+ {
+ return rslt;
+ }
+ }
+ else if (!model.IsEnabled())
+ {
+ reply.printf("Heater %u is disabled", heater);
+ }
+ else
+ {
+ const char* const mode = (!model.UsePid()) ? "bang-bang"
+ : (model.ArePidParametersOverridden()) ? "custom PID"
+ : "PID";
+ reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, calibration voltage %.1f, mode %s", heater,
+ (double)model.GetGain(), (double)model.GetTimeConstant(), (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode);
+ if (model.IsInverted())
+ {
+ reply.cat(", inverted temperature control");
+ }
+ if (model.UsePid())
+ {
+ // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
+ M301PidParameters params = model.GetM301PidParameters(false);
+ reply.catf("\nComputed PID parameters for setpoint change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
+ params = model.GetM301PidParameters(true);
+ reply.catf("\nComputed PID parameters for load change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
+ }
+ }
+ }
+
+ reply.printf("Heater %u not found", heater);
+ return GCodeResult::error;
+ }
+
+ return GCodeResult::badOrMissingParameter;
+}
+
+// Process M301 or M304. 'heater' is the default heater number to use.
+GCodeResult Heat::SetPidParameters(unsigned int heater, GCodeBuffer& gb, const StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ heater = gb.GetUIValue();
+ }
+
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
+ {
+ const FopDt& model = h->GetModel();
+ M301PidParameters pp = model.GetM301PidParameters(false);
+ bool seen = false;
+ gb.TryGetFValue('P', pp.kP, seen);
+ gb.TryGetFValue('I', pp.kI, seen);
+ gb.TryGetFValue('D', pp.kD, seen);
+
+ if (seen)
+ {
+ h->SetM301PidParameters(pp);
+ }
+ else if (!model.UsePid())
+ {
+ reply.printf("Heater %d is in bang-bang mode", heater);
+ }
+ else if (model.ArePidParametersOverridden())
+ {
+ reply.printf("Heater %d P:%.1f I:%.3f D:%.1f", heater, (double)pp.kP, (double)pp.kI, (double)pp.kD);
+ }
+ else
+ {
+ reply.printf("Heater %d uses model-derived PID parameters. Use M307 H%d to view them", heater, heater);
+ }
+ return GCodeResult::ok;
+ }
+
+ reply.printf("Heater %u not found", heater);
+ return GCodeResult::error;
+}
+
+// Is the heater enabled?
+bool Heat::IsHeaterEnabled(size_t heater) const
+{
+ Heater * const h = FindHeater(heater);
+ return h != nullptr && h->IsHeaterEnabled();
}
// Get a pointer to the temperature sensor entry, or nullptr if the sensor number is bad
@@ -147,17 +280,19 @@ void Heat::InsertSensor(TemperatureSensor *sensor)
// Reset all heater models to defaults. Called when running M502.
void Heat::ResetHeaterModels()
{
- for (size_t heater : ARRAY_INDICES(pids))
+ for (size_t heater : ARRAY_INDICES(heaters))
{
- if (pids[heater]->IsHeaterEnabled())
+ Heater * const h = heaters[heater];
+ if (h != nullptr && h->IsHeaterEnabled())
{
+ String<1> dummy;
if (IsBedOrChamberHeater(heater))
{
- pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, 0.0, false, false);
+ h->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, 0.0, false, false, dummy.GetRef());
}
else
{
- pids[heater]->SetModel(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, 1.0, 0.0, true, false);
+ h->SetModel(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, 1.0, 0.0, true, false, dummy.GetRef());
}
}
}
@@ -172,37 +307,6 @@ void Heat::Init()
const float tempLimit = (IsBedOrChamberHeater(index)) ? DefaultBedTemperatureLimit : DefaultExtruderTemperatureLimit;
prot->Init(tempLimit);
-
- if (index < MaxHeaters)
- {
- pids[index]->SetHeaterProtection(prot);
- }
- }
-
- // Then set up the real heaters and the corresponding PIDs
- for (size_t heater : ARRAY_INDICES(pids))
- {
-#ifdef PCCB
- // PCCB has no heaters by default, but we pretend that the LED outputs are heaters. So disable the PID controllers.
- pids[heater]->Init(-1.0, -1.0, -1.0, true, false);
-#else
- if (IsBedOrChamberHeater(heater))
- {
- pids[heater]->Init(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, false, false);
- }
-#if defined(DUET_06_085)
- else if (heater == MaxHeaters - 1)
- {
- // On the Duet 085, the heater 6 pin is also the fan 1 pin. By default we support fan 1, so disable heater 6.
- pids[heater]->Init(-1.0, -1.0, -1.0, true, false);
- }
-#endif
- else
- {
- pids[heater]->Init(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, true, false);
- }
-#endif
- lastStandbyTools[heater] = nullptr;
}
#if SUPPORT_DHT_SENSOR
@@ -219,9 +323,12 @@ void Heat::Init()
void Heat::Exit()
{
- for (PID *pid : pids)
+ for (Heater *h : heaters)
{
- pid->SwitchOff();
+ if (h != nullptr)
+ {
+ h->SwitchOff();
+ }
}
heaterTask.Suspend();
@@ -232,13 +339,16 @@ void Heat::Exit()
uint32_t lastWakeTime = xTaskGetTickCount();
for (;;)
{
- for (PID *& p : pids)
+ for (Heater *h : heaters)
{
- p->Spin();
+ if (h != nullptr)
+ {
+ h->Spin();
+ }
}
// See if we have finished tuning a PID
- if (heaterBeingTuned != -1 && !pids[heaterBeingTuned]->IsTuning())
+ if (heaterBeingTuned != -1 && heaters[heaterBeingTuned]->GetStatus() != HeaterStatus::tuning)
{
lastHeaterTuned = heaterBeingTuned;
heaterBeingTuned = -1;
@@ -266,21 +376,68 @@ void Heat::Diagnostics(MessageType mtype)
}
platform.Message(mtype, "\n");
- for (size_t heater : ARRAY_INDICES(pids))
+ for (size_t heater : ARRAY_INDICES(heaters))
{
- if (pids[heater]->Active())
+ if (heaters[heater] != nullptr && heaters[heater]->GetStatus() == HeaterStatus::active)
{
- platform.MessageF(mtype, "Heater %d is on, I-accum = %.1f\n", heater, (double)(pids[heater]->GetAccumulator()));
+ platform.MessageF(mtype, "Heater %u is on, I-accum = %.1f\n", heater, (double)(heaters[heater]->GetAccumulator()));
}
}
}
-// Configure a heater. 'freq' is 0 if no PWM frequency has been given.
+// Configure a heater
GCodeResult Heat::ConfigureHeater(size_t heater, GCodeBuffer& gb, const StringRef& reply)
{
if (heater < MaxHeaters)
{
- return pids[heater]->ConfigurePortAndSensor(gb, reply);
+ Heater *h = heaters[heater];
+
+#if SUPPORT_CAN_EXPANSION
+ CanAddress boardAddr = CanId::NoCanAddress;
+ if (gb.Seen('C'))
+ {
+ String<StringLength20> portName;
+ if (!gb.GetReducedString(portName.GetRef()))
+ {
+ reply.copy("missing port name");
+ return GCodeResult::error;
+ }
+ boardAddr = IoPort::RemoveBoardAddress(portName.GetRef());
+ }
+
+ if (boardAddr == CanId::NoCanAddress)
+ {
+ // No port given, so just configure the existing heater if there is one
+ if (h == nullptr)
+ {
+ reply.printf("port name needed to create new heater %u", heater);
+ return GCodeResult::error;
+ }
+ }
+ else
+ {
+ // A port has been provided, so create a new heater
+ if (boardAddr == 0)
+ {
+ heaters[heater] = (h == nullptr) ? new LocalHeater(heater) : new LocalHeater(*h);
+ }
+ else
+ {
+ heaters[heater] = (h == nullptr) ? new RemoteHeater(heater, boardAddr) : new RemoteHeater(*h, boardAddr);
+ }
+ if (h != nullptr)
+ {
+ h->SwitchOff();
+ delete h;
+ }
+ }
+#else
+ if (h == nullptr)
+ {
+ heaters[heater] = new LocalHeater(heater);
+ }
+#endif
+ return heaters[heater]->ConfigurePortAndSensor(gb, reply);
}
reply.copy("Heater number out of range");
@@ -289,7 +446,7 @@ GCodeResult Heat::ConfigureHeater(size_t heater, GCodeBuffer& gb, const StringRe
bool Heat::AllHeatersAtSetTemperatures(bool includingBed, float tolerance) const
{
- for (size_t heater : ARRAY_INDICES(pids))
+ for (size_t heater : ARRAY_INDICES(heaters))
{
if (!HeaterAtSetTemperature(heater, true, tolerance) && (includingBed || !IsBedHeater(heater)))
{
@@ -302,39 +459,37 @@ bool Heat::AllHeatersAtSetTemperatures(bool includingBed, float tolerance) const
//query an individual heater
bool Heat::HeaterAtSetTemperature(int heater, bool waitWhenCooling, float tolerance) const
{
- // If it hasn't anything to do, it must be right wherever it is...
- if (heater < 0 || heater >= (int)MaxHeaters || pids[heater]->SwitchedOff() || pids[heater]->FaultOccurred())
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- return true;
+ const HeaterStatus stat = h->GetStatus();
+ if (stat == HeaterStatus::active || stat == HeaterStatus::standby)
+ {
+ const float dt = h->GetTemperature();
+ const float target = (stat == HeaterStatus::active) ? h->GetActiveTemperature() : h->GetStandbyTemperature();
+ return (target < TEMPERATURE_LOW_SO_DONT_CARE)
+ || (fabsf(dt - target) <= tolerance)
+ || (target < dt && !waitWhenCooling);
+
+ }
}
- const float dt = GetHeaterTemperature(heater);
- const float target = (pids[heater]->Active()) ? GetActiveTemperature(heater) : GetStandbyTemperature(heater);
- return (target < TEMPERATURE_LOW_SO_DONT_CARE)
- || (fabsf(dt - target) <= tolerance)
- || (target < dt && !waitWhenCooling);
+ // If the heater doesn't exist or is switched off or in a fault state, there is nothing to wait for
+ return true;
}
-Heat::HeaterStatus Heat::GetStatus(int heater) const
+HeaterStatus Heat::GetStatus(int heater) const
{
- if (heater < 0 || heater >= (int)MaxHeaters)
- {
- return HS_off;
- }
-
- return (pids[heater]->FaultOccurred()) ? HS_fault
- : (pids[heater]->SwitchedOff()) ? HS_off
- : (pids[heater]->IsTuning()) ? HS_tuning
- : (pids[heater]->Active()) ? HS_active
- : HS_standby;
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? HeaterStatus::off : heaters[heater]->GetStatus();
}
void Heat::SetBedHeater(size_t index, int heater)
{
- const int bedHeater = bedHeaters[index];
- if (bedHeater >= 0)
+ Heater * const h = FindHeater(bedHeaters[index]);
+ if (h != nullptr)
{
- pids[bedHeater]->SwitchOff();
+ h->SwitchOff();
}
bedHeaters[index] = heater;
}
@@ -353,10 +508,10 @@ bool Heat::IsBedHeater(int heater) const
void Heat::SetChamberHeater(size_t index, int heater)
{
- const int chamberHeater = chamberHeaters[index];
- if (chamberHeater >= 0)
+ Heater * const h = FindHeater(chamberHeaters[index]);
+ if (h != nullptr)
{
- pids[chamberHeater]->SwitchOff();
+ h->SwitchOff();
}
chamberHeaters[index] = heater;
}
@@ -375,28 +530,32 @@ bool Heat::IsChamberHeater(int heater) const
void Heat::SetActiveTemperature(int heater, float t)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->SetActiveTemperature(t);
+ h->SetActiveTemperature(t);
}
}
float Heat::GetActiveTemperature(int heater) const
{
- return (heater >= 0 && heater < (int)MaxHeaters) ? pids[heater]->GetActiveTemperature() : ABS_ZERO;
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? ABS_ZERO : h->GetActiveTemperature();
}
void Heat::SetStandbyTemperature(int heater, float t)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->SetStandbyTemperature(t);
+ h->SetStandbyTemperature(t);
}
}
float Heat::GetStandbyTemperature(int heater) const
{
- return (heater >= 0 && heater < (int)MaxHeaters) ? pids[heater]->GetStandbyTemperature() : ABS_ZERO;
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? ABS_ZERO : h->GetStandbyTemperature();
}
float Heat::GetHighestTemperatureLimit(int heater) const
@@ -443,31 +602,34 @@ float Heat::GetLowestTemperatureLimit(int heater) const
// Return ABS_ZERO if the heater doesn't exist. The Z probe class relies on this.
float Heat::GetHeaterTemperature(int heater) const
{
- return (heater >= 0 && heater < (int)MaxHeaters) ? pids[heater]->GetTemperature() : ABS_ZERO;
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? ABS_ZERO : h->GetTemperature();
}
// Get the target temperature of a heater
float Heat::GetTargetTemperature(int heater) const
{
- const Heat::HeaterStatus hs = GetStatus(heater);
- return (hs == HS_active) ? GetActiveTemperature(heater)
- : (hs == HS_standby) ? GetStandbyTemperature(heater)
+ const HeaterStatus hs = GetStatus(heater);
+ return (hs == HeaterStatus::active) ? GetActiveTemperature(heater)
+ : (hs == HeaterStatus::standby) ? GetStandbyTemperature(heater)
: 0.0;
}
void Heat::Activate(int heater)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->Activate();
+ h->Activate();
}
}
void Heat::SwitchOff(int heater)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->SwitchOff();
+ h->SwitchOff();
lastStandbyTools[heater] = nullptr;
}
}
@@ -476,38 +638,37 @@ void Heat::SwitchOffAll(bool includingChamberAndBed)
{
for (int heater = 0; heater < (int)MaxHeaters; ++heater)
{
- if (includingChamberAndBed || !IsBedOrChamberHeater(heater))
+ Heater * const h = heaters[heater];
+ if (h != nullptr && (includingChamberAndBed || !IsBedOrChamberHeater(heater)))
{
- pids[heater]->SwitchOff();
+ h->SwitchOff();
}
}
}
void Heat::Standby(int heater, const Tool *tool)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->Standby();
+ h->Standby();
lastStandbyTools[heater] = tool;
}
}
void Heat::ResetFault(int heater)
{
- if (heater >= 0 && heater < (int)MaxHeaters)
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
{
- pids[heater]->ResetFault();
+ h->ResetFault();
}
}
float Heat::GetAveragePWM(size_t heater) const
{
- return pids[heater]->GetAveragePWM();
-}
-
-uint32_t Heat::GetLastSampleTime(size_t heater) const
-{
- return pids[heater]->GetLastSampleTime();
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? 0.0 : h->GetAveragePWM();
}
bool Heat::IsBedOrChamberHeater(int heater) const
@@ -515,39 +676,6 @@ bool Heat::IsBedOrChamberHeater(int heater) const
return IsBedHeater(heater) || IsChamberHeater(heater);
}
-// Auto tune a PID
-void Heat::StartAutoTune(size_t heater, float temperature, float maxPwm, const StringRef& reply)
-{
- if (heaterBeingTuned == -1)
- {
- heaterBeingTuned = (int8_t)heater;
- pids[heater]->StartAutoTune(temperature, maxPwm, reply);
- }
- else
- {
- // Trying to start a new auto tune, but we are already tuning a heater
- reply.printf("Error: cannot start auto tuning heater %u because heater %d is being tuned", heater, heaterBeingTuned);
- }
-}
-
-bool Heat::IsTuning(size_t heater) const
-{
- return pids[heater]->IsTuning();
-}
-
-void Heat::GetAutoTuneStatus(const StringRef& reply) const
-{
- int8_t whichPid = (heaterBeingTuned == -1) ? lastHeaterTuned : heaterBeingTuned;
- if (whichPid != -1)
- {
- pids[whichPid]->GetAutoTuneStatus(reply);
- }
- else
- {
- reply.copy("No heater has been tuned yet");
- }
-}
-
// Get the highest temperature limit of any heater
float Heat::GetHighestTemperatureLimit() const
{
@@ -566,24 +694,21 @@ float Heat::GetHighestTemperatureLimit() const
return limit;
}
-// Override the model-generated PID parameters
-void Heat::SetM301PidParameters(size_t heater, const M301PidParameters& params)
-{
- pids[heater]->SetM301PidParameters(params);
-}
-
#if HAS_MASS_STORAGE
// Write heater model parameters to file returning true if no error
bool Heat::WriteModelParameters(FileStore *f) const
{
bool ok = f->Write("; Heater model parameters\n");
- for (size_t h : ARRAY_INDICES(pids))
+ for (size_t h : ARRAY_INDICES(heaters))
{
- const FopDt& model = pids[h]->GetModel();
- if (model.IsEnabled())
+ if (heaters[h] != nullptr)
{
- ok = model.WriteParameters(f, h);
+ const FopDt& model = heaters[h]->GetModel();
+ if (model.IsEnabled())
+ {
+ ok = model.WriteParameters(f, h);
+ }
}
}
return ok;
@@ -591,6 +716,89 @@ bool Heat::WriteModelParameters(FileStore *f) const
#endif
+// Process M570
+GCodeResult Heat::ConfigureHeaterMonitoring(size_t heater, GCodeBuffer& gb, const StringRef& reply)
+{
+ if (heater < MaxHeaters && heaters[heater] != nullptr)
+ {
+ bool seenValue = false;
+ float maxTempExcursion, maxFaultTime;
+ heaters[heater]->GetFaultDetectionParameters(maxTempExcursion, maxFaultTime);
+ gb.TryGetFValue('P', maxFaultTime, seenValue);
+ gb.TryGetFValue('T', maxTempExcursion, seenValue);
+ if (seenValue)
+ {
+ heaters[heater]->SetFaultDetectionParameters(maxTempExcursion, maxFaultTime);
+ }
+ else
+ {
+ reply.printf("Heater %u allowed excursion %.1f" DEGREE_SYMBOL "C, fault trigger time %.1f seconds", heater, (double)maxTempExcursion, (double)maxFaultTime);
+ }
+ }
+ return GCodeResult::ok;
+}
+
+// Process M303
+GCodeResult Heat::TuneHeater(GCodeBuffer& gb, const StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ const unsigned int heater = gb.GetUIValue();
+ Heater * const h = FindHeater(heater);
+ if (h != nullptr)
+ {
+ const float temperature = (gb.Seen('S')) ? gb.GetFValue()
+ : reprap.GetHeat().IsBedHeater(heater) ? 75.0
+ : reprap.GetHeat().IsChamberHeater(heater) ? 50.0
+ : 200.0;
+ const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : h->GetModel().GetMaxPwm();
+ if (!h->CheckGood())
+ {
+ reply.copy("Heater is not ready to perform PID auto-tuning");
+ }
+ else if (maxPwm < 0.1 || maxPwm > 1.0)
+ {
+ reply.copy("Invalid PWM value");
+ }
+ else
+ {
+ if (heaterBeingTuned == -1)
+ {
+ heaterBeingTuned = (int8_t)heater;
+ h->StartAutoTune(temperature, maxPwm, reply);
+ return GCodeResult::ok;
+ }
+ else
+ {
+ // Trying to start a new auto tune, but we are already tuning a heater
+ reply.printf("Error: cannot start auto tuning heater %u because heater %d is being tuned", heater, heaterBeingTuned);
+ }
+ }
+ }
+ else
+ {
+ reply.printf("Heater %u not found", heater);
+ }
+ return GCodeResult::error;
+ }
+ else
+ {
+ // Report the auto tune status
+ const int whichPid = (heaterBeingTuned == -1) ? lastHeaterTuned : heaterBeingTuned;
+ Heater * const h = FindHeater(whichPid);
+
+ if (h != nullptr)
+ {
+ h->GetAutoTuneStatus(reply);
+ }
+ else
+ {
+ reply.copy("No heater has been tuned yet");
+ }
+ return GCodeResult::ok;
+ }
+}
+
// Process M308
GCodeResult Heat::ConfigureSensor(GCodeBuffer& gb, const StringRef& reply)
{
@@ -684,17 +892,10 @@ GCodeResult Heat::ConfigureSensor(GCodeBuffer& gb, const StringRef& reply)
}
// Get the name of a heater, or nullptr if it hasn't been named
-const char *Heat::GetHeaterName(size_t heater) const
+const char *Heat::GetHeaterSensorName(size_t heater) const
{
- if (heater < MaxHeaters)
- {
- TemperatureSensor *sensor = pids[heater]->GetSensor();
- if (sensor != nullptr)
- {
- return sensor->GetSensorName();
- }
- }
- return nullptr;
+ Heater * const h = FindHeater(heater);
+ return (h != nullptr) ? h->GetSensorName() : nullptr;
}
// Return the protection parameters of the given index
@@ -712,7 +913,7 @@ void Heat::UpdateHeaterProtection()
{
// Reassign the first mapped heater protection item of each PID where applicable
// and rebuild the linked list of heater protection elements per heater
- for (size_t heater : ARRAY_INDICES(pids))
+ for (size_t heater : ARRAY_INDICES(heaters))
{
// Rebuild linked lists
HeaterProtection *firstProtectionItem = nullptr;
@@ -740,16 +941,13 @@ void Heat::UpdateHeaterProtection()
}
// Update reference to the first item so that we can achieve better performance
- pids[heater]->SetHeaterProtection(firstProtectionItem);
+ if (heaters[heater] != nullptr)
+ {
+ heaters[heater]->SetHeaterProtection(firstProtectionItem);
+ }
}
}
-// Check if the heater is able to operate returning true if everything is OK
-bool Heat::CheckHeater(size_t heater)
-{
- return !pids[heater]->FaultOccurred() && pids[heater]->CheckProtection();
-}
-
// Get the temperature of a sensor
float Heat::GetSensorTemperature(int sensorNum, TemperatureError& err) const
{
@@ -768,15 +966,19 @@ float Heat::GetSensorTemperature(int sensorNum, TemperatureError& err) const
// Get the temperature of a heater
float Heat::GetHeaterTemperature(size_t heater) const
{
- return pids[heater]->GetTemperature();
+ Heater * const h = FindHeater(heater);
+ return (h == nullptr) ? ABS_ZERO : h->GetTemperature();
}
// Suspend the heaters to conserve power or while doing Z probing
void Heat::SuspendHeaters(bool sus)
{
- for (PID *p : pids)
+ for (Heater *h : heaters)
{
- p->Suspend(sus);
+ if (h != nullptr)
+ {
+ h->Suspend(sus);
+ }
}
}
@@ -790,18 +992,18 @@ bool Heat::WriteBedAndChamberTempSettings(FileStore *f) const
const StringRef buf = bufSpace.GetRef();
for (size_t index : ARRAY_INDICES(bedHeaters))
{
- const int bedHeater = bedHeaters[index];
- if (bedHeater >= 0 && pids[bedHeater]->Active() && !pids[bedHeater]->SwitchedOff())
+ Heater * const h = FindHeater(bedHeaters[index]);
+ if (h != nullptr && h->GetStatus() == HeaterStatus::active)
{
- buf.printf("M140 P%u S%.1f\n", index, (double)GetActiveTemperature(bedHeater));
+ buf.printf("M140 P%u S%.1f\n", index, (double)h->GetActiveTemperature());
}
}
for (size_t index : ARRAY_INDICES(chamberHeaters))
{
- const int chamberHeater = chamberHeaters[index];
- if (chamberHeater >= 0 && pids[chamberHeater]->Active() && !pids[chamberHeater]->SwitchedOff())
+ Heater * const h = FindHeater(chamberHeaters[index]);
+ if (h != nullptr && h->GetStatus() == HeaterStatus::active)
{
- buf.printf("M141 P%u S%.1f\n", index, (double)GetActiveTemperature(chamberHeater));
+ buf.printf("M141 P%u S%.1f\n", index, (double)h->GetActiveTemperature());
}
}
return (buf.strlen() == 0) || f->Write(buf.c_str());
diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h
index 9459094d..967de8d5 100644
--- a/src/Heating/Heat.h
+++ b/src/Heating/Heat.h
@@ -26,7 +26,8 @@ Licence: GPL
*/
#include "RepRapFirmware.h"
-#include "Pid.h"
+#include "Heater.h"
+#include "TemperatureError.h"
#include "MessageType.h"
#include "GCodes/GCodeResult.h"
@@ -37,12 +38,9 @@ class GCodeBuffer;
class Heat
{
public:
- // Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them.
- enum HeaterStatus { HS_off = 0, HS_standby = 1, HS_active = 2, HS_fault = 3, HS_tuning = 4 };
-
Heat();
- // Methods that don't relate to a particuar heater
+ // Methods that don't relate to a particular heater
void Task();
void Init(); // Set everything up
void Exit(); // Shut everything down
@@ -72,9 +70,10 @@ public:
void SwitchOffAll(bool includingChamberAndBed); // Turn all heaters off
void ResetFault(int heater); // Reset a heater fault for a specific heater or all heaters
- void GetAutoTuneStatus(const StringRef& reply) const; // Get the status of the current or last auto tune
-
+ GCodeResult SetOrReportHeaterModel(GCodeBuffer& gb, const StringRef& reply);
+ GCodeResult TuneHeater(GCodeBuffer& gb, const StringRef& reply);
GCodeResult ConfigureSensor(GCodeBuffer& gb, const StringRef& reply); // Create a sensor or change the parameters for an existing sensor
+ GCodeResult SetPidParameters(unsigned int heater, GCodeBuffer& gb, const StringRef& reply); // Set the P/I/D parameters for a heater
HeaterProtection& AccessHeaterProtection(size_t index) const; // Return the protection parameters of the given index
void UpdateHeaterProtection(); // Updates the PIDs and HeaterProtection items when a heater is remapped
@@ -91,15 +90,12 @@ public:
void Diagnostics(MessageType mtype); // Output useful information
// Methods that relate to a particular heater
- const char *GetHeaterName(size_t heater) const; // Get the name of a heater, or nullptr if it hasn't been named
+ const char *GetHeaterSensorName(size_t heater) const; // Get the name of the sensor associated with heater, or nullptr if it hasn't been named
float GetAveragePWM(size_t heater) const // Return the running average PWM to the heater as a fraction in [0, 1].
pre(heater < MaxHeaters);
bool IsBedOrChamberHeater(int heater) const; // Queried by the Platform class
- uint32_t GetLastSampleTime(size_t heater) const
- pre(heater < MaxHeaters);
-
float GetHeaterTemperature(size_t heater) const; // Result is in degrees Celsius
const Tool* GetLastStandbyTool(int heater) const
@@ -121,35 +117,13 @@ public:
bool HeaterAtSetTemperature(int heater, bool waitWhenCooling, float tolerance) const;
GCodeResult ConfigureHeater(size_t heater, GCodeBuffer& gb, const StringRef& reply);
+ GCodeResult ConfigureHeaterMonitoring(size_t heater, GCodeBuffer& gb, const StringRef& reply);
+
void SetActiveTemperature(int heater, float t);
void SetStandbyTemperature(int heater, float t);
void Activate(int heater); // Turn on a heater
void Standby(int heater, const Tool* tool); // Set a heater to standby
void SwitchOff(int heater); // Turn off a specific heater
- // Is a specific heater at temperature within tolerance?
- void StartAutoTune(size_t heater, float temperature, float maxPwm, const StringRef& reply) // Auto tune a PID
- pre(heater < MaxHeaters);
-
- bool IsTuning(size_t heater) const // Return true if the specified heater is auto tuning
- pre(heater < MaxHeaters);
-
- const FopDt& GetHeaterModel(size_t heater) const // Get the process model for the specified heater
- pre(heater < MaxHeaters);
-
- bool SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted) // Set the heater process model
- pre(heater < MaxHeaters);
-
- void GetFaultDetectionParameters(size_t heater, float& maxTempExcursion, float& maxFaultTime) const
- pre(heater < MaxHeaters);
-
- void SetFaultDetectionParameters(size_t heater, float maxTempExcursion, float maxFaultTime)
- pre(heater < MaxHeaters);
-
- bool CheckHeater(size_t heater) // Check if the heater is able to operate
- pre(heater < MaxHeaters);
-
- void SetM301PidParameters(size_t heater, const M301PidParameters& params)
- pre(heater < MaxHeaters);
#if HAS_MASS_STORAGE
bool WriteModelParameters(FileStore *f) const; // Write heater model parameters to file returning true if no error
@@ -159,14 +133,16 @@ public:
private:
Heat(const Heat&) = delete; // Private copy constructor to prevent copying
+ Heater * FindHeater(int heater) const;
+
void RemoveSensor(unsigned int sensorNum);
void InsertSensor(TemperatureSensor *sensor);
TemperatureSensor *sensorsRoot; // The sensor list
HeaterProtection *heaterProtections[MaxHeaters + NumExtraHeaterProtections]; // Heater protection instances to guarantee legal heater temperature ranges
- PID* pids[MaxHeaters]; // A PID controller for each heater
- const Tool* lastStandbyTools[MaxHeaters]; // The last tool that caused the corresponding heater to be set to standby
+ Heater* heaters[MaxHeaters]; // A local or remote heater
+ const Tool* lastStandbyTools[MaxHeaters]; // The last tool that caused the corresponding heater to be set to standby
float extrusionMinTemp; // Minimum temperature to allow regular extrusion
float retractionMinTemp; // Minimum temperature to allow regular retraction
@@ -219,32 +195,4 @@ inline int Heat::GetChamberHeater(size_t index) const
return chamberHeaters[index];
}
-// Get the process model for the specified heater
-inline const FopDt& Heat::GetHeaterModel(size_t heater) const
-{
- return pids[heater]->GetModel();
-}
-
-// Set the heater process model
-inline bool Heat::SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted)
-{
- return pids[heater]->SetModel(gain, tc, td, maxPwm, voltage, usePid, inverted);
-}
-
-// Is the heater enabled?
-inline bool Heat::IsHeaterEnabled(size_t heater) const
-{
- return pids[heater]->IsHeaterEnabled();
-}
-
-inline void Heat::GetFaultDetectionParameters(size_t heater, float& maxTempExcursion, float& maxFaultTime) const
-{
- pids[heater]->GetFaultDetectionParameters(maxTempExcursion, maxFaultTime);
-}
-
-inline void Heat::SetFaultDetectionParameters(size_t heater, float maxTempExcursion, float maxFaultTime)
-{
- pids[heater]->SetFaultDetectionParameters(maxTempExcursion, maxFaultTime);
-}
-
#endif
diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp
new file mode 100644
index 00000000..f1fe12c4
--- /dev/null
+++ b/src/Heating/Heater.cpp
@@ -0,0 +1,175 @@
+/*
+ * Heater.cpp
+ *
+ * Created on: 24 Jul 2019
+ * Author: David
+ */
+
+#include "Heater.h"
+#include "RepRap.h"
+#include "Platform.h"
+#include "Heat.h"
+#include "HeaterProtection.h"
+#include "Sensors/TemperatureSensor.h"
+
+Heater::Heater(unsigned int num)
+ : heaterNumber(num), sensorNumber(-1), activeTemperature(0.0), standbyTemperature(0.0),
+ maxTempExcursion(DefaultMaxTempExcursion), maxHeatingFaultTime(DefaultMaxHeatingFaultTime),
+ heaterProtection(nullptr), active(false)
+{
+}
+
+Heater::~Heater()
+{
+}
+
+// Set the process model returning true if successful
+GCodeResult Heater::SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply)
+{
+ const float temperatureLimit = GetHighestTemperatureLimit();
+ const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, voltage, usePid, inverted);
+ if (rslt)
+ {
+ if (model.IsEnabled())
+ {
+ const GCodeResult rslt = UpdateModel(reply);
+ if (rslt != GCodeResult::ok)
+ {
+ return rslt;
+ }
+ const float predictedMaxTemp = gain + NormalAmbientTemperature;
+ const float noWarnTemp = (temperatureLimit - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C
+ if (predictedMaxTemp > noWarnTemp)
+ {
+ reply.printf("heater %u appears to be over-powered. If left on at full power, its temperature is predicted to reach %dC.\n",
+ GetHeaterNumber(), (int)predictedMaxTemp);
+ return GCodeResult::warning;
+ }
+ }
+ else
+ {
+ Reset();
+ }
+ return GCodeResult::ok;
+ }
+
+ reply.copy("bad model parameters");
+ return GCodeResult::error;
+}
+
+HeaterStatus Heater::GetStatus() const
+{
+ const HeaterMode mode = GetMode();
+ return (mode == HeaterMode::fault) ? HeaterStatus::fault
+ : (mode == HeaterMode::off) ? HeaterStatus::off
+ : (mode >= HeaterMode::tuning0) ? HeaterStatus::tuning
+ : (active) ? HeaterStatus::active
+ : HeaterStatus::standby;
+}
+
+const char* Heater::GetSensorName() const
+{
+ const TemperatureSensor * const sensor = GetSensor();
+ return (sensor != nullptr) ? sensor->GetSensorName() : nullptr;
+}
+
+TemperatureSensor *Heater::GetSensor() const
+{
+ return reprap.GetHeat().GetSensor(sensorNumber);
+}
+
+void Heater::Activate()
+{
+ if (GetMode() != HeaterMode::fault)
+ {
+ active = true;
+ SwitchOn();
+ }
+}
+
+void Heater::Standby()
+{
+ if (GetMode() != HeaterMode::fault)
+ {
+ active = false;
+ SwitchOn();
+ }
+}
+
+void Heater::SetActiveTemperature(float t)
+{
+ if (t > GetHighestTemperatureLimit())
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %u\n", (double)t, GetHeaterNumber());
+ }
+ else if (t < GetLowestTemperatureLimit())
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %u\n", (double)t, GetHeaterNumber());
+ }
+ else
+ {
+ activeTemperature = t;
+ if (GetMode() > HeaterMode::suspended && active)
+ {
+ SwitchOn();
+ }
+ }
+}
+
+void Heater::SetStandbyTemperature(float t)
+{
+ if (t > GetHighestTemperatureLimit())
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %u\n", (double)t, GetHeaterNumber());
+ }
+ else if (t < GetLowestTemperatureLimit())
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %u\n", (double)t, GetHeaterNumber());
+ }
+ else
+ {
+ standbyTemperature = t;
+ if (GetMode() > HeaterMode::suspended && !active)
+ {
+ SwitchOn();
+ }
+ }
+}
+
+// Get the highest temperature limit
+float Heater::GetHighestTemperatureLimit() const
+{
+ return reprap.GetHeat().GetHighestTemperatureLimit(GetHeaterNumber());
+}
+
+// Get the lowest temperature limit
+float Heater::GetLowestTemperatureLimit() const
+{
+ return reprap.GetHeat().GetLowestTemperatureLimit(GetHeaterNumber());
+}
+
+void Heater::SetHeaterProtection(HeaterProtection *h)
+{
+ heaterProtection = h;
+}
+
+// Check heater protection elements and return true if everything is good
+bool Heater::CheckProtection() const
+{
+ for (HeaterProtection *prot = heaterProtection; prot != nullptr; prot = prot->Next())
+ {
+ if (!prot->Check())
+ {
+ // Something is not right
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Heater::CheckGood() const
+{
+ return GetMode() == HeaterMode::fault && CheckProtection();
+}
+
+// End
diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h
new file mode 100644
index 00000000..d495024b
--- /dev/null
+++ b/src/Heating/Heater.h
@@ -0,0 +1,120 @@
+/*
+ * Heater.h
+ *
+ * Created on: 24 Jul 2019
+ * Author: David
+ */
+
+#ifndef SRC_HEATING_HEATER_H_
+#define SRC_HEATING_HEATER_H_
+
+#include "RepRapFirmware.h"
+#include "FOPDT.h"
+#include "GCodes/GCodeResult.h"
+
+#if SUPPORT_CAN_EXPANSION
+# include "CanId.h"
+#endif
+
+class HeaterProtection;
+
+// Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them.
+// Status 'running' is not returned to the web interface, we return active or standby instead.
+enum class HeaterStatus { off = 0, standby = 1, active = 2, fault = 3, tuning = 4, running = 5 };
+
+class Heater
+{
+public:
+ Heater(unsigned int num);
+ virtual ~Heater();
+
+ virtual float GetTemperature() const = 0; // Get the current temperature
+ virtual float GetAveragePWM() const = 0; // Return the running average PWM to the heater. Answer is a fraction in [0, 1].
+ virtual void ResetFault() = 0; // Reset a fault condition - only call this if you know what you are doing
+ virtual void SwitchOff() = 0;
+ virtual void Spin() = 0;
+ virtual void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) = 0; // Start an auto tune cycle for this PID
+ virtual void GetAutoTuneStatus(const StringRef& reply) const = 0; // Get the auto tune status or last result
+ virtual void Suspend(bool sus) = 0; // Suspend the heater to conserve power or while doing Z probing
+ virtual float GetAccumulator() const = 0; // get the inertial term accumulator
+ virtual GCodeResult ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply) = 0;
+
+ unsigned int GetHeaterNumber() const { return heaterNumber; }
+ const char *GetSensorName() const; // Get the name of the sensor for this heater, or nullptr if it hasn't been named
+ HeaterStatus GetStatus() const; // Get the status of the heater
+ void SetActiveTemperature(float t);
+ float GetActiveTemperature() const { return activeTemperature; }
+ void SetStandbyTemperature(float t);
+ float GetStandbyTemperature() const { return standbyTemperature; }
+ void Activate(); // Switch from idle to active
+ void Standby(); // Switch from active to idle
+
+ void GetFaultDetectionParameters(float& pMaxTempExcursion, float& pMaxFaultTime) const
+ { pMaxTempExcursion = maxTempExcursion; pMaxFaultTime = maxHeatingFaultTime; }
+
+ void SetFaultDetectionParameters(float pMaxTempExcursion, float pMaxFaultTime)
+ { maxTempExcursion = pMaxTempExcursion; maxHeatingFaultTime = pMaxFaultTime; }
+
+ float GetHighestTemperatureLimit() const; // Get the highest temperature limit
+ float GetLowestTemperatureLimit() const; // Get the lowest temperature limit
+ void SetHeaterProtection(HeaterProtection *h);
+
+ const FopDt& GetModel() const { return model; } // Get the process model
+
+ GCodeResult SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply); // Set the process model
+
+ bool IsHeaterEnabled() const // Is this heater enabled?
+ { return model.IsEnabled(); }
+
+ void SetM301PidParameters(const M301PidParameters& params)
+ { model.SetM301PidParameters(params); }
+
+ TemperatureSensor *GetSensor() const;
+ bool CheckGood() const;
+
+protected:
+ enum class HeaterMode : uint8_t
+ {
+ // The order of these is important because we test "mode > HeatingMode::suspended" to determine whether the heater is active
+ // and "mode >= HeatingMode::off" to determine whether the heater is either active or suspended
+ fault,
+ off,
+ suspended,
+ heating,
+ cooling,
+ stable,
+ // All states from here onwards must be PID tuning states because function IsTuning assumes that
+ tuning0,
+ tuning1,
+ tuning2,
+ tuning3,
+ lastTuningMode = tuning3
+ };
+
+ virtual HeaterMode GetMode() const = 0;
+ virtual void SwitchOn() = 0;
+ virtual GCodeResult UpdateModel(const StringRef& reply) = 0;
+
+ int GetSensorNumber() const { return sensorNumber; }
+ void SetSensorNumber(int sn) { sensorNumber = sn; }
+ float GetMaxTemperatureExcursion() const { return maxTempExcursion; }
+ float GetMaxHeatingFaultTime() const { return maxHeatingFaultTime; }
+ float GetTargetTemperature() const { return (active) ? activeTemperature : standbyTemperature; }
+ HeaterProtection *GetHeaterProtections() const { return heaterProtection; }
+
+private:
+ bool CheckProtection() const; // Check heater protection elements and return true if everything is good
+
+ unsigned int heaterNumber;
+ int sensorNumber; // the sensor number used by this heater
+ float activeTemperature; // The required active temperature
+ float standbyTemperature; // The required standby temperature
+ float maxTempExcursion; // The maximum temperature excursion permitted while maintaining the setpoint
+ float maxHeatingFaultTime; // How long a heater fault is permitted to persist before a heater fault is raised
+ HeaterProtection *heaterProtection; // The first element of assigned heater protection items
+
+ FopDt model;
+ bool active; // Are we active or standby?
+};
+
+#endif /* SRC_HEATING_HEATER_H_ */
diff --git a/src/Heating/Pid.cpp b/src/Heating/LocalHeater.cpp
index fb4ba006..31feca4e 100644
--- a/src/Heating/Pid.cpp
+++ b/src/Heating/LocalHeater.cpp
@@ -5,7 +5,7 @@
* Author: David
*/
-#include "Pid.h"
+#include <Heating/LocalHeater.h>
#include "GCodes/GCodes.h"
#include "GCodes/GCodeBuffer/GCodeBuffer.h"
#include "Heat.h"
@@ -19,19 +19,19 @@ const uint32_t TempSettleTimeout = 20000; // how long we allow the initial tempe
// Static class variables
-float *PID::tuningTempReadings = nullptr; // the readings from the heater being tuned
-float PID::tuningStartTemp; // the temperature when we turned on the heater
-float PID::tuningPwm; // the PWM to use
-float PID::tuningTargetTemp; // the maximum temperature we are allowed to reach
-uint32_t PID::tuningBeginTime; // when we started the tuning process
-uint32_t PID::tuningPhaseStartTime; // when we started the current tuning phase
-uint32_t PID::tuningReadingInterval; // how often we are sampling
-size_t PID::tuningReadingsTaken; // how many samples we have taken
+float *LocalHeater::tuningTempReadings = nullptr; // the readings from the heater being tuned
+float LocalHeater::tuningStartTemp; // the temperature when we turned on the heater
+float LocalHeater::tuningPwm; // the PWM to use
+float LocalHeater::tuningTargetTemp; // the maximum temperature we are allowed to reach
+uint32_t LocalHeater::tuningBeginTime; // when we started the tuning process
+uint32_t LocalHeater::tuningPhaseStartTime; // when we started the current tuning phase
+uint32_t LocalHeater::tuningReadingInterval; // how often we are sampling
+size_t LocalHeater::tuningReadingsTaken; // how many samples we have taken
-float PID::tuningHeaterOffTemp; // the temperature when we turned the heater off
-float PID::tuningPeakTemperature; // the peak temperature reached, averaged over 3 readings (so slightly less than the true peak)
-uint32_t PID::tuningHeatingTime; // how long we had the heating on for
-uint32_t PID::tuningPeakDelay; // how many milliseconds the temperature continues to rise after turning the heater off
+float LocalHeater::tuningHeaterOffTemp; // the temperature when we turned the heater off
+float LocalHeater::tuningPeakTemperature; // the peak temperature reached, averaged over 3 readings (so slightly less than the true peak)
+uint32_t LocalHeater::tuningHeatingTime; // how long we had the heating on for
+uint32_t LocalHeater::tuningPeakDelay; // how many milliseconds the temperature continues to rise after turning the heater off
#if HAS_VOLTAGE_MONITOR
unsigned int voltageSamplesTaken; // how many readings we accumulated
@@ -40,27 +40,19 @@ float tuningVoltageAccumulator; // sum of the voltage readings we take during
// Member functions and constructors
-PID::PID(unsigned int h) : heater(h), sensorNumber(-1), heaterProtection(nullptr), mode(HeaterMode::off)
+LocalHeater::LocalHeater(unsigned int heaterNum) : Heater(heaterNum), mode(HeaterMode::off)
{
-}
-
-TemperatureSensor *PID::GetSensor() const
-{
- return reprap.GetHeat().GetSensor(sensorNumber);
-}
+ Reset();
+ SetHeater(0.0); // set up the pin even if the heater is not enabled (for PCCB)
-inline void PID::SetHeater(float power) const
-{
- port.WriteAnalog(power);
+ // Time the sensor was last sampled. During startup, we use the current
+ // time as the initial value so as to not trigger an immediate warning from the Tick ISR.
+ lastSampleTime = millis();
}
-void PID::Init(float pGain, float pTc, float pTd, bool usePid, bool inverted)
+LocalHeater::LocalHeater(const Heater& h) : Heater(h), mode(HeaterMode::off)
{
- maxTempExcursion = DefaultMaxTempExcursion;
- maxHeatingFaultTime = DefaultMaxHeatingFaultTime;
- model.SetParameters(pGain, pTc, pTd, 1.0, GetHighestTemperatureLimit(), 0.0, usePid, inverted);
Reset();
-
SetHeater(0.0); // set up the pin even if the heater is not enabled (for PCCB)
// Time the sensor was last sampled. During startup, we use the current
@@ -68,16 +60,28 @@ void PID::Init(float pGain, float pTc, float pTd, bool usePid, bool inverted)
lastSampleTime = millis();
}
-void PID::Reset()
+float LocalHeater::GetTemperature() const
+{
+ return temperature;
+}
+
+float LocalHeater::GetAccumulator() const
+{
+ return iAccumulator;
+}
+
+inline void LocalHeater::SetHeater(float power) const
+{
+ port.WriteAnalog(power);
+}
+
+void LocalHeater::Reset()
{
mode = HeaterMode::off;
previousTemperaturesGood = 0;
previousTemperatureIndex = 0;
- activeTemperature = 0.0;
- standbyTemperature = 0.0;
iAccumulator = 0.0;
badTemperatureCount = 0;
- active = false; // default to standby temperature
tuned = false;
averagePWM = lastPwm = 0.0;
heatingFaultCount = 0;
@@ -85,11 +89,11 @@ void PID::Reset()
}
// Configure the heater port and the sensor number
-GCodeResult PID::ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply)
+GCodeResult LocalHeater::ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply)
{
const bool seenFreq = gb.Seen('Q');
const PwmFrequency freq = (seenFreq) ? min<PwmFrequency>(gb.GetPwmFrequency(), MaxHeaterPwmFrequency)
- : (reprap.GetHeat().IsBedOrChamberHeater(heater))
+ : (reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber()))
? SlowHeaterPwmFreq : NormalHeaterPwmFreq;
const bool seenPin = gb.Seen('C');
if (seenPin)
@@ -112,91 +116,46 @@ GCodeResult PID::ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply)
SwitchOff();
if (sn < 0 || reprap.GetHeat().GetSensor(sn) != nullptr)
{
- sensorNumber = sn;
+ SetSensorNumber(sn);
}
else
{
- sensorNumber = -1;
+ SetSensorNumber(-1);
reply.printf("Sensor number %d has not been defined", sn);
}
}
else if (!seenPin && !seenFreq)
{
- reply.printf("Heater %u", heater);
+ reply.printf("Heater %u", GetHeaterNumber());
port.AppendDetails(reply);
}
return GCodeResult::ok;
}
-// Set the process model
-bool PID::SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted)
-{
- const float temperatureLimit = GetHighestTemperatureLimit();
- const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, voltage, usePid, inverted);
- if (rslt)
- {
-#if defined(DUET_06_085)
- if (heater == NumHeaters - 1)
- {
- // The last heater on the Duet 0.8.5 + DueX4 shares its pin with Fan1
- platform.EnableSharedFan(!model.IsEnabled());
- }
-#endif
- if (model.IsEnabled())
- {
- const float predictedMaxTemp = gain + NormalAmbientTemperature;
- const float noWarnTemp = (temperatureLimit - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C
- if (predictedMaxTemp > noWarnTemp)
- {
- reprap.GetPlatform().MessageF(WarningMessage,
- "Heater %u appears to be over-powered. If left on at full power, its temperature is predicted to reach %dC.\n",
- heater, (int)predictedMaxTemp);
- }
- }
- else
- {
- Reset();
- }
- }
- return rslt;
-}
-
-// Get the highest temperature limit
-float PID::GetHighestTemperatureLimit() const
-{
- return reprap.GetHeat().GetHighestTemperatureLimit(heater);
-}
-
-// Get the lowest temperature limit
-float PID::GetLowestTemperatureLimit() const
-{
- return reprap.GetHeat().GetLowestTemperatureLimit(heater);
-}
-
// Read and store the temperature of this heater and returns the error code.
-TemperatureError PID::ReadTemperature()
+TemperatureError LocalHeater::ReadTemperature()
{
TemperatureError err;
- temperature = reprap.GetHeat().GetSensorTemperature(sensorNumber, err); // in the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned
+ temperature = reprap.GetHeat().GetSensorTemperature(GetSensorNumber(), err); // in the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned
return err;
}
// This must be called whenever the heater is turned on, and any time the heater is active and the target temperature is changed
-void PID::SwitchOn()
+void LocalHeater::SwitchOn()
{
- if (model.IsEnabled())
+ if (GetModel().IsEnabled())
{
if (mode == HeaterMode::fault)
{
if (reprap.Debug(Module::moduleHeat))
{
- reprap.GetPlatform().MessageF(WarningMessage, "Heater %d not switched on due to temperature fault\n", heater);
+ reprap.GetPlatform().MessageF(WarningMessage, "Heater %u not switched on due to temperature fault\n", GetHeaterNumber());
}
}
- else if (model.IsEnabled())
+ else if (GetModel().IsEnabled())
{
//debugPrintf("Heater %d on, temp %.1f\n", heater, temperature);
- const float target = (active) ? activeTemperature : standbyTemperature;
+ const float target = GetTargetTemperature();
const HeaterMode oldMode = mode;
mode = (temperature + TEMPERATURE_CLOSE_ENOUGH < target) ? HeaterMode::heating
: (temperature > target + TEMPERATURE_CLOSE_ENOUGH) ? HeaterMode::cooling
@@ -210,7 +169,7 @@ void PID::SwitchOn()
}
if (reprap.Debug(Module::moduleHeat) && oldMode == HeaterMode::off)
{
- reprap.GetPlatform().MessageF(GenericMessage, "Heater %d switched on\n", heater);
+ reprap.GetPlatform().MessageF(GenericMessage, "Heater %u switched on\n", GetHeaterNumber());
}
}
}
@@ -218,13 +177,13 @@ void PID::SwitchOn()
}
// Switch off the specified heater. If in tuning mode, delete the array used to store tuning temperature readings.
-void PID::SwitchOff()
+void LocalHeater::SwitchOff()
{
lastPwm = 0.0;
- if (model.IsEnabled())
+ if (GetModel().IsEnabled())
{
SetHeater(0.0);
- if (IsTuning())
+ if (mode >= HeaterMode::tuning0)
{
delete tuningTempReadings;
tuningTempReadings = nullptr;
@@ -234,16 +193,22 @@ void PID::SwitchOff()
mode = HeaterMode::off;
if (reprap.Debug(Module::moduleHeat))
{
- reprap.GetPlatform().MessageF(GenericMessage, "Heater %d switched off\n", heater);
+ reprap.GetPlatform().MessageF(GenericMessage, "Heater %u switched off\n", GetHeaterNumber());
}
}
}
}
+// This is called when the heater model has been updated. Returns true if successful.
+GCodeResult LocalHeater::UpdateModel(const StringRef& reply)
+{
+ return GCodeResult::ok;
+}
+
// This is the main heater control loop function
-void PID::Spin()
+void LocalHeater::Spin()
{
- if (model.IsEnabled())
+ if (GetModel().IsEnabled())
{
// Read the temperature even if the heater is suspended
const TemperatureError err = ReadTemperature();
@@ -260,15 +225,15 @@ void PID::Spin()
{
lastPwm = 0.0;
SetHeater(0.0); // do this here just to be sure, in case the call to platform.Message causes a delay
- if (IsTuning())
+ if (mode >= HeaterMode::tuning0)
{
delete tuningTempReadings;
tuningTempReadings = nullptr;
}
mode = HeaterMode::fault;
- reprap.GetGCodes().HandleHeaterFault(heater);
- reprap.GetPlatform().MessageF(ErrorMessage, "Temperature reading fault on heater %d: %s\n", heater, TemperatureErrorString(err));
- reprap.FlagTemperatureFault(heater);
+ reprap.GetGCodes().HandleHeaterFault(GetHeaterNumber());
+ reprap.GetPlatform().MessageF(ErrorMessage, "Temperature reading fault on heater %u: %s\n", GetHeaterNumber(), TemperatureErrorString(err));
+ reprap.FlagTemperatureFault(GetHeaterNumber());
}
}
// We leave lastPWM alone if we have a temporary temperature reading error
@@ -294,7 +259,7 @@ void PID::Spin()
previousTemperaturesGood = (previousTemperaturesGood << 1) | 1;
// Get the target temperature and the error
- const float targetTemperature = (active) ? activeTemperature : standbyTemperature;
+ const float targetTemperature = GetTargetTemperature();
const float error = targetTemperature - temperature;
// Do the heating checks
@@ -311,17 +276,17 @@ void PID::Spin()
{
const float expectedRate = GetExpectedHeatingRate();
if (derivative + AllowedTemperatureDerivativeNoise < expectedRate
- && (float)(millis() - timeSetHeating) > model.GetDeadTime() * SecondsToMillis * 2)
+ && (float)(millis() - timeSetHeating) > GetModel().GetDeadTime() * SecondsToMillis * 2)
{
++heatingFaultCount;
- if (heatingFaultCount * HeatSampleIntervalMillis > maxHeatingFaultTime * SecondsToMillis)
+ if (heatingFaultCount * HeatSampleIntervalMillis > GetMaxHeatingFaultTime() * SecondsToMillis)
{
SetHeater(0.0); // do this here just to be sure
mode = HeaterMode::fault;
- reprap.GetGCodes().HandleHeaterFault(heater);
+ reprap.GetGCodes().HandleHeaterFault(GetHeaterNumber());
reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %d, temperature rising much more slowly than the expected %.1f" DEGREE_SYMBOL "C/sec\n",
- heater, (double)expectedRate);
- reprap.FlagTemperatureFault(heater);
+ GetHeaterNumber(), (double)expectedRate);
+ reprap.FlagTemperatureFault(GetHeaterNumber());
}
}
else if (heatingFaultCount != 0)
@@ -337,16 +302,16 @@ void PID::Spin()
break;
case HeaterMode::stable:
- if (fabsf(error) > maxTempExcursion && temperature > MaxAmbientTemperature)
+ if (fabsf(error) > GetMaxTemperatureExcursion() && temperature > MaxAmbientTemperature)
{
++heatingFaultCount;
- if (heatingFaultCount * HeatSampleIntervalMillis > maxHeatingFaultTime * SecondsToMillis)
+ if (heatingFaultCount * HeatSampleIntervalMillis > GetMaxHeatingFaultTime() * SecondsToMillis)
{
SetHeater(0.0); // do this here just to be sure
mode = HeaterMode::fault;
- reprap.GetGCodes().HandleHeaterFault(heater);
- reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %d, temperature excursion exceeded %.1f" DEGREE_SYMBOL "C\n",
- heater, (double)maxTempExcursion);
+ reprap.GetGCodes().HandleHeaterFault(GetHeaterNumber());
+ reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %u, temperature excursion exceeded %.1f" DEGREE_SYMBOL "C\n",
+ GetHeaterNumber(), (double)GetMaxTemperatureExcursion());
}
}
else if (heatingFaultCount != 0)
@@ -381,19 +346,19 @@ void PID::Spin()
else if (mode < HeaterMode::tuning0)
{
// Performing normal temperature control
- if (model.UsePid())
+ if (GetModel().UsePid())
{
// Using PID mode. Determine the PID parameters to use.
const bool inLoadMode = (mode == HeaterMode::stable) || fabsf(error) < 3.0; // use standard PID when maintaining temperature
- const PidParameters& params = model.GetPidParameters(inLoadMode);
+ const PidParameters& params = GetModel().GetPidParameters(inLoadMode);
// If the P and D terms together demand that the heater is full on or full off, disregard the I term
const float errorMinusDterm = error - (params.tD * derivative);
const float pPlusD = params.kP * errorMinusDterm;
- const float expectedPwm = constrain<float>((temperature - NormalAmbientTemperature)/model.GetGain(), 0.0, model.GetMaxPwm());
- if (pPlusD + expectedPwm > model.GetMaxPwm())
+ const float expectedPwm = constrain<float>((temperature - NormalAmbientTemperature)/GetModel().GetGain(), 0.0, GetModel().GetMaxPwm());
+ if (pPlusD + expectedPwm > GetModel().GetMaxPwm())
{
- lastPwm = model.GetMaxPwm();
+ lastPwm = GetModel().GetMaxPwm();
// If we are heating up, preset the I term to the expected PWM at this temperature, ready for the switch over to PID
if (mode == HeaterMode::heating && error > 0.0 && derivative > 0.0)
{
@@ -409,19 +374,19 @@ void PID::Spin()
const float errorToUse = error;
iAccumulator = constrain<float>
(iAccumulator + (errorToUse * params.kP * params.recipTi * HeatSampleIntervalMillis * MillisToSeconds),
- 0.0, model.GetMaxPwm());
- lastPwm = constrain<float>(pPlusD + iAccumulator, 0.0, model.GetMaxPwm());
+ 0.0, GetModel().GetMaxPwm());
+ lastPwm = constrain<float>(pPlusD + iAccumulator, 0.0, GetModel().GetMaxPwm());
}
#if HAS_VOLTAGE_MONITOR
// Scale the PID based on the current voltage vs. the calibration voltage
- if (lastPwm < 1.0 && model.GetVoltage() >= 10.0) // if heater is not fully on and we know the voltage we tuned the heater at
+ if (lastPwm < 1.0 && GetModel().GetVoltage() >= 10.0) // if heater is not fully on and we know the voltage we tuned the heater at
{
- if (!reprap.GetHeat().IsBedOrChamberHeater(heater))
+ if (!reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber()))
{
const float currentVoltage = reprap.GetPlatform().GetCurrentPowerVoltage();
if (currentVoltage >= 10.0) // if we have a sensible reading
{
- lastPwm = min<float>(lastPwm * fsquare(model.GetVoltage()/currentVoltage), 1.0); // adjust the PWM by the square of the voltage ratio
+ lastPwm = min<float>(lastPwm * fsquare(GetModel().GetVoltage()/currentVoltage), 1.0); // adjust the PWM by the square of the voltage ratio
}
}
}
@@ -430,17 +395,17 @@ void PID::Spin()
else
{
// Using bang-bang mode
- lastPwm = (error > 0.0) ? model.GetMaxPwm() : 0.0;
+ lastPwm = (error > 0.0) ? GetModel().GetMaxPwm() : 0.0;
}
// Check if the generated PWM signal needs to be inverted for inverse temperature control
- if (model.IsInverted())
+ if (GetModel().IsInverted())
{
- lastPwm = model.GetMaxPwm() - lastPwm;
+ lastPwm = GetModel().GetMaxPwm() - lastPwm;
}
// Verify that everything is operating in the required temperature range
- for (HeaterProtection *prot = heaterProtection; prot != nullptr; prot = prot->Next())
+ for (HeaterProtection *prot = GetHeaterProtections(); prot != nullptr; prot = prot->Next())
{
if (!prot->Check())
{
@@ -449,8 +414,8 @@ void PID::Spin()
{
case HeaterProtectionAction::GenerateFault:
mode = HeaterMode::fault;
- reprap.GetGCodes().HandleHeaterFault(heater);
- reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %d\n", heater);
+ reprap.GetGCodes().HandleHeaterFault(GetHeaterNumber());
+ reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %u\n", GetHeaterNumber());
break;
case HeaterProtectionAction::TemporarySwitchOff:
@@ -487,84 +452,7 @@ void PID::Spin()
}
}
-void PID::SetActiveTemperature(float t)
-{
- if (t > GetHighestTemperatureLimit())
- {
- reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %d\n", (double)t, heater);
- }
- else if (t < GetLowestTemperatureLimit())
- {
- reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %d\n", (double)t, heater);
- }
- else
- {
- activeTemperature = t;
- if (mode > HeaterMode::suspended && active)
- {
- SwitchOn();
- }
- }
-}
-
-void PID::SetStandbyTemperature(float t)
-{
- if (t > GetHighestTemperatureLimit())
- {
- reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %d\n", (double)t, heater);
- }
- else if (t < GetLowestTemperatureLimit())
- {
- reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %d\n", (double)t, heater);
- }
- else
- {
- standbyTemperature = t;
- if (mode > HeaterMode::suspended && !active)
- {
- SwitchOn();
- }
- }
-}
-
-void PID::SetHeaterProtection(HeaterProtection *h)
-{
- heaterProtection = h;
-}
-
-void PID::Activate()
-{
- if (mode != HeaterMode::fault)
- {
- active = true;
- SwitchOn();
- }
-}
-
-void PID::Standby()
-{
- if (mode != HeaterMode::fault)
- {
- active = false;
- SwitchOn();
- }
-}
-
-// Check heater protection elements and return true if everything is good
-bool PID::CheckProtection() const
-{
- for (HeaterProtection *prot = heaterProtection; prot != nullptr; prot = prot->Next())
- {
- if (!prot->Check())
- {
- // Something is not right
- return false;
- }
- }
- return true;
-}
-
-void PID::ResetFault()
+void LocalHeater::ResetFault()
{
badTemperatureCount = 0;
if (mode == HeaterMode::fault)
@@ -574,40 +462,40 @@ void PID::ResetFault()
}
}
-float PID::GetAveragePWM() const
+float LocalHeater::GetAveragePWM() const
{
return averagePWM * HeatSampleIntervalMillis/(HeatPwmAverageTime * SecondsToMillis);
}
// Get a conservative estimate of the expected heating rate at the current temperature and average PWM. The result may be negative.
-float PID::GetExpectedHeatingRate() const
+float LocalHeater::GetExpectedHeatingRate() const
{
// In the following we allow for the gain being only 75% of what we think it should be, to avoid false alarms
- const float maxTemperatureRise = 0.75 * model.GetGain() * GetAveragePWM(); // this is the highest temperature above ambient we expect the heater can reach at this PWM
- const float initialHeatingRate = maxTemperatureRise/model.GetTimeConstant(); // this is the expected heating rate at ambient temperature
+ const float maxTemperatureRise = 0.75 * GetModel().GetGain() * GetAveragePWM(); // this is the highest temperature above ambient we expect the heater can reach at this PWM
+ const float initialHeatingRate = maxTemperatureRise/GetModel().GetTimeConstant(); // this is the expected heating rate at ambient temperature
return (maxTemperatureRise >= 20.0)
? (maxTemperatureRise + NormalAmbientTemperature - temperature) * initialHeatingRate/maxTemperatureRise
: 0.0;
}
// Auto tune this PID
-void PID::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply)
+void LocalHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply)
{
// Starting an auto tune
- if (!model.IsEnabled())
+ if (!GetModel().IsEnabled())
{
- reply.printf("Error: heater %d cannot be auto tuned while it is disabled", heater);
+ reply.printf("Error: heater %u cannot be auto tuned while it is disabled", GetHeaterNumber());
}
else if (lastPwm > 0.0 || GetAveragePWM() > 0.02)
{
- reply.printf("Error: heater %d must be off and cold before auto tuning it", heater);
+ reply.printf("Error: heater %u must be off and cold before auto tuning it", GetHeaterNumber());
}
else
{
const TemperatureError err = ReadTemperature();
if (err != TemperatureError::success)
{
- reply.printf("Error: heater %d reported error '%s' at start of auto tuning", heater, TemperatureErrorString(err));
+ reply.printf("Error: heater %u reported error '%s' at start of auto tuning", GetHeaterNumber(), TemperatureErrorString(err));
}
else
{
@@ -622,27 +510,29 @@ void PID::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply)
tuningReadingInterval = HeatSampleIntervalMillis;
tuningPwm = maxPwm;
tuningTargetTemp = targetTemp;
- reply.printf("Auto tuning heater %d using target temperature %.1f" DEGREE_SYMBOL "C and PWM %.2f - do not leave printer unattended", heater, (double)targetTemp, (double)maxPwm);
+ reply.printf("Auto tuning heater %u using target temperature %.1f" DEGREE_SYMBOL "C and PWM %.2f - do not leave printer unattended",
+ GetHeaterNumber(), (double)targetTemp, (double)maxPwm);
}
}
}
-void PID::GetAutoTuneStatus(const StringRef& reply) // Get the auto tune status or last result
+// Get the auto tune status or last result
+void LocalHeater::GetAutoTuneStatus(const StringRef& reply) const
{
if (mode >= HeaterMode::tuning0)
{
- reply.printf("Heater %d is being tuned, phase %u of %u",
- heater,
+ reply.printf("Heater %u is being tuned, phase %u of %u",
+ GetHeaterNumber(),
(unsigned int)mode - (unsigned int)HeaterMode::tuning0 + 1,
(unsigned int)HeaterMode::lastTuningMode - (unsigned int)HeaterMode::tuning0 + 1);
}
else if (tuned)
{
- reply.printf("Heater %d tuning succeeded, use M307 H%d to see result", heater, heater);
+ reply.printf("Heater %u tuning succeeded, use M307 H%u to see result", GetHeaterNumber(), GetHeaterNumber());
}
else
{
- reply.printf("Heater %d tuning failed", heater);
+ reply.printf("Heater %u tuning failed", GetHeaterNumber());
}
}
@@ -693,7 +583,7 @@ void PID::GetAutoTuneStatus(const StringRef& reply) // Get the auto tune status
// This is called on each temperature sample when auto tuning
// It must set lastPWM to the required PWM, unless it is the same as last time.
-void PID::DoTuningStep()
+void LocalHeater::DoTuningStep()
{
// See if another sample is due
if (tuningReadingsTaken == 0)
@@ -755,10 +645,10 @@ void PID::DoTuningStep()
case HeaterMode::tuning1:
// Heating up
{
- const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(heater);
+ const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber());
const uint32_t heatingTime = millis() - tuningPhaseStartTime;
const float extraTimeAllowed = (isBedOrChamberHeater) ? 60.0 : 30.0;
- if (heatingTime > (uint32_t)((model.GetDeadTime() + extraTimeAllowed) * SecondsToMillis) && (temperature - tuningStartTemp) < 3.0)
+ if (heatingTime > (uint32_t)((GetModel().GetDeadTime() + extraTimeAllowed) * SecondsToMillis) && (temperature - tuningStartTemp) < 3.0)
{
reprap.GetPlatform().Message(GenericMessage, "Auto tune cancelled because temperature is not increasing\n");
break;
@@ -853,7 +743,7 @@ void PID::DoTuningStep()
}
// Return true if the last 'numReadings' readings are stable
-/*static*/ bool PID::ReadingsStable(size_t numReadings, float maxDiff)
+/*static*/ bool LocalHeater::ReadingsStable(size_t numReadings, float maxDiff)
{
if (tuningTempReadings == nullptr || tuningReadingsTaken < numReadings)
{
@@ -875,7 +765,7 @@ void PID::DoTuningStep()
// Calculate which reading gave us the peak temperature.
// Return -1 if peak not identified yet, 0 if we are never going to find a peak, else the index of the peak
// If the readings show a continuous decrease then we return 1, because zero dead time would lead to infinities
-/*static*/ int PID::GetPeakTempIndex()
+/*static*/ int LocalHeater::GetPeakTempIndex()
{
// Check we have enough readings to look for the peak
if (tuningReadingsTaken < 15)
@@ -909,7 +799,7 @@ void PID::DoTuningStep()
// See if there is exactly one peak in the readings.
// Return -1 if more than one peak, else the index of the peak. The so-called peak may be right at the end, in which case it isn't really a peak.
// With a well-insulated bed heater the temperature may not start dropping appreciably within the 120 second time limit allowed.
-/*static*/ int PID::IdentifyPeak(size_t numToAverage)
+/*static*/ int LocalHeater::IdentifyPeak(size_t numToAverage)
{
int firstPeakIndex = -1, lastSameIndex = -1;
float peakTempTimesN = -999.0;
@@ -941,7 +831,7 @@ void PID::DoTuningStep()
}
// Calculate the heater model from the accumulated heater parameters
-void PID::CalculateModel()
+void LocalHeater::CalculateModel()
{
if (reprap.Debug(moduleHeat))
{
@@ -959,27 +849,29 @@ void PID::CalculateModel()
//const float td = (float)(tuningPeakDelay + 500) * 0.00065; // take the dead time as 65% of the delay to peak rounded up to a half second
const float td = tc * logf((gain + tuningStartTemp - tuningHeaterOffTemp)/(gain + tuningStartTemp - tuningPeakTemperature)) * 1.3;
- tuned = SetModel(gain, tc, td, tuningPwm,
+ String<1> dummy;
+ const GCodeResult rslt = SetModel(gain, tc, td, tuningPwm,
#if HAS_VOLTAGE_MONITOR
- tuningVoltageAccumulator/voltageSamplesTaken,
+ tuningVoltageAccumulator/voltageSamplesTaken,
#else
- 0.0,
+ 0.0,
#endif
- true, false);
- if (tuned)
+ true, false, dummy.GetRef());
+ if (rslt == GCodeResult::ok)
{
reprap.GetPlatform().MessageF(LoggedGenericMessage,
- "Auto tune heater %d completed in %" PRIu32 " sec\n"
- "Use M307 H%d to see the result, or M500 to save the result in config-override.g\n",
- heater, (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis, heater);
+ "Auto tune heater %u completed in %" PRIu32 " sec\n"
+ "Use M307 H%u to see the result, or M500 to save the result in config-override.g\n",
+ GetHeaterNumber(), (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis, GetHeaterNumber());
}
else
{
- reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (A=%.1f, C=%.1f, D=%.1f)\n", heater, (double)gain, (double)tc, (double)td);
+ reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (A=%.1f, C=%.1f, D=%.1f)\n",
+ GetHeaterNumber(), (double)gain, (double)tc, (double)td);
}
}
-void PID::DisplayBuffer(const char *intro)
+void LocalHeater::DisplayBuffer(const char *intro)
{
OutputBuffer *buf;
if (OutputBuffer::Allocate(buf))
@@ -995,7 +887,7 @@ void PID::DisplayBuffer(const char *intro)
}
// Suspend the heater, or resume it
-void PID::Suspend(bool sus)
+void LocalHeater::Suspend(bool sus)
{
if (sus)
{
diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h
new file mode 100644
index 00000000..6e291473
--- /dev/null
+++ b/src/Heating/LocalHeater.h
@@ -0,0 +1,96 @@
+/*
+ * Pid.h
+ *
+ * Created on: 21 Jul 2016
+ * Author: David
+ */
+
+#ifndef SRC_LOCALHEATER_H_
+#define SRC_LOCALHEATER_H_
+
+/**
+ * This class implements a PID controller for the heaters
+ */
+
+#include "Heater.h"
+#include "FOPDT.h"
+#include "TemperatureError.h"
+#include "Hardware/IoPorts.h"
+#include "GCodes/GCodeResult.h"
+
+class HeaterProtection;
+
+class LocalHeater : public Heater
+{
+ static const size_t NumPreviousTemperatures = 4; // How many samples we average the temperature derivative over
+
+public:
+ LocalHeater(unsigned int heaterNum);
+ LocalHeater(const Heater& h);
+
+ void Spin() override; // Called in a tight loop to keep things running
+ GCodeResult ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply) override;
+ void SwitchOff() override; // Not even standby - all heater power off
+ void ResetFault() override; // Reset a fault condition - only call this if you know what you are doing
+ float GetTemperature() const override; // Get the current temperature
+ float GetAveragePWM() const override; // Return the running average PWM to the heater. Answer is a fraction in [0, 1].
+ float GetAccumulator() const override; // Return the integral accumulator
+ void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) override; // Start an auto tune cycle for this PID
+ void GetAutoTuneStatus(const StringRef& reply) const override; // Get the auto tune status or last result
+ void Suspend(bool sus) override; // Suspend the heater to conserve power or while doing Z probing
+
+protected:
+ HeaterMode GetMode() const override { return mode; }
+ void SwitchOn() override; // Turn the heater on and set the mode
+ GCodeResult UpdateModel(const StringRef& reply) override; // Called when the heater model has been changed
+
+private:
+ void Reset();
+ void SetHeater(float power) const; // Power is a fraction in [0,1]
+ TemperatureError ReadTemperature(); // Read and store the temperature of this heater
+ void DoTuningStep(); // Called on each temperature sample when auto tuning
+ static bool ReadingsStable(size_t numReadings, float maxDiff)
+ pre(numReadings >= 2; numReadings <= MaxTuningTempReadings);
+ static int GetPeakTempIndex(); // Auto tune helper function
+ static int IdentifyPeak(size_t numToAverage); // Auto tune helper function
+ void CalculateModel(); // Calculate G, td and tc from the accumulated readings
+ void DisplayBuffer(const char *intro); // Debug helper
+ float GetExpectedHeatingRate() const; // Get the minimum heating rate we expect
+
+ PwmPort port; // The port that drives the heater
+ float temperature; // The current temperature
+ float previousTemperatures[NumPreviousTemperatures]; // The temperatures of the previous NumDerivativeSamples measurements, used for calculating the derivative
+ size_t previousTemperatureIndex; // Which slot in previousTemperature we fill in next
+ float iAccumulator; // The integral LocalHeater component
+ float lastPwm; // The last PWM value we output, before scaling by kS
+ float averagePWM; // The running average of the PWM, after scaling.
+ uint32_t timeSetHeating; // When we turned on the heater
+ uint32_t lastSampleTime; // Time when the temperature was last sampled by Spin()
+
+ uint16_t heatingFaultCount; // Count of questionable heating behaviours
+
+ uint8_t previousTemperaturesGood; // Bitmap indicating which previous temperature were good readings
+ HeaterMode mode; // Current state of the heater
+ bool tuned; // True if tuning was successful
+ uint8_t badTemperatureCount; // Count of sequential dud readings
+
+ static_assert(sizeof(previousTemperaturesGood) * 8 >= NumPreviousTemperatures, "too few bits in previousTemperaturesGood");
+
+ // Variables used during heater tuning
+ static const size_t MaxTuningTempReadings = 128; // The maximum number of readings we keep. Must be an even number.
+
+ static float *tuningTempReadings; // the readings from the heater being tuned
+ static float tuningStartTemp; // the temperature when we turned on the heater
+ static float tuningPwm; // the PWM to use, 0..1
+ static float tuningTargetTemp; // the maximum temperature we are allowed to reach
+ static uint32_t tuningBeginTime; // when we started the tuning process
+ static uint32_t tuningPhaseStartTime; // when we started the current tuning phase
+ static uint32_t tuningReadingInterval; // how often we are sampling, in milliseconds
+ static size_t tuningReadingsTaken; // how many temperature samples we have taken
+ static float tuningHeaterOffTemp; // the temperature when we turned the heater off
+ static float tuningPeakTemperature; // the peak temperature reached, averaged over 3 readings (so slightly less than the true peak)
+ static uint32_t tuningHeatingTime; // how long we had the heating on for
+ static uint32_t tuningPeakDelay; // how many milliseconds the temperature continues to rise after turning the heater off
+};
+
+#endif /* SRC_LOCALHEATER_H_ */
diff --git a/src/Heating/Pid.h b/src/Heating/Pid.h
deleted file mode 100644
index 882b6a66..00000000
--- a/src/Heating/Pid.h
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Pid.h
- *
- * Created on: 21 Jul 2016
- * Author: David
- */
-
-#ifndef SRC_PID_H_
-#define SRC_PID_H_
-
-/**
- * This class implements a PID controller for the heaters
- */
-
-#include "RepRapFirmware.h"
-#include "FOPDT.h"
-#include "TemperatureError.h"
-#include "Hardware/IoPorts.h"
-#include "GCodes/GCodeResult.h"
-
-class HeaterProtection;
-
-class PID
-{
- enum class HeaterMode : uint8_t
- {
- // The order of these is important because we test "mode > HeatingMode::suspended" to determine whether the heater is active
- // and "mode >= HeatingMode::off" to determine whether the heater is eitehr active or suspended
- fault,
- off,
- suspended,
- heating,
- cooling,
- stable,
- // All states from here onwards must be PID tuning states because function IsTuning assumes that
- tuning0,
- tuning1,
- tuning2,
- tuning3,
- lastTuningMode = tuning3
- };
-
- static const size_t NumPreviousTemperatures = 4; // How many samples we average the temperature derivative over
-
-public:
-
- PID(unsigned int h);
- void Init(float pGain, float pTc, float pTd, bool usePid, bool inverted); // (Re)Set everything to start
- void Reset();
- void Spin(); // Called in a tight loop to keep things running
-
- GCodeResult ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply);
- void SetActiveTemperature(float t);
- float GetActiveTemperature() const;
- void SetStandbyTemperature(float t);
- float GetStandbyTemperature() const;
- void SetHeaterProtection(HeaterProtection *h);
- float GetHighestTemperatureLimit() const; // Get the highest temperature limit
- float GetLowestTemperatureLimit() const; // Get the lowest temperature limit
- void Activate(); // Switch from idle to active
- void Standby(); // Switch from active to idle
- bool Active() const; // Are we active?
- void SwitchOff(); // Not even standby - all heater power off
- bool SwitchedOff() const; // Are we switched off?
- bool CheckProtection() const; // Check heater protection elements and return true if everything is good
- bool FaultOccurred() const; // Has a heater fault occurred?
- void ResetFault(); // Reset a fault condition - only call this if you know what you are doing
- float GetTemperature() const; // Get the current temperature
- float GetAveragePWM() const; // Return the running average PWM to the heater. Answer is a fraction in [0, 1].
- uint32_t GetLastSampleTime() const; // Return when the temp sensor was last sampled
- float GetAccumulator() const; // Return the integral accumulator
- void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply); // Start an auto tune cycle for this PID
- bool IsTuning() const;
- void GetAutoTuneStatus(const StringRef& reply); // Get the auto tune status or last result
-
- const FopDt& GetModel() const // Get the process model
- { return model; }
-
- bool SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted); // Set the process model
-
- bool IsHeaterEnabled() const // Is this heater enabled?
- { return model.IsEnabled(); }
-
- void GetFaultDetectionParameters(float& pMaxTempExcursion, float& pMaxFaultTime) const
- { pMaxTempExcursion = maxTempExcursion; pMaxFaultTime = maxHeatingFaultTime; }
-
- void SetFaultDetectionParameters(float pMaxTempExcursion, float pMaxFaultTime)
- { maxTempExcursion = pMaxTempExcursion; maxHeatingFaultTime = pMaxFaultTime; }
-
- void SetM301PidParameters(const M301PidParameters& params)
- { model.SetM301PidParameters(params); }
-
- void Suspend(bool sus); // Suspend the heater to conserve power or while doing Z probing
-
- TemperatureSensor *GetSensor() const;
-
-private:
-
- void SwitchOn(); // Turn the heater on and set the mode
- void SetHeater(float power) const; // Power is a fraction in [0,1]
- TemperatureError ReadTemperature(); // Read and store the temperature of this heater
- void DoTuningStep(); // Called on each temperature sample when auto tuning
- static bool ReadingsStable(size_t numReadings, float maxDiff)
- pre(numReadings >= 2; numReadings <= MaxTuningTempReadings);
- static int GetPeakTempIndex(); // Auto tune helper function
- static int IdentifyPeak(size_t numToAverage); // Auto tune helper function
- void CalculateModel(); // Calculate G, td and tc from the accumulated readings
- void DisplayBuffer(const char *intro); // Debug helper
- float GetExpectedHeatingRate() const; // Get the minimum heating rate we expect
-
- PwmPort port; // The port that drives the heater
- unsigned int heater; // The index of our heater
- int sensorNumber; // the sensor number used by this heater
- FopDt model; // The process model and PID parameters
-
- HeaterProtection *heaterProtection; // The first element of assigned heater protection items
- float activeTemperature; // The required active temperature
- float standbyTemperature; // The required standby temperature
- float maxTempExcursion; // The maximum temperature excursion permitted while maintaining the setpoint
- float maxHeatingFaultTime; // How long a heater fault is permitted to persist before a heater fault is raised
- float temperature; // The current temperature
- float previousTemperatures[NumPreviousTemperatures]; // The temperatures of the previous NumDerivativeSamples measurements, used for calculating the derivative
- size_t previousTemperatureIndex; // Which slot in previousTemperature we fill in next
- float iAccumulator; // The integral PID component
- float lastPwm; // The last PWM value we output, before scaling by kS
- float averagePWM; // The running average of the PWM, after scaling.
- uint32_t timeSetHeating; // When we turned on the heater
- uint32_t lastSampleTime; // Time when the temperature was last sampled by Spin()
-
- uint16_t heatingFaultCount; // Count of questionable heating behaviours
-
- uint8_t previousTemperaturesGood; // Bitmap indicating which previous temperature were good readings
- HeaterMode mode; // Current state of the heater
- bool active; // Are we active or standby?
- bool tuned; // True if tuning was successful
- uint8_t badTemperatureCount; // Count of sequential dud readings
-
- static_assert(sizeof(previousTemperaturesGood) * 8 >= NumPreviousTemperatures, "too few bits in previousTemperaturesGood");
-
- // Variables used during heater tuning
- static const size_t MaxTuningTempReadings = 128; // The maximum number of readings we keep. Must be an even number.
-
- static float *tuningTempReadings; // the readings from the heater being tuned
- static float tuningStartTemp; // the temperature when we turned on the heater
- static float tuningPwm; // the PWM to use, 0..1
- static float tuningTargetTemp; // the maximum temperature we are allowed to reach
- static uint32_t tuningBeginTime; // when we started the tuning process
- static uint32_t tuningPhaseStartTime; // when we started the current tuning phase
- static uint32_t tuningReadingInterval; // how often we are sampling, in milliseconds
- static size_t tuningReadingsTaken; // how many temperature samples we have taken
- static float tuningHeaterOffTemp; // the temperature when we turned the heater off
- static float tuningPeakTemperature; // the peak temperature reached, averaged over 3 readings (so slightly less than the true peak)
- static uint32_t tuningHeatingTime; // how long we had the heating on for
- static uint32_t tuningPeakDelay; // how many milliseconds the temperature continues to rise after turning the heater off
-};
-
-
-inline bool PID::Active() const
-{
- return active;
-}
-
-inline float PID::GetActiveTemperature() const
-{
- return activeTemperature;
-}
-
-inline float PID::GetStandbyTemperature() const
-{
- return standbyTemperature;
-}
-
-inline float PID::GetTemperature() const
-{
- return temperature;
-}
-
-inline bool PID::FaultOccurred() const
-{
- return mode == HeaterMode::fault;
-}
-
-inline bool PID::SwitchedOff() const
-{
- return mode == HeaterMode::off;
-}
-
-inline uint32_t PID::GetLastSampleTime() const
-{
- return lastSampleTime;
-}
-
-inline float PID::GetAccumulator() const
-{
- return iAccumulator;
-}
-
-inline bool PID::IsTuning() const
-{
- return mode >= HeaterMode::tuning0;
-}
-
-#endif /* SRC_PID_H_ */
diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp
new file mode 100644
index 00000000..a3afee77
--- /dev/null
+++ b/src/Heating/RemoteHeater.cpp
@@ -0,0 +1,97 @@
+/*
+ * RemoteHeater.cpp
+ *
+ * Created on: 24 Jul 2019
+ * Author: David
+ */
+
+#include "RemoteHeater.h"
+
+#if SUPPORT_CAN_EXPANSION
+
+#include "CAN/CanMessageGenericConstructor.h"
+
+void RemoteHeater::Spin()
+{
+}
+
+GCodeResult RemoteHeater::ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply)
+{
+ CanMessageGenericConstructor cons(M950Params);
+ if (!cons.PopulateFromCommand(gb, reply))
+ {
+ return GCodeResult::error;
+ }
+ return cons.SendAndGetResponse(CanMessageType::m950, boardAddress, reply);
+}
+
+void RemoteHeater::SwitchOff()
+{
+}
+
+void RemoteHeater::ResetFault()
+{
+}
+
+float RemoteHeater::GetTemperature() const
+{
+ return 0.0;
+}
+
+float RemoteHeater::GetAveragePWM() const
+{
+ return 0.0; // not yet supported
+}
+
+// Return the integral accumulator
+float RemoteHeater::GetAccumulator() const
+{
+ return 0.0; // not yet supported
+}
+
+void RemoteHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply)
+{
+}
+
+void RemoteHeater::GetAutoTuneStatus(const StringRef& reply) const
+{
+}
+
+void RemoteHeater::Suspend(bool sus)
+{
+}
+
+Heater::HeaterMode RemoteHeater::GetMode() const
+{
+ return HeaterMode::off;
+}
+
+void RemoteHeater::SwitchOn()
+{
+}
+
+// This is called when the heater model has been updated. Returns true if successful.
+GCodeResult RemoteHeater::UpdateModel(const StringRef& reply)
+{
+ CanMessageGenericConstructor cons(M307Params);
+ cons.AddUParam('H', GetHeaterNumber());
+ cons.AddFParam('A', GetModel().GetGain());
+ cons.AddFParam('C', GetModel().GetTimeConstant());
+ cons.AddFParam('D', GetModel().GetDeadTime());
+ cons.AddFParam('V', GetModel().GetVoltage());
+ cons.AddFParam('S', GetModel().GetMaxPwm());
+ cons.AddUParam('B', (GetModel().UsePid()) ? 0 : 1);
+ cons.AddUParam('I', (GetModel().IsInverted()) ? 1 : 0);
+
+ const char* const err = cons.GetErrorMessage();
+ if (err != nullptr)
+ {
+ reply.copy(err);
+ return GCodeResult::error;
+ }
+ return cons.SendAndGetResponse(CanMessageType::m307, boardAddress, reply);
+}
+
+#endif
+
+// End
diff --git a/src/Heating/RemoteHeater.h b/src/Heating/RemoteHeater.h
new file mode 100644
index 00000000..bcb58e75
--- /dev/null
+++ b/src/Heating/RemoteHeater.h
@@ -0,0 +1,43 @@
+/*
+ * RemoteHeater.h
+ *
+ * Created on: 24 Jul 2019
+ * Author: David
+ */
+
+#ifndef SRC_HEATING_REMOTEHEATER_H_
+#define SRC_HEATING_REMOTEHEATER_H_
+
+#include "Heater.h"
+
+#if SUPPORT_CAN_EXPANSION
+
+class RemoteHeater : public Heater
+{
+public:
+ RemoteHeater(unsigned int num, CanAddress board) : Heater(num), boardAddress(board) { }
+ RemoteHeater(const Heater& h, CanAddress board) : Heater(h), boardAddress(board) { }
+
+ void Spin() override; // Called in a tight loop to keep things running
+ GCodeResult ConfigurePortAndSensor(GCodeBuffer& gb, const StringRef& reply) override;
+ void SwitchOff() override; // Not even standby - all heater power off
+ void ResetFault() override; // Reset a fault condition - only call this if you know what you are doing
+ float GetTemperature() const override; // Get the current temperature
+ float GetAveragePWM() const override; // Return the running average PWM to the heater. Answer is a fraction in [0, 1].
+ float GetAccumulator() const override; // Return the integral accumulator
+ void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) override; // Start an auto tune cycle for this PID
+ void GetAutoTuneStatus(const StringRef& reply) const override; // Get the auto tune status or last result
+ void Suspend(bool sus) override; // Suspend the heater to conserve power or while doing Z probing
+
+protected:
+ HeaterMode GetMode() const override;
+ void SwitchOn() override; // Turn the heater on and set the mode
+ GCodeResult UpdateModel(const StringRef& reply) override; // Called when the heater model has been changed
+
+private:
+ CanAddress boardAddress;
+};
+
+#endif
+
+#endif /* SRC_HEATING_REMOTEHEATER_H_ */