diff options
author | David Crocker <dcrocker@eschertech.com> | 2021-01-24 18:10:05 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2021-01-24 18:10:05 +0300 |
commit | b75f3b14644b1dcc5aeb8170d1e1b6d4a9f8e52e (patch) | |
tree | bf4afa1f53eb91f2fc175283025fa14a99745e1e /src | |
parent | fec7a47b928ab9bcd0c11ec4f9b6926f2e732401 (diff) |
More work on remote heater tuning
Diffstat (limited to 'src')
-rw-r--r-- | src/CAN/CommandProcessor.cpp | 4 | ||||
-rw-r--r-- | src/Heating/Heat.cpp | 13 | ||||
-rw-r--r-- | src/Heating/Heat.h | 3 | ||||
-rw-r--r-- | src/Heating/Heater.cpp | 118 | ||||
-rw-r--r-- | src/Heating/Heater.h | 26 | ||||
-rw-r--r-- | src/Heating/LocalHeater.cpp | 124 | ||||
-rw-r--r-- | src/Heating/LocalHeater.h | 14 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.cpp | 215 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.h | 16 | ||||
-rw-r--r-- | src/Tools/Tool.cpp | 2 |
10 files changed, 370 insertions, 165 deletions
diff --git a/src/CAN/CommandProcessor.cpp b/src/CAN/CommandProcessor.cpp index 86936b56..38635bd4 100644 --- a/src/CAN/CommandProcessor.cpp +++ b/src/CAN/CommandProcessor.cpp @@ -515,6 +515,10 @@ void CommandProcessor::ProcessReceivedMessage(CanMessageBuffer *buf) noexcept reprap.GetHeat().ProcessRemoteHeatersReport(buf->id.Src(), buf->msg.heatersStatusBroadcast); break; + case CanMessageType::heaterTuningReport: + reprap.GetHeat().ProcessRemoteHeaterTuningReport(buf->id.Src(), buf->msg.heaterTuningReport); + break; + case CanMessageType::fansReport: reprap.GetFansManager().ProcessRemoteFanRpms(buf->id.Src(), buf->msg.fansReport); break; diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index 89875819..6fab76b7 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -750,12 +750,12 @@ void Heat::Standby(int heater, const Tool *tool) noexcept } } -void Heat::PrintCoolingFanPwmChanged(unsigned int heater, float pwmChange) const noexcept +void Heat::FeedForwardAdjustment(unsigned int heater, float fanPwmChange, float extrusionChange) const noexcept { const auto h = FindHeater(heater); if (h.IsNotNull()) { - h->PrintCoolingFanPwmChanged(pwmChange); + h->FeedForwardAdjustment(fanPwmChange, extrusionChange); } } @@ -1234,6 +1234,15 @@ void Heat::ProcessRemoteHeatersReport(CanAddress src, const CanMessageHeatersSta ); } +void Heat::ProcessRemoteHeaterTuningReport(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept +{ + const auto h = FindHeater(msg.heater); + if (h.IsNotNull()) + { + h->UpdateHeaterTuning(src, msg); + } +} + #endif #if SUPPORT_REMOTE_COMMANDS diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index 8b67f807..872c72dc 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -130,7 +130,7 @@ public: GCodeResult Activate(int heater, const StringRef& reply) noexcept; // Turn on a heater void Standby(int heater, const Tool* tool) noexcept; // Set a heater to standby void SwitchOff(int heater) noexcept; // Turn off a specific heater - void PrintCoolingFanPwmChanged(unsigned int heater, float pwmChange) const noexcept; + void FeedForwardAdjustment(unsigned int heater, float fanPwmChange, float extrusionChange) const noexcept; #if HAS_MASS_STORAGE bool WriteModelParameters(FileStore *f) const noexcept; // Write heater model parameters to file returning true if no error @@ -140,6 +140,7 @@ public: #if SUPPORT_CAN_EXPANSION void ProcessRemoteSensorsReport(CanAddress src, const CanMessageSensorTemperatures& msg) noexcept; void ProcessRemoteHeatersReport(CanAddress src, const CanMessageHeatersStatus& msg) noexcept; + void ProcessRemoteHeaterTuningReport(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept; #endif #if SUPPORT_REMOTE_COMMANDS diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp index d3bd47f5..a0d582e7 100644 --- a/src/Heating/Heater.cpp +++ b/src/Heating/Heater.cpp @@ -11,6 +11,7 @@ #include "Heat.h" #include "HeaterMonitor.h" #include "Sensors/TemperatureSensor.h" +#include <GCodes/GCodes.h> #include <GCodes/GCodeBuffer/GCodeBuffer.h> #include <GCodes/GCodeException.h> @@ -64,14 +65,17 @@ DEFINE_GET_OBJECT_MODEL_TABLE(Heater) float Heater::tuningPwm; // the PWM to use, 0..1 float Heater::tuningTargetTemp; // the target temperature + DeviationAccumulator Heater::tuningStartTemp; // the temperature when we turned on the heater -uint32_t Heater::tuningBeginTime; // when we started the tuning process DeviationAccumulator Heater::dHigh; DeviationAccumulator Heater::dLow; DeviationAccumulator Heater::tOn; DeviationAccumulator Heater::tOff; DeviationAccumulator Heater::heatingRate; DeviationAccumulator Heater::coolingRate; +DeviationAccumulator Heater::tuningVoltage; // sum of the voltage readings we take during the heating phase + +uint32_t Heater::tuningBeginTime; // when we started the tuning process uint32_t Heater::lastOffTime; uint32_t Heater::lastOnTime; float Heater::peakTemp; // max or min temperature @@ -83,6 +87,8 @@ FansBitmap Heater::tuningFans; unsigned int Heater::tuningPhase; uint8_t Heater::idleCyclesDone; +Heater::HeaterParameters Heater::fanOffParams, Heater::fanOnParams; + // Clear all the counters except tuning voltage and start temperature /*static*/ void Heater::ClearCounters() noexcept { @@ -292,6 +298,116 @@ GCodeResult Heater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansB return rslt; } +// Get the auto tune status or last result +void Heater::GetAutoTuneStatus(const StringRef& reply) const noexcept +{ + if (GetStatus() == HeaterStatus::tuning) + { + // Phases are: 1 = stabilising, 2 = heating, 3 = settling, 4 = cycling with fan off, 5 = cycling with fan on + const unsigned int numPhases = (tuningFans.IsEmpty()) ? 4 +#if TUNE_WITH_HALF_FAN + : 6; +#else + : 5; +#endif + reply.printf("Heater %u is being tuned, phase %u of %u", GetHeaterNumber(), tuningPhase, numPhases); + } + else if (tuned) + { + reply.printf("Heater %u tuning succeeded, use M307 H%u to see result", GetHeaterNumber(), GetHeaterNumber()); + } + else + { + reply.printf("Heater %u tuning failed", GetHeaterNumber()); + } +} + +void Heater::CalculateModel(HeaterParameters& params) noexcept +{ + if (reprap.Debug(moduleHeat)) + { +#define PLUS_OR_MINUS "\xC2\xB1" + 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 %.3f" PLUS_OR_MINUS "%.3f, C %.3f" PLUS_OR_MINUS "%.3f," +#if HAS_VOLTAGE_MONITOR + " V %.1f" PLUS_OR_MINUS "%.1f," +#endif + " 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(), +#if HAS_VOLTAGE_MONITOR + (double)tuningVoltage.GetMean(), (double)tuningVoltage.GetDeviation(), +#endif + coolingRate.GetNumSamples() + ); + } + + 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(); + params.deadTime = (((dHigh.GetMean() * tOff.GetMean()) + (dLow.GetMean() * tOn.GetMean())) * MillisToSeconds)/cycleTime; // in seconds + params.coolingRate = coolingRate.GetMean()/averageTemperatureRiseCooling; // in seconds + params.heatingRate = (heatingRate.GetMean() + (coolingRate.GetMean() * averageTemperatureRiseHeating/averageTemperatureRiseCooling)) / tuningPwm; + params.numCycles = dHigh.GetNumSamples(); +} + +void Heater::SetAndReportModel(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; + const float fanOnCoolingRate = (usingFans) ? fanOnParams.coolingRate : fanOffParams.coolingRate; + String<StringLength256> str; + const GCodeResult rslt = SetModel( hRate, + fanOffParams.coolingRate, fanOnCoolingRate, + deadTime, + tuningPwm, +#if HAS_VOLTAGE_MONITOR + tuningVoltage.GetMean(), +#else + 0.0, +#endif + true, false, str.GetRef()); + 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 R%.3f C%.1f", + GetHeaterNumber(), + idleCyclesDone, + (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"); + } + else + { + reprap.GetPlatform().MessageF(GenericMessage, "Edit the M307 H%u command in config.g to match this. Omit the V parameter if the heater is not powered from VIN.\n", GetHeaterNumber()); + } + } + 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", + GetHeaterNumber(), (double)hRate, + (double)fanOffParams.coolingRate, (double)fanOnCoolingRate, + (double)fanOffParams.deadTime); + } +} + GCodeResult Heater::SetFaultDetectionParameters(float pMaxTempExcursion, float pMaxFaultTime, const StringRef& reply) noexcept { maxTempExcursion = pMaxTempExcursion; diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h index 4600ab0c..d1719a2f 100644 --- a/src/Heating/Heater.h +++ b/src/Heating/Heater.h @@ -20,7 +20,10 @@ # include "CanId.h" #endif +#define TUNE_WITH_HALF_FAN 0 + class HeaterMonitor; +struct CanMessageHeaterTuningReport; struct CanHeaterReport; // Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them. @@ -43,13 +46,13 @@ public: virtual GCodeResult ResetFault(const StringRef& reply) noexcept = 0; // Reset a fault condition - only call this if you know what you are doing virtual void SwitchOff() noexcept = 0; virtual void Spin() noexcept = 0; - virtual void GetAutoTuneStatus(const StringRef& reply) const noexcept = 0; // Get the auto tune status or last result virtual void Suspend(bool sus) noexcept = 0; // Suspend the heater to conserve power or while doing Z probing virtual float GetAccumulator() const noexcept = 0; // Get the inertial term accumulator - virtual void PrintCoolingFanPwmChanged(float pwmChange) noexcept = 0; + virtual void FeedForwardAdjustment(float fanPwmChange, float extrusionChange) noexcept = 0; #if SUPPORT_CAN_EXPANSION virtual void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept = 0; + virtual void UpdateHeaterTuning(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept = 0; #endif HeaterStatus GetStatus() const noexcept; // Get the status of the heater @@ -61,6 +64,7 @@ public: GCodeResult Activate(const StringRef& reply) noexcept; // Switch from idle to active void Standby() noexcept; // Switch from active to idle GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException); // Start an auto tune cycle for this heater + void GetAutoTuneStatus(const StringRef& reply) const noexcept; // Get the auto tune status or last result void GetFaultDetectionParameters(float& pMaxTempExcursion, float& pMaxFaultTime) const noexcept { pMaxTempExcursion = maxTempExcursion; pMaxFaultTime = maxHeatingFaultTime; } @@ -107,7 +111,14 @@ protected: lastTuningMode = tuning3 }; -protected: + struct HeaterParameters + { + float heatingRate; + float coolingRate; + float deadTime; + unsigned int numCycles; + }; + virtual void ResetHeater() noexcept = 0; virtual HeaterMode GetMode() const noexcept = 0; virtual GCodeResult SwitchOn(const StringRef& reply) noexcept = 0; @@ -121,7 +132,10 @@ 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 td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model + GCodeResult SetModel(float hr, float coolingRateFanOff, float coolingRateFanOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; + // set the process model + void CalculateModel(HeaterParameters& params) noexcept; // calculate G, td and tc from the accumulated readings + void SetAndReportModel(bool usingFans) noexcept; HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation bool tuned; // true if tuning was successful @@ -148,6 +162,8 @@ protected: static DeviationAccumulator tOff; static DeviationAccumulator heatingRate; static DeviationAccumulator coolingRate; + static DeviationAccumulator tuningVoltage; // sum of the voltage readings we take during the heating phase + static uint32_t lastOffTime; static uint32_t lastOnTime; static float peakTemp; // max or min temperature @@ -159,6 +175,8 @@ protected: static unsigned int tuningPhase; static uint8_t idleCyclesDone; + static HeaterParameters fanOffParams, fanOnParams; + static void ClearCounters() noexcept; private: diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp index 9a5db5c9..e9da73ab 100644 --- a/src/Heating/LocalHeater.cpp +++ b/src/Heating/LocalHeater.cpp @@ -14,14 +14,6 @@ #include "RepRap.h" #include <Tools/Tool.h> -#define TUNE_WITH_HALF_FAN 0 - -static LocalHeater::HeaterParameters fanOffParams, fanOnParams; - -#if HAS_VOLTAGE_MONITOR -static DeviationAccumulator tuningVoltage; // sum of the voltage readings we take during the heating phase -#endif - // Member functions and constructors LocalHeater::LocalHeater(unsigned int heaterNum) noexcept : Heater(heaterNum), mode(HeaterMode::off) @@ -474,36 +466,12 @@ GCodeResult LocalHeater::StartAutoTune(const StringRef& reply, FansBitmap fans, return GCodeResult::ok; } -// Get the auto tune status or last result -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()) ? 4 -#if TUNE_WITH_HALF_FAN - : 6; -#else - : 5; -#endif - reply.printf("Heater %u is being tuned, phase %u of %u", GetHeaterNumber(), tuningPhase + 1, numPhases); - } - else if (tuned) - { - reply.printf("Heater %u tuning succeeded, use M307 H%u to see result", GetHeaterNumber(), GetHeaterNumber()); - } - else - { - reply.printf("Heater %u tuning failed", GetHeaterNumber()); - } -} - // Call this when the PWM of a cooling fan has changed. If there are multiple fans, caller must divide pwmChange by the number of fans. -void LocalHeater::PrintCoolingFanPwmChanged(float pwmChange) noexcept +void LocalHeater::FeedForwardAdjustment(float fanPwmChange, float extrusionChange) noexcept { if (mode == HeaterMode::stable) { - const float coolingRateIncrease = GetModel().GetCoolingRateChangeFanOn() * pwmChange; + const float coolingRateIncrease = GetModel().GetCoolingRateChangeFanOn() * fanPwmChange; const float boost = (coolingRateIncrease * (GetTargetTemperature() - NormalAmbientTemperature) * FeedForwardMultiplier)/GetModel().GetHeatingRate(); #if 0 if (reprap.Debug(moduleHeat)) @@ -610,9 +578,7 @@ void LocalHeater::DoTuningStep() noexcept SetHeater(0.0); peakTemp = afterPeakTemp = temperature; lastOffTime = peakTime = afterPeakTime = now; -#if HAS_VOLTAGE_MONITOR tuningVoltage.Clear(); -#endif idleCyclesDone = 0; mode = HeaterMode::tuning2; tuningPhase = 2; @@ -757,92 +723,6 @@ void LocalHeater::DoTuningStep() noexcept } // Calculate the heater model from the accumulated heater parameters -void LocalHeater::CalculateModel(HeaterParameters& params) noexcept -{ - if (reprap.Debug(moduleHeat)) - { -#define PLUS_OR_MINUS "\xC2\xB1" - 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 %.3f" PLUS_OR_MINUS "%.3f, C %.3f" PLUS_OR_MINUS "%.3f," -#if HAS_VOLTAGE_MONITOR - " V %.1f" PLUS_OR_MINUS "%.1f," -#endif - " 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(), -#if HAS_VOLTAGE_MONITOR - (double)tuningVoltage.GetMean(), (double)tuningVoltage.GetDeviation(), -#endif - coolingRate.GetNumSamples() - ); - } - - 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(); - params.deadTime = (((dHigh.GetMean() * tOff.GetMean()) + (dLow.GetMean() * tOn.GetMean())) * MillisToSeconds)/cycleTime; // in seconds - params.coolingRate = coolingRate.GetMean()/averageTemperatureRiseCooling; // in seconds - params.heatingRate = (heatingRate.GetMean() + (coolingRate.GetMean() * averageTemperatureRiseHeating/averageTemperatureRiseCooling)) / tuningPwm; - params.numCycles = dHigh.GetNumSamples(); -} - -void LocalHeater::SetAndReportModel(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; - const float fanOnCoolingRate = (usingFans) ? fanOnParams.coolingRate : fanOffParams.coolingRate; - String<StringLength256> str; - const GCodeResult rslt = SetModel( hRate, - fanOffParams.coolingRate, fanOnCoolingRate, - deadTime, - tuningPwm, -#if HAS_VOLTAGE_MONITOR - tuningVoltage.GetMean(), -#else - 0.0, -#endif - true, false, str.GetRef()); - 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 R%.3f C%.1f", - GetHeaterNumber(), - idleCyclesDone, - (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"); - } - else - { - reprap.GetPlatform().MessageF(GenericMessage, "Edit the M307 H%u command in config.g to match this. Omit the V parameter if the heater is not powered from VIN.\n", GetHeaterNumber()); - } - } - 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", - GetHeaterNumber(), (double)hRate, - (double)fanOffParams.coolingRate, (double)fanOnCoolingRate, - (double)fanOffParams.deadTime); - } -} - // Suspend the heater, or resume it void LocalHeater::Suspend(bool sus) noexcept { diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h index 80afa0ad..02fde4f9 100644 --- a/src/Heating/LocalHeater.h +++ b/src/Heating/LocalHeater.h @@ -25,14 +25,6 @@ class LocalHeater : public Heater static const size_t NumPreviousTemperatures = 4; // How many samples we average the temperature derivative over public: - struct HeaterParameters - { - float heatingRate; - float coolingRate; - float deadTime; - unsigned int numCycles; - }; - LocalHeater(unsigned int heaterNum) noexcept; ~LocalHeater() noexcept; @@ -46,12 +38,12 @@ public: float GetTemperature() const noexcept override; // Get the latest temperature float GetAveragePWM() const noexcept override; // Return the running average PWM to the heater. Answer is a fraction in [0, 1]. float GetAccumulator() const noexcept override; // Return the integral accumulator - void GetAutoTuneStatus(const StringRef& reply) const noexcept override; // Get the auto tune status or last result void Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing - void PrintCoolingFanPwmChanged(float pwmChange) noexcept override; + void FeedForwardAdjustment(float fanPwmChange, float extrusionChange) noexcept override; #if SUPPORT_CAN_EXPANSION void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept override { } + void UpdateHeaterTuning(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept override { } #endif protected: @@ -67,8 +59,6 @@ private: void SetHeater(float power) const noexcept; // Power is a fraction in [0,1] 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 SetAndReportModel(bool usingFans) noexcept; float GetExpectedHeatingRate() const noexcept; // Get the minimum heating rate we expect void RaiseHeaterFault(const char *format, ...) noexcept; diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp index 1620bf1d..7517ba0e 100644 --- a/src/Heating/RemoteHeater.cpp +++ b/src/Heating/RemoteHeater.cpp @@ -17,6 +17,12 @@ #include <CanMessageFormats.h> #include <CanMessageBuffer.h> +// Static variables used only during tuning +uint32_t RemoteHeater::timeSetHeating; +float RemoteHeater::currentCoolingRate; +unsigned int RemoteHeater::tuningCyclesDone; +bool RemoteHeater::newTuningResult = false; + RemoteHeater::RemoteHeater(unsigned int num, CanAddress board) noexcept : Heater(num), boardAddress(board), lastMode(HeaterMode::offline), averagePwm(0), tuningState(TuningState::notTuning), lastTemperature(0.0), whenLastStatusReceived(0) { @@ -33,34 +39,162 @@ RemoteHeater::~RemoteHeater() noexcept void RemoteHeater::Spin() noexcept { + const uint32_t now = millis(); switch (tuningState) { case TuningState::notTuning: break; case TuningState::stabilising: - //TODO wait for temp to stabilise + if (tuningStartTemp.GetNumSamples() < 5000/HeatSampleIntervalMillis) + { + tuningStartTemp.Add(lastTemperature); // take another reading until we have samples temperatures for 5 seconds + } + else if (tuningStartTemp.GetDeviation() <= 2.0) { + timeSetHeating = now; + ClearCounters(); + timeSetHeating = millis(); String<StringLength100> reply; - if (SendTuningCommand(reply.GetRef(), true) != GCodeResult::ok) + if (SendTuningCommand(reply.GetRef(), true) == GCodeResult::ok) + { + tuningState = TuningState::heatingUp; + tuningPhase = 1; + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 1, heater on\n"); + } + else { - reprap.GetPlatform().MessageF(ErrorMessage, "Heater tuning cancelled: %s\n", reply.c_str()); - SwitchOff(); + reprap.GetPlatform().Message(ErrorMessage, "Failed to start heater tuning\n"); tuningState = TuningState::notTuning; } } + else if (now - tuningBeginTime >= 20000) // allow up to 20 seconds for starting temperature to settle + { + reprap.GetPlatform().Message(GenericMessage, "Auto tune cancelled because starting temperature is not stable\n"); + StopTuning(); + } break; case TuningState::heatingUp: - //TODO + { + const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber()); + const uint32_t heatingTime = now - timeSetHeating; + const float extraTimeAllowed = (isBedOrChamberHeater) ? 120.0 : 30.0; + if (heatingTime > (uint32_t)((GetModel().GetDeadTime() + extraTimeAllowed) * SecondsToMillis) && (lastTemperature - tuningStartTemp.GetMean()) < 3.0) + { + reprap.GetPlatform().Message(GenericMessage, "Auto tune cancelled because temperature is not increasing\n"); + StopTuning(); + break; + } + + const uint32_t timeoutMinutes = (isBedOrChamberHeater) ? 30 : 7; + if (heatingTime >= timeoutMinutes * 60 * (uint32_t)SecondsToMillis) + { + reprap.GetPlatform().Message(GenericMessage, "Auto tune cancelled because target temperature was not reached\n"); + StopTuning(); + break; + } + + if (lastTemperature >= tuningTargetTemp) // if reached target + { + // Move on to next phase + peakTemp = afterPeakTemp = lastTemperature; + lastOffTime = peakTime = afterPeakTime = now; + tuningVoltage.Clear(); + idleCyclesDone = 0; + newTuningResult = false; + tuningState = TuningState::idleCycles; + tuningPhase = 2; + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 2, heater settling\n"); + } + } break; - case TuningState::cyclingFanOff: - //TODO + case TuningState::idleCycles: + if (newTuningResult) + { + // To allow for heat reservoirs, we do idle cycles until the cooling rate decreases by no more than a certain amount in a single cycle + if (idleCyclesDone == TuningHeaterMaxIdleCycles || (idleCyclesDone >= TuningHeaterMinIdleCycles && currentCoolingRate >= lastCoolingRate * HeaterSettledCoolingTimeRatio)) + { + tuningPhase = 3; + tuningState = TuningState::cyclingFanOff; + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan off\n"); + } + else + { + lastCoolingRate = currentCoolingRate; + ClearCounters(); + ++idleCyclesDone; + } + newTuningResult = false; + } break; + case TuningState::cyclingFanOff: +#if TUNE_WITH_HALF_FAN + case TuningState::cyclingHalfFan: +#endif case TuningState::cyclingFanOn: - //TODO + if (newTuningResult) + { + if (coolingRate.GetNumSamples() >= MinTuningHeaterCycles) + { + const bool isConsistent = dLow.DeviationFractionWithin(0.2) + && dHigh.DeviationFractionWithin(0.2) + && heatingRate.DeviationFractionWithin(0.1) + && coolingRate.DeviationFractionWithin(0.1); + if (isConsistent || coolingRate.GetNumSamples() == MaxTuningHeaterCycles) + { + if (!isConsistent) + { + reprap.GetPlatform().Message(WarningMessage, "heater behaviour was not consistent during tuning\n"); + } + + if (tuningState == TuningState::cyclingFanOff) + { + CalculateModel(fanOffParams); + if (tuningFans.IsEmpty()) + { + SetAndReportModel(false); + break; + } + else + { + tuningPhase = 4; + ClearCounters(); +#if TUNE_WITH_HALF_FAN + tuningState = TuningState::cyclingFanHalf; + reprap.GetFansManager().SetFansValue(tuningFans, 0.5); // turn fans on at half PWM + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan 50%\n"); +#else + tuningState = TuningState::cyclingFanOn; + reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans on at full PWM + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan on\n"); +#endif + } + } +#if TUNE_WITH_HALF_FAN + else if (tuningState == TuningState::cyclingFanHalf) + { + CalculateModel(fanOnParams); + tuningPhase = 5; + tuningState = TuningState::cyclingFanOn; + ClearCounters(); + reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans fully on + reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 4, fan 100%\n"); + } +#endif + else + { + reprap.GetFansManager().SetFansValue(tuningFans, 0.0); // turn fans off + CalculateModel(fanOnParams); + SetAndReportModel(true); + break; + } + } + } + newTuningResult = false; + } break; } } @@ -98,10 +232,11 @@ GCodeResult RemoteHeater::ReportDetails(const StringRef& reply) const noexcept void RemoteHeater::SwitchOff() noexcept { + constexpr const char *errMsg = "Failed to switch off remote heater %u: %s\n"; CanMessageBuffer * const buf = CanMessageBuffer::Allocate(); if (buf == nullptr) { - reprap.GetPlatform().MessageF(ErrorMessage, "Failed to switch off remote heater %u: no CAN buffer available\n", GetHeaterNumber()); + reprap.GetPlatform().MessageF(ErrorMessage, errMsg, GetHeaterNumber(), "no CAN buffer available"); } else { @@ -113,7 +248,7 @@ void RemoteHeater::SwitchOff() noexcept String<StringLength100> reply; if (CanInterface::SendRequestAndGetStandardReply(buf, rid, reply.GetRef()) != GCodeResult::ok) { - reprap.GetPlatform().MessageF(ErrorMessage, "Failed to switch off remote heater %u: %s\n", GetHeaterNumber(), reply.c_str()); + reprap.GetPlatform().MessageF(ErrorMessage, errMsg, GetHeaterNumber(), reply.c_str()); } } } @@ -173,8 +308,7 @@ GCodeResult RemoteHeater::StartAutoTune(const StringRef& reply, FansBitmap fans, tuningTargetTemp = targetTemp; tuningStartTemp.Clear(); tuningBeginTime = millis(); - tuningPhase = 0; - tuned = false; // assume failure + tuned = false; if (seenA) { @@ -187,23 +321,38 @@ GCodeResult RemoteHeater::StartAutoTune(const StringRef& reply, FansBitmap fans, return rslt; } tuningState = TuningState::heatingUp; + tuningPhase = 1; } else { tuningState = TuningState::stabilising; + tuningPhase = 0; } return GCodeResult::ok; } -void RemoteHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept -{ - reply.copy("remote heater auto tune not implemented"); -} - -void RemoteHeater::PrintCoolingFanPwmChanged(float pwmChange) noexcept +void RemoteHeater::FeedForwardAdjustment(float fanPwmChange, float extrusionChange) noexcept { - //TODO send a CAN message to remote + constexpr const char* warnMsg = "Failed to make heater feedforward adjustment: %s\n"; + CanMessageBuffer * const buf = CanMessageBuffer::Allocate(); + if (buf == nullptr) + { + reprap.GetPlatform().MessageF(WarningMessage, warnMsg, "no CAN buffer"); + } + else + { + const CanRequestId rid = CanInterface::AllocateRequestId(boardAddress); + auto msg = buf->SetupRequestMessage<CanMessageHeaterFeedForward>(rid, CanInterface::GetCanAddress(), boardAddress); + msg->heaterNumber = GetHeaterNumber(); + msg->fanPwmAdjustment = fanPwmChange; + msg->extrusionAdjustment = extrusionChange; + String<StringLength100> reply; + if (CanInterface::SendRequestAndGetStandardReply(buf, rid, reply.GetRef()) != GCodeResult::ok) + { + reprap.GetPlatform().MessageF(WarningMessage, reply.c_str()); + } + } } void RemoteHeater::Suspend(bool sus) noexcept @@ -311,6 +460,23 @@ void RemoteHeater::UpdateRemoteStatus(CanAddress src, const CanHeaterReport& rep } } +void RemoteHeater::UpdateHeaterTuning(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept +{ + if (src == boardAddress && tuningState >= TuningState::idleCycles && !newTuningResult) + { + tOn.Add((float)msg.ton); + tOff.Add((float)msg.toff); + dHigh.Add((float)msg.dhigh); + dLow.Add((float)msg.dlow); + heatingRate.Add(msg.heatingRate); + coolingRate.Add(msg.coolingRate); + tuningVoltage.Add(msg.voltage); + currentCoolingRate = msg.coolingRate; + tuningCyclesDone = msg.cyclesDone; + newTuningResult = true; + } +} + GCodeResult RemoteHeater::SendTuningCommand(const StringRef& reply, bool on) noexcept { CanMessageBuffer * const buf = CanMessageBuffer::Allocate(); @@ -331,6 +497,17 @@ GCodeResult RemoteHeater::SendTuningCommand(const StringRef& reply, bool on) noe return CanInterface::SendRequestAndGetStandardReply(buf, rid, reply); } +void RemoteHeater::StopTuning() noexcept +{ + tuningState = TuningState::notTuning; + String<StringLength100> reply; + if (SendTuningCommand(reply.GetRef(), false) != GCodeResult::ok) + { + reprap.GetPlatform().MessageF(ErrorMessage, "%s\n", reply.c_str()); + reprap.GetPlatform().MessageF(ErrorMessage, "DANGER! Failed to stop tuning heater %u on CAN board %u, suggest turn power off\n", GetHeaterNumber(), boardAddress); + } +} + #endif // End diff --git a/src/Heating/RemoteHeater.h b/src/Heating/RemoteHeater.h index d39558d6..3ff8e066 100644 --- a/src/Heating/RemoteHeater.h +++ b/src/Heating/RemoteHeater.h @@ -28,10 +28,10 @@ public: float GetTemperature() const noexcept override; // Get the latest temperature float GetAveragePWM() const noexcept override; // Return the running average PWM to the heater. Answer is a fraction in [0, 1]. float GetAccumulator() const noexcept override; // Return the integral accumulator - void GetAutoTuneStatus(const StringRef& reply) const noexcept override; // Get the auto tune status or last result void Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing - void PrintCoolingFanPwmChanged(float pwmChange) noexcept override; + void FeedForwardAdjustment(float fanPwmChange, float extrusionChange) noexcept override; void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept override; + void UpdateHeaterTuning(CanAddress src, const CanMessageHeaterTuningReport& msg) noexcept override; protected: void ResetHeater() noexcept override; @@ -48,11 +48,16 @@ private: notTuning = 0, stabilising, heatingUp, + idleCycles, cyclingFanOff, +#if TUNE_WITH_HALF_FAN + cyclingHalfFan, +#endif cyclingFanOn }; GCodeResult SendTuningCommand(const StringRef& reply, bool on) noexcept; + void StopTuning() noexcept; static constexpr uint32_t RemoteStatusTimeout = 2000; @@ -62,7 +67,12 @@ private: TuningState tuningState; float lastTemperature; uint32_t whenLastStatusReceived; - uint32_t timeSetHeating; // When we turned on the heater at the start of auto tuning + + // Variables used only during tuning + static uint32_t timeSetHeating; // When we turned on the heater at the start of auto tuning + static float currentCoolingRate; + static unsigned int tuningCyclesDone; + static bool newTuningResult; }; #endif diff --git a/src/Tools/Tool.cpp b/src/Tools/Tool.cpp index 5f346381..460d0506 100644 --- a/src/Tools/Tool.cpp +++ b/src/Tools/Tool.cpp @@ -622,7 +622,7 @@ void Tool::SetFansPwm(float f) const noexcept const float pwmChange = reprap.GetFansManager().SetFansValue(fanMapping, f); if (pwmChange != 0.0) { - IterateHeaters([pwmChange](unsigned int heater) { reprap.GetHeat().PrintCoolingFanPwmChanged(heater, pwmChange); }); + IterateHeaters([pwmChange](unsigned int heater) { reprap.GetHeat().FeedForwardAdjustment(heater, pwmChange, 0.0); }); } } |