diff options
author | David Crocker <dcrocker@eschertech.com> | 2020-11-11 12:40:01 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2020-11-11 12:40:01 +0300 |
commit | e872a0a257405c1bb916b06f64dcb9db950d720a (patch) | |
tree | aa272063380fa07f2e49efaccaa5aebb15e6611f /src | |
parent | 3648af1fb778a6e553ae75d579873121978214d0 (diff) |
Improvements to heater tuning
Changed heater model parameters to have both fan on and fan off cooling
rates
Diffstat (limited to 'src')
-rw-r--r-- | src/Configuration.h | 6 | ||||
-rw-r--r-- | src/GCodes/GCodes.h | 2 | ||||
-rw-r--r-- | src/GCodes/GCodes3.cpp | 2 | ||||
-rw-r--r-- | src/Heating/FOPDT.cpp | 51 | ||||
-rw-r--r-- | src/Heating/FOPDT.h | 18 | ||||
-rw-r--r-- | src/Heating/Heater.cpp | 52 | ||||
-rw-r--r-- | src/Heating/Heater.h | 4 | ||||
-rw-r--r-- | src/Heating/LocalHeater.cpp | 156 | ||||
-rw-r--r-- | src/Heating/LocalHeater.h | 10 |
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; |