diff options
author | David Crocker <dcrocker@eschertech.com> | 2020-11-11 15:42:20 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2020-11-11 15:42:20 +0300 |
commit | 56cc6e24532e74919c29f42cc9ae4e7c564cdb0f (patch) | |
tree | 847b9b7f419ec65d49efc5598470bdc90a6dd2b8 | |
parent | d2e3056950eef02a5614ef8f0e3d34c06140bdb3 (diff) |
Implemented heater feedforward for fan RPM changes
-rw-r--r-- | src/Fans/FansManager.cpp | 25 | ||||
-rw-r--r-- | src/Fans/FansManager.h | 4 | ||||
-rw-r--r-- | src/GCodes/GCodes.cpp | 18 | ||||
-rw-r--r-- | src/Heating/FOPDT.cpp | 19 | ||||
-rw-r--r-- | src/Heating/FOPDT.h | 11 | ||||
-rw-r--r-- | src/Heating/Heat.cpp | 9 | ||||
-rw-r--r-- | src/Heating/Heat.h | 1 | ||||
-rw-r--r-- | src/Heating/Heater.cpp | 6 | ||||
-rw-r--r-- | src/Heating/Heater.h | 5 | ||||
-rw-r--r-- | src/Heating/LocalHeater.cpp | 10 | ||||
-rw-r--r-- | src/Heating/LocalHeater.h | 1 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.cpp | 13 | ||||
-rw-r--r-- | src/Heating/RemoteHeater.h | 1 | ||||
-rw-r--r-- | src/Tools/Tool.cpp | 9 | ||||
-rw-r--r-- | src/Tools/Tool.h | 2 | ||||
-rw-r--r-- | src/Version.h | 2 |
16 files changed, 100 insertions, 36 deletions
diff --git a/src/Fans/FansManager.cpp b/src/Fans/FansManager.cpp index 1b9f2c5f..cbadd375 100644 --- a/src/Fans/FansManager.cpp +++ b/src/Fans/FansManager.cpp @@ -182,15 +182,30 @@ GCodeResult FansManager::SetFanValue(size_t fanNum, float speed, const StringRef return GCodeResult::error; } -void FansManager::SetFanValue(size_t fanNum, float speed) noexcept +// Update the PWM of the specified fan, returning the PWM change +float FansManager::SetFanValue(size_t fanNum, float speed) noexcept { - String<1> dummy; - (void)SetFanValue(fanNum, speed, dummy.GetRef()); + auto fan = FindFan(fanNum); + if (fan.IsNotNull()) + { + const float oldPwm = fan->GetPwm(); + String<1> dummy; + (void)fan->SetPwm(speed, dummy.GetRef()); + return fan->GetPwm() - oldPwm; + } + return 0.0; } -void FansManager::SetFansValue(FansBitmap whichFans, float speed) noexcept +// Update the PWM of the specified fans, returning the total PWM change divided by the number of fans +float FansManager::SetFansValue(FansBitmap whichFans, float speed) noexcept { - whichFans.Iterate([speed, this](unsigned int i, unsigned int) noexcept { SetFanValue(i, speed); }); + float pwmChange = 0; + if (!whichFans.IsEmpty()) + { + whichFans.Iterate([speed, this, &pwmChange](unsigned int i, unsigned int) noexcept { pwmChange += SetFanValue(i, speed); }); + pwmChange /= whichFans.CountSetBits(); + } + return pwmChange; } // Check if the given fan can be controlled manually so that DWC can decide whether or not to show the corresponding fan diff --git a/src/Fans/FansManager.h b/src/Fans/FansManager.h index 85208625..a1fa0f8e 100644 --- a/src/Fans/FansManager.h +++ b/src/Fans/FansManager.h @@ -34,8 +34,8 @@ public: bool ConfigureFan(unsigned int mcode, size_t fanNum, GCodeBuffer& gb, const StringRef& reply, bool& error) THROWS(GCodeException); float GetFanValue(size_t fanNum) const noexcept; GCodeResult SetFanValue(size_t fanNum, float speed, const StringRef& reply) noexcept; - void SetFanValue(size_t fanNum, float speed) noexcept; - void SetFansValue(FansBitmap whichFans, float speed) noexcept; + float SetFanValue(size_t fanNum, float speed) noexcept; + float SetFansValue(FansBitmap whichFans, float speed) noexcept; bool IsFanControllable(size_t fanNum) const noexcept; const char *GetFanName(size_t fanNum) const noexcept; int32_t GetFanRPM(size_t fanNum) const noexcept; diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index 559c4708..2ee2617d 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -496,6 +496,7 @@ bool GCodes::SpinGCodeBuffer(GCodeBuffer& gb) noexcept { // Set up a buffer for the reply String<GCodeReplyLength> reply; + bool result; MutexLocker gbLock(gb.mutex); if (gb.GetState() == GCodeState::normal) @@ -503,7 +504,7 @@ bool GCodes::SpinGCodeBuffer(GCodeBuffer& gb) noexcept if (gb.MachineState().messageAcknowledged) { const bool wasCancelled = gb.MachineState().messageCancelled; - gb.PopState(false); // this could fail if the current macro has already been aborted + gb.PopState(false); // this could fail if the current macro has already been aborted if (wasCancelled) { @@ -516,20 +517,25 @@ bool GCodes::SpinGCodeBuffer(GCodeBuffer& gb) noexcept FileMacroCyclesReturn(gb); } } - return wasCancelled; + result = wasCancelled; + } + else + { + result = StartNextGCode(gb, reply.GetRef()); } - - return StartNextGCode(gb, reply.GetRef()); } else { RunStateMachine(gb, reply.GetRef()); // execute the state machine + result = true; // assume we did something useful (not necessarily true, e.g. could be waiting for movement to stop) } + if (gb.IsExecuting()) { CheckReportDue(gb, reply.GetRef()); } - return true; + + return result; } // Start a new gcode, or continue to execute one that has already been started. Return true if we found something significant to do. @@ -3409,7 +3415,7 @@ void GCodes::SetMappedFanSpeed(float f) noexcept } else { - reprap.GetFansManager().SetFansValue(ct->GetFanMapping(), f); + ct->SetFansPwm(f); } } diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp index bb62bb9a..4c5e81fd 100644 --- a/src/Heating/FOPDT.cpp +++ b/src/Heating/FOPDT.cpp @@ -38,7 +38,7 @@ constexpr ObjectModelTableEntry FopDt::objectModelTable[] = { "pid", OBJECT_MODEL_FUNC(self, 1), ObjectModelEntryFlags::none }, { "standardVoltage", OBJECT_MODEL_FUNC(self->standardVoltage, 1), ObjectModelEntryFlags::none }, { "timeConstant", OBJECT_MODEL_FUNC(self->GetTimeConstantFanOff(), 1), ObjectModelEntryFlags::none }, - { "timeConstantFanOn", OBJECT_MODEL_FUNC(self->GetTimeConstantFanOn(), 1), ObjectModelEntryFlags::none }, + { "timeConstantFansOn", OBJECT_MODEL_FUNC(self->GetTimeConstantFanOn(), 1), ObjectModelEntryFlags::none }, // 1. PID members { "d", OBJECT_MODEL_FUNC(self->loadChangeParams.tD * self->loadChangeParams.kP, 1), ObjectModelEntryFlags::none }, @@ -58,7 +58,7 @@ DEFINE_GET_OBJECT_MODEL_TABLE(FopDt) // Set up sensible defaults here in case the user enables the heater without specifying values for all the parameters. FopDt::FopDt() noexcept : heatingRate(DefaultHotEndHeaterHeatingRate), - coolingRateFanOff(DefaultHotEndHeaterCoolingRate), coolingRateFanOn(DefaultHotEndHeaterCoolingRate), + coolingRateFanOff(DefaultHotEndHeaterCoolingRate), coolingRateChangeFanOn(0.0), deadTime(DefaultHotEndHeaterDeadTime), maxPwm(1.0), standardVoltage(0.0), enabled(false), usePid(true), inverted(false), pidParametersOverridden(false) { @@ -80,7 +80,7 @@ bool FopDt::SetParameters(float phr, float pcrFanOff, float pcrFanOn, float pdt, { heatingRate = phr; coolingRateFanOff = pcrFanOff; - coolingRateFanOn = pcrFanOn; + coolingRateChangeFanOn = pcrFanOn - pcrFanOff; deadTime = pdt; maxPwm = pMaxPwm; standardVoltage = pVoltage; @@ -121,7 +121,7 @@ bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept { String<StringLength256> scratchString; scratchString.printf("M307 H%u R%.3f C%.3f:%.3f D%.2f S%.2f V%.1f B%d\n", - heater, (double)heatingRate, (double)coolingRateFanOff, (double)coolingRateFanOn, (double)deadTime, (double)maxPwm, (double)standardVoltage, (usePid) ? 0 : 1); + heater, (double)heatingRate, (double)GetTimeConstantFanOff(), (double)GetTimeConstantFanOn(), (double)deadTime, (double)maxPwm, (double)standardVoltage, (usePid) ? 0 : 1); bool ok = f->Write(scratchString.c_str()); if (ok && pidParametersOverridden) { @@ -166,7 +166,7 @@ bool FopDt::WriteParameters(FileStore *f, size_t heater) const noexcept void FopDt::CalcPidConstants() noexcept { - const float averageCoolingRate = (coolingRateFanOff + coolingRateFanOn) * 0.5; + const float averageCoolingRate = coolingRateFanOff + 0.5 * coolingRateChangeFanOn; loadChangeParams.kP = 0.7/(heatingRate * deadTime); loadChangeParams.recipTi = powf(averageCoolingRate, 0.25)/(1.14 * powf(deadTime, 0.75)); // Ti = 1.14 * timeConstant^0.25 * deadTime^0.75 (Ho et al) loadChangeParams.tD = deadTime * 0.7; @@ -180,11 +180,14 @@ void FopDt::CalcPidConstants() noexcept #if SUPPORT_CAN_EXPANSION -void FopDt::SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) const noexcept +void FopDt::SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModelNew& msg) const noexcept { msg.heater = heater; - msg.gain = gain; - msg.timeConstant = timeConstant; + msg.heatingRate = heatingRate; + msg.coolingRate = coolingRateFanOff; + msg.coolingRateChangeFanOn = coolingRateChangeFanOn; + msg.coolingRateChangeExtruding = 0.0; + msg.zero2 = 0.0; msg.deadTime = deadTime; msg.maxPwm = maxPwm; msg.standardVoltage = standardVoltage; diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h index 728b3c78..eab8d1fb 100644 --- a/src/Heating/FOPDT.h +++ b/src/Heating/FOPDT.h @@ -34,7 +34,7 @@ class FileStore; #endif #if SUPPORT_CAN_EXPANSION -struct CanMessageUpdateHeaterModel; +struct CanMessageUpdateHeaterModelNew; #endif class FopDt INHERIT_OBJECT_MODEL @@ -47,7 +47,8 @@ public: // Stored parameters float GetHeatingRate() const noexcept { return heatingRate; } float GetCoolingRateFanOff() const noexcept { return coolingRateFanOff; } - float GetCoolingRateFanOn() const noexcept { return coolingRateFanOn; } + float GetCoolingRateFanOn() const noexcept { return coolingRateFanOff + coolingRateChangeFanOn; } + float GetCoolingRateChangeFanOn() const noexcept { return coolingRateChangeFanOn; } float GetDeadTime() const noexcept { return deadTime; } float GetMaxPwm() const noexcept { return maxPwm; } float GetVoltage() const noexcept { return standardVoltage; } @@ -58,7 +59,7 @@ public: // Derived parameters float GetGainFanOff() const noexcept { return heatingRate/coolingRateFanOff; } float GetTimeConstantFanOff() const noexcept { return 1.0/coolingRateFanOff; } - float GetTimeConstantFanOn() const noexcept { return 1.0/coolingRateFanOn; } + float GetTimeConstantFanOn() const noexcept { return 1.0/GetCoolingRateFanOn(); } bool ArePidParametersOverridden() const noexcept { return pidParametersOverridden; } M301PidParameters GetM301PidParameters(bool forLoadChange) const noexcept; void SetM301PidParameters(const M301PidParameters& params) noexcept; @@ -73,7 +74,7 @@ public: #endif #if SUPPORT_CAN_EXPANSION - void SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModel& msg) const noexcept; + void SetupCanMessage(unsigned int heater, CanMessageUpdateHeaterModelNew& msg) const noexcept; #endif protected: @@ -84,7 +85,7 @@ private: float heatingRate; float coolingRateFanOff; - float coolingRateFanOn; + float coolingRateChangeFanOn; float deadTime; float maxPwm; float standardVoltage; // power voltage reading at which tuning was done, or 0 if unknown diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index bdd8bf1a..2a3d8e94 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -727,6 +727,15 @@ void Heat::Standby(int heater, const Tool *tool) noexcept } } +void Heat::PrintCoolingFanPwmChanged(unsigned int heater, float pwmChange) const noexcept +{ + const auto h = FindHeater(heater); + if (h.IsNotNull()) + { + h->PrintCoolingFanPwmChanged(pwmChange); + } +} + GCodeResult Heat::ResetFault(int heater, const StringRef& reply) noexcept { // This gets called for all heater numbers when clearing all temperature faults, so don't report an error if the heater was not found diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index bbe9bff9..74bccc26 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -131,6 +131,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; #if HAS_MASS_STORAGE bool WriteModelParameters(FileStore *f) const noexcept; // Write heater model parameters to file returning true if no error diff --git a/src/Heating/Heater.cpp b/src/Heating/Heater.cpp index 4d7133bf..9a862383 100644 --- a/src/Heating/Heater.cpp +++ b/src/Heating/Heater.cpp @@ -179,17 +179,17 @@ GCodeResult Heater::SetOrReportModel(unsigned int heater, GCodeBuffer& gb, const } // Set the process model returning true if successful -GCodeResult Heater::SetModel(float gain, float tcOff, float tcOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept +GCodeResult Heater::SetModel(float heatingRate, float coolingRateFanOff, float coolingRateFanOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept { GCodeResult rslt; - if (model.SetParameters(gain, tcOff, tcOn, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted)) + if (model.SetParameters(heatingRate, coolingRateFanOff, coolingRateFanOn, td, maxPwm, GetHighestTemperatureLimit(), voltage, usePid, inverted)) { if (model.IsEnabled()) { rslt = UpdateModel(reply); if (rslt == GCodeResult::ok) { - const float predictedMaxTemp = gain + NormalAmbientTemperature; + const float predictedMaxTemp = heatingRate/coolingRateFanOff + NormalAmbientTemperature; const float noWarnTemp = (GetHighestTemperatureLimit() - NormalAmbientTemperature) * 1.5 + 50.0; // allow 50% extra power plus enough for an extra 50C if (predictedMaxTemp > noWarnTemp) { diff --git a/src/Heating/Heater.h b/src/Heating/Heater.h index 502e2e74..8c8231e0 100644 --- a/src/Heating/Heater.h +++ b/src/Heating/Heater.h @@ -43,9 +43,10 @@ public: virtual void SwitchOff() noexcept = 0; virtual void Spin() noexcept = 0; virtual GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException) = 0; // Start an auto tune cycle for this heater - virtual void GetAutoTuneStatus(const StringRef& reply) const = 0; // Get the auto tune status or last result + 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; #if SUPPORT_CAN_EXPANSION virtual void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept = 0; @@ -117,7 +118,7 @@ protected: float GetMaxTemperatureExcursion() const noexcept { return maxTempExcursion; } float GetMaxHeatingFaultTime() const noexcept { return maxHeatingFaultTime; } float GetTargetTemperature() const noexcept { return (active) ? activeTemperature : standbyTemperature; } - GCodeResult SetModel(float gain, float tcOff, float tcOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model + GCodeResult SetModel(float heatingRate, float coolingRateFanOff, float coolingRateFanOn, float td, float maxPwm, float voltage, bool usePid, bool inverted, const StringRef& reply) noexcept; // Set the process model HeaterMonitor monitors[MaxMonitorsPerHeater]; // embedding them in the Heater uses less memory than dynamic allocation diff --git a/src/Heating/LocalHeater.cpp b/src/Heating/LocalHeater.cpp index d5764c04..1d0af3f3 100644 --- a/src/Heating/LocalHeater.cpp +++ b/src/Heating/LocalHeater.cpp @@ -596,6 +596,16 @@ void LocalHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept } } +// 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 +{ + if (mode == HeaterMode::stable) + { + const float coolingRateIncrease = GetModel().GetCoolingRateChangeFanOn() * pwmChange; + iAccumulator += (coolingRateIncrease * (GetTargetTemperature() - NormalAmbientTemperature))/GetModel().GetHeatingRate(); + } +} + /* Notes on the auto tune algorithm * * Most 3D printer firmwares use the �str�m-H�gglund relay tuning method (sometimes called Ziegler-Nichols + relay). diff --git a/src/Heating/LocalHeater.h b/src/Heating/LocalHeater.h index 8c32dc39..5cad973b 100644 --- a/src/Heating/LocalHeater.h +++ b/src/Heating/LocalHeater.h @@ -50,6 +50,7 @@ public: GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException) override; // Start an auto tune cycle for this heater void GetAutoTuneStatus(const StringRef& reply) const noexcept override; // Get the auto tune status or last result void Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing + void PrintCoolingFanPwmChanged(float pwmChange) noexcept override; #if SUPPORT_CAN_EXPANSION void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept override { } diff --git a/src/Heating/RemoteHeater.cpp b/src/Heating/RemoteHeater.cpp index 70dfe8bb..94e6f377 100644 --- a/src/Heating/RemoteHeater.cpp +++ b/src/Heating/RemoteHeater.cpp @@ -41,13 +41,13 @@ void RemoteHeater::ResetHeater() noexcept //TODO } -GCodeResult RemoteHeater::ConfigurePortAndSensor(const char *portName, PwmFrequency freq, unsigned int sensorNumber, const StringRef& reply) +GCodeResult RemoteHeater::ConfigurePortAndSensor(const char *portName, PwmFrequency freq, unsigned int sn, const StringRef& reply) { - SetSensorNumber(sensorNumber); + SetSensorNumber(sn); CanMessageGenericConstructor cons(M950HeaterParams); cons.AddUParam('H', GetHeaterNumber()); cons.AddUParam('Q', freq); - cons.AddUParam('T', sensorNumber); + cons.AddUParam('T', sn); cons.AddStringParam('C', portName); return cons.SendAndGetResponse(CanMessageType::m950Heater, boardAddress, reply); } @@ -139,6 +139,11 @@ void RemoteHeater::GetAutoTuneStatus(const StringRef& reply) const noexcept reply.copy("remote heater auto tune not implemented"); } +void RemoteHeater::PrintCoolingFanPwmChanged(float pwmChange) noexcept +{ + //TODO send a CAN message to remote +} + void RemoteHeater::Suspend(bool sus) noexcept { CanMessageBuffer * const buf = CanMessageBuffer::Allocate(); @@ -189,7 +194,7 @@ GCodeResult RemoteHeater::UpdateModel(const StringRef& reply) noexcept if (buf != nullptr) { const CanRequestId rid = CanInterface::AllocateRequestId(boardAddress); - CanMessageUpdateHeaterModel * const msg = buf->SetupRequestMessage<CanMessageUpdateHeaterModel>(rid, CanInterface::GetCanAddress(), boardAddress); + CanMessageUpdateHeaterModelNew * const msg = buf->SetupRequestMessage<CanMessageUpdateHeaterModelNew>(rid, CanInterface::GetCanAddress(), boardAddress); GetModel().SetupCanMessage(GetHeaterNumber(), *msg); return CanInterface::SendRequestAndGetStandardReply(buf, rid, reply); } diff --git a/src/Heating/RemoteHeater.h b/src/Heating/RemoteHeater.h index 676dfdfa..558b49b4 100644 --- a/src/Heating/RemoteHeater.h +++ b/src/Heating/RemoteHeater.h @@ -31,6 +31,7 @@ public: GCodeResult StartAutoTune(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException) override; // Start an auto tune cycle for this heater void GetAutoTuneStatus(const StringRef& reply) const noexcept override; // Get the auto tune status or last result void Suspend(bool sus) noexcept override; // Suspend the heater to conserve power or while doing Z probing + void PrintCoolingFanPwmChanged(float pwmChange) noexcept override; void UpdateRemoteStatus(CanAddress src, const CanHeaterReport& report) noexcept override; protected: diff --git a/src/Tools/Tool.cpp b/src/Tools/Tool.cpp index a7595645..067cdfdc 100644 --- a/src/Tools/Tool.cpp +++ b/src/Tools/Tool.cpp @@ -610,6 +610,15 @@ void Tool::IterateHeaters(std::function<void(int)> f) const noexcept } } +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); }); + } +} + // Return true if this tool uses the specified heater bool Tool::UsesHeater(int8_t heater) const noexcept { diff --git a/src/Tools/Tool.h b/src/Tools/Tool.h index 1fba89e7..b7b7699f 100644 --- a/src/Tools/Tool.h +++ b/src/Tools/Tool.h @@ -102,6 +102,8 @@ public: void IterateExtruders(std::function<void(unsigned int)> f) const noexcept; void IterateHeaters(std::function<void(int)> f) const noexcept; + void SetFansPwm(float f) const noexcept; + protected: DECLARE_OBJECT_MODEL OBJECT_MODEL_ARRAY(activeTemps) diff --git a/src/Version.h b/src/Version.h index 53f20eda..886c2f6e 100644 --- a/src/Version.h +++ b/src/Version.h @@ -9,7 +9,7 @@ #define SRC_VERSION_H_ #ifndef VERSION -# define MAIN_VERSION "3.2-beta3-nht" +# define MAIN_VERSION "3.2-beta3-nht-b2" # ifdef USE_CAN0 # define VERSION_SUFFIX " (CAN0)" # else |