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

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManuel Coenen <manuel@duet3d.com>2021-01-25 14:51:51 +0300
committerManuel Coenen <manuel@duet3d.com>2021-01-25 14:51:51 +0300
commit1a9bfaec7448b46c1e000144877560577eb9bff4 (patch)
treea84dfd2fea8867581a4129a94f27cdaf35e79406 /src/Heating
parent3578600a1dde59727142ad7b54b92609b31b6d0f (diff)
parent3da46dfed8a41ee2e697bd207ae816e3b0c009c2 (diff)
Merge remote-tracking branch 'origin/3.3-dev' into wil-convert-same70-to-coren2g
Diffstat (limited to 'src/Heating')
-rw-r--r--src/Heating/Heat.cpp20
-rw-r--r--src/Heating/Heat.h5
-rw-r--r--src/Heating/Heater.cpp227
-rw-r--r--src/Heating/Heater.h75
-rw-r--r--src/Heating/LocalHeater.cpp221
-rw-r--r--src/Heating/LocalHeater.h45
-rw-r--r--src/Heating/RemoteHeater.cpp286
-rw-r--r--src/Heating/RemoteHeater.h43
8 files changed, 628 insertions, 294 deletions
diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp
index 076f81c0..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);
}
}
@@ -1092,13 +1092,6 @@ size_t Heat::GetNumSensorsToReport() const noexcept
return s->GetSensorNumber() + 1;
}
-// Get the temperature of a heater
-float Heat::GetHeaterTemperature(size_t heater) const noexcept
-{
- const auto h = FindHeater(heater);
- return (h.IsNull()) ? ABS_ZERO : h->GetTemperature();
-}
-
// Suspend the heaters to conserve power or while doing Z probing
void Heat::SuspendHeaters(bool sus) noexcept
{
@@ -1241,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 3d86ed5e..872c72dc 100644
--- a/src/Heating/Heat.h
+++ b/src/Heating/Heat.h
@@ -104,8 +104,6 @@ public:
float GetAveragePWM(size_t heater) const noexcept // Return the running average PWM to the heater as a fraction in [0, 1].
pre(heater < MaxHeaters);
- float GetHeaterTemperature(size_t heater) const noexcept; // Result is in degrees Celsius
-
const Tool* GetLastStandbyTool(int heater) const noexcept
pre(heater >= 0; heater < MaxHeaters)
{
@@ -132,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
@@ -142,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 952f50ce..b0dc08f2 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>
@@ -60,8 +61,47 @@ DEFINE_GET_OBJECT_MODEL_TABLE(Heater)
#endif
+// Static members of class 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
+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
+uint32_t Heater::peakTime; // the time at which we recorded peakTemp
+float Heater::afterPeakTemp; // temperature after max from which we start timing the cooling rate
+uint32_t Heater::afterPeakTime; // the time at which we recorded afterPeakTemp
+float Heater::lastCoolingRate;
+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
+{
+ dHigh.Clear();
+ dLow.Clear();
+ tOn.Clear();
+ tOff.Clear();
+ heatingRate.Clear();
+ coolingRate.Clear();
+}
+
Heater::Heater(unsigned int num) noexcept
- : heaterNumber(num), sensorNumber(-1), activeTemperature(0.0), standbyTemperature(0.0),
+ : tuned(false), heaterNumber(num), sensorNumber(-1), activeTemperature(0.0), standbyTemperature(0.0),
maxTempExcursion(DefaultMaxTempExcursion), maxHeatingFaultTime(DefaultMaxHeatingFaultTime),
active(false), modelSetByUser(false), monitorsSetByUser(false)
{
@@ -170,17 +210,17 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const
}
// Set the process model returning true if successful
-GCodeResult Heater::SetModel(float heatingRate, float coolingRateFanOff, float coolingRateFanOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept
+GCodeResult Heater::SetModel(float hr, float coolingRateFanOff, float coolingRateFanOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept
{
GCodeResult rslt;
- if (model.SetParameters(heatingRate, coolingRateFanOff, coolingRateFanOn, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
+ if (model.SetParameters(hr, coolingRateFanOff, coolingRateFanOn, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted))
{
if (model.IsEnabled())
{
rslt = UpdateModel(reply);
if (rslt == GCodeResult::ok)
{
- const float predictedMaxTemp = heatingRate/coolingRateFanOff + NormalAmbientTemperature;
+ const float predictedMaxTemp = hr/coolingRateFanOff + NormalAmbientTemperature;
const float noWarnTemp = (GetHighestTemperatureLimit() - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C
if (predictedMaxTemp > noWarnTemp)
{
@@ -192,6 +232,7 @@ GCodeResult Heater::SetModel(float heatingRate, float coolingRateFanOff, float c
else
{
ResetHeater();
+ tuned = false;
rslt = GCodeResult::ok;
}
}
@@ -205,6 +246,184 @@ GCodeResult Heater::SetModel(float heatingRate, float coolingRateFanOff, float c
return rslt;
}
+// Start an auto tune cycle for this heater
+GCodeResult Heater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException)
+{
+ // Get the target temperature (required)
+ gb.MustSee('S');
+ const float targetTemp = gb.GetFValue();
+
+ // Get the optional PWM
+ const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : GetModel().GetMaxPwm();
+ if (maxPwm < 0.1 || maxPwm > 1.0)
+ {
+ reply.copy("Invalid PWM value");
+ return GCodeResult::error;
+ }
+
+ if (!GetModel().IsEnabled())
+ {
+ reply.printf("heater %u cannot be auto tuned while it is disabled", GetHeaterNumber());
+ return GCodeResult::error;
+ }
+
+ const float limit = GetHighestTemperatureLimit();
+ if (targetTemp >= limit)
+ {
+ reply.printf("heater %u target temperature must be below the temperature limit for this heater (%.1fC)", GetHeaterNumber(), (double)limit);
+ return GCodeResult::error;
+ }
+
+ TemperatureError err;
+ const float currentTemp = reprap.GetHeat().GetSensorTemperature(GetSensorNumber(), err);
+ if (err != TemperatureError::success)
+ {
+ reply.printf("heater %u reported error '%s' at start of auto tuning", GetHeaterNumber(), TemperatureErrorString(err));
+ return GCodeResult::error;
+ }
+
+ const bool seenA = gb.Seen('A');
+ const float ambientTemp = (seenA) ? gb.GetFValue() : currentTemp;
+ if (ambientTemp + 20 >= targetTemp)
+ {
+ reply.printf("Target temperature must be at least 20C above ambient temperature");
+ }
+
+ const GCodeResult rslt = StartAutoTune(reply, fans, targetTemp, maxPwm, seenA, ambientTemp);
+ if (rslt == GCodeResult::ok)
+ {
+ reply.printf("Auto tuning heater %u using target temperature %.1f" DEGREE_SYMBOL "C and PWM %.2f - do not leave printer unattended",
+ GetHeaterNumber(), (double)targetTemp, (double)maxPwm);
+ }
+ return rslt;
+}
+
+const char *const Heater::TuningPhaseText[] =
+{
+ "checking temperature is stable",
+ "heating up",
+ "heating system settling",
+ "tuning with fan off",
+#if TUNE_WITH_HALF_FAN
+ "tuning with 50% fan",
+#endif
+ "tuning with fan on"
+};
+
+// 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 : ARRAY_SIZE(TuningPhaseText);
+ reply.printf("Heater %u is being tuned, phase %u of %u, %s", GetHeaterNumber(), tuningPhase + 1, numPhases, TuningPhaseText[tuningPhase]);
+ }
+ 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());
+ }
+}
+
+// Tell the user what's happening, called after the tuning phase has been updated
+void Heater::ReportTuningUpdate() noexcept
+{
+ if (tuningPhase < ARRAY_SIZE(TuningPhaseText))
+ {
+ reprap.GetPlatform().MessageF(GenericMessage, "Auto tune starting phase %u, %s\n", tuningPhase + 1, TuningPhaseText[tuningPhase]);
+ }
+}
+
+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 eaed5621..dc9d5e8e 100644
--- a/src/Heating/Heater.h
+++ b/src/Heating/Heater.h
@@ -14,12 +14,16 @@
#include "HeaterMonitor.h"
#include <GCodes/GCodeResult.h>
#include <ObjectModel/ObjectModel.h>
+#include <Math/DeviationAccumulator.h>
#if SUPPORT_CAN_EXPANSION
# 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.
@@ -37,19 +41,18 @@ public:
virtual GCodeResult SetPwmFrequency(PwmFrequency freq, const StringRef& reply) = 0;
virtual GCodeResult ReportDetails(const StringRef& reply) const noexcept = 0;
- virtual float GetTemperature() const noexcept = 0; // Get the current temperature
+ virtual float GetTemperature() const noexcept = 0; // Get the current temperature and error status
virtual float GetAveragePWM() const noexcept = 0; // Return the running average PWM to the heater. Answer is a fraction in [0, 1].
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 GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException) = 0; // Start an auto tune cycle for this heater
- 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
@@ -60,6 +63,8 @@ public:
float GetStandbyTemperature() const noexcept { return standbyTemperature; }
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; }
@@ -106,24 +111,78 @@ 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;
virtual GCodeResult UpdateModel(const StringRef& reply) noexcept = 0;
virtual GCodeResult UpdateFaultDetectionParameters(const StringRef& reply) noexcept = 0;
virtual GCodeResult UpdateHeaterMonitors(const StringRef& reply) noexcept = 0;
+ virtual GCodeResult StartAutoTune(const StringRef& reply, FansBitmap fans, float targetTemp, float pwm, bool seenA, float ambientTemp) noexcept = 0;
int GetSensorNumber() const noexcept { return sensorNumber; }
void SetSensorNumber(int sn) noexcept;
float GetMaxTemperatureExcursion() const noexcept { return maxTempExcursion; }
float GetMaxHeatingFaultTime() const noexcept { return maxHeatingFaultTime; }
float GetTargetTemperature() const noexcept { return (active) ? activeTemperature : standbyTemperature; }
- GCodeResult SetModel(float heatingRate, float coolingRateFanOff, float coolingRateFanOn, 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
+ 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 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;
+
+ HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation
+ bool tuned; // true if tuning was successful
+
+ // Constants used during heater tuning
+ static constexpr uint32_t TempSettleTimeout = 20000; // how long we allow the initial temperature to settle
+ static constexpr unsigned int TuningHeaterMinIdleCycles = 3; // minimum number of idle cycles after heating up, including the initial overshoot and cool down
+ static constexpr unsigned int TuningHeaterMaxIdleCycles = 10;
+ static constexpr unsigned int MinTuningHeaterCycles = 5;
+ static constexpr unsigned int MaxTuningHeaterCycles = 25;
+ static constexpr float TuningHysteresis = 5.0;
+ static constexpr float TuningPeakTempDrop = 2.0; // must be well below TuningHysteresis
+ static constexpr float FeedForwardMultiplier = 1.3; // how much we over-compensate feedforward to allow for heat reservoirs during tuning
+ static constexpr float HeaterSettledCoolingTimeRatio = 0.93;
+
+ // Variables used during heater tuning
+ static float tuningPwm; // the PWM to use, 0..1
+ static float tuningTargetTemp; // the target temperature
+ static DeviationAccumulator tuningStartTemp; // the temperature when we turned on the heater
+ static uint32_t tuningBeginTime; // when we started the tuning process
+ static DeviationAccumulator dHigh;
+ static DeviationAccumulator dLow;
+ static DeviationAccumulator tOn;
+ 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
+ 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 float lastCoolingRate;
+ static FansBitmap tuningFans;
+ static unsigned int tuningPhase;
+ static uint8_t idleCyclesDone;
+
+ static HeaterParameters fanOffParams, fanOnParams;
+
+ static void ClearCounters() noexcept;
private:
+ static const char* const TuningPhaseText[];
+
FopDt model;
unsigned int heaterNumber;
int sensorNumber; // the sensor number used by this heater
diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp
index 949c6096..d1480263 100644
--- a/src/Heating/LocalHeater.cpp
+++ b/src/Heating/LocalHeater.cpp
@@ -14,51 +14,6 @@
#include "RepRap.h"
#include <Tools/Tool.h>
-#define TUNE_WITH_HALF_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
-
-// Variables used during heater tuning
-static float tuningPwm; // the PWM to use, 0..1
-static float tuningTargetTemp; // the target temperature
-static DeviationAccumulator tuningStartTemp; // the temperature when we turned on the heater
-static uint32_t tuningBeginTime; // when we started the tuning process
-static DeviationAccumulator dHigh;
-static DeviationAccumulator dLow;
-static DeviationAccumulator tOn;
-static DeviationAccumulator tOff;
-static DeviationAccumulator heatingRate;
-static DeviationAccumulator coolingRate;
-static uint32_t lastOffTime;
-static uint32_t lastOnTime;
-static float peakTemp; // max or min temperature
-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 float lastCoolingRate;
-static FansBitmap tuningFans;
-static unsigned int tuningPhase;
-static uint8_t idleCyclesDone;
-
-static LocalHeater::HeaterParameters fanOffParams, fanOnParams;
-
-#if HAS_VOLTAGE_MONITOR
-static DeviationAccumulator tuningVoltage; // sum of the voltage readings we take during the heating phase
-#endif
-
-// Clear all the counters except tuning voltage and start temperature
-static void ClearCounters() noexcept
-{
- dHigh.Clear();
- dLow.Clear();
- tOn.Clear();
- tOff.Clear();
- heatingRate.Clear();
- coolingRate.Clear();
-}
-
// Member functions and constructors
LocalHeater::LocalHeater(unsigned int heaterNum) noexcept : Heater(heaterNum), mode(HeaterMode::off)
@@ -99,7 +54,6 @@ void LocalHeater::ResetHeater() noexcept
previousTemperatureIndex = 0;
iAccumulator = 0.0;
badTemperatureCount = 0;
- tuned = false;
averagePWM = lastPwm = 0.0;
heatingFaultCount = 0;
temperature = BadErrorTemperature;
@@ -478,64 +432,21 @@ float LocalHeater::GetExpectedHeatingRate() const noexcept
}
// Auto tune this heater. The caller has already checked that on other heater is being tuned.
-GCodeResult LocalHeater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException)
+GCodeResult LocalHeater::StartAutoTune(const StringRef& reply, FansBitmap fans, float targetTemp, float pwm, bool seenA, float ambientTemp) noexcept
{
- // Get the target temperature (required)
- gb.MustSee('S');
- const float targetTemp = gb.GetFValue();
-
- // Get the optional PWM
- const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : GetModel().GetMaxPwm();
- if (maxPwm < 0.1 || maxPwm > 1.0)
- {
- reply.copy("Invalid PWM value");
- return GCodeResult::error;
- }
-
- if (!GetModel().IsEnabled())
- {
- reply.printf("heater %u cannot be auto tuned while it is disabled", GetHeaterNumber());
- return GCodeResult::error;
- }
-
if (lastPwm > 0.0 || GetAveragePWM() > 0.02)
{
reply.printf("heater %u must be off and cold before auto tuning it", GetHeaterNumber());
return GCodeResult::error;
}
- const float limit = GetHighestTemperatureLimit();
- if (targetTemp >= limit)
- {
- reply.printf("heater %u target temperature must be below the temperature limit for this heater (%.1fC)", GetHeaterNumber(), (double)limit);
- return GCodeResult::error;
- }
-
- const TemperatureError err = ReadTemperature();
- if (err != TemperatureError::success)
- {
- reply.printf("heater %u reported error '%s' at start of auto tuning", GetHeaterNumber(), TemperatureErrorString(err));
- return GCodeResult::error;
- }
-
- const bool seenA = gb.Seen('A');
- const float ambientTemp = (seenA) ? gb.GetFValue() : temperature;
- if (ambientTemp + 20 >= targetTemp)
- {
- reply.printf("Target temperature must be at least 20C above ambient temperature");
- }
-
- reply.printf("Auto tuning heater %u using target temperature %.1f" DEGREE_SYMBOL "C and PWM %.2f - do not leave printer unattended",
- GetHeaterNumber(), (double)targetTemp, (double)maxPwm);
-
tuningFans = fans;
reprap.GetFansManager().SetFansValue(tuningFans, 0.0);
- tuningPwm = maxPwm;
+ tuningPwm = pwm;
tuningTargetTemp = targetTemp;
tuningStartTemp.Clear();
tuningBeginTime = millis();
- tuningPhase = 0;
tuned = false; // assume failure
if (seenA)
@@ -544,46 +455,25 @@ GCodeResult LocalHeater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply,
ClearCounters();
timeSetHeating = millis();
lastPwm = tuningPwm; // turn on heater at specified power
+ tuningPhase = 1;
mode = HeaterMode::tuning1;
+ ReportTuningUpdate();
}
else
{
+ tuningPhase = 0;
mode = HeaterMode::tuning0;
}
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))
@@ -690,13 +580,11 @@ 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;
- reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 2, heater settling\n");
+ ReportTuningUpdate();
}
}
return;
@@ -726,7 +614,7 @@ void LocalHeater::DoTuningStep() noexcept
if (idleCyclesDone == TuningHeaterMaxIdleCycles || (idleCyclesDone >= TuningHeaterMinIdleCycles && currentCoolingRate >= lastCoolingRate * HeaterSettledCoolingTimeRatio))
{
tuningPhase = 3;
- reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 3, fan off\n");
+ ReportTuningUpdate();
}
else
{
@@ -762,11 +650,10 @@ void LocalHeater::DoTuningStep() noexcept
ClearCounters();
#if TUNE_WITH_HALF_FAN
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
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
+ ReportTuningUpdate();
}
}
#if TUNE_WITH_HALF_FAN
@@ -776,7 +663,7 @@ void LocalHeater::DoTuningStep() noexcept
tuningPhase = 5;
ClearCounters();
reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans fully on
- reprap.GetPlatform().Message(GenericMessage, "Auto tune starting phase 4, fan 100%\n");
+ ReportTuningUpdate();
}
#endif
else
@@ -837,92 +724,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 0486bccf..02fde4f9 100644
--- a/src/Heating/LocalHeater.h
+++ b/src/Heating/LocalHeater.h
@@ -17,7 +17,6 @@
#include "TemperatureError.h"
#include <Hardware/IoPorts.h>
#include <GCodes/GCodeResult.h>
-#include <Math/DeviationAccumulator.h>
class HeaterMonitor;
@@ -26,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;
@@ -41,35 +32,33 @@ public:
GCodeResult SetPwmFrequency(PwmFrequency freq, const StringRef& reply) noexcept override;
GCodeResult ReportDetails(const StringRef& reply) const noexcept override;
- void Spin() noexcept override; // Called in a tight loop to keep things running
- void SwitchOff() noexcept override; // Not even standby - all heater power off
- GCodeResult ResetFault(const StringRef& reply) noexcept override; // Reset a fault condition - only call this if you know what you are doing
- float GetTemperature() const noexcept override; // Get the current 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
- GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException) override; // Start an auto tune cycle for this heater
- 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 Spin() noexcept override; // Called in a tight loop to keep things running
+ void SwitchOff() noexcept override; // Not even standby - all heater power off
+ GCodeResult ResetFault(const StringRef& reply) noexcept override; // Reset a fault condition - only call this if you know what you are doing
+ 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 Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing
+ 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:
void ResetHeater() noexcept override;
HeaterMode GetMode() const noexcept override { return mode; }
- GCodeResult SwitchOn(const StringRef& reply) noexcept override; // Turn the heater on and set the mode
- GCodeResult UpdateModel(const StringRef& reply) noexcept override; // Called when the heater model has been changed
+ GCodeResult SwitchOn(const StringRef& reply) noexcept override; // Turn the heater on and set the mode
+ GCodeResult UpdateModel(const StringRef& reply) noexcept override; // Called when the heater model has been changed
GCodeResult UpdateFaultDetectionParameters(const StringRef& reply) noexcept override { return GCodeResult::ok; }
GCodeResult UpdateHeaterMonitors(const StringRef& reply) noexcept override { return GCodeResult::ok; }
+ GCodeResult StartAutoTune(const StringRef& reply, FansBitmap fans, float targetTemp, float pwm, bool seenA, float ambientTemp) noexcept override; // Start an auto tune cycle for this heater
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;
@@ -87,19 +76,9 @@ private:
uint8_t previousTemperaturesGood; // Bitmap indicating which previous temperature were good readings
HeaterMode mode; // Current state of the heater
- bool tuned; // True if tuning was successful
uint8_t badTemperatureCount; // Count of sequential dud readings
static_assert(sizeof(previousTemperaturesGood) * 8 >= NumPreviousTemperatures, "too few bits in previousTemperaturesGood");
-
- static constexpr unsigned int TuningHeaterMinIdleCycles = 3; // minimum number of idle cycles after heating up, including the initial overshoot and cool down
- static constexpr unsigned int TuningHeaterMaxIdleCycles = 10;
- static constexpr unsigned int MinTuningHeaterCycles = 5;
- static constexpr unsigned int MaxTuningHeaterCycles = 25;
- static constexpr float TuningHysteresis = 5.0;
- static constexpr float TuningPeakTempDrop = 2.0; // must be well below TuningHysteresis
- static constexpr float FeedForwardMultiplier = 1.3; // how much we over-compensate feedforward to allow for heat reservoirs during tuning
- static constexpr float HeaterSettledCoolingTimeRatio = 0.93;
};
#endif /* SRC_LOCALHEATER_H_ */
diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp
index 4012e387..34badf58 100644
--- a/src/Heating/RemoteHeater.cpp
+++ b/src/Heating/RemoteHeater.cpp
@@ -17,8 +17,14 @@
#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), lastTemperature(0.0), whenLastStatusReceived(0)
+ : Heater(num), boardAddress(board), lastMode(HeaterMode::offline), averagePwm(0), tuningState(TuningState::notTuning), lastTemperature(0.0), whenLastStatusReceived(0)
{
}
@@ -33,12 +39,163 @@ RemoteHeater::~RemoteHeater() noexcept
void RemoteHeater::Spin() noexcept
{
- // Nothing needed here unless we want to copy the sensor temperature across. For now we don't store the temperature locally.
+ const uint32_t now = millis();
+ switch (tuningState)
+ {
+ case TuningState::notTuning:
+ break;
+
+ case TuningState::stabilising:
+ 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)
+ {
+ tuningState = TuningState::heatingUp;
+ tuningPhase = 1;
+ ReportTuningUpdate();
+ }
+ else
+ {
+ 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:
+ {
+ 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;
+ ReportTuningUpdate();
+ }
+ }
+ break;
+
+ 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::cycling;
+ ReportTuningUpdate();
+ }
+ else
+ {
+ lastCoolingRate = currentCoolingRate;
+ ClearCounters();
+ ++idleCyclesDone;
+ }
+ newTuningResult = false;
+ }
+ break;
+
+ case TuningState::cycling:
+ 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 (tuningPhase == 3)
+ {
+ CalculateModel(fanOffParams);
+ if (tuningFans.IsEmpty())
+ {
+ SetAndReportModel(false);
+ StopTuning();
+ break;
+ }
+ else
+ {
+ tuningPhase = 4;
+ ClearCounters();
+#if TUNE_WITH_HALF_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
+ ReportTuningUpdate();
+ }
+ }
+#if TUNE_WITH_HALF_FAN
+ else if (tuningPhase == 4)
+ {
+ CalculateModel(fanOnParams);
+ tuningPhase = 5;
+ ClearCounters();
+ reprap.GetFansManager().SetFansValue(tuningFans, 1.0); // turn fans fully on
+ ReportTuningUpdate();
+ }
+#endif
+ else
+ {
+ reprap.GetFansManager().SetFansValue(tuningFans, 0.0); // turn fans off
+ CalculateModel(fanOnParams);
+ SetAndReportModel(true);
+ StopTuning();
+ break;
+ }
+ }
+ }
+ newTuningResult = false;
+ }
+ break;
+ }
}
void RemoteHeater::ResetHeater() noexcept
{
- //TODO
+ // This is only called by UpdateModel. Nothing needed here.
}
GCodeResult RemoteHeater::ConfigurePortAndSensor(const char *portName, PwmFrequency freq, unsigned int sn, const StringRef& reply)
@@ -69,10 +226,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
{
@@ -84,7 +242,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());
}
}
}
@@ -128,20 +286,68 @@ float RemoteHeater::GetAccumulator() const noexcept
return 0.0; // not supported
}
-GCodeResult RemoteHeater::StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException)
+GCodeResult RemoteHeater::StartAutoTune(const StringRef& reply, FansBitmap fans, float targetTemp, float pwm, bool seenA, float ambientTemp) noexcept
{
- reply.copy("remote heater auto tune not implemented");
- return GCodeResult::error;
-}
+ CanMessageBuffer * const buf = CanMessageBuffer::Allocate();
+ if (buf == nullptr)
+ {
+ reply.copy("No CAN buffer");
+ return GCodeResult::error;
+ }
-void RemoteHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept
-{
- reply.copy("remote heater auto tune not implemented");
+ tuningFans = fans;
+ reprap.GetFansManager().SetFansValue(tuningFans, 0.0);
+
+ tuningPwm = pwm;
+ tuningTargetTemp = targetTemp;
+ tuningStartTemp.Clear();
+ tuningBeginTime = millis();
+ tuned = false;
+
+ if (seenA)
+ {
+ tuningStartTemp.Add(ambientTemp);
+ ClearCounters();
+ timeSetHeating = millis();
+ GCodeResult rslt = SendTuningCommand(reply, true);
+ if (rslt != GCodeResult::ok)
+ {
+ return rslt;
+ }
+ tuningState = TuningState::heatingUp;
+ tuningPhase = 1;
+ ReportTuningUpdate();
+ }
+ else
+ {
+ tuningState = TuningState::stabilising;
+ tuningPhase = 0;
+ }
+
+ return GCodeResult::ok;
}
-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
@@ -161,7 +367,9 @@ void RemoteHeater::Suspend(bool sus) noexcept
Heater::HeaterMode RemoteHeater::GetMode() const noexcept
{
- return (millis() - whenLastStatusReceived < RemoteStatusTimeout) ? lastMode : HeaterMode::offline;
+ return (tuningState != TuningState::notTuning) ? HeaterMode::tuning0
+ : (millis() - whenLastStatusReceived < RemoteStatusTimeout) ? lastMode
+ : HeaterMode::offline;
}
// This isn't just called to turn the heater on, it is called when the temperature needs to be updated
@@ -249,6 +457,54 @@ 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();
+ if (buf == nullptr)
+ {
+ reply.copy("No CAN buffer");
+ return GCodeResult::error;
+ }
+
+ const CanRequestId rid = CanInterface::AllocateRequestId(boardAddress);
+ auto msg = buf->SetupRequestMessage<CanMessageHeaterTuningCommand>(rid, CanInterface::GetCanAddress(), boardAddress);
+ msg->heaterNumber = GetHeaterNumber();
+ msg->on = on;
+ msg->highTemp = tuningTargetTemp;
+ msg->lowTemp = tuningTargetTemp - TuningHysteresis;
+ msg->pwm = tuningPwm;
+ msg->peakTempDrop = TuningPeakTempDrop;
+ 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 907908e7..548abe9e 100644
--- a/src/Heating/RemoteHeater.h
+++ b/src/Heating/RemoteHeater.h
@@ -22,34 +22,53 @@ public:
GCodeResult SetPwmFrequency(PwmFrequency freq, const StringRef& reply) override;
GCodeResult ReportDetails(const StringRef& reply) const noexcept override;
- void Spin() noexcept override; // Called in a tight loop to keep things running
- void SwitchOff() noexcept override; // Not even standby - all heater power off
- GCodeResult ResetFault(const StringRef& reply) noexcept override; // Reset a fault condition - only call this if you know what you are doing
- float GetTemperature() const noexcept override; // Get the current 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
- GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply, FansBitmap fans) THROWS(GCodeException) override; // Start an auto tune cycle for this heater
- 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 Spin() noexcept override; // Called in a tight loop to keep things running
+ void SwitchOff() noexcept override; // Not even standby - all heater power off
+ GCodeResult ResetFault(const StringRef& reply) noexcept override; // Reset a fault condition - only call this if you know what you are doing
+ 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 Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing
+ 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;
HeaterMode GetMode() const noexcept override;
- GCodeResult SwitchOn(const StringRef& reply) noexcept override; // Turn the heater on and set the mode
- GCodeResult UpdateModel(const StringRef& reply) noexcept override; // Called when the heater model has been changed
+ GCodeResult SwitchOn(const StringRef& reply) noexcept override; // Turn the heater on and set the mode
+ GCodeResult UpdateModel(const StringRef& reply) noexcept override; // Called when the heater model has been changed
GCodeResult UpdateFaultDetectionParameters(const StringRef& reply) noexcept override;
GCodeResult UpdateHeaterMonitors(const StringRef& reply) noexcept override;
+ GCodeResult StartAutoTune(const StringRef& reply, FansBitmap fans, float targetTemp, float pwm, bool seenA, float ambientTemp) noexcept override; // Start an auto tune cycle for this heater
private:
+ enum class TuningState : uint8_t
+ {
+ notTuning = 0,
+ stabilising,
+ heatingUp,
+ idleCycles,
+ cycling
+ };
+
+ GCodeResult SendTuningCommand(const StringRef& reply, bool on) noexcept;
+ void StopTuning() noexcept;
+
static constexpr uint32_t RemoteStatusTimeout = 2000;
CanAddress boardAddress;
HeaterMode lastMode;
uint8_t averagePwm;
+ TuningState tuningState;
float lastTemperature;
uint32_t whenLastStatusReceived;
+
+ // 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