diff options
author | David Crocker <dcrocker@eschertech.com> | 2020-02-16 17:10:43 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2020-02-16 17:10:43 +0300 |
commit | 315f4f47613fe09e613b6c639a7ddc37af3516d2 (patch) | |
tree | 45133ce063bf3bb2c40f522471f0074038168149 /src/Heating | |
parent | 9885ed7cc55cb19fde1be14c8adfd11dfa7f86cf (diff) |
Replaced HeaterProtection instances by HeaterMonitors
Diffstat (limited to 'src/Heating')
-rw-r--r-- | src/Heating/FOPDT.cpp | 2 | ||||
-rw-r--r-- | src/Heating/FOPDT.h | 2 | ||||
-rw-r--r-- | src/Heating/Heat.cpp | 333 | ||||
-rw-r--r-- | src/Heating/Heat.h | 21 | ||||
-rw-r--r-- | src/Heating/Heater.cpp | 242 | ||||
-rw-r--r-- | src/Heating/Heater.h | 39 | ||||
-rw-r--r-- | src/Heating/HeaterMonitor.cpp | 99 | ||||
-rw-r--r-- | src/Heating/HeaterMonitor.h | 81 | ||||
-rw-r--r-- | src/Heating/HeaterProtection.cpp | 71 | ||||
-rw-r--r-- | src/Heating/HeaterProtection.h | 96 | ||||
-rw-r--r-- | src/Heating/LocalHeater.cpp | 76 | ||||
-rw-r--r-- | src/Heating/LocalHeater.h | 37 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.cpp | 32 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.h | 3 | ||||
-rw-r--r-- | src/Heating/Sensors/TemperatureSensor.h | 1 |
15 files changed, 522 insertions, 613 deletions
diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp index 95f4ec54..c2347cf1 100644 --- a/src/Heating/FOPDT.cpp +++ b/src/Heating/FOPDT.cpp @@ -138,7 +138,7 @@ void FopDt::CalcPidConstants() noexcept #if SUPPORT_CAN_EXPANSION -void FopDt::SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) noexcept +void FopDt::SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) const noexcept { msg.heater = heater; msg.gain = gain; diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h index 0a7c033e..fa911f9c 100644 --- a/src/Heating/FOPDT.h +++ b/src/Heating/FOPDT.h @@ -65,7 +65,7 @@ public: #endif #if SUPPORT_CAN_EXPANSION - void SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) noexcept; + void SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) const noexcept; #endif private: diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index 914dbfde..f1d6e1ec 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -20,7 +20,7 @@ Licence: GPL #include "Heat.h" #include "LocalHeater.h" -#include "HeaterProtection.h" +#include "HeaterMonitor.h" #include "Platform.h" #include "RepRap.h" #include "Sensors/TemperatureSensor.h" @@ -67,13 +67,6 @@ constexpr ObjectModelArrayDescriptor Heat::heatersArrayDescriptor = [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const Heat*)self)->heaters[context.GetLastIndex()]); } }; -constexpr ObjectModelArrayDescriptor Heat::sensorsArrayDescriptor = -{ - &sensorsLock, - [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return ((const Heat*)self)->GetNumSensorsToReport(); }, - [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const Heat*)self)->FindSensor(context.GetLastIndex()).Ptr()); } -}; - // Macro to build a standard lambda function that includes the necessary type conversions #define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(Heat, __VA_ARGS__) @@ -84,10 +77,9 @@ constexpr ObjectModelTableEntry Heat::objectModelTable[] = { "coldExtrudeTemperature", OBJECT_MODEL_FUNC(self->extrusionMinTemp, 1), ObjectModelEntryFlags::none}, { "coldRetractTemperature", OBJECT_MODEL_FUNC(self->retractionMinTemp, 1), ObjectModelEntryFlags::none}, { "heaters", OBJECT_MODEL_FUNC_NOSELF(&heatersArrayDescriptor), ObjectModelEntryFlags::live }, - { "sensors", OBJECT_MODEL_FUNC_NOSELF(&sensorsArrayDescriptor), ObjectModelEntryFlags::live }, }; -constexpr uint8_t Heat::objectModelTableDescriptor[] = { 1, 4 }; +constexpr uint8_t Heat::objectModelTableDescriptor[] = { 1, 3 }; DEFINE_GET_OBJECT_MODEL_TABLE(Heat) @@ -112,11 +104,6 @@ Heat::Heat() noexcept h = -1; } - for (size_t index : ARRAY_INDICES(heaterProtections)) - { - heaterProtections[index] = new HeaterProtection(index); - } - for (Heater*& h : heaters) { h = nullptr; @@ -142,58 +129,7 @@ GCodeResult Heat::SetOrReportHeaterModel(GCodeBuffer& gb, const StringRef& reply const auto h = FindHeater(heater); if (h.IsNotNull()) { - const FopDt& model = h->GetModel(); - bool seen = false; - float gain = model.GetGain(), - tc = model.GetTimeConstant(), - td = model.GetDeadTime(), - maxPwm = model.GetMaxPwm(), - voltage = model.GetVoltage(); - int32_t dontUsePid = model.UsePid() ? 0 : 1; - int32_t inversionParameter = 0; - - gb.TryGetFValue('A', gain, seen); - gb.TryGetFValue('C', tc, seen); - gb.TryGetFValue('D', td, seen); - gb.TryGetIValue('B', dontUsePid, seen); - gb.TryGetFValue('S', maxPwm, seen); - gb.TryGetFValue('V', voltage, seen); - gb.TryGetIValue('I', inversionParameter, seen); - - if (seen) - { - const bool inverseTemperatureControl = (inversionParameter == 1 || inversionParameter == 3); - const GCodeResult rslt = h->SetModel(gain, tc, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply); - if (rslt != GCodeResult::ok) - { - return rslt; - } - } - else if (!model.IsEnabled()) - { - reply.printf("Heater %u is disabled", heater); - } - else - { - const char* const mode = (!model.UsePid()) ? "bang-bang" - : (model.ArePidParametersOverridden()) ? "custom PID" - : "PID"; - reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, calibration voltage %.1f, mode %s", heater, - (double)model.GetGain(), (double)model.GetTimeConstant(), (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode); - if (model.IsInverted()) - { - reply.cat(", inverted temperature control"); - } - if (model.UsePid()) - { - // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware - M301PidParameters params = model.GetM301PidParameters(false); - reply.catf("\nComputed PID parameters for setpoint change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD); - params = model.GetM301PidParameters(true); - reply.catf("\nComputed PID parameters for load change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD); - } - } - return GCodeResult::ok; + return h->SetOrReportModel(heater, gb, reply); } reply.printf("Heater %u not found", heater); @@ -299,15 +235,6 @@ void Heat::ResetHeaterModels() noexcept void Heat::Init() noexcept { - // Initialise the heater protection items first - for (size_t index : ARRAY_INDICES(heaterProtections)) - { - HeaterProtection * const prot = heaterProtections[index]; - - const float tempLimit = (IsBedOrChamberHeater(index)) ? DefaultBedTemperatureLimit : DefaultHotEndTemperatureLimit; - prot->Init(tempLimit); - } - extrusionMinTemp = HOT_ENOUGH_TO_EXTRUDE; retractionMinTemp = HOT_ENOUGH_TO_RETRACT; coldExtrude = false; @@ -635,12 +562,21 @@ HeaterStatus Heat::GetStatus(int heater) const noexcept void Heat::SetBedHeater(size_t index, int heater) noexcept { - const auto h = FindHeater(bedHeaters[index]); - if (h.IsNotNull()) { - h->SwitchOff(); + const auto h = FindHeater(bedHeaters[index]); + if (h.IsNotNull()) + { + h->SwitchOff(); + } } bedHeaters[index] = heater; + { + const auto h = FindHeater(bedHeaters[index]); + if (h.IsNotNull()) + { + h->SetDefaultMonitors(); + } + } } bool Heat::IsBedHeater(int heater) const noexcept @@ -657,12 +593,19 @@ bool Heat::IsBedHeater(int heater) const noexcept void Heat::SetChamberHeater(size_t index, int heater) noexcept { + { + const auto h = FindHeater(chamberHeaters[index]); + if (h.IsNotNull()) + { + h->SwitchOff(); + } + } + chamberHeaters[index] = heater; const auto h = FindHeater(chamberHeaters[index]); if (h.IsNotNull()) { - h->SwitchOff(); + h->SetDefaultMonitors(); } - chamberHeaters[index] = heater; } bool Heat::IsChamberHeater(int heater) const noexcept @@ -677,12 +620,12 @@ bool Heat::IsChamberHeater(int heater) const noexcept return false; } -void Heat::SetActiveTemperature(int heater, float t) noexcept +void Heat::SetTemperature(int heater, float t, bool activeNotStandby) THROWS(GCodeException) { const auto h = FindHeater(heater); if (h.IsNotNull()) { - h->SetActiveTemperature(t); + h->SetTemperature(t, activeNotStandby); } } @@ -692,15 +635,6 @@ float Heat::GetActiveTemperature(int heater) const noexcept return (h.IsNull()) ? ABS_ZERO : h->GetActiveTemperature(); } -void Heat::SetStandbyTemperature(int heater, float t) noexcept -{ - const auto h = FindHeater(heater); - if (h.IsNotNull()) - { - h->SetStandbyTemperature(t); - } -} - float Heat::GetStandbyTemperature(int heater) const noexcept { const auto h = FindHeater(heater); @@ -709,42 +643,14 @@ float Heat::GetStandbyTemperature(int heater) const noexcept float Heat::GetHighestTemperatureLimit(int heater) const noexcept { - float limit = BadErrorTemperature; - if (heater >= 0 && heater < (int)MaxHeaters) - { - for (const HeaterProtection *prot : heaterProtections) - { - if (prot->GetHeater() == heater && prot->GetTrigger() == HeaterProtectionTrigger::TemperatureExceeded) - { - const float t = prot->GetTemperatureLimit(); - if (limit == BadErrorTemperature || t > limit) - { - limit = t; - } - } - } - } - return limit; + const auto h = FindHeater(heater); + return (h.IsNull()) ? BadErrorTemperature : h->GetHighestTemperatureLimit(); } float Heat::GetLowestTemperatureLimit(int heater) const noexcept { - float limit = ABS_ZERO; - if (heater >= 0 && heater < (int)MaxHeaters) - { - for (const HeaterProtection *prot : heaterProtections) - { - if (prot->GetHeater() == heater && prot->GetTrigger() == HeaterProtectionTrigger::TemperatureTooLow) - { - const float t = prot->GetTemperatureLimit(); - if (limit == ABS_ZERO || t < limit) - { - limit = t; - } - } - } - } - return limit; + const auto h = FindHeater(heater); + return (h.IsNull()) ? ABS_ZERO : h->GetLowestTemperatureLimit(); } // Get the current temperature of a real or virtual heater @@ -831,14 +737,15 @@ bool Heat::IsBedOrChamberHeater(int heater) const noexcept float Heat::GetHighestTemperatureLimit() const noexcept { float limit = ABS_ZERO; - for (HeaterProtection *prot : heaterProtections) + ReadLocker lock(heatersLock); + for (const Heater *h : heaters) { - if (prot->GetHeater() >= 0 && prot->GetTrigger() == HeaterProtectionTrigger::TemperatureExceeded) + if (h != nullptr) { - const float t = prot->GetTemperatureLimit(); - if (t > limit) + const float tlimit = h->GetHighestTemperatureLimit(); + if (tlimit > limit) { - limit = t; + limit = tlimit; } } } @@ -903,11 +810,7 @@ GCodeResult Heat::TuneHeater(GCodeBuffer& gb, const StringRef& reply) THROWS(GCo gb.MustSee('S'); const float temperature = gb.GetFValue(); const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : h->GetModel().GetMaxPwm(); - if (!h->CheckGood()) - { - reply.copy("Heater is not ready to perform PID auto-tuning"); - } - else if (maxPwm < 0.1 || maxPwm > 1.0) + if (maxPwm < 0.1 || maxPwm > 1.0) { reply.copy("Invalid PWM value"); } @@ -916,8 +819,7 @@ GCodeResult Heat::TuneHeater(GCodeBuffer& gb, const StringRef& reply) THROWS(GCo if (heaterBeingTuned == -1) { heaterBeingTuned = (int8_t)heater; - h->StartAutoTune(temperature, maxPwm, reply); - return GCodeResult::ok; + return h->StartAutoTune(temperature, maxPwm, reply); } else { @@ -1042,164 +944,17 @@ const char *Heat::GetHeaterSensorName(size_t heater) const noexcept } // Configure heater protection (M143). Returns true if an error occurred -GCodeResult Heat::SetHeaterProtection(GCodeBuffer& gb, const StringRef& reply) +GCodeResult Heat::HandleM143(GCodeBuffer& gb, const StringRef& reply) { - WriteLocker lock(heatersLock); - - bool seen = false; - int32_t heaterNumber = 1; // default to extruder 1 if no heater number provided - gb.TryGetIValue('H', heaterNumber, seen); - const int index = (gb.Seen('P')) ? gb.GetIValue() : heaterNumber; - - if ( index < 0 - || (index >= (int)MaxHeaters && index < (int)FirstExtraHeaterProtection) - || index >= (int)(FirstExtraHeaterProtection + MaxExtraHeaterProtections) - ) + const size_t heaterNumber = (gb.Seen('H')) ? gb.GetLimitedUIValue('H', MaxHeaters) : 1; + const auto h = FindHeater(heaterNumber); + if (h.IsNull()) { - reply.printf("Invalid heater protection item '%d'", index); + reply.printf("Heater %u does not exist", heaterNumber); return GCodeResult::error; } - HeaterProtection &item = (index >= (int)FirstExtraHeaterProtection) - ? *heaterProtections[index - FirstExtraHeaterProtection + MaxHeaters] - : *heaterProtections[index]; - // Set heater to control - if (seen && heaterNumber != item.GetHeater()) - { - const int oldHeaterNumber = item.GetHeater(); - item.SetHeater(heaterNumber); - UpdateHeaterProtection(oldHeaterNumber); - UpdateHeaterProtection(heaterNumber); - } - - // Set sensor that supervises the heater - if (gb.Seen('X')) - { - item.SetSensorNumber(gb.GetIValue()); - seen = true; - } - - // Set trigger action - if (gb.Seen('A')) - { - const int action = gb.GetIValue(); - if (action < 0 || action > (int)MaxHeaterProtectionAction) - { - reply.printf("Invalid heater protection action '%d'", action); - } - - seen = true; - item.SetAction(static_cast<HeaterProtectionAction>(action)); - } - - // Set trigger condition - if (gb.Seen('C')) - { - const int trigger = gb.GetIValue(); - if (trigger < 0 || trigger > (int)MaxHeaterProtectionTrigger) - { - reply.printf("Invalid heater protection trigger '%d'", trigger); - } - - seen = true; - item.SetTrigger(static_cast<HeaterProtectionTrigger>(trigger)); - } - - // Set temperature limit - if (gb.Seen('S')) - { - const float limit = gb.GetFValue(); - if (limit <= BadLowTemperature || limit >= BadErrorTemperature) - { - reply.copy("Invalid temperature limit"); - return GCodeResult::error; - } - - seen = true; - item.SetTemperatureLimit(limit); - } - - // Report current parameters - if (!seen) - { - if (item.GetHeater() < 0) - { - reply.printf("Temperature protection item %d is not configured", index); - } - else - { - const char *actionString, *triggerString; - switch (item.GetAction()) - { - case HeaterProtectionAction::GenerateFault: - actionString = "generate a heater fault"; - break; - case HeaterProtectionAction::PermanentSwitchOff: - actionString = "permanently switch off"; - break; - case HeaterProtectionAction::TemporarySwitchOff: - actionString = "temporarily switch off"; - break; - default: - actionString = "(undefined)"; - break; - } - - switch (item.GetTrigger()) - { - case HeaterProtectionTrigger::TemperatureExceeded: - triggerString = "exceeds"; - break; - case HeaterProtectionTrigger::TemperatureTooLow: - triggerString = "falls below"; - break; - default: - triggerString = "(undefined)"; - break; - } - - reply.printf("Temperature protection item %d is configured for heater %d and uses sensor %d to %s if the temperature %s %.1f" DEGREE_SYMBOL "C", - index, item.GetHeater(), item.GetSensorNumber(), actionString, triggerString, (double)item.GetTemperatureLimit()); - } - } - - return GCodeResult::ok; -} - -// Updates the PIDs and HeaterProtection items after a heater change. Caller must already have a write lock on the heaters. -void Heat::UpdateHeaterProtection(int heaterNumber) noexcept -{ - auto h = FindHeater(heaterNumber); - if (h.IsNotNull()) - { - // Rebuild linked lists - h->SetHeaterProtection(nullptr); - HeaterProtection *firstProtectionItem = nullptr; - HeaterProtection *lastElementInList = nullptr; - for (HeaterProtection *prot : heaterProtections) - { - if (prot->GetHeater() == heaterNumber) - { - if (firstProtectionItem == nullptr) - { - firstProtectionItem = prot; - prot->SetNext(nullptr); - } - else if (lastElementInList == nullptr) - { - firstProtectionItem->SetNext(prot); - lastElementInList = prot; - } - else - { - lastElementInList->SetNext(prot); - lastElementInList = prot; - } - } - } - - h->SetHeaterProtection(firstProtectionItem); - } + return h->ConfigureMonitor(gb, reply); } // Get the temperature of a sensor diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index 6878bb8e..3789a721 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -34,7 +34,7 @@ Licence: GPL #include <RTOSIface/RTOSIface.h> class TemperatureSensor; -class HeaterProtection; +class HeaterMonitor; class GCodeBuffer; class CanMessageSensorTemperatures; class CanMessageHeatersStatus; @@ -43,6 +43,7 @@ class Heat INHERIT_OBJECT_MODEL { public: Heat() noexcept; + Heat(const Heat&) = delete; // Methods that don't relate to a particular heater void HeaterTask() noexcept; @@ -79,9 +80,7 @@ public: GCodeResult TuneHeater(GCodeBuffer& gb, const StringRef& reply) THROWS_GCODE_EXCEPTION; GCodeResult ConfigureSensor(GCodeBuffer& gb, const StringRef& reply) THROWS_GCODE_EXCEPTION; // Create a sensor or change the parameters for an existing sensor GCodeResult SetPidParameters(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) THROWS_GCODE_EXCEPTION; // Set the P/I/D parameters for a heater - GCodeResult SetHeaterProtection(GCodeBuffer &gb, const StringRef &reply) THROWS_GCODE_EXCEPTION; // Configure heater protection (M143) - - void UpdateHeaterProtection(int heaterNumber) noexcept; // Updates the PIDs and HeaterProtection items when a heater is remapped + GCodeResult HandleM143(GCodeBuffer &gb, const StringRef &reply) THROWS_GCODE_EXCEPTION; // Configure heater protection (M143) void SensorsTask() noexcept; static void EnsureSensorsTask() noexcept; @@ -119,16 +118,16 @@ public: float GetStandbyTemperature(int heater) const noexcept; float GetHighestTemperatureLimit(int heater) const noexcept; float GetLowestTemperatureLimit(int heater) const noexcept; - float GetHeaterTemperature(int heater) const noexcept; // Get the current temperature of a heater float GetTargetTemperature(int heater) const noexcept; // Get the target temperature + float GetHeaterTemperature(int heater) const noexcept; // Get the current temperature of a heater HeaterStatus GetStatus(int heater) const noexcept; // Get the off/standby/active status bool HeaterAtSetTemperature(int heater, bool waitWhenCooling, float tolerance) const noexcept; GCodeResult ConfigureHeater(size_t heater, GCodeBuffer& gb, const StringRef& reply); GCodeResult ConfigureHeaterMonitoring(size_t heater, GCodeBuffer& gb, const StringRef& reply); - void SetActiveTemperature(int heater, float t) noexcept; - void SetStandbyTemperature(int heater, float t) noexcept; + void SetActiveTemperature(int heater, float t) THROWS(GCodeException) { SetTemperature(heater, t, true); } + void SetStandbyTemperature(int heater, float t) THROWS(GCodeException) { SetTemperature(heater, t, false); } 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 @@ -143,24 +142,22 @@ public: void ProcessRemoteHeatersReport(CanAddress src, const CanMessageHeatersStatus& msg) noexcept; #endif + static ReadWriteLock sensorsLock; // needs to be public so that the OMT in EndstopsManager can lock it + protected: DECLARE_OBJECT_MODEL OBJECT_MODEL_ARRAY(heaters) - OBJECT_MODEL_ARRAY(sensors) private: - Heat(const Heat&) = delete; // Private copy constructor to prevent copying - ReadLockedPointer<Heater> FindHeater(int heater) const noexcept; void DeleteSensor(unsigned int sn) noexcept; void InsertSensor(TemperatureSensor *newSensor) noexcept; + void SetTemperature(int heater, float t, bool activeNotStandby) THROWS(GCodeException); static ReadWriteLock heatersLock; - static ReadWriteLock sensorsLock; uint8_t volatile sensorCount; TemperatureSensor * volatile sensorsRoot; // The sensor list - HeaterProtection *heaterProtections[MaxHeaters + MaxExtraHeaterProtections]; // Heater protection instances to guarantee legal heater temperature ranges Heater* heaters[MaxHeaters]; // A local or remote heater const Tool* lastStandbyTools[MaxHeaters]; // The last tool that caused the corresponding heater to be set to standby diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp index a3a64add..24b10302 100644 --- a/src/Heating/Heater.cpp +++ b/src/Heating/Heater.cpp @@ -9,8 +9,10 @@ #include "RepRap.h" #include "Platform.h" #include "Heat.h" -#include "HeaterProtection.h" +#include "HeaterMonitor.h" #include "Sensors/TemperatureSensor.h" +#include <GCodes/GCodeBuffer/GCodeBuffer.h> +#include <GCodes/GCodeException.h> #if SUPPORT_OBJECT_MODEL @@ -41,19 +43,100 @@ DEFINE_GET_OBJECT_MODEL_TABLE(Heater) Heater::Heater(unsigned int num) noexcept : heaterNumber(num), sensorNumber(-1), activeTemperature(0.0), standbyTemperature(0.0), maxTempExcursion(DefaultMaxTempExcursion), maxHeatingFaultTime(DefaultMaxHeatingFaultTime), - heaterProtection(nullptr), active(false) + active(false) { } Heater::~Heater() noexcept { + for (HeaterMonitor& h : monitors) + { + h.Disable(); + } +} + +void Heater::SetSensorNumber(int sn) noexcept +{ + if (sn != sensorNumber) + { + sensorNumber = sn; + SetDefaultMonitors(); + } +} + +void Heater::SetDefaultMonitors() noexcept +{ + for (HeaterMonitor& h : monitors) + { + h.Disable(); + } + + if (sensorNumber >= 0 && sensorNumber < (int)MaxSensors) + { + const float limit = (reprap.GetHeat().IsBedOrChamberHeater(heaterNumber)) ? DefaultBedTemperatureLimit : DefaultHotEndTemperatureLimit; + monitors[0].Set(sensorNumber, limit, HeaterMonitorAction::GenerateFault, HeaterMonitorTrigger::TemperatureExceeded); + } +} + +GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) noexcept +{ + bool seen = false; + float gain = model.GetGain(), + tc = model.GetTimeConstant(), + td = model.GetDeadTime(), + maxPwm = model.GetMaxPwm(), + voltage = model.GetVoltage(); + int32_t dontUsePid = model.UsePid() ? 0 : 1; + int32_t inversionParameter = 0; + + gb.TryGetFValue('A', gain, seen); + gb.TryGetFValue('C', tc, seen); + gb.TryGetFValue('D', td, seen); + gb.TryGetIValue('B', dontUsePid, seen); + gb.TryGetFValue('S', maxPwm, seen); + gb.TryGetFValue('V', voltage, seen); + gb.TryGetIValue('I', inversionParameter, seen); + + if (seen) + { + const bool inverseTemperatureControl = (inversionParameter == 1 || inversionParameter == 3); + const GCodeResult rslt = SetModel(gain, tc, td, maxPwm, voltage, dontUsePid == 0, inverseTemperatureControl, reply); + if (rslt != GCodeResult::ok) + { + return rslt; + } + } + else if (!model.IsEnabled()) + { + reply.printf("Heater %u is disabled", heater); + } + else + { + const char* const mode = (!model.UsePid()) ? "bang-bang" + : (model.ArePidParametersOverridden()) ? "custom PID" + : "PID"; + reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, calibration voltage %.1f, mode %s", heater, + (double)model.GetGain(), (double)model.GetTimeConstant(), (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode); + if (model.IsInverted()) + { + reply.cat(", inverted temperature control"); + } + if (model.UsePid()) + { + // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware + M301PidParameters params = model.GetM301PidParameters(false); + reply.catf("\nComputed PID parameters for setpoint change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD); + params = model.GetM301PidParameters(true); + reply.catf("\nComputed PID parameters for load change: P%.1f, I%.3f, D%.1f", (double)params.kP, (double)params.kI, (double)params.kD); + } + } + return GCodeResult::ok; } // Set the process model returning true if successful GCodeResult Heater::SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept { - const float temperatureLimit = GetHighestTemperatureLimit(); - const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, voltage, usePid, inverted); + const bool rslt = model.SetParameters(gain, tc, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted); if (rslt) { if (model.IsEnabled()) @@ -64,7 +147,7 @@ GCodeResult Heater::SetModel(float gain, float tc, float td, float maxPwm, float return rslt; } const float predictedMaxTemp = gain + NormalAmbientTemperature; - const float noWarnTemp = (temperatureLimit - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C + const float noWarnTemp = (GetHighestTemperatureLimit() - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C if (predictedMaxTemp > noWarnTemp) { reply.printf("heater %u appears to be over-powered. If left on at full power, its temperature is predicted to reach %dC.\n", @@ -90,6 +173,88 @@ GCodeResult Heater::SetFaultDetectionParameters(float pMaxTempExcursion, float p return UpdateFaultDetectionParameters(reply); } +GCodeResult Heater::ConfigureMonitor(GCodeBuffer &gb, const StringRef &reply) THROWS(GCodeException) +{ + // Get any parameters that have been provided + const bool seenP = gb.Seen('P'); + const size_t index = (seenP) ? gb.GetLimitedUIValue('P', MaxMonitorsPerHeater) : 0; + + const bool seenSensor = gb.Seen('T'); + const int monitoringSensor = (seenSensor) ? gb.GetLimitedUIValue('T', MaxSensors) : GetSensorNumber(); + + const bool seenAction = gb.Seen('A'); + const HeaterMonitorAction action = (seenAction) + ? static_cast<HeaterMonitorAction>(gb.GetLimitedUIValue('A', (unsigned int)MaxHeaterMonitorAction + 1)) + : HeaterMonitorAction::GenerateFault; + + const bool seenCondition = gb.Seen('C'); + const HeaterMonitorTrigger trigger = (seenCondition) + ? static_cast<HeaterMonitorTrigger>(gb.GetLimitedIValue('C', -1, (int)MaxHeaterMonitorTrigger)) + : HeaterMonitorTrigger::TemperatureExceeded; + + const bool seenLimit = gb.Seen('S'); + const float limit = (seenLimit) ? gb.GetFValue() : monitors[index].GetTemperatureLimit(); + if (limit <= BadLowTemperature || limit >= BadErrorTemperature) + { + reply.copy("Invalid temperature limit"); + return GCodeResult::error; + } + + if (seenSensor || seenLimit || seenAction || seenCondition) + { + monitors[index].Set(monitoringSensor, limit, action, trigger); + return UpdateHeaterMonitors(reply); + } + + // Else we are reporting on one or all of the monitors + if (seenP) + { + monitors[index].Report(heaterNumber, index, reply); + } + else + { + for (size_t i = 0; i < MaxMonitorsPerHeater; ++i) + { + monitors[i].Report(heaterNumber, i, reply); + } + } + return GCodeResult::ok; +} + +float Heater::GetHighestTemperatureLimit() const noexcept +{ + float limit = BadErrorTemperature; + for (const HeaterMonitor& prot : monitors) + { + if (prot.GetTrigger() == HeaterMonitorTrigger::TemperatureExceeded) + { + const float t = prot.GetTemperatureLimit(); + if (limit == BadErrorTemperature || t > limit) + { + limit = t; + } + } + } + return limit; +} + +float Heater::GetLowestTemperatureLimit() const noexcept +{ + float limit = ABS_ZERO; + for (const HeaterMonitor& prot : monitors) + { + if (prot.GetTrigger() == HeaterMonitorTrigger::TemperatureTooLow) + { + const float t = prot.GetTemperatureLimit(); + if (limit == ABS_ZERO || t < limit) + { + limit = t; + } + } + } + return limit; +} + HeaterStatus Heater::GetStatus() const noexcept { const HeaterMode mode = GetMode(); @@ -128,19 +293,19 @@ void Heater::Standby() noexcept } } -void Heater::SetActiveTemperature(float t) noexcept +void Heater::SetTemperature(float t, bool activeNotStandby) THROWS(GCodeException) { if (t > GetHighestTemperatureLimit()) { - reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %u\n", (double)t, GetHeaterNumber()); + throw GCodeException(-1, -1, "Temperature too high for heater %" PRIu32, (uint32_t)GetHeaterNumber()); } else if (t < GetLowestTemperatureLimit()) { - reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %u\n", (double)t, GetHeaterNumber()); + throw GCodeException(-1, -1, "Temperature too low for heater %" PRIu32, (uint32_t)GetHeaterNumber()); } else { - activeTemperature = t; + ((activeNotStandby) ? activeTemperature : standbyTemperature) = t; if (GetMode() > HeaterMode::suspended && active) { String<1> dummy; @@ -149,66 +314,9 @@ void Heater::SetActiveTemperature(float t) noexcept } } -void Heater::SetStandbyTemperature(float t) noexcept -{ - if (t > GetHighestTemperatureLimit()) - { - reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too high for heater %u\n", (double)t, GetHeaterNumber()); - } - else if (t < GetLowestTemperatureLimit()) - { - reprap.GetPlatform().MessageF(ErrorMessage, "Temperature %.1f" DEGREE_SYMBOL "C too low for heater %u\n", (double)t, GetHeaterNumber()); - } - else - { - standbyTemperature = t; - if (GetMode() > HeaterMode::suspended && !active) - { - String<1> dummy; - (void)SwitchOn(dummy.GetRef()); - } - } -} - -// Get the highest temperature limit -float Heater::GetHighestTemperatureLimit() const noexcept -{ - return reprap.GetHeat().GetHighestTemperatureLimit(GetHeaterNumber()); -} - -// Get the lowest temperature limit -float Heater::GetLowestTemperatureLimit() const noexcept -{ - return reprap.GetHeat().GetLowestTemperatureLimit(GetHeaterNumber()); -} - -void Heater::SetHeaterProtection(HeaterProtection *h) noexcept -{ - heaterProtection = h; -} - -// Check heater protection elements and return true if everything is good -bool Heater::CheckProtection() const noexcept -{ - for (HeaterProtection *prot = heaterProtection; prot != nullptr; prot = prot->Next()) - { - if (!prot->Check()) - { - // Something is not right - return false; - } - } - return true; -} - -bool Heater::CheckGood() const noexcept -{ - return GetMode() != HeaterMode::fault && CheckProtection(); -} - void Heater::SetModelDefaults() noexcept { - if (reprap.GetHeat().IsBedOrChamberHeater(GetHeaterNumber())) + if (reprap.GetHeat().IsBedOrChamberHeater(heaterNumber)) { model.SetParameters(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, DefaultBedTemperatureLimit, 0.0, false, false); } diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h index c237195e..5108d548 100644 --- a/src/Heating/Heater.h +++ b/src/Heating/Heater.h @@ -11,6 +11,7 @@ #include <RepRapFirmware.h> #include <NamedEnum.h> #include "FOPDT.h" +#include "HeaterMonitor.h" #include "GCodes/GCodeResult.h" #include <ObjectModel/ObjectModel.h> @@ -18,7 +19,7 @@ # include "CanId.h" #endif -class HeaterProtection; +class HeaterMonitor; struct CanHeaterReport; // Enumeration to describe the status of a heater. Note that the web interface returns the numerical values, so don't change them. @@ -29,6 +30,7 @@ class Heater INHERIT_OBJECT_MODEL public: Heater(unsigned int num) noexcept; virtual ~Heater() noexcept; + Heater(const Heater&) = delete; // Configuration methods virtual GCodeResult ConfigurePortAndSensor(const char *portName, PwmFrequency freq, unsigned int sensorNumber, const StringRef& reply) = 0; @@ -40,36 +42,35 @@ 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 StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept = 0; // Start an auto tune cycle for this PID + virtual GCodeResult StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept = 0; // Start an auto tune cycle for this PID virtual void GetAutoTuneStatus(const StringRef& reply) const = 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 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 #if SUPPORT_CAN_EXPANSION virtual void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept = 0; #endif - HeaterStatus GetStatus() const noexcept; // Get the status of the heater + HeaterStatus GetStatus() const noexcept; // Get the status of the heater unsigned int GetHeaterNumber() const noexcept { return heaterNumber; } const char *GetSensorName() const noexcept; // Get the name of the sensor for this heater, or nullptr if it hasn't been named - void SetActiveTemperature(float t) noexcept; + void SetTemperature(float t, bool activeNotStandby) THROWS(GCodeException); float GetActiveTemperature() const noexcept { return activeTemperature; } - void SetStandbyTemperature(float t) noexcept; 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 + void Standby() noexcept; // Switch from active to idle void GetFaultDetectionParameters(float& pMaxTempExcursion, float& pMaxFaultTime) const noexcept { pMaxTempExcursion = maxTempExcursion; pMaxFaultTime = maxHeatingFaultTime; } - GCodeResult SetFaultDetectionParameters(float pMaxTempExcursion, float pMaxFaultTime, const StringRef& reply) noexcept; - float GetHighestTemperatureLimit() const noexcept; // Get the highest temperature limit + GCodeResult ConfigureMonitor(GCodeBuffer &gb, const StringRef &reply) THROWS(GCodeException); + + float GetHighestTemperatureLimit() const noexcept; float GetLowestTemperatureLimit() const noexcept; // Get the lowest temperature limit - void SetHeaterProtection(HeaterProtection *h) noexcept; - const FopDt& GetModel() const noexcept { return model; } // Get the process model - GCodeResult SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model + const FopDt& GetModel() const noexcept { return model; } // Get the process model + GCodeResult SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const StringRef& reply) noexcept; void SetModelDefaults() noexcept; bool IsHeaterEnabled() const noexcept // Is this heater enabled? @@ -78,7 +79,7 @@ public: void SetM301PidParameters(const M301PidParameters& params) noexcept { model.SetM301PidParameters(params); } - bool CheckGood() const noexcept; + void SetDefaultMonitors() noexcept; protected: DECLARE_OBJECT_MODEL @@ -102,31 +103,31 @@ protected: lastTuningMode = tuning3 }; +protected: 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; int GetSensorNumber() const noexcept { return sensorNumber; } - void SetSensorNumber(int sn) noexcept { sensorNumber = sn; } + 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; } - HeaterProtection *GetHeaterProtections() const noexcept { return heaterProtection; } + GCodeResult SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model - FopDt model; + HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation private: - bool CheckProtection() const noexcept; // Check heater protection elements and return true if everything is good - + FopDt model; unsigned int heaterNumber; int sensorNumber; // the sensor number used by this heater float activeTemperature; // The required active temperature float standbyTemperature; // The required standby temperature float maxTempExcursion; // The maximum temperature excursion permitted while maintaining the setpoint float maxHeatingFaultTime; // How long a heater fault is permitted to persist before a heater fault is raised - HeaterProtection *heaterProtection; // The first element of assigned heater protection items bool active; // Are we active or standby? }; diff --git a/src/Heating/HeaterMonitor.cpp b/src/Heating/HeaterMonitor.cpp new file mode 100644 index 00000000..bb1874b3 --- /dev/null +++ b/src/Heating/HeaterMonitor.cpp @@ -0,0 +1,99 @@ +/* + * HeaterProtection.cpp + * + * Created on: 16 Nov 2017 + * Author: Christian + */ + +#include "HeaterMonitor.h" + +#include <Platform.h> +#include <RepRap.h> +#include "Heat.h" + +HeaterMonitor::HeaterMonitor() noexcept + : sensorNumber(-1), trigger(HeaterMonitorTrigger::Disabled), badTemperatureCount(0) +{ +} + +// Check if any action needs to be taken. Returns true if everything is OK +bool HeaterMonitor::Check() noexcept +{ + if (sensorNumber >= 0 && trigger != HeaterMonitorTrigger::Disabled) + { + TemperatureError err; + const float temperature = reprap.GetHeat().GetSensorTemperature(sensorNumber, err); + + if (err != TemperatureError::success) + { + badTemperatureCount++; + if (badTemperatureCount > MaxBadTemperatureCount) + { + reprap.GetPlatform().MessageF(ErrorMessage, "Temperature reading error on sensor %d\n", sensorNumber); + return false; + } + } + else + { + badTemperatureCount = 0; + switch (trigger) + { + case HeaterMonitorTrigger::TemperatureExceeded: + return (temperature <= limit); + + case HeaterMonitorTrigger::TemperatureTooLow: + return (temperature >= limit); + + default: + break; + } + } + } + return true; +} + +// Append a report of this monitor to the string +void HeaterMonitor::Report(unsigned int heater, unsigned int index, const StringRef& reply) const noexcept +{ + reply.lcatf("Heater %d monitor %d ", heater, index); + if (trigger == HeaterMonitorTrigger::Disabled) + { + reply.cat("is disabled"); + } + else + { + const char *actionString, *triggerString; + switch (action) + { + case HeaterMonitorAction::GenerateFault: + actionString = "generate a heater fault"; + break; + case HeaterMonitorAction::PermanentSwitchOff: + actionString = "permanently switch off"; + break; + case HeaterMonitorAction::TemporarySwitchOff: + actionString = "temporarily switch off"; + break; + default: + actionString = "(undefined)"; + break; + } + + switch (trigger) + { + case HeaterMonitorTrigger::TemperatureExceeded: + triggerString = "exceeds"; + break; + case HeaterMonitorTrigger::TemperatureTooLow: + triggerString = "falls below"; + break; + default: + triggerString = "(undefined)"; + break; + } + + reply.catf("uses sensor %d to %s if the reading %s %.1f" DEGREE_SYMBOL "C", sensorNumber, actionString, triggerString, (double)limit); + } +} + +// End diff --git a/src/Heating/HeaterMonitor.h b/src/Heating/HeaterMonitor.h new file mode 100644 index 00000000..42bdac38 --- /dev/null +++ b/src/Heating/HeaterMonitor.h @@ -0,0 +1,81 @@ +/* + * HeaterMonitor.h + * + * Created on: 16 Nov 2017 + * Author: Christian + */ + +#ifndef HEATERMONITOR_H +#define HEATERMONITOR_H + +#include <RepRapFirmware.h> +#include <General/FreelistManager.h> + +// Condition of a heater monitor event +enum class HeaterMonitorTrigger : int8_t +{ + Disabled = -1, + TemperatureExceeded = 0, + TemperatureTooLow +}; + +const HeaterMonitorTrigger MaxHeaterMonitorTrigger = HeaterMonitorTrigger::TemperatureTooLow; + +// The action to trigger when the target condition is met +enum class HeaterMonitorAction : uint8_t +{ + GenerateFault = 0, + PermanentSwitchOff, + TemporarySwitchOff +}; + +const HeaterMonitorAction MaxHeaterMonitorAction = HeaterMonitorAction::TemporarySwitchOff; + +// A note about using this class. Its size is currently 8 bytes, and will be 12 bytes of object model support is added. +// - If we allocate them statically within the heater object, then assuming 3 per heater we need 24 bytes, or 36 bytes with OM support. +// - If we allocate them dynamically then there is an overhead of at least 8 bytes per object. All heaters have at least 2. +// So each heater needs 12 bytes for the pointers plus 32 bytes (without OM support) or 40 bytes (with OM support). Total 44 or 52 bytes. +// For unconfigured heaters, the cost is 24 or 36 bytes each using static allocation, or 4 bytes each using dynamic allocation. +// Summary: +// - Static allocation saves 20 bytes (no OM) or 16 bytes (with OM) per configured heater, if the 3rd heater monitor is not used (and more if it is used) +// - Dynamic allocation saves 20 bytes (no OM) or 32 bytes (with OM) per unconfigured heater +// For now we use static allocation, i.e. we embed the heater monitor in the heater object. +class HeaterMonitor +{ +public: + HeaterMonitor() noexcept; + + void Set(int sn, float lim, HeaterMonitorAction act, HeaterMonitorTrigger trig) noexcept; + void Disable() noexcept; + bool Check() noexcept; // Check if any action needs to be taken + + int GetSensorNumber() const noexcept { return sensorNumber; } // Get the supervisory sensor number + float GetTemperatureLimit() const noexcept { return limit; } // Get the temperature limit + HeaterMonitorAction GetAction() const noexcept { return action; } // Get the action to trigger when a temperature event occurs + HeaterMonitorTrigger GetTrigger() const noexcept { return trigger; } // Get the condition for a temperature event + + void Report(unsigned int heater, unsigned int index, const StringRef& reply) const noexcept; // Append a report of this monitor to the string + +private: + float limit; // temperature limit + int8_t sensorNumber; // the sensor that we use to monitor the heater + HeaterMonitorAction action; // what action we take of we detect a fault + HeaterMonitorTrigger trigger; // what is treated a fault + uint8_t badTemperatureCount; // how many consecutive sensor reading faults we have had +}; + +inline void HeaterMonitor::Set(int sn, float lim, HeaterMonitorAction act, HeaterMonitorTrigger trig) noexcept +{ + sensorNumber = sn; + limit = lim; + action = act; + trigger = trig; + badTemperatureCount = 0; +} + +inline void HeaterMonitor::Disable() noexcept +{ + trigger = HeaterMonitorTrigger::Disabled; +} + +#endif diff --git a/src/Heating/HeaterProtection.cpp b/src/Heating/HeaterProtection.cpp deleted file mode 100644 index f13911d0..00000000 --- a/src/Heating/HeaterProtection.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * HeaterProtection.cpp - * - * Created on: 16 Nov 2017 - * Author: Christian - */ - -#include "HeaterProtection.h" - -#include "Platform.h" -#include "RepRap.h" -#include "Heat.h" - - -HeaterProtection::HeaterProtection(size_t index) noexcept : next(nullptr) -{ - // By default each heater protection element is mapped to its corresponding heater. - // All other heater protection elements are unused and can be optionally assigned. - heater = (index >= MaxHeaters) ? -1 : (int8_t)index; - sensorNumber = -1; -} - -void HeaterProtection::Init(float tempLimit) noexcept -{ - next = nullptr; - limit = tempLimit; - action = HeaterProtectionAction::GenerateFault; - trigger = HeaterProtectionTrigger::TemperatureExceeded; - - badTemperatureCount = 0; -} - -// Check if any action needs to be taken. Returns true if everything is OK -bool HeaterProtection::Check() noexcept -{ - if (sensorNumber >= 0) - { - TemperatureError err; - const float temperature = reprap.GetHeat().GetSensorTemperature(sensorNumber, err); - - if (err != TemperatureError::success) - { - badTemperatureCount++; - if (badTemperatureCount > MaxBadTemperatureCount) - { - reprap.GetPlatform().MessageF(ErrorMessage, "Temperature reading error on sensor %d\n", sensorNumber); - return false; - } - } - else - { - badTemperatureCount = 0; - switch (trigger) - { - case HeaterProtectionTrigger::TemperatureExceeded: - return (temperature <= limit); - - case HeaterProtectionTrigger::TemperatureTooLow: - return (temperature >= limit); - } - } - } - return true; -} - -void HeaterProtection::SetHeater(int newHeater) noexcept -{ - heater = newHeater; -} - -// End diff --git a/src/Heating/HeaterProtection.h b/src/Heating/HeaterProtection.h deleted file mode 100644 index 19f5df36..00000000 --- a/src/Heating/HeaterProtection.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * HeaterProtection.h - * - * Created on: 16 Nov 2017 - * Author: Christian - */ - -#ifndef HEATERPROTECTION_H -#define HEATERPROTECTION_H - -#include "RepRapFirmware.h" - - -// Condition of a heater protection event -enum class HeaterProtectionTrigger : uint8_t -{ - TemperatureExceeded, - TemperatureTooLow -}; - -const HeaterProtectionTrigger MaxHeaterProtectionTrigger = HeaterProtectionTrigger::TemperatureTooLow; - -// The action to trigger when the target condition is met -enum class HeaterProtectionAction : uint8_t -{ - GenerateFault = 0, - PermanentSwitchOff, - TemporarySwitchOff -}; - -const HeaterProtectionAction MaxHeaterProtectionAction = HeaterProtectionAction::TemporarySwitchOff; - - -class Heat; - -class HeaterProtection -{ -public: - friend class Heat; - - HeaterProtection(size_t index) noexcept; - void Init(float tempLimit) noexcept; - - HeaterProtection *Next() const noexcept { return next; } - void SetNext(HeaterProtection *n) noexcept { next = n; } - - bool Check() noexcept; // Check if any action needs to be taken - - int GetHeater() const noexcept { return heater; } - void SetHeater(int newHeater) noexcept; // Set the heater to control - - int GetSensorNumber() const noexcept { return sensorNumber; } // Get the supervisory sensor number - void SetSensorNumber(int sn) noexcept; // Set the supervisory sensor number - - float GetTemperatureLimit() const noexcept { return limit; } // Get the temperature limit - void SetTemperatureLimit(float newLimit) noexcept; // Set the temperature limit - - HeaterProtectionAction GetAction() const noexcept { return action; } // Get the action to trigger when a temperature event occurs - void SetAction(HeaterProtectionAction newAction) noexcept; // Set the action to trigger when a temperature event occurs - - HeaterProtectionTrigger GetTrigger() const noexcept { return trigger; } // Get the condition for a temperature event - void SetTrigger(HeaterProtectionTrigger newTrigger) noexcept; // Set the condition for a temperature event - -private: - HeaterProtection *next; // link to next HeaterProtection item for the same heater - - float limit; // temperature limit - int heater; // number of the heater we are protecting - int sensorNumber; // the sensor that we use to monitor the heater - HeaterProtectionAction action; // what action we take of we detect a fault - HeaterProtectionTrigger trigger; // what is treated a fault - - size_t badTemperatureCount; // how many consecutive sensor reading faults we have had -}; - -inline void HeaterProtection::SetSensorNumber(int sn) noexcept -{ - sensorNumber = sn; -} - -inline void HeaterProtection::SetTemperatureLimit(float newLimit) noexcept -{ - limit = newLimit; -} - -inline void HeaterProtection::SetAction(HeaterProtectionAction newAction) noexcept -{ - action = newAction; -} - -inline void HeaterProtection::SetTrigger(HeaterProtectionTrigger newTrigger) noexcept -{ - trigger = newTrigger; -} - -#endif diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp index 5cb822e4..f4429f87 100644 --- a/src/Heating/LocalHeater.cpp +++ b/src/Heating/LocalHeater.cpp @@ -9,7 +9,7 @@ #include "GCodes/GCodes.h" #include "GCodes/GCodeBuffer/GCodeBuffer.h" #include "Heat.h" -#include "HeaterProtection.h" +#include "HeaterMonitor.h" #include "Platform.h" #include "RepRap.h" @@ -395,24 +395,24 @@ void LocalHeater::Spin() noexcept } // Verify that everything is operating in the required temperature range - for (HeaterProtection *prot = GetHeaterProtections(); prot != nullptr; prot = prot->Next()) + for (HeaterMonitor& prot : monitors) { - if (!prot->Check()) + if (!prot.Check()) { lastPwm = 0.0; - switch (prot->GetAction()) + switch (prot.GetAction()) { - case HeaterProtectionAction::GenerateFault: + case HeaterMonitorAction::GenerateFault: mode = HeaterMode::fault; reprap.GetGCodes().HandleHeaterFault(GetHeaterNumber()); - reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %u\n", GetHeaterNumber()); + reprap.GetPlatform().MessageF(ErrorMessage, "Heating fault on heater %u: heater monitor was triggered\n", GetHeaterNumber()); break; - case HeaterProtectionAction::TemporarySwitchOff: + case HeaterMonitorAction::TemporarySwitchOff: // Do nothing, the PWM value has already been set above break; - case HeaterProtectionAction::PermanentSwitchOff: + case HeaterMonitorAction::PermanentSwitchOff: SwitchOff(); break; } @@ -474,41 +474,49 @@ float LocalHeater::GetExpectedHeatingRate() const noexcept } // Auto tune this PID -void LocalHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept +GCodeResult LocalHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept { // Starting an auto tune if (!GetModel().IsEnabled()) { - reply.printf("Error: heater %u cannot be auto tuned while it is disabled", GetHeaterNumber()); + reply.printf("heater %u cannot be auto tuned while it is disabled", GetHeaterNumber()); + return GCodeResult::error; } - else if (lastPwm > 0.0 || GetAveragePWM() > 0.02) + + if (lastPwm > 0.0 || GetAveragePWM() > 0.02) { - reply.printf("Error: heater %u must be off and cold before auto tuning it", GetHeaterNumber()); + reply.printf("heater %u must be off and cold before auto tuning it", GetHeaterNumber()); + return GCodeResult::error; } - else + + const float limit = GetHighestTemperatureLimit(); + if (targetTemp > limit) { - const TemperatureError err = ReadTemperature(); - if (err != TemperatureError::success) - { - reply.printf("Error: heater %u reported error '%s' at start of auto tuning", GetHeaterNumber(), TemperatureErrorString(err)); - } - else - { - mode = HeaterMode::tuning0; - tuningReadingsTaken = 0; - tuned = false; // assume failure - - // We don't normally allow dynamic memory allocation when running. However, auto tuning is rarely done and it - // would be wasteful to allocate a permanent array just in case we are going to run it, so we make an exception here. - tuningTempReadings = new float[MaxTuningTempReadings]; - tuningTempReadings[0] = temperature; - tuningReadingInterval = HeatSampleIntervalMillis; - tuningPwm = maxPwm; - tuningTargetTemp = targetTemp; - 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); - } + reply.printf("heater %u target temperature must be no hiher than 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; } + + mode = HeaterMode::tuning0; + tuningReadingsTaken = 0; + tuned = false; // assume failure + + // We don't normally allow dynamic memory allocation when running. However, auto tuning is rarely done and it + // would be wasteful to allocate a permanent array just in case we are going to run it, so we make an exception here. + tuningTempReadings = new float[MaxTuningTempReadings]; + tuningTempReadings[0] = temperature; + tuningReadingInterval = HeatSampleIntervalMillis; + tuningPwm = maxPwm; + tuningTargetTemp = targetTemp; + 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 GCodeResult::ok; } // Get the auto tune status or last result diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h index 3ad48f5a..c4e283ef 100644 --- a/src/Heating/LocalHeater.h +++ b/src/Heating/LocalHeater.h @@ -18,7 +18,7 @@ #include "Hardware/IoPorts.h" #include "GCodes/GCodeResult.h" -class HeaterProtection; +class HeaterMonitor; class LocalHeater : public Heater { @@ -38,7 +38,7 @@ public: 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 - void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept override; // Start an auto tune cycle for this PID + GCodeResult StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept override; // Start an auto tune cycle for this PID 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 @@ -52,10 +52,11 @@ protected: 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; } 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 + TemperatureError ReadTemperature() noexcept; // Read and store the temperature of this heater void DoTuningStep() noexcept; // Called on each temperature sample when auto tuning static bool ReadingsStable(size_t numReadings, float maxDiff) noexcept pre(numReadings >= 2; numReadings <= MaxTuningTempReadings); @@ -65,27 +66,27 @@ private: void DisplayBuffer(const char *intro) noexcept; // Debug helper float GetExpectedHeatingRate() const noexcept; // Get the minimum heating rate we expect - PwmPort port; // The port that drives the heater - float temperature; // The current temperature - float previousTemperatures[NumPreviousTemperatures]; // The temperatures of the previous NumDerivativeSamples measurements, used for calculating the derivative - size_t previousTemperatureIndex; // Which slot in previousTemperature we fill in next - float iAccumulator; // The integral LocalHeater component - float lastPwm; // The last PWM value we output, before scaling by kS - float averagePWM; // The running average of the PWM, after scaling. - uint32_t timeSetHeating; // When we turned on the heater - uint32_t lastSampleTime; // Time when the temperature was last sampled by Spin() + PwmPort port; // The port that drives the heater + float temperature; // The current temperature + float previousTemperatures[NumPreviousTemperatures]; // The temperatures of the previous NumDerivativeSamples measurements, used for calculating the derivative + size_t previousTemperatureIndex; // Which slot in previousTemperature we fill in next + float iAccumulator; // The integral LocalHeater component + float lastPwm; // The last PWM value we output, before scaling by kS + float averagePWM; // The running average of the PWM, after scaling. + uint32_t timeSetHeating; // When we turned on the heater + uint32_t lastSampleTime; // Time when the temperature was last sampled by Spin() - uint16_t heatingFaultCount; // Count of questionable heating behaviours + uint16_t heatingFaultCount; // Count of questionable heating behaviours - 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 + 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"); // Variables used during heater tuning - static const size_t MaxTuningTempReadings = 128; // The maximum number of readings we keep. Must be an even number. + static const size_t MaxTuningTempReadings = 128; // The maximum number of readings we keep. Must be an even number. static float *tuningTempReadings; // the readings from the heater being tuned static float tuningStartTemp; // the temperature when we turned on the heater diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp index ace55336..ba00d3e2 100644 --- a/src/Heating/RemoteHeater.cpp +++ b/src/Heating/RemoteHeater.cpp @@ -128,14 +128,15 @@ float RemoteHeater::GetAccumulator() const noexcept return 0.0; // not supported } -void RemoteHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept +GCodeResult RemoteHeater::StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept { - //TODO + reply.copy("remote heater auto tune not implemented"); + return GCodeResult::error; } void RemoteHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept { - //TODO + reply.copy("remote heater auto tune not implemented"); } void RemoteHeater::Suspend(bool sus) noexcept @@ -189,7 +190,7 @@ GCodeResult RemoteHeater::UpdateModel(const StringRef& reply) noexcept { const CanRequestId rid = CanInterface::AllocateRequestId(boardAddress); CanMessageUpdateHeaterModel * const msg = buf->SetupRequestMessage<CanMessageUpdateHeaterModel>(rid, CanInterface::GetCanAddress(), boardAddress); - model.SetupCanMessage(GetHeaterNumber(), *msg); + GetModel().SetupCanMessage(GetHeaterNumber(), *msg); return CanInterface::SendRequestAndGetStandardReply(buf, rid, reply); } @@ -214,6 +215,29 @@ GCodeResult RemoteHeater::UpdateFaultDetectionParameters(const StringRef& reply) return GCodeResult::error; } +GCodeResult RemoteHeater::UpdateHeaterMonitors(const StringRef& reply) noexcept +{ + CanMessageBuffer *buf = CanMessageBuffer::Allocate(); + if (buf != nullptr) + { + const CanRequestId rid = CanInterface::AllocateRequestId(boardAddress); + CanMessageSetHeaterMonitors * const msg = buf->SetupRequestMessage<CanMessageSetHeaterMonitors>(rid, CanInterface::GetCanAddress(), boardAddress); + msg->heater = GetHeaterNumber(); + msg->numMonitors = MaxMonitorsPerHeater; + for (size_t i = 0; i < MaxMonitorsPerHeater; ++i) + { + msg->monitors[i].limit = monitors[i].GetTemperatureLimit(); + msg->monitors[i].sensor = monitors[i].GetSensorNumber(); + msg->monitors[i].action = (uint8_t)monitors[i].GetAction(); + msg->monitors[i].trigger = (int8_t)monitors[i].GetTrigger(); + } + return CanInterface::SendRequestAndGetStandardReply(buf, rid, reply); + } + + reply.copy("No CAN buffer"); + return GCodeResult::error; +} + void RemoteHeater::UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept { if (src == boardAddress) diff --git a/src/Heating/RemoteHeater.h b/src/Heating/RemoteHeater.h index dab1b70d..5801fe7e 100644 --- a/src/Heating/RemoteHeater.h +++ b/src/Heating/RemoteHeater.h @@ -28,7 +28,7 @@ public: 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 - void StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept override; // Start an auto tune cycle for this PID + GCodeResult StartAutoTune(float targetTemp, float maxPwm, const StringRef& reply) noexcept override; // Start an auto tune cycle for this PID 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 UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept override; @@ -39,6 +39,7 @@ protected: 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; private: static constexpr uint32_t RemoteStatusTimeout = 2000; diff --git a/src/Heating/Sensors/TemperatureSensor.h b/src/Heating/Sensors/TemperatureSensor.h index fc522974..7b561c4f 100644 --- a/src/Heating/Sensors/TemperatureSensor.h +++ b/src/Heating/Sensors/TemperatureSensor.h @@ -14,6 +14,7 @@ class TemperatureSensor INHERIT_OBJECT_MODEL { public: TemperatureSensor(unsigned int sensorNum, const char *type) noexcept; + TemperatureSensor(const TemperatureSensor&) = delete; // Virtual destructor virtual ~TemperatureSensor() noexcept; |