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
path: root/src
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2021-11-20 22:55:00 +0300
committerDavid Crocker <dcrocker@eschertech.com>2021-11-20 22:55:00 +0300
commit87efb232ad022318edfa2b9e2d1fde096e8b1323 (patch)
tree20fad91cf8ac0b852dea06ff45fa3195bc217f66 /src
parent8c307452cfdc46f5f63a3f84ef10f5325ef72a75 (diff)
Changed heater model to support non-Newtonian cooling
Diffstat (limited to 'src')
-rw-r--r--src/Heating/FOPDT.cpp241
-rw-r--r--src/Heating/FOPDT.h48
-rw-r--r--src/Heating/Heat.cpp5
-rw-r--r--src/Heating/Heater.cpp151
-rw-r--r--src/Heating/Heater.h8
-rw-r--r--src/Heating/LocalHeater.cpp95
-rw-r--r--src/Heating/LocalHeater.h4
-rw-r--r--src/Heating/RemoteHeater.cpp4
-rw-r--r--src/Version.h2
9 files changed, 326 insertions, 232 deletions
diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp
index 4e75d534..b9018466 100644
--- a/src/Heating/FOPDT.cpp
+++ b/src/Heating/FOPDT.cpp
@@ -62,36 +62,82 @@ FopDt::FopDt() noexcept
}
// Check the model parameters are sensible, if they are then save them and return true.
-bool FopDt::SetParameters(float phr, float pcrFanOff, float pcrFanOn, float pcrExponent, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept
+bool FopDt::SetParameters(float phr, float pbcr, float pfcr, float pcrExponent, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept
{
// 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 maxTempIncrease = max<float>(1500.0, temperatureLimit + 500.0);
- if ( phr/pcrFanOff > 10.0 // minimum 10C temperature rise (same as with earlier heater model)
- && phr/pcrFanOff <= maxTempIncrease // max temperature increase within limits
- && pcrFanOn >= pcrFanOff
+ if ( phr/pbcr > 0.1 // minimum 10C temperature rise (same as with earlier heater model)
+ && EstimateMaxTemperatureRise(phr, pbcr, pcrExponent) <= maxTempIncrease // max temperature increase within limits
+ && pfcr >= 0.0
&& pcrExponent >= 1.0
&& pcrExponent <= 1.6
&& pdt > 0.099
- && 0.5 >= pdt * pcrFanOn // dead time less then cooling time constant
+ && pdt * pbcr <= 50.0 // dead time less than half cooling time constant
&& pMaxPwm > 0.0099
&& pMaxPwm <= 1.0
)
{
heatingRate = phr;
- coolingRateFanOff = pcrFanOff;
- coolingRateChangeFanOn = pcrFanOn - pcrFanOff;
+ basicCoolingRate = pbcr;
+ fanCoolingRate = pfcr;
+ coolingRateExponent = pcrExponent;
deadTime = pdt;
maxPwm = pMaxPwm;
standardVoltage = pVoltage;
usePid = pUsePid;
inverted = pInverted;
enabled = true;
- CalcPidConstants();
+ CalcPidConstants(100.0);
return true;
}
return false;
}
+#if SUPPORT_REMOTE_COMMANDS
+
+bool FopDt::SetParameters(const CanMessageHeaterModelNewNew& msg, float temperatureLimit) noexcept
+{
+ // 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 maxTempIncrease = max<float>(1500.0, temperatureLimit + 500.0);
+ if ( msg.heatingRate/msg.basicCoolingRate > 0.1 // minimum 10C temperature rise (same as with earlier heater model)
+ && EstimateMaxTemperatureRise(msg.heatingRate, msg.basicCoolingRate, msg.coolingRateExponent) <= maxTempIncrease
+ // max temperature increase within limits
+ && msg.fanCoolingRate >= 0.0
+ && msg.coolingRateExponent >= 1.0
+ && msg.coolingRateExponent <= 1.6
+ && msg.deadTime > 0.099
+ && msg.deadTime * msg.basicCoolingRate <= 50.0 // dead time less than half the cooling time constant
+ && msg.maxPwm > 0.0099
+ && msg.maxPwm <= 1.0
+ )
+ {
+ heatingRate = msg.heatingRate;
+ basicCoolingRate = msg.basicCoolingRate;
+ fanCoolingRate = msg.fanCoolingRate;
+ coolingRateExponent = msg.coolingRateExponent;
+ deadTime = msg.deadTime;
+ maxPwm = msg.maxPwm;
+ standardVoltage = msg.standardVoltage;
+ usePid = msg.usePid;
+ inverted = msg.inverted;
+ pidParametersOverridden = msg.pidParametersOverridden;
+
+ if (msg.pidParametersOverridden)
+ {
+ SetRawPidParameters(msg.kP, msg.recipTi, msg.tD);
+ }
+ else
+ {
+ CalcPidConstants(100.0);
+ }
+ enabled = true;
+ return true;
+ }
+ return false;
+}
+
+#endif
+
void FopDt::Reset() noexcept
{
SetDefaultToolParameters(); // set some values so that we don't report rubbish in the OM
@@ -101,33 +147,33 @@ void FopDt::Reset() noexcept
// Set up default parameters for a tool heater and enable the model
void FopDt::SetDefaultToolParameters() noexcept
{
- heatingRate = DefaultHotEndHeaterHeatingRate;
- coolingRateFanOff = DefaultHotEndHeaterCoolingRate;
- deadTime = DefaultHotEndHeaterDeadTime;
- coolingRateChangeFanOn = 0.0;
- coolingRateExponent = DefaultHotEndHeaterCoolingRateExponent;
+ heatingRate = DefaultToolHeaterHeatingRate;
+ basicCoolingRate = DefaultToolHeaterBasicCoolingRate;
+ deadTime = DefaultToolHeaterDeadTime;
+ fanCoolingRate = 0.0;
+ coolingRateExponent = DefaultToolHeaterCoolingRateExponent;
maxPwm = 1.0;
standardVoltage = 0.0;
usePid = true;
- inverted = false;
+ inverted = pidParametersOverridden = false;
+ CalcPidConstants(200.0);
enabled = true;
- CalcPidConstants();
}
// Set up default parameters for a bed/chamber heater and enable the model
void FopDt::SetDefaultBedOrChamberParameters() noexcept
{
heatingRate = DefaultBedHeaterHeatingRate;
- coolingRateFanOff = DefaultBedHeaterCoolingRate;
+ basicCoolingRate = DefaultBedHeaterBasicCoolingRate;
deadTime = DefaultBedHeaterDeadTime;
- coolingRateChangeFanOn = 0.0;
+ fanCoolingRate = 0.0;
coolingRateExponent = DefaultBedHeaterCoolingRateExponent;
maxPwm = 1.0;
standardVoltage = 0.0;
usePid = false;
- inverted = false;
+ inverted = pidParametersOverridden = false;
+ CalcPidConstants(60.0);
enabled = true;
- CalcPidConstants();
}
// Get the PID parameters as reported by M301
@@ -156,33 +202,67 @@ void FopDt::SetRawPidParameters(float p_kP, float p_recipTi, float p_tD) noexcep
pidParametersOverridden = true;
}
-#if HAS_MASS_STORAGE || HAS_SBC_INTERFACE
+// Append a M307 command describing this heater followed by a newline to the string
+void FopDt::AppendM307Command(unsigned int heaterNumber, const StringRef& str, bool includeVoltage) const noexcept
+{
+ str.catf("M307 H%u R%.3f K%.3f:%.3f D%.2f E%.2f S%.2f B%d",
+ heaterNumber,
+ (double)heatingRate,
+ (double)basicCoolingRate,
+ (double)fanCoolingRate,
+ (double)deadTime,
+ (double)coolingRateExponent,
+ (double)maxPwm,
+ (usePid) ? 0 : 1);
+ if (inverted)
+ {
+ str.cat(" I1");
+ }
+ if (includeVoltage)
+ {
+ str.catf(" V%.1f", (double)standardVoltage);
+ }
+ str.cat('\n');
+}
-// Write the model parameters to file returning true if no error
-bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept
+// If PID parameters are overridden, append a M307 command for this heater followed by a newline to the string
+void FopDt::AppendM301Command(unsigned int heaterNumber, const StringRef& str) const noexcept
{
- String<StringLength256> scratchString;
- scratchString.printf("M307 H%u R%.3f C%.3f:%.3f D%.2f S%.2f V%.1f B%d I%d\n",
- heater,
- (double)heatingRate,
- (double)GetTimeConstantFanOff(),
- (double)GetTimeConstantFanOn(),
- (double)deadTime,
- (double)maxPwm,
- (double)standardVoltage,
- (usePid) ? 0 : 1,
- (inverted) ? 1 : 0);
- bool ok = f->Write(scratchString.c_str());
- if (ok && pidParametersOverridden)
+ if (pidParametersOverridden)
{
const M301PidParameters pp = GetM301PidParameters(false);
- scratchString.printf("M301 H%u P%.1f I%.3f D%.1f\n", heater, (double)pp.kP, (double)pp.kI, (double)pp.kD);
- ok = f->Write(scratchString.c_str());
+ str.catf("M301 H%u P%.1f I%.3f D%.1f\n", heaterNumber, (double)pp.kP, (double)pp.kI, (double)pp.kD);
}
- return ok;
}
-#endif
+// Append the model parameters to a reply string
+void FopDt::AppendModelParameters(unsigned int heaterNumber, const StringRef& str, bool includeVoltage) const noexcept
+{
+ const char* const mode = (!usePid) ? "bang-bang"
+ : (pidParametersOverridden) ? "custom PID"
+ : "PID";
+ str.catf("Heater %u model: heating rate %.3f, basic cooling rate %.3f", heaterNumber, (double)heatingRate, (double)basicCoolingRate);
+ if (fanCoolingRate > 0.0)
+ {
+ str.catf("/%.3f", (double)fanCoolingRate);
+ }
+ str.catf(", dead time %.2f, max PWM %.2f, mode %s", (double)deadTime, (double)maxPwm, mode);
+ if (inverted)
+ {
+ str.cat(", inverted control");
+ }
+ if (includeVoltage)
+ {
+ str.catf(", calibration voltage %.1f", (double)standardVoltage);
+ }
+ if (usePid)
+ {
+ M301PidParameters params = GetM301PidParameters(false);
+ str.catf("\nComputed PID parameters: setpoint change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
+ params = GetM301PidParameters(true);
+ str.catf(", load change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
+ }
+}
/* Re-calculate the PID parameters.
* For some possible formulas, see "Comparison of some well-known PID tuning formulas", Computers and Chemical Engineering 30 (2006) 1416�1423,
@@ -214,22 +294,23 @@ bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept
* ones tend to have massive overshoot when the setpoint is changed, and even in the steady state some of them have marginal stability.
*/
-void FopDt::CalcPidConstants() noexcept
+void FopDt::CalcPidConstants(float targetTemperature) noexcept
{
- const float averageCoolingRate = coolingRateFanOff + 0.5 * coolingRateChangeFanOn;
- 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/(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;
+ if (!pidParametersOverridden)
+ {
+ const float averageCoolingRate = GetCoolingRate(targetTemperature - NormalAmbientTemperature, 0.2);
+ 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/(heatingRate * deadTime);
+ setpointChangeParams.recipTi = powf(averageCoolingRate, 0.5)/powf(deadTime, 0.5); // Ti = timeConstant^0.5 * deadTime^0.5
+ setpointChangeParams.tD = deadTime * 0.7;
+ }
}
// Adjust the actual heater PWM for supply voltage
-float FopDt::CorrectPwm(float requiredPwm, float actualVoltage) const noexcept
+float FopDt::CorrectPwmForVoltage(float requiredPwm, float actualVoltage) const noexcept
{
if (requiredPwm < maxPwm && standardVoltage >= 10.0 && actualVoltage >= 10.0)
{
@@ -238,10 +319,16 @@ float FopDt::CorrectPwm(float requiredPwm, float actualVoltage) const noexcept
return max<float>(requiredPwm, maxPwm);
}
-// Calculate the expected cooling rate for a given temperature rise abiie ambient
+float FopDt::GetPwmCorrectionForFan(float temperatureRise, float fanPwmChange) const noexcept
+{
+ return temperatureRise * 0.01 * fanCoolingRate / heatingRate;
+}
+
+// Calculate the expected cooling rate for a given temperature rise above ambient
float FopDt::GetCoolingRate(float temperatureRise, float fanPwm) const noexcept
{
- return coolingRateFanOff * powf(temperatureRise, coolingRateExponent) + temperatureRise * coolingRateChangeFanOn;
+ temperatureRise *= 0.01;
+ return basicCoolingRate * powf(temperatureRise, coolingRateExponent) + temperatureRise * fanCoolingRate * fanPwm;
}
// Get an estimate of the expected heating rate at the specified temperature rise and PWM. The result may be negative.
@@ -256,51 +343,25 @@ float FopDt::EstimateRequiredPwm(float temperatureRise, float fanPwm) const noex
return GetCoolingRate(temperatureRise, fanPwm)/heatingRate;
}
-#if SUPPORT_CAN_EXPANSION
-
-bool FopDt::SetParameters(const CanMessageHeaterModelNewNew& msg, float temperatureLimit) noexcept
+float FopDt::EstimateMaxTemperatureRise() const noexcept
{
- // 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 maxTempIncrease = max<float>(1500.0, temperatureLimit + 500.0);
- if ( msg.heatingRate/msg.coolingRate > 10.0 // minimum 10C temperature rise (same as with earlier heater model)
- && msg.heatingRate/msg.coolingRate <= maxTempIncrease // max temperature increase within limits
- && msg.coolingRateChangeFanOn >= 0.0
- && msg.coolingRateExponent >= 1.0
- && msg.coolingRateExponent <= 1.6
- && msg.deadTime > 0.099
- && 0.5 >= msg.deadTime * (msg.coolingRate + msg.coolingRateChangeFanOn) // dead time less then cooling time constant
- && msg.maxPwm > 0.0099
- && msg.maxPwm <= 1.0
- )
- {
- heatingRate = msg.heatingRate;
- coolingRateFanOff = msg.coolingRate;
- coolingRateChangeFanOn = msg.coolingRateChangeFanOn;
- coolingRateExponent = msg.coolingRateExponent;
- deadTime = msg.deadTime;
- maxPwm = msg.maxPwm;
- standardVoltage = msg.standardVoltage;
- usePid = msg.usePid;
- inverted = msg.inverted;
- enabled = true;
- CalcPidConstants();
+ return EstimateMaxTemperatureRise(heatingRate, basicCoolingRate, coolingRateExponent);
+}
- if (msg.pidParametersOverridden)
- {
- SetRawPidParameters(msg.kP, msg.recipTi, msg.tD);
- }
- return true;
- }
- return false;
+/*static*/ float FopDt::EstimateMaxTemperatureRise(float hr, float cr, float cre) noexcept
+{
+ return 100.0 * powf(hr/cr, 1.0/cre);
}
+#if SUPPORT_CAN_EXPANSION
+
void FopDt::SetupCanMessage(unsigned int heater, CanMessageHeaterModelNewNew& msg) const noexcept
{
msg.heater = heater;
msg.heatingRate = heatingRate;
- msg.coolingRate = coolingRateFanOff;
- msg.coolingRateChangeFanOn = coolingRateChangeFanOn;
- msg.coolingRateChangeExtruding = 0.0;
+ msg.basicCoolingRate = basicCoolingRate;
+ msg.fanCoolingRate = fanCoolingRate;
+ msg.fZero = 0.0;
msg.coolingRateExponent = coolingRateExponent;
msg.deadTime = deadTime;
msg.maxPwm = maxPwm;
diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h
index 9f39b301..b175a6b1 100644
--- a/src/Heating/FOPDT.h
+++ b/src/Heating/FOPDT.h
@@ -43,36 +43,41 @@ public:
FopDt() noexcept;
void Reset() noexcept;
- bool SetParameters(float phr, float pcrFanOff, float pcrFanOn, float pcrExponent, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept;
+ bool SetParameters(float phr, float pbcr, float pfcr, float pcrExponent, float pdt, float pMaxPwm, float temperatureLimit, float pVoltage, bool pUsePid, bool pInverted) noexcept;
void SetDefaultToolParameters() noexcept;
void SetDefaultBedOrChamberParameters() noexcept;
+#if SUPPORT_REMOTE_COMMANDS
+ bool SetParameters(const CanMessageHeaterModelNewNew& msg, float temperatureLimit) noexcept;
+#endif
// Stored parameters
float GetHeatingRate() const noexcept { return heatingRate; }
- float GetCoolingRateFanOff() const noexcept { return coolingRateFanOff; }
- float GetCoolingRateFanOn() const noexcept { return coolingRateFanOff + coolingRateChangeFanOn; }
- float GetCoolingRateChangeFanOn() const noexcept { return coolingRateChangeFanOn; }
+ float GetBasicCoolingRate() const noexcept { return basicCoolingRate; }
+ float GetFanCoolingRate() const noexcept { return fanCoolingRate; }
+ float GetCoolingRateExponent() const noexcept { return coolingRateExponent; }
float GetDeadTime() const noexcept { return deadTime; }
float GetMaxPwm() const noexcept { return maxPwm; }
float GetVoltage() const noexcept { return standardVoltage; }
- float EstimateRequiredPwm(float temperatureRise, float fanPwm) const noexcept;
- float GetCoolingRate(float temperatureRise, float fanPwm) const noexcept;
- float GetNetHeatingRate(float temperatureRise, float fanPwm, float heaterPwm) const noexcept;
- float CorrectPwm(float requiredPwm, float actualVoltage) const noexcept;
- void AppendM307Command(unsigned int heaterNumber, const StringRef& str) const noexcept;
- void AppendParameters(const StringRef& str) const noexcept;
bool UsePid() const noexcept { return usePid; }
bool IsInverted() const noexcept { return inverted; }
bool IsEnabled() const noexcept { return enabled; }
+ float EstimateRequiredPwm(float temperatureRise, float fanPwm) const noexcept;
+ float EstimateMaxTemperatureRise() const noexcept;
+
+ float GetNetHeatingRate(float temperatureRise, float fanPwm, float heaterPwm) const noexcept;
+ float CorrectPwmForVoltage(float requiredPwm, float actualVoltage) const noexcept;
+ float GetPwmCorrectionForFan(float temperatureRise, float fanPwmChange) const noexcept;
+ void CalcPidConstants(float targetTemperature) noexcept;
+
+ void AppendM307Command(unsigned int heaterNumber, const StringRef& str, bool includeVoltage) const noexcept;
+ void AppendM301Command(unsigned int heaterNumber, const StringRef& str) const noexcept;
+ void AppendModelParameters(unsigned int heaterNumber, const StringRef& str, bool includeVoltage) const noexcept;
+
// Derived parameters
- float GetGainFanOff() const noexcept { return heatingRate/coolingRateFanOff; }
- float GetTimeConstantFanOff() const noexcept { return 1.0/coolingRateFanOff; }
- float GetTimeConstantFanOn() const noexcept { return 1.0/GetCoolingRateFanOn(); }
bool ArePidParametersOverridden() const noexcept { return pidParametersOverridden; }
M301PidParameters GetM301PidParameters(bool forLoadChange) const noexcept;
void SetM301PidParameters(const M301PidParameters& params) noexcept;
- void SetRawPidParameters(float p_kP, float p_recipTi, float p_tD) noexcept;
const PidParameters& GetPidParameters(bool forLoadChange) const noexcept
{
@@ -80,11 +85,10 @@ public:
}
#if HAS_MASS_STORAGE || HAS_SBC_INTERFACE
- bool WriteParameters(FileStore *f, size_t heater) const noexcept; // write the model parameters to file returning true if no error
+// bool WriteParameters(FileStore *f, size_t heater) const noexcept; // write the model parameters to file returning true if no error
#endif
#if SUPPORT_CAN_EXPANSION
- bool SetParameters(const CanMessageHeaterModelNewNew& msg, float temperatureLimit) noexcept;
void SetupCanMessage(unsigned int heater, CanMessageHeaterModelNewNew& msg) const noexcept;
#endif
@@ -92,12 +96,14 @@ protected:
DECLARE_OBJECT_MODEL
private:
- void CalcPidConstants() noexcept;
+ float GetCoolingRate(float temperatureRise, float fanPwm) const noexcept;
+ void SetRawPidParameters(float p_kP, float p_recipTi, float p_tD) noexcept;
+ static float EstimateMaxTemperatureRise(float hr, float cr, float cre) noexcept;
- float heatingRate;
- float coolingRateFanOff;
- float coolingRateChangeFanOn;
- float coolingRateExponent;
+ float heatingRate; // the rate at which the heater heats up at full PWM with no cooling
+ float basicCoolingRate; // the rate at which the heater cools down when it is 100C above ambient and the fan is off
+ float fanCoolingRate; // the additional cooling rate at 100C above ambient with the fan on at full PWM
+ float coolingRateExponent; // how the basic cooling rate varies with temperature difference
float deadTime;
float maxPwm;
float standardVoltage; // power voltage reading at which tuning was done, or 0 if unknown
diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp
index 206e87e4..47eb288f 100644
--- a/src/Heating/Heat.cpp
+++ b/src/Heating/Heat.cpp
@@ -904,7 +904,10 @@ bool Heat::WriteModelParameters(FileStore *f) const noexcept
const FopDt& model = heaters[h]->GetModel();
if (model.IsEnabled())
{
- ok = model.WriteParameters(f, h);
+ String<StringLength256> scratchString;
+ model.AppendM307Command(h, scratchString.GetRef(), !IsBedOrChamberHeater(h));
+ model.AppendM301Command(h, scratchString.GetRef());
+ ok = f->Write(scratchString.c_str());
}
}
}
diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp
index 265912d4..01397163 100644
--- a/src/Heating/Heater.cpp
+++ b/src/Heating/Heater.cpp
@@ -132,37 +132,57 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
float heatingRate = model.GetHeatingRate();
float td = model.GetDeadTime(),
maxPwm = model.GetMaxPwm(),
- voltage = model.GetVoltage();
- float coolingRates[2] = { model.GetCoolingRateFanOff(), model.GetCoolingRateFanOn() };
+ voltage = model.GetVoltage(),
+ coolingRateExponent = model.GetCoolingRateExponent(),
+ basicCoolingRate = model.GetBasicCoolingRate(),
+ fanCoolingRate = model.GetFanCoolingRate();
int32_t dontUsePid = model.UsePid() ? 0 : 1;
int32_t inversionParameter = 0;
- // Get the cooling time constant(s) first
- float timeConstants[2];
- size_t numValues = 2;
- if (gb.TryGetFloatArray('C', numValues, timeConstants, reply, seen, true))
+ if (gb.Seen('K'))
{
- return GCodeResult::error;
+ // New style model parameters
+ seen = true;
+ float coolingRates[2];
+ size_t numValues = 2;
+ gb.GetFloatArray(coolingRates, numValues, false);
+ basicCoolingRate = coolingRates[0];
+ fanCoolingRate = (numValues == 2) ? coolingRates[1] : 0.0;
+ if (gb.Seen('R'))
+ {
+ seen = true;
+ heatingRate = gb.GetFValue();
+ }
+ gb.TryGetFValue('E', coolingRateExponent, seen);
}
- else if (seen)
+ else if (gb.Seen('C'))
{
- coolingRates[0] = 1.0/timeConstants[0];
- coolingRates[1] = 1.0/timeConstants[1];
+ // Old style model parameters
+ seen = true;
+ float timeConstants[2];
+ size_t numValues = 2;
+ gb.GetFloatArray(timeConstants, numValues, true);
+ basicCoolingRate = 100.0/timeConstants[0];
+ fanCoolingRate = 100.0/timeConstants[1] - basicCoolingRate;
+ coolingRateExponent = 1.0;
}
- if (gb.Seen('R'))
+ if (gb.TryGetFValue('R', heatingRate, seen))
{
- // New style heater model. R = heating rate, C[2] = cooling rates
- seen = true;
- heatingRate = gb.GetFValue();
+ // We have the heating rate
}
- else if (gb.Seen('A'))
+ else
{
- // Old style heating model. A = gain, C = cooling time constant
- seen = true;
- const float gain = gb.GetFValue();
- heatingRate = gain * coolingRates[0];
+ float gain;
+ if (gb.TryGetFValue('A', gain, seen))
+ {
+ // Old style heating model. A = gain, C = cooling time constant
+ seen = true;
+ const float gain = gb.GetFValue();
+ heatingRate = gain * basicCoolingRate * 100.0;
+ }
}
+
gb.TryGetFValue('D', td, seen);
gb.TryGetIValue('B', dontUsePid, seen);
gb.TryGetFValue('S', maxPwm, seen);
@@ -173,7 +193,7 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
{
// Set the model
const bool inverseTemperatureControl = (inversionParameter == 1 || inversionParameter == 3);
- const GCodeResult rslt = SetModel(heatingRate, coolingRates[0], coolingRates[1], 1.0, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply);
+ const GCodeResult rslt = SetModel(heatingRate, basicCoolingRate, fanCoolingRate, coolingRateExponent, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply);
if (rslt <= GCodeResult::warning)
{
modelSetByUser = true;
@@ -188,46 +208,27 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
}
else
{
- const char* const mode = (!model.UsePid()) ? "bang-bang"
- : (model.ArePidParametersOverridden()) ? "custom PID"
- : "PID";
- reply.printf("Heater %u model: heating rate %.3f, cooling time constant %.1f", heater, (double)model.GetHeatingRate(), (double)model.GetTimeConstantFanOff());
- if (model.GetCoolingRateChangeFanOn() > 0.0)
- {
- reply.catf("/%.1f", (double)model.GetTimeConstantFanOn());
- }
- reply.catf(", dead time %.2f, max PWM %.2f, calibration voltage %.1f, mode %s", (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode);
- if (model.IsInverted())
- {
- reply.cat(", inverted control");
- }
- if (model.UsePid())
- {
- M301PidParameters params = model.GetM301PidParameters(false);
- reply.catf("\nComputed PID parameters: setpoint change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
- params = model.GetM301PidParameters(true);
- reply.catf(", load change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD);
- }
+ model.AppendModelParameters(heater, reply, !reprap.GetHeat().IsBedOrChamberHeater(heater));
}
return GCodeResult::ok;
}
// Set the process model returning true if successful
-GCodeResult Heater::SetModel(float hr, float coolingRateFanOff, float coolingRateFanOn, float coolingRateExponent, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept
+GCodeResult Heater::SetModel(float hr, float bcr, float fcr, float coolingRateExponent, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept
{
GCodeResult rslt;
- if (model.SetParameters(hr, coolingRateFanOff, coolingRateFanOn, coolingRateExponent, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
+ if (model.SetParameters(hr, bcr, fcr, coolingRateExponent, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
{
if (model.IsEnabled())
{
rslt = UpdateModel(reply);
if (rslt == GCodeResult::ok)
{
- const float predictedMaxTemp = hr/coolingRateFanOff + NormalAmbientTemperature;
+ const float predictedMaxTemp = GetModel().EstimateMaxTemperatureRise() + NormalAmbientTemperature;
const float noWarnTemp = (GetHighestTemperatureLimit() - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C
+ reply.printf("Heater %u predicted maximum temperature at full power is %d" DEGREE_SYMBOL "C", GetHeaterNumber(), (int)predictedMaxTemp);
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", GetHeaterNumber(), (int)predictedMaxTemp);
rslt = GCodeResult::warning;
}
}
@@ -304,12 +305,12 @@ const char *const Heater::TuningPhaseText[] =
{
"checking temperature is stable",
"heating up",
- "heating system settling",
- "tuning with fan off",
+ "settling",
+ "measuring",
#if TUNE_WITH_HALF_FAN
- "tuning with 50% fan",
+ "measuring with 50% fan",
#endif
- "tuning with fan on"
+ "measuring with fan on"
};
// Get the auto tune status or last result
@@ -336,7 +337,7 @@ void Heater::ReportTuningUpdate() noexcept
{
if (tuningPhase < ARRAY_SIZE(TuningPhaseText))
{
- reprap.GetPlatform().MessageF(GenericMessage, "Auto tune starting phase %u, %s\n", tuningPhase + 1, TuningPhaseText[tuningPhase]);
+ reprap.GetPlatform().MessageF(GenericMessage, "Auto tune starting phase %u, %s\n", tuningPhase, TuningPhaseText[tuningPhase]);
}
}
@@ -369,25 +370,28 @@ void Heater::CalculateModel(HeaterParameters& params) noexcept
const float cycleTime = tOn.GetMean() + tOff.GetMean(); // in milliseconds
const float averageTemperatureRiseHeating = tuningTargetTemp - 0.5 * (tuningHysteresis - TuningPeakTempDrop) - tuningStartTemp.GetMean();
const float averageTemperatureRiseCooling = tuningTargetTemp - TuningPeakTempDrop - 0.5 * tuningHysteresis - tuningStartTemp.GetMean();
+ const float averageTemperatureRise = (averageTemperatureRiseHeating * tOn.GetMean() + averageTemperatureRiseCooling * tOff.GetMean()) / cycleTime;
params.deadTime = (((dHigh.GetMean() * tOff.GetMean()) + (dLow.GetMean() * tOn.GetMean())) * MillisToSeconds)/cycleTime; // in seconds
- params.coolingRate = coolingRate.GetMean()/averageTemperatureRiseCooling; // in seconds
+ params.coolingRate = coolingRate.GetMean();
params.heatingRate = (heatingRate.GetMean() + (coolingRate.GetMean() * averageTemperatureRiseHeating/averageTemperatureRiseCooling)) / tuningPwm;
- params.gain = (tOn.GetMean() + tOff.GetMean()) * averageTemperatureRiseHeating/tOn.GetMean();
+ params.gain = (tOn.GetMean() + tOff.GetMean()) * averageTemperatureRise/tOn.GetMean();
params.numCycles = dHigh.GetNumSamples();
}
-void Heater::SetAndReportModel(bool usingFans) noexcept
+void Heater::SetAndReportModelAfterTuning(bool usingFans) noexcept
{
const float hRate = (usingFans) ? (fanOffParams.heatingRate + fanOnParams.heatingRate) * 0.5 : fanOffParams.heatingRate;
const float deadTime = (usingFans) ? (fanOffParams.deadTime + fanOnParams.deadTime) * 0.5 : fanOffParams.deadTime;
-
- float fanOnCoolingRate = fanOffParams.coolingRate;
+ const float coolingRateExponent = (reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber())) ? DefaultBedHeaterCoolingRateExponent : DefaultToolHeaterCoolingRateExponent;
+ const float averageTemperatureRiseCooling = tuningTargetTemp - TuningPeakTempDrop - 0.5 * tuningHysteresis - tuningStartTemp.GetMean();
+ const float basicCoolingRate = fanOffParams.coolingRate/powf(averageTemperatureRiseCooling * 0.01, coolingRateExponent);
+ float fanOnCoolingRate = 0.0;
if (usingFans)
{
// Sometimes the print cooling fan makes no difference to the cooling rate. The SetModel call will fail if the rate with fan on is lower than the rate with fan off.
if (fanOnParams.coolingRate > fanOffParams.coolingRate)
{
- fanOnCoolingRate = fanOffParams.coolingRate + (fanOnParams.coolingRate - fanOffParams.coolingRate)/tuningFanPwm;
+ fanOnCoolingRate = ((fanOnParams.coolingRate - fanOffParams.coolingRate) * 100.0)/(averageTemperatureRiseCooling * tuningFanPwm);
}
else
{
@@ -397,8 +401,9 @@ void Heater::SetAndReportModel(bool usingFans) noexcept
String<StringLength256> str;
const GCodeResult rslt = SetModel( hRate,
- fanOffParams.coolingRate, fanOnCoolingRate,
- 1.0,
+ basicCoolingRate,
+ fanOnCoolingRate,
+ coolingRateExponent,
deadTime,
tuningPwm,
#if HAS_VOLTAGE_MONITOR
@@ -410,27 +415,25 @@ void Heater::SetAndReportModel(bool usingFans) noexcept
if (rslt == GCodeResult::ok || rslt == GCodeResult::warning)
{
tuned = true;
- str.printf( "Auto tuning heater %u completed after %u idle and %u tuning cycles in %" PRIu32 " seconds. This heater needs the following M307 command:\n"
- " M307 H%u B0 R%.3f C%.1f",
+ str.printf( "Auto tuning heater %u completed after %u idle and %u tuning cycles in %" PRIu32 " seconds. This heater needs the following M307 command:\n ",
GetHeaterNumber(),
idleCyclesDone,
(usingFans) ? fanOffParams.numCycles + fanOnParams.numCycles : fanOffParams.numCycles,
- (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis,
- GetHeaterNumber(), (double)GetModel().GetHeatingRate(), (double)(1.0/GetModel().GetCoolingRateFanOff())
+ (millis() - tuningBeginTime)/(uint32_t)SecondsToMillis
);
- 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());
+ GetModel().AppendM307Command(GetHeaterNumber(), str.GetRef(), !reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber()));
reprap.GetPlatform().Message(LoggedGenericMessage, str.c_str());
- str.printf("Gain %.1f/%.1f", (double)fanOffParams.GetNormalGain(), (double)fanOffParams.gain);
- if (usingFans)
+ if (reprap.Debug(moduleHeat))
{
- str.catf(" : %.1f/.1%f", (double)fanOnParams.GetNormalGain(), (double)fanOnParams.gain);
+ str.printf("Long term gain %.1f/%.1f", (double)fanOffParams.GetNormalGain(), (double)fanOffParams.gain);
+ if (usingFans)
+ {
+ str.catf(" : %.1f/.1%f", (double)fanOnParams.GetNormalGain(), (double)fanOnParams.gain);
+ }
+ str.cat('\n');
+ reprap.GetPlatform().Message(GenericMessage, str.c_str());
}
- str.cat('\n');
- reprap.GetPlatform().Message(GenericMessage, str.c_str());
+
if (reprap.GetGCodes().SawM501InConfigFile())
{
reprap.GetPlatform().Message(GenericMessage, "Send M500 to save this command in config-override.g\n");
@@ -442,10 +445,10 @@ void Heater::SetAndReportModel(bool usingFans) noexcept
}
else
{
- reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (R=%.3f, 1/C=%.4f:%.4f, D=%.1f)\n",
+ reprap.GetPlatform().MessageF(WarningMessage, "Auto tune of heater %u failed due to bad curve fit (R=%.3f K=%.3f:%.3f D=%.2f)\n",
GetHeaterNumber(), (double)hRate,
- (double)fanOffParams.coolingRate, (double)fanOnCoolingRate,
- (double)fanOffParams.deadTime);
+ (double)basicCoolingRate, (double)fanOnCoolingRate,
+ (double)deadTime);
}
}
diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h
index 699cf648..f768e4a7 100644
--- a/src/Heating/Heater.h
+++ b/src/Heating/Heater.h
@@ -138,15 +138,11 @@ protected:
float GetMaxTemperatureExcursion() const noexcept { return maxTempExcursion; }
float GetMaxHeatingFaultTime() const noexcept { return maxHeatingFaultTime; }
float GetTargetTemperature() const noexcept { return (active) ? activeTemperature : standbyTemperature; }
- GCodeResult SetModel(float hr, float coolingRateFanOff, float coolingRateFanOn, float coolingRateExponent, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept;
+ GCodeResult SetModel(float hr, float bcr, float fcr, float coolingRateExponent, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept;
// set the process model
void ReportTuningUpdate() noexcept; // tell the user what's happening
void CalculateModel(HeaterParameters& params) noexcept; // calculate G, td and tc from the accumulated readings
- void SetAndReportModel(bool usingFans) noexcept;
-
-#if SUPPORT_REMOTE_COMMANDS
- void SetRawPidParameters(float p_kP, float p_recipTi, float p_tD) noexcept { model.SetRawPidParameters(p_kP, p_recipTi, p_tD); }
-#endif
+ void SetAndReportModelAfterTuning(bool usingFans) noexcept;
HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation
bool tuned; // true if tuning was successful
diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp
index 6c8b4eae..eb0ffdd1 100644
--- a/src/Heating/LocalHeater.cpp
+++ b/src/Heating/LocalHeater.cpp
@@ -176,29 +176,38 @@ TemperatureError LocalHeater::ReadTemperature() noexcept
// This must be called whenever the heater is turned on, and any time the heater is active and the target temperature is changed
GCodeResult LocalHeater::SwitchOn(const StringRef& reply) noexcept
{
+ if (!GetModel().IsEnabled())
+ {
+ reply.printf("Heater %u not switched on due to bad model", GetHeaterNumber());
+ return GCodeResult::error;
+ }
+
if (mode == HeaterMode::fault)
{
- reply.printf("Heater %u not switched on due to temperature fault\n", GetHeaterNumber());
- return GCodeResult::warning;
+ reply.printf("Heater %u not switched on due to temperature fault", GetHeaterNumber());
+ return GCodeResult::error;
}
- //debugPrintf("Heater %d on, temp %.1f\n", heater, temperature);
const float target = GetTargetTemperature();
- const HeaterMode oldMode = mode;
- mode = (temperature + TEMPERATURE_CLOSE_ENOUGH < target) ? HeaterMode::heating
- : (temperature > target + TEMPERATURE_CLOSE_ENOUGH) ? HeaterMode::cooling
- : HeaterMode::stable;
- if (mode != oldMode)
+ const HeaterMode newMode = (temperature + TEMPERATURE_CLOSE_ENOUGH < target) ? HeaterMode::heating
+ : (temperature > target + TEMPERATURE_CLOSE_ENOUGH) ? HeaterMode::cooling
+ : HeaterMode::stable;
+ if (newMode != mode)
{
- heatingFaultCount = 0;
- if (mode == HeaterMode::heating)
+ if (reprap.Debug(Module::moduleHeat) && mode == HeaterMode::off)
{
- timeSetHeating = millis();
+ reprap.GetPlatform().MessageF(GenericMessage, "Heater %u switched on\n", GetHeaterNumber());
}
- if (reprap.Debug(Module::moduleHeat) && oldMode == HeaterMode::off)
+
+ // The Heat task can preempt the GCodes task that calls this, so lock out the Heat task while we update multiple variables
+ TaskCriticalSectionLocker lock;
+ if (newMode == HeaterMode::heating)
{
- reprap.GetPlatform().MessageF(GenericMessage, "Heater %u switched on\n", GetHeaterNumber());
+ lastTemperatureValue = temperature;
+ lastTemperatureMillis = timeSetHeating = millis();
}
+ heatingFaultCount = 0;
+ mode = newMode;
}
return GCodeResult::ok;
}
@@ -285,28 +294,43 @@ void LocalHeater::Spin() noexcept
mode = HeaterMode::stable;
heatingFaultCount = 0;
}
- else if (gotDerivative)
+ else
{
- const float expectedRate = GetExpectedHeatingRate();
- if (derivative + AllowedTemperatureDerivativeNoise < expectedRate * 0.7
- && (float)(millis() - timeSetHeating) > GetModel().GetDeadTime() * SecondsToMillis * 2)
+ const uint32_t now = millis();
+ if ((float)(millis() - timeSetHeating) < GetModel().GetDeadTime() * SecondsToMillis * 1.5)
{
- ++heatingFaultCount;
- if (heatingFaultCount * HeatSampleIntervalMillis > GetMaxHeatingFaultTime() * SecondsToMillis)
- {
- RaiseHeaterFault("Heater %u fault: at %.1f" DEGREE_SYMBOL "C temperature is rising at %.1f" DEGREE_SYMBOL "C/sec, well below the expected %.1f" DEGREE_SYMBOL "C/sec\n",
- GetHeaterNumber(), (double)temperature, (double)derivative, (double)expectedRate);
- }
+ // Record the temperature for when we are past the dead time
+ lastTemperatureValue = temperature;
+ lastTemperatureMillis = now;
}
- else if (heatingFaultCount != 0)
+ else if (gotDerivative) // this is a check in case we just had a temperature spike
{
- --heatingFaultCount;
+ const float expectedRate = GetExpectedHeatingRate();
+ const float minSamplingInterval = 3.0/expectedRate; // check the temperature if we expect a 3C rise since last time
+ const float actualInterval = (float)(now - lastTemperatureMillis) * MillisToSeconds;
+ if (actualInterval >= minSamplingInterval)
+ {
+ // Check that we are heating fast enough, and if so, take another sample
+ const float expectedTemperatureRise = expectedRate * actualInterval;
+ const float actualTemperatureRise = temperature - lastTemperatureValue;
+ if (actualTemperatureRise < expectedTemperatureRise * 0.7)
+ {
+ ++heatingFaultCount;
+ if (heatingFaultCount * HeatSampleIntervalMillis > GetMaxHeatingFaultTime() * SecondsToMillis)
+ {
+ RaiseHeaterFault("Heater %u fault: at %.1f" DEGREE_SYMBOL "C temperature is rising at %.1f" DEGREE_SYMBOL "C/sec, well below the expected %.1f" DEGREE_SYMBOL "C/sec\n",
+ GetHeaterNumber(), (double)temperature, (double)derivative, (double)expectedRate);
+ }
+ }
+ else if (heatingFaultCount != 0)
+ {
+ --heatingFaultCount;
+ }
+ lastTemperatureValue = temperature;
+ lastTemperatureMillis = now;
+ }
}
}
- else
- {
- // Leave the heating fault count alone
- }
}
break;
@@ -391,7 +415,7 @@ void LocalHeater::Spin() noexcept
// Scale the PID based on the current voltage vs. the calibration voltage
if (!reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber()))
{
- lastPwm = GetModel().CorrectPwm(lastPwm, reprap.GetPlatform().GetCurrentPowerVoltage());
+ lastPwm = GetModel().CorrectPwmForVoltage(lastPwm, reprap.GetPlatform().GetCurrentPowerVoltage());
}
#endif
}
@@ -524,8 +548,7 @@ void LocalHeater::FeedForwardAdjustment(float fanPwmChange, float extrusionChang
{
if (mode == HeaterMode::stable)
{
- const float coolingRateIncrease = GetModel().GetCoolingRateChangeFanOn() * fanPwmChange;
- const float boost = (coolingRateIncrease * (GetTargetTemperature() - NormalAmbientTemperature) * FeedForwardMultiplier)/GetModel().GetHeatingRate();
+ const float boost = GetModel().GetPwmCorrectionForFan(GetTargetTemperature() - NormalAmbientTemperature, fanPwmChange) * FeedForwardMultiplier;
#if 0
if (reprap.Debug(moduleHeat))
{
@@ -598,7 +621,8 @@ void LocalHeater::DoTuningStep() noexcept
lastPwm = tuningPwm; // turn on heater at specified power
mode = HeaterMode::tuning1;
- reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 1, heater on\n");
+ tuningPhase = 1;
+ ReportTuningUpdate();
return;
}
@@ -631,7 +655,6 @@ void LocalHeater::DoTuningStep() noexcept
return;
}
#endif
- tuningPhase = 1;
{
const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber());
const uint32_t heatingTime = now - timeSetHeating;
@@ -748,7 +771,7 @@ void LocalHeater::DoTuningStep() noexcept
CalculateModel(fanOffParams);
if (tuningFans.IsEmpty())
{
- SetAndReportModel(false);
+ SetAndReportModelAfterTuning(false);
break;
}
else
@@ -777,7 +800,7 @@ void LocalHeater::DoTuningStep() noexcept
{
reprap.GetFansManager().SetFansValue(tuningFans, 0.0); // turn fans off
CalculateModel(fanOnParams);
- SetAndReportModel(true);
+ SetAndReportModelAfterTuning(true);
break;
}
}
diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h
index 5dc2c6bd..fd31dde9 100644
--- a/src/Heating/LocalHeater.h
+++ b/src/Heating/LocalHeater.h
@@ -21,7 +21,7 @@ class HeaterMonitor;
class LocalHeater : public Heater
{
- static const size_t NumPreviousTemperatures = 8; // How many samples we average the temperature derivative over
+ static const size_t NumPreviousTemperatures = 4; // How many samples we average the temperature derivative over
public:
LocalHeater(unsigned int heaterNum) noexcept;
@@ -76,6 +76,8 @@ private:
float lastPwm; // The last PWM value set for this heater
float averagePWM; // The running average of the PWM, after scaling.
volatile float extrusionBoost; // The amount of extrusion feedforward to apply
+ float lastTemperatureValue; // the last temperature we recorded while heating up
+ uint32_t lastTemperatureMillis; // when we recorded the last temperature
uint32_t timeSetHeating; // When we turned on the heater
uint32_t lastSampleTime; // Time when the temperature was last sampled by Spin()
diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp
index 215edb48..a9cd8aff 100644
--- a/src/Heating/RemoteHeater.cpp
+++ b/src/Heating/RemoteHeater.cpp
@@ -152,7 +152,7 @@ void RemoteHeater::Spin() noexcept
CalculateModel(fanOffParams);
if (tuningFans.IsEmpty())
{
- SetAndReportModel(false);
+ SetAndReportModelAfterTuning(false);
StopTuning();
break;
}
@@ -182,7 +182,7 @@ void RemoteHeater::Spin() noexcept
{
reprap.GetFansManager().SetFansValue(tuningFans, 0.0); // turn fans off
CalculateModel(fanOnParams);
- SetAndReportModel(true);
+ SetAndReportModelAfterTuning(true);
StopTuning();
break;
}
diff --git a/src/Version.h b/src/Version.h
index f5c2e6ad..7ad8e8df 100644
--- a/src/Version.h
+++ b/src/Version.h
@@ -10,7 +10,7 @@
#ifndef VERSION
// Note: the complete VERSION string must be in standard version number format and must not contain spaces! This is so that DWC can parse it.
-# define MAIN_VERSION "3.4.0beta6+1"
+# define MAIN_VERSION "3.4.0beta6+2"
# ifdef USE_CAN0
# define VERSION_SUFFIX "(CAN0)"
# else