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

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2021-01-24 18:10:05 +0300
committerDavid Crocker <dcrocker@eschertech.com>2021-01-24 18:10:05 +0300
commitb75f3b14644b1dcc5aeb8170d1e1b6d4a9f8e52e (patch)
treebf4afa1f53eb91f2fc175283025fa14a99745e1e /src
parentfec7a47b928ab9bcd0c11ec4f9b6926f2e732401 (diff)
More work on remote heater tuning
Diffstat (limited to 'src')
-rw-r--r--src/CAN/CommandProcessor.cpp4
-rw-r--r--src/Heating/Heat.cpp13
-rw-r--r--src/Heating/Heat.h3
-rw-r--r--src/Heating/Heater.cpp118
-rw-r--r--src/Heating/Heater.h26
-rw-r--r--src/Heating/LocalHeater.cpp124
-rw-r--r--src/Heating/LocalHeater.h14
-rw-r--r--src/Heating/RemoteHeater.cpp215
-rw-r--r--src/Heating/RemoteHeater.h16
-rw-r--r--src/Tools/Tool.cpp2
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); });
}
}