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:
-rw-r--r--src/Configuration.h6
-rw-r--r--src/GCodes/GCodes.h2
-rw-r--r--src/GCodes/GCodes3.cpp2
-rw-r--r--src/Heating/FOPDT.cpp51
-rw-r--r--src/Heating/FOPDT.h18
-rw-r--r--src/Heating/Heater.cpp52
-rw-r--r--src/Heating/Heater.h4
-rw-r--r--src/Heating/LocalHeater.cpp156
-rw-r--r--src/Heating/LocalHeater.h10
9 files changed, 188 insertions, 113 deletions
diff --git a/src/Configuration.h b/src/Configuration.h
index bb0bbf9c..aead18c6 100644
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -100,9 +100,9 @@ constexpr uint32_t DefaultHeaterFaultTimeout = 10 * 60 * 1000; // How long we wa
// Heating model default parameters. For the chamber heater, we use the same values as for the bed heater.
// These parameters are about right for an E3Dv6 hot end with 30W heater.
-constexpr float DefaultHotEndHeaterGain = 340.0;
-constexpr float DefaultHotEndHeaterTimeConstant = 140.0;
-constexpr float DefaultHotEndHeaterDeadTime = 5.5;
+constexpr float DefaultHotEndHeaterCoolingRate = 1.0/140.0; // E3D V6 has a cooling time constant of about 140 seconds with the fan off
+constexpr float DefaultHotEndHeaterHeatingRate = 340.0 * DefaultHotEndHeaterCoolingRate;
+constexpr float DefaultHotEndHeaterDeadTime = 5.5; // E3D v6
constexpr unsigned int FirstExtraHeaterProtection = 100; // Index of the first extra heater protection item
diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h
index 3836ec9d..95689f04 100644
--- a/src/GCodes/GCodes.h
+++ b/src/GCodes/GCodes.h
@@ -405,7 +405,7 @@ private:
bool IsMappedFan(unsigned int fanNumber) noexcept; // Return true if this fan number is currently being used as a print cooling fan
void SaveFanSpeeds() noexcept; // Save the speeds of all fans
- GCodeResult DefineGrid(GCodeBuffer& gb, const StringRef &reply); // Define the probing grid, returning true if error
+ GCodeResult DefineGrid(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException); // Define the probing grid, returning true if error
#if HAS_MASS_STORAGE
GCodeResult LoadHeightMap(GCodeBuffer& gb, const StringRef& reply); // Load the height map from file
bool TrySaveHeightMap(const char *filename, const StringRef& reply) const noexcept; // Save the height map to the specified file
diff --git a/src/GCodes/GCodes3.cpp b/src/GCodes/GCodes3.cpp
index ded97adb..710d6114 100644
--- a/src/GCodes/GCodes3.cpp
+++ b/src/GCodes/GCodes3.cpp
@@ -234,7 +234,7 @@ bool GCodes::WriteWorkplaceCoordinates(FileStore *f) const noexcept
#endif
// Define the probing grid, called when we see an M557 command
-GCodeResult GCodes::DefineGrid(GCodeBuffer& gb, const StringRef &reply)
+GCodeResult GCodes::DefineGrid(GCodeBuffer& gb, const StringRef &reply) THROWS(GCodeException)
{
if (!LockMovement(gb)) // to ensure that probing is not already in progress
{
diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp
index cb8e49a5..bb62bb9a 100644
--- a/src/Heating/FOPDT.cpp
+++ b/src/Heating/FOPDT.cpp
@@ -31,12 +31,14 @@ constexpr ObjectModelTableEntry FopDt::objectModelTable[] =
// 0. FopDt members
{ "deadTime", OBJECT_MODEL_FUNC(self->deadTime, 1), ObjectModelEntryFlags::none },
{ "enabled", OBJECT_MODEL_FUNC(self->enabled), ObjectModelEntryFlags::none },
- { "gain", OBJECT_MODEL_FUNC(self->gain, 1), ObjectModelEntryFlags::none },
+ { "gain", OBJECT_MODEL_FUNC(self->GetGainFanOff(), 1), ObjectModelEntryFlags::none }, // legacy, to be removed
+ { "heatingRate", OBJECT_MODEL_FUNC(self->heatingRate, 3), ObjectModelEntryFlags::none },
{ "inverted", OBJECT_MODEL_FUNC(self->inverted), ObjectModelEntryFlags::none },
{ "maxPwm", OBJECT_MODEL_FUNC(self->maxPwm, 2), ObjectModelEntryFlags::none },
{ "pid", OBJECT_MODEL_FUNC(self, 1), ObjectModelEntryFlags::none },
{ "standardVoltage", OBJECT_MODEL_FUNC(self->standardVoltage, 1), ObjectModelEntryFlags::none },
- { "timeConstant", OBJECT_MODEL_FUNC(self->timeConstant, 1), ObjectModelEntryFlags::none },
+ { "timeConstant", OBJECT_MODEL_FUNC(self->GetTimeConstantFanOff(), 1), ObjectModelEntryFlags::none },
+ { "timeConstantFanOn", OBJECT_MODEL_FUNC(self->GetTimeConstantFanOn(), 1), ObjectModelEntryFlags::none },
// 1. PID members
{ "d", OBJECT_MODEL_FUNC(self->loadChangeParams.tD * self->loadChangeParams.kP, 1), ObjectModelEntryFlags::none },
@@ -46,7 +48,7 @@ constexpr ObjectModelTableEntry FopDt::objectModelTable[] =
{ "used", OBJECT_MODEL_FUNC(self->usePid), ObjectModelEntryFlags::none },
};
-constexpr uint8_t FopDt::objectModelTableDescriptor[] = { 2, 8, 5 };
+constexpr uint8_t FopDt::objectModelTableDescriptor[] = { 2, 10, 5 };
DEFINE_GET_OBJECT_MODEL_TABLE(FopDt)
@@ -55,27 +57,30 @@ DEFINE_GET_OBJECT_MODEL_TABLE(FopDt)
// Heater 6 on the Duet 0.8.5 is disabled by default at startup so that we can use fan 2.
// Set up sensible defaults here in case the user enables the heater without specifying values for all the parameters.
FopDt::FopDt() noexcept
- : gain(DefaultHotEndHeaterGain), timeConstant(DefaultHotEndHeaterTimeConstant), deadTime(DefaultHotEndHeaterDeadTime), maxPwm(1.0), standardVoltage(0.0),
+ : heatingRate(DefaultHotEndHeaterHeatingRate),
+ coolingRateFanOff(DefaultHotEndHeaterCoolingRate), coolingRateFanOn(DefaultHotEndHeaterCoolingRate),
+ deadTime(DefaultHotEndHeaterDeadTime), maxPwm(1.0), standardVoltage(0.0),
enabled(false), usePid(true), inverted(false), pidParametersOverridden(false)
{
}
// Check the model parameters are sensible, if they are then save them and return true.
-bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept
+bool FopDt::SetParameters(float phr, float pcrFanOff, float pcrFanOn, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept
{
- if (pg == -1.0 && ptc == -1.0 && pdt == -1.0)
- {
- // Setting all parameters to -1 disables the heater control completely so we can use the pin for other purposes
- enabled = false;
- return true;
- }
-
// DC 2017-06-20: allow S down to 0.01 for one of our OEMs (use > 0.0099 because >= 0.01 doesn't work due to rounding error)
- const float maxGain = max<float>(1500.0, temperatureLimit + 500.0);
- if (pg > 10.0 && pg <= maxGain && pdt > 0.099 && ptc >= 2 * pdt && pMaxPwm > 0.0099 && pMaxPwm <= 1.0)
+ const float maxTempIncrease = max<float>(1500.0, temperatureLimit + 500.0);
+ if ( phr > 0.1 // minimum 0.1C/sec at room temperature
+ && phr/pcrFanOff <= maxTempIncrease // max temperature increase within limits
+ && pcrFanOn >= pcrFanOff
+ && pdt > 0.099
+ && 0.5 >= pdt * pcrFanOn // dead time less then cooling time constant
+ && pMaxPwm > 0.0099
+ && pMaxPwm <= 1.0
+ )
{
- gain = pg;
- timeConstant = ptc;
+ heatingRate = phr;
+ coolingRateFanOff = pcrFanOff;
+ coolingRateFanOn = pcrFanOn;
deadTime = pdt;
maxPwm = pMaxPwm;
standardVoltage = pVoltage;
@@ -115,8 +120,8 @@ void FopDt::SetM301PidParameters(const M301PidParameters& pp) noexcept
bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept
{
String<StringLength256> scratchString;
- scratchString.printf("M307 H%u A%.1f C%.1f D%.1f S%.2f V%.1f B%d\n",
- heater, (double)gain, (double)timeConstant, (double)deadTime, (double)maxPwm, (double)standardVoltage, (usePid) ? 0 : 1);
+ scratchString.printf("M307 H%u R%.3f C%.3f:%.3f D%.2f S%.2f V%.1f B%d\n",
+ heater, (double)heatingRate, (double)coolingRateFanOff, (double)coolingRateFanOn, (double)deadTime, (double)maxPwm, (double)standardVoltage, (usePid) ? 0 : 1);
bool ok = f->Write(scratchString.c_str());
if (ok && pidParametersOverridden)
{
@@ -161,13 +166,13 @@ bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept
void FopDt::CalcPidConstants() noexcept
{
- const float timeFrac = deadTime/timeConstant;
- loadChangeParams.kP = 0.7/(gain * timeFrac);
- loadChangeParams.recipTi = (1.0/1.14)/(powf(timeConstant, 0.25) * powf(deadTime, 0.75)); // Ti = 1.14 * timeConstant^0.25 * deadTime^0.75 (Ho et al)
+ const float averageCoolingRate = (coolingRateFanOff + coolingRateFanOn) * 0.5;
+ loadChangeParams.kP = 0.7/(heatingRate * deadTime);
+ loadChangeParams.recipTi = powf(averageCoolingRate, 0.25)/(1.14 * powf(deadTime, 0.75)); // Ti = 1.14 * timeConstant^0.25 * deadTime^0.75 (Ho et al)
loadChangeParams.tD = deadTime * 0.7;
- setpointChangeParams.kP = 0.7/(gain * timeFrac);
- setpointChangeParams.recipTi = 1.0/(powf(timeConstant, 0.5) * powf(deadTime, 0.5)); // Ti = timeConstant^0.5 * deadTime^0.5
+ setpointChangeParams.kP = 0.7/(heatingRate * deadTime);
+ setpointChangeParams.recipTi = powf(coolingRateFanOff, 0.5)/powf(deadTime, 0.5); // Ti = timeConstant^0.5 * deadTime^0.5
setpointChangeParams.tD = deadTime * 0.7;
pidParametersOverridden = false;
diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h
index f56cf3ad..728b3c78 100644
--- a/src/Heating/FOPDT.h
+++ b/src/Heating/FOPDT.h
@@ -42,16 +42,23 @@ class FopDt INHERIT_OBJECT_MODEL
public:
FopDt() noexcept;
- bool SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept;
+ bool SetParameters(float phr, float pcrFanOff, float pcrFanOn, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept;
- float GetGain() const noexcept { return gain; }
- float GetTimeConstant() const noexcept { return timeConstant; }
+ // Stored parameters
+ float GetHeatingRate() const noexcept { return heatingRate; }
+ float GetCoolingRateFanOff() const noexcept { return coolingRateFanOff; }
+ float GetCoolingRateFanOn() const noexcept { return coolingRateFanOn; }
float GetDeadTime() const noexcept { return deadTime; }
float GetMaxPwm() const noexcept { return maxPwm; }
float GetVoltage() const noexcept { return standardVoltage; }
bool UsePid() const noexcept { return usePid; }
bool IsInverted() const noexcept { return inverted; }
bool IsEnabled() const noexcept { return enabled; }
+
+ // Derived parameters
+ float GetGainFanOff() const noexcept { return heatingRate/coolingRateFanOff; }
+ float GetTimeConstantFanOff() const noexcept { return 1.0/coolingRateFanOff; }
+ float GetTimeConstantFanOn() const noexcept { return 1.0/coolingRateFanOn; }
bool ArePidParametersOverridden() const noexcept { return pidParametersOverridden; }
M301PidParameters GetM301PidParameters(bool forLoadChange) const noexcept;
void SetM301PidParameters(const M301PidParameters& params) noexcept;
@@ -75,8 +82,9 @@ protected:
private:
void CalcPidConstants() noexcept;
- float gain;
- float timeConstant;
+ float heatingRate;
+ float coolingRateFanOff;
+ float coolingRateFanOn;
float deadTime;
float maxPwm;
float standardVoltage; // power voltage reading at which tuning was done, or 0 if unknown
diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp
index 14786a52..4d7133bf 100644
--- a/src/Heating/Heater.cpp
+++ b/src/Heating/Heater.cpp
@@ -98,19 +98,43 @@ void Heater::SetDefaultMonitors() noexcept
}
}
-GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) noexcept
+GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException)
{
bool seen = false;
- float gain = model.GetGain(),
- tc = model.GetTimeConstant(),
- td = model.GetDeadTime(),
+ float heatingRate = model.GetHeatingRate();
+ float td = model.GetDeadTime(),
maxPwm = model.GetMaxPwm(),
voltage = model.GetVoltage();
+ float coolingRates[2] = { model.GetCoolingRateFanOff(), model.GetCoolingRateFanOn() };
int32_t dontUsePid = model.UsePid() ? 0 : 1;
int32_t inversionParameter = 0;
- gb.TryGetFValue('A', gain, seen);
- gb.TryGetFValue('C', tc, seen);
+ // Get the cooling time constant(s) first
+ float timeConstants[2];
+ size_t numValues = 2;
+ if (gb.TryGetFloatArray('C', numValues, timeConstants, reply, seen, true))
+ {
+ return GCodeResult::error;
+ }
+ else if (seen)
+ {
+ coolingRates[0] = 1.0/timeConstants[0];
+ coolingRates[1] = 1.0/timeConstants[1];
+ }
+
+ if (gb.Seen('R'))
+ {
+ // New style heater model. R = heating rate, C[2] = cooling rates
+ seen = true;
+ heatingRate = gb.GetFValue();
+ }
+ else if (gb.Seen('A'))
+ {
+ // Old style heating model. A = gain, C = cooling time constant
+ seen = true;
+ const float gain = gb.GetFValue();
+ heatingRate = gain * coolingRates[0];
+ }
gb.TryGetFValue('D', td, seen);
gb.TryGetIValue('B', dontUsePid, seen);
gb.TryGetFValue('S', maxPwm, seen);
@@ -120,7 +144,7 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
if (seen)
{
const bool inverseTemperatureControl = (inversionParameter == 1 || inversionParameter == 3);
- const GCodeResult rslt = SetModel(gain, tc, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply);
+ const GCodeResult rslt = SetModel(heatingRate, coolingRates[0], coolingRates[1], td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply);
if (rslt != GCodeResult::ok)
{
return rslt;
@@ -135,8 +159,9 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
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);
+ reply.printf("Heater %u model: heating rate %.3f, cooling time constant %.1f (fan off) %.1f (fan on), dead time %.2f, max PWM %.2f, calibration voltage %.1f, mode %s", heater,
+ (double)model.GetHeatingRate(), (double)model.GetTimeConstantFanOff(), (double)model.GetTimeConstantFanOn(),
+ (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode);
if (model.IsInverted())
{
reply.cat(", inverted temperature control");
@@ -154,10 +179,10 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
}
// 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) noexcept
+GCodeResult Heater::SetModel(float gain, float tcOff, float tcOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept
{
GCodeResult rslt;
- if (model.SetParameters(gain, tc, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
+ if (model.SetParameters(gain, tcOff, tcOn, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
{
if (model.IsEnabled())
{
@@ -345,11 +370,12 @@ void Heater::SetModelDefaults() noexcept
{
if (reprap.GetHeat().IsBedOrChamberHeater(heaterNumber))
{
- model.SetParameters(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, DefaultBedTemperatureLimit, 0.0, false, false);
+ model.SetParameters(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, DefaultBedTemperatureLimit, 0.0, false, false);
}
else
{
- model.SetParameters(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, 1.0, DefaultHotEndTemperatureLimit, 0.0, true, false);
+ model.SetParameters(DefaultHotEndHeaterHeatingRate, DefaultHotEndHeaterCoolingRate, DefaultHotEndHeaterCoolingRate, DefaultHotEndHeaterDeadTime,
+ 1.0, DefaultHotEndTemperatureLimit, 0.0, true, false);
}
String<1> dummy;
diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h
index 9dfe82e1..502e2e74 100644
--- a/src/Heating/Heater.h
+++ b/src/Heating/Heater.h
@@ -70,7 +70,7 @@ public:
float GetLowestTemperatureLimit() const noexcept; // Get the lowest temperature limit
const FopDt& GetModel() const noexcept { return model; } // Get the process model
- GCodeResult SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) noexcept;
+ GCodeResult SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException);
void SetModelDefaults() noexcept;
bool IsHeaterEnabled() const noexcept // Is this heater enabled?
@@ -117,7 +117,7 @@ protected:
float GetMaxTemperatureExcursion() const noexcept { return maxTempExcursion; }
float GetMaxHeatingFaultTime() const noexcept { return maxHeatingFaultTime; }
float GetTargetTemperature() const noexcept { return (active) ? activeTemperature : standbyTemperature; }
- GCodeResult SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model
+ GCodeResult SetModel(float gain, float tcOff, float tcOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model
HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation
diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp
index 71934e94..d5764c04 100644
--- a/src/Heating/LocalHeater.cpp
+++ b/src/Heating/LocalHeater.cpp
@@ -14,6 +14,8 @@
#include "RepRap.h"
#include <Tools/Tool.h>
+#define TUNE_WITH_PART_FAN 0
+
// Private constants
const uint32_t InitialTuningReadingInterval = 250; // the initial reading interval in milliseconds
const uint32_t TempSettleTimeout = 20000; // how long we allow the initial temperature to settle
@@ -36,8 +38,7 @@ static uint32_t peakTime; // the time at which we recorded peakTemp
static float afterPeakTemp; // temperature after max from which we start timing the cooling rate
static uint32_t afterPeakTime; // the time at which we recorded afterPeakTemp
static FansBitmap tuningFans;
-static bool collecting;
-static bool fanOn; // whether we are running with the fan on or off
+static unsigned int tuningPhase;
static LocalHeater::HeaterParameters fanOffParams, fanOnParams;
@@ -347,7 +348,7 @@ void LocalHeater::Spin() noexcept
// 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)/GetModel().GetGain(), 0.0, GetModel().GetMaxPwm());
+ const float expectedPwm = constrain<float>((temperature - NormalAmbientTemperature)/GetModel().GetGainFanOff(), 0.0, GetModel().GetMaxPwm());
if (pPlusD + expectedPwm > GetModel().GetMaxPwm())
{
lastPwm = GetModel().GetMaxPwm();
@@ -472,8 +473,8 @@ float LocalHeater::GetAveragePWM() const noexcept
float LocalHeater::GetExpectedHeatingRate() const noexcept
{
// 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 * 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
+ const float maxTemperatureRise = 0.75 * GetModel().GetGainFanOff() * GetAveragePWM(); // this is the highest temperature above ambient we expect the heater can reach at this PWM
+ const float initialHeatingRate = maxTemperatureRise/GetModel().GetTimeConstantFanOn(); // this is the expected heating rate at ambient temperature
return (maxTemperatureRise >= 20.0)
? (maxTemperatureRise + NormalAmbientTemperature - temperature) * initialHeatingRate/maxTemperatureRise
: 0.0;
@@ -552,8 +553,8 @@ GCodeResult LocalHeater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply)
tuningTargetTemp = targetTemp;
tuningStartTemp.Clear();
tuningBeginTime = millis();
+ tuningPhase = 0;
tuned = false; // assume failure
- fanOn = collecting = false;
if (seenA)
{
@@ -577,12 +578,13 @@ void LocalHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept
if (mode >= HeaterMode::tuning0)
{
// Phases are: 1 = stabilising, 2 = heating, 3 = settling, 4 = cycling with fan off, 5 = cycling with fan on
- const unsigned int numPhases = (tuningFans.IsEmpty()) ? 5 : 4;
- const unsigned int currentPhase = (mode < HeaterMode::tuning2) ? (unsigned int)mode - (unsigned int)HeaterMode::tuning0 + 1
- : (!collecting) ? 3
- : (fanOn) ? 5
- : 4;
- reply.printf("Heater %u is being tuned, phase %u of %u", GetHeaterNumber(), currentPhase, numPhases);
+ const unsigned int numPhases = (tuningFans.IsEmpty()) ? 4
+#if TUNE_WITH_PART_FAN
+ : 6;
+#else
+ : 5;
+#endif
+ reply.printf("Heater %u is being tuned, phase %u of %u", GetHeaterNumber(), tuningPhase + 1, numPhases);
}
else if (tuned)
{
@@ -674,6 +676,7 @@ void LocalHeater::DoTuningStep() noexcept
break;
case HeaterMode::tuning1:
+ tuningPhase = 1;
// Heating up
{
const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber());
@@ -704,7 +707,8 @@ void LocalHeater::DoTuningStep() noexcept
#endif
ClearCounters();
mode = HeaterMode::tuning2;
- reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 2, heater cycling\n");
+ tuningPhase = 2;
+ reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 2, heater settling\n");
}
}
return;
@@ -724,47 +728,66 @@ void LocalHeater::DoTuningStep() noexcept
coolingRate.Add((afterPeakTemp - temperature) * SecondsToMillis/(now - afterPeakTime));
// Decide whether to finish this phase
- if (collecting)
+ if (tuningPhase > 2)
{
- if ( coolingRate.GetNumSamples() == MaxTuningHeaterCycles
- || ( coolingRate.GetNumSamples() >= MinTuningHeaterCycles
-// && tOn.DeviationFractionWithin(0.1)
-// && tOff.DeviationFractionWithin(0.1)
- && dLow.DeviationFractionWithin(0.2)
- && dHigh.DeviationFractionWithin(0.2)
- && heatingRate.DeviationFractionWithin(0.1)
- && coolingRate.DeviationFractionWithin(0.1)
- )
- )
+ if (coolingRate.GetNumSamples() >= MinTuningHeaterCycles)
{
- if (fanOn)
- {
- reprap.GetFansManager().SetFansValue(tuningFans, 0.0);
- CalculateModel(fanOnParams);
- ReportModel();
- break;
- }
- else
+ const bool isConsistent = dLow.DeviationFractionWithin(0.2)
+ && dHigh.DeviationFractionWithin(0.2)
+ && heatingRate.DeviationFractionWithin(0.1)
+ && coolingRate.DeviationFractionWithin(0.1);
+ if (isConsistent || coolingRate.GetNumSamples() == MaxTuningHeaterCycles)
{
- CalculateModel(fanOffParams);
- if (tuningFans.IsEmpty())
+ if (!isConsistent)
{
- ReportModel();
- break;
+ reprap.GetPlatform().Message(WarningMessage, "heater behaviour was not consistent during tuning\n");
}
- else
+
+ if (tuningPhase == 3)
{
- reprap.GetFansManager().SetFansValue(tuningFans, 1.0);
- fanOn = true;
+ CalculateModel(fanOffParams);
+ if (tuningFans.IsEmpty())
+ {
+ SetAndReportModel(false);
+ break;
+ }
+ else
+ {
+#if TUNE_WITH_PART_FAN
+ reprap.GetFansManager().SetFansValue(tuningFans, 0.5); // turn fans on at half PWM
+#else
+ reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans on at full PWM
+#endif
+ tuningPhase = 4;
+ ClearCounters();
+ reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan 50%\n");
+ }
+ }
+#if TUNE_WITH_PART_FAN
+ else if (tuningPhase == 4)
+ {
+ reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans fully on
+ CalculateModel(fanOnParams);
+ tuningPhase = 5;
ClearCounters();
+ reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan 100%\n");
+ }
+#endif
+ else
+ {
+ reprap.GetFansManager().SetFansValue(tuningFans, 0.0); // turn fans off
+ CalculateModel(fanOnParams);
+ SetAndReportModel(true);
+ break;
}
}
}
}
else if (coolingRate.GetNumSamples() == TuningHeaterSettleCycles)
{
- collecting = true;
+ tuningPhase = 3;
ClearCounters();
+ reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan off\n");
}
}
lastOnTime = peakTime = now;
@@ -823,47 +846,58 @@ void LocalHeater::CalculateModel(HeaterParameters& params) noexcept
reprap.GetPlatform().MessageF(GenericMessage,
"tOn %ld" PLUS_OR_MINUS "%ld, tOff %ld" PLUS_OR_MINUS "%ld,"
" dHigh %ld" PLUS_OR_MINUS "%ld, dLow %ld" PLUS_OR_MINUS "%ld,"
- " R %.2f" PLUS_OR_MINUS "%.2f, C %.2f" PLUS_OR_MINUS "%.2f,"
- " V %.1f" PLUS_OR_MINUS "%.1f\n",
+ " R %.3f" PLUS_OR_MINUS "%.3f, C %.3f" PLUS_OR_MINUS "%.3f,"
+ " V %.1f" PLUS_OR_MINUS "%.1f, cycles %u\n",
lrintf(tOn.GetMean()), lrintf(tOn.GetDeviation()),
lrintf(tOff.GetMean()), lrintf(tOff.GetDeviation()),
lrintf(dHigh.GetMean()), lrintf(dHigh.GetDeviation()),
lrintf(dLow.GetMean()), lrintf(dLow.GetDeviation()),
(double)heatingRate.GetMean(), (double)heatingRate.GetDeviation(),
(double)coolingRate.GetMean(), (double)coolingRate.GetDeviation(),
- (double)tuningVoltage.GetMean(), (double)tuningVoltage.GetDeviation()
+ (double)tuningVoltage.GetMean(), (double)tuningVoltage.GetDeviation(),
+ coolingRate.GetNumSamples()
);
}
const float cycleTime = tOn.GetMean() + tOff.GetMean(); // in milliseconds
const float averageTemperatureRise = tuningTargetTemp - 0.5 * TuningHysteresis - tuningStartTemp.GetMean();
params.deadTime = (((dHigh.GetMean() * tOff.GetMean()) + (dLow.GetMean() * tOn.GetMean())) * MillisToSeconds)/cycleTime; // in seconds
- params.coolingTimeConstant = averageTemperatureRise/coolingRate.GetMean(); // in seconds
- params.heatingRate = (heatingRate.GetMean() + coolingRate.GetMean());
- params.gain = averageTemperatureRise * params.heatingRate/(coolingRate.GetMean() * tuningPwm);
- reprap.GetPlatform().MessageF(LoggedGenericMessage, "R%.1f A%.1f C%.1f D%.2f, cycles %u\n",
- (double)params.heatingRate, (double)params.gain, (double)params.coolingTimeConstant, (double)params.deadTime, tOn.GetNumSamples());
+ params.coolingRate = coolingRate.GetMean()/averageTemperatureRise; // in seconds
+ params.heatingRate = (heatingRate.GetMean() + coolingRate.GetMean()) / tuningPwm;
+ params.numCycles = dHigh.GetNumSamples();
}
-void LocalHeater::ReportModel() noexcept
+void LocalHeater::SetAndReportModel(bool usingFans) noexcept
{
- String<1> dummy;
- const GCodeResult rslt = SetModel(fanOffParams.gain, fanOffParams.coolingTimeConstant, fanOffParams.deadTime, tuningPwm,
+ const float hRate = (usingFans) ? (fanOffParams.heatingRate + fanOnParams.heatingRate) * 0.5 : fanOffParams.heatingRate;
+ const float deadTime = (usingFans) ? (fanOffParams.deadTime + fanOnParams.deadTime) * 0.5 : fanOffParams.deadTime;
+ String<StringLength256> str;
+ const GCodeResult rslt = SetModel( hRate,
+ fanOffParams.coolingRate, (usingFans) ? fanOnParams.coolingRate : fanOffParams.coolingRate,
+ deadTime,
+ tuningPwm,
#if HAS_VOLTAGE_MONITOR
tuningVoltage.GetMean(),
#else
0.0,
#endif
- true, false, dummy.GetRef());
+ true, false, str.GetRef());
if (rslt == GCodeResult::ok || rslt == GCodeResult::warning)
{
tuned = true;
- reprap.GetPlatform().MessageF(LoggedGenericMessage,
- "Auto tuning heater %u completed after %u cycles in %" PRIu32 " seconds. This heater needs the following M307 command:\n"
- " M307 H%u A%.1f C%.1f D%.2f S%.2f V%.1f\n",
- GetHeaterNumber(), tOff.GetNumSamples(), (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis,
- GetHeaterNumber(), (double)GetModel().GetGain(), (double)GetModel().GetTimeConstant(), (double)GetModel().GetDeadTime(),
- (double)GetModel().GetMaxPwm(), (double)GetModel().GetVoltage());
+ str.printf("Auto tuning heater %u completed after %u cycles in %" PRIu32 " seconds. This heater needs the following M307 command:\n"
+ " M307 H%u R%.3f C%.1f",
+ GetHeaterNumber(),
+ (usingFans) ? fanOffParams.numCycles + fanOnParams.numCycles : fanOffParams.numCycles,
+ (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis,
+ GetHeaterNumber(), (double)GetModel().GetHeatingRate(), (double)(1.0/GetModel().GetCoolingRateFanOff())
+ );
+ if (usingFans)
+ {
+ str.catf(":%.1f", (double)(1.0/GetModel().GetCoolingRateFanOn()));
+ }
+ str.catf(" D%.2f S%.2f V%.1f\n", (double)GetModel().GetDeadTime(), (double)GetModel().GetMaxPwm(), (double)GetModel().GetVoltage());
+ reprap.GetPlatform().Message(LoggedGenericMessage, str.c_str());
if (reprap.GetGCodes().SawM501InConfigFile())
{
reprap.GetPlatform().Message(GenericMessage, "Send M500 to save this command in config-override.g\n");
@@ -875,8 +909,10 @@ void LocalHeater::ReportModel() noexcept
}
else
{
- reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (A=%.1f, C=%.1f, D=%.1f)\n",
- GetHeaterNumber(), (double)fanOffParams.gain, (double)fanOffParams.coolingTimeConstant, (double)fanOffParams.deadTime);
+ reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (R=%.3f, C=%.3f:%.3f, D=%.1f)\n",
+ GetHeaterNumber(), (double)hRate,
+ (double)fanOffParams.coolingRate, (double)fanOnParams.coolingRate,
+ (double)fanOffParams.deadTime);
}
}
diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h
index 07e0abfe..8c32dc39 100644
--- a/src/Heating/LocalHeater.h
+++ b/src/Heating/LocalHeater.h
@@ -15,8 +15,8 @@
#include "Heater.h"
#include "FOPDT.h"
#include "TemperatureError.h"
-#include "Hardware/IoPorts.h"
-#include "GCodes/GCodeResult.h"
+#include <Hardware/IoPorts.h>
+#include <GCodes/GCodeResult.h>
#include <Math/DeviationAccumulator.h>
class HeaterMonitor;
@@ -29,9 +29,9 @@ public:
struct HeaterParameters
{
float heatingRate;
- float coolingTimeConstant;
+ float coolingRate;
float deadTime;
- float gain;
+ unsigned int numCycles;
};
LocalHeater(unsigned int heaterNum) noexcept;
@@ -68,7 +68,7 @@ private:
TemperatureError ReadTemperature() noexcept; // Read and store the temperature of this heater
void DoTuningStep() noexcept; // Called on each temperature sample when auto tuning
void CalculateModel(HeaterParameters& params) noexcept; // Calculate G, td and tc from the accumulated readings
- void ReportModel() noexcept;
+ void SetAndReportModel(bool usingFans) noexcept;
float GetExpectedHeatingRate() const noexcept; // Get the minimum heating rate we expect
void RaiseHeaterFault(const char *format, ...) noexcept;