diff options
Diffstat (limited to 'src/Heating')
24 files changed, 1281 insertions, 619 deletions
diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index ecd184d0..eb5b5ff9 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -19,14 +19,14 @@ Licence: GPL ****************************************************************************************************/ #include "Heat.h" - #include "Platform.h" #include "RepRap.h" +#include "Sensors/TemperatureSensor.h" Heat::Heat(Platform& p) : platform(p), active(false), coldExtrude(false), bedHeater(DefaultBedHeater), chamberHeater(DefaultChamberHeater), heaterBeingTuned(-1), lastHeaterTuned(-1) { - for (size_t heater = 0; heater < HEATERS; heater++) + for (size_t heater : ARRAY_INDICES(pids)) { pids[heater] = new PID(platform, heater); } @@ -35,11 +35,11 @@ Heat::Heat(Platform& p) // Reset all heater models to defaults. Called when running M502. void Heat::ResetHeaterModels() { - for (int heater = 0; heater < HEATERS; heater++) + for (size_t heater : ARRAY_INDICES(pids)) { if (pids[heater]->IsHeaterEnabled()) { - if (heater == DefaultBedHeater || heater == DefaultChamberHeater) + if ((int)heater == DefaultBedHeater || (int)heater == DefaultChamberHeater) { pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, false); } @@ -53,27 +53,44 @@ void Heat::ResetHeaterModels() void Heat::Init() { - for (int heater = 0; heater < HEATERS; heater++) + // Set up the real heaters and the corresponding PIDs + for (size_t heater : ARRAY_INDICES(pids)) { - if (heater == DefaultBedHeater || heater == DefaultChamberHeater) + heaterSensors[heater] = nullptr; // no temperature sensor assigned yet + if ((int)heater == DefaultBedHeater || (int)heater == DefaultChamberHeater) { - pids[heater]->Init(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, - DefaultBedTemperatureLimit, false); + pids[heater]->Init(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, DefaultBedTemperatureLimit, false); } #if !defined(DUET_NG) && !defined(__RADDS__) && !defined(__ALLIGATOR__) - else if (heater == HEATERS - 1) + else if (heater == Heaters - 1) { - // Heater 6 pin is shared with fan 1. By default we support fan 1, so disable heater 6. + // On the Duet 085, the heater 6 pin is also the fan 1 pin. By default we support fan 1, so disable heater 6. pids[heater]->Init(-1.0, -1.0, -1.0, DefaultExtruderTemperatureLimit, true); } #endif else { - pids[heater]->Init(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, - DefaultExtruderTemperatureLimit, true); + pids[heater]->Init(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, DefaultExtruderTemperatureLimit, true); } } + // Set up the virtual heaters + // Clear the user-defined virtual heaters + for (TemperatureSensor* &v : virtualHeaterSensors) + { + v = nullptr; + } + + // Set up default virtual heaters for MCU temperature and TMC driver overheat sensors +#ifndef __RADDS + virtualHeaterSensors[0] = TemperatureSensor::Create(CpuTemperatureSenseChannel); + virtualHeaterSensors[0]->SetHeaterName("MCU"); // name this virtual heater so that it appears in DWC +#endif +#ifdef DUET_NG + virtualHeaterSensors[1] = TemperatureSensor::Create(FirstTmcDriversSenseChannel); + virtualHeaterSensors[2] = TemperatureSensor::Create(FirstTmcDriversSenseChannel + 1); +#endif + lastTime = millis() - platform.HeatSampleInterval(); // flag the PIDS as due for spinning longWait = platform.Time(); coldExtrude = false; @@ -82,9 +99,9 @@ void Heat::Init() void Heat::Exit() { - for (size_t heater = 0; heater < HEATERS; heater++) + for (PID *pid : pids) { - pids[heater]->SwitchOff(); + pid->SwitchOff(); } platform.Message(HOST_MESSAGE, "Heat class exited.\n"); active = false; @@ -99,7 +116,7 @@ void Heat::Spin() if (now - lastTime >= platform.HeatSampleInterval()) { lastTime = now; - for (size_t heater=0; heater < HEATERS; heater++) + for (size_t heater=0; heater < Heaters; heater++) { pids[heater]->Spin(); } @@ -118,7 +135,7 @@ void Heat::Spin() void Heat::Diagnostics(MessageType mtype) { platform.MessageF(mtype, "=== Heat ===\nBed heater = %d, chamber heater = %d\n", bedHeater, chamberHeater); - for (size_t heater=0; heater < HEATERS; heater++) + for (size_t heater : ARRAY_INDICES(pids)) { if (pids[heater]->Active()) { @@ -129,9 +146,9 @@ void Heat::Diagnostics(MessageType mtype) bool Heat::AllHeatersAtSetTemperatures(bool includingBed) const { - for(int8_t heater = 0; heater < HEATERS; heater++) + for (size_t heater : ARRAY_INDICES(pids)) { - if (!HeaterAtSetTemperature(heater, true) && (includingBed || heater != bedHeater)) + if (!HeaterAtSetTemperature(heater, true) && (includingBed || (int)heater != bedHeater)) { return false; } @@ -143,7 +160,7 @@ bool Heat::AllHeatersAtSetTemperatures(bool includingBed) const bool Heat::HeaterAtSetTemperature(int8_t heater, bool waitWhenCooling) const { // If it hasn't anything to do, it must be right wherever it is... - if (heater < 0 || heater >= HEATERS || pids[heater]->SwitchedOff() || pids[heater]->FaultOccurred()) + if (heater < 0 || heater >= (int)Heaters || pids[heater]->SwitchedOff() || pids[heater]->FaultOccurred()) { return true; } @@ -157,7 +174,7 @@ bool Heat::HeaterAtSetTemperature(int8_t heater, bool waitWhenCooling) const Heat::HeaterStatus Heat::GetStatus(int8_t heater) const { - if (heater < 0 || heater >= HEATERS) + if (heater < 0 || heater >= (int)Heaters) { return HS_off; } @@ -171,7 +188,7 @@ Heat::HeaterStatus Heat::GetStatus(int8_t heater) const void Heat::SetActiveTemperature(int8_t heater, float t) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->SetActiveTemperature(t); } @@ -179,12 +196,12 @@ void Heat::SetActiveTemperature(int8_t heater, float t) float Heat::GetActiveTemperature(int8_t heater) const { - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetActiveTemperature() : ABS_ZERO; + return (heater >= 0 && heater < (int)Heaters) ? pids[heater]->GetActiveTemperature() : ABS_ZERO; } void Heat::SetStandbyTemperature(int8_t heater, float t) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->SetStandbyTemperature(t); } @@ -192,12 +209,12 @@ void Heat::SetStandbyTemperature(int8_t heater, float t) float Heat::GetStandbyTemperature(int8_t heater) const { - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetStandbyTemperature() : ABS_ZERO; + return (heater >= 0 && heater < (int)Heaters) ? pids[heater]->GetStandbyTemperature() : ABS_ZERO; } void Heat::SetTemperatureLimit(int8_t heater, float t) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->SetTemperatureLimit(t); } @@ -205,17 +222,17 @@ void Heat::SetTemperatureLimit(int8_t heater, float t) float Heat::GetTemperatureLimit(int8_t heater) const { - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetTemperatureLimit() : ABS_ZERO; + return (heater >= 0 && heater < (int)Heaters) ? pids[heater]->GetTemperatureLimit() : ABS_ZERO; } float Heat::GetTemperature(int8_t heater) const { - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetTemperature() : ABS_ZERO; + return (heater >= 0 && heater < (int)Heaters) ? pids[heater]->GetTemperature() : ABS_ZERO; } void Heat::Activate(int8_t heater) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->Activate(); } @@ -223,7 +240,7 @@ void Heat::Activate(int8_t heater) void Heat::SwitchOff(int8_t heater) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->SwitchOff(); } @@ -231,7 +248,7 @@ void Heat::SwitchOff(int8_t heater) void Heat::SwitchOffAll() { - for (size_t heater = 0; heater < HEATERS; ++heater) + for (size_t heater = 0; heater < Heaters; ++heater) { pids[heater]->SwitchOff(); } @@ -239,7 +256,7 @@ void Heat::SwitchOffAll() void Heat::Standby(int8_t heater) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->Standby(); } @@ -247,7 +264,7 @@ void Heat::Standby(int8_t heater) void Heat::ResetFault(int8_t heater) { - if (heater >= 0 && heater < HEATERS) + if (heater >= 0 && heater < (int)Heaters) { pids[heater]->ResetFault(); } @@ -305,7 +322,7 @@ void Heat::GetAutoTuneStatus(StringRef& reply) const float Heat::GetHighestTemperatureLimit() const { float limit = ABS_ZERO; - for (size_t h = 0; h < HEATERS; ++h) + for (size_t h : ARRAY_INDICES(pids)) { if (h < reprap.GetToolHeatersInUse() || (int)h == bedHeater || (int)h == chamberHeater) { @@ -329,7 +346,7 @@ void Heat::SetM301PidParameters(size_t heater, const M301PidParameters& params) bool Heat::WriteModelParameters(FileStore *f) const { bool ok = f->Write("; Heater model parameters\n"); - for (size_t h = 0; ok && h < HEATERS; ++h) + for (size_t h : ARRAY_INDICES(pids)) { const FopDt& model = pids[h]->GetModel(); if (model.IsEnabled()) @@ -340,4 +357,105 @@ bool Heat::WriteModelParameters(FileStore *f) const return ok; } +// Return the channel used by a particular heater, or -1 if not configured +int Heat::GetHeaterChannel(size_t heater) const +{ + const TemperatureSensor * const * const spp = GetSensor(heater); + return (spp != nullptr && *spp != nullptr) ? (*spp)->GetSensorChannel() : -1; +} + +// Set the channel used by a heater, returning true if bad heater or channel number +bool Heat::SetHeaterChannel(size_t heater, int channel) +{ + TemperatureSensor ** const spp = GetSensor(heater); + if (spp == nullptr) + { + return true; // bad heater number + } + + TemperatureSensor *sp = TemperatureSensor::Create(channel); + if (sp == nullptr) + { + return true; // bad channel number + } + + delete *spp; // release the old sensor object, if any + *spp = sp; + return false; +} + +// Configure the temperature sensor for a channel +bool Heat::ConfigureHeaterSensor(unsigned int mcode, size_t heater, GCodeBuffer& gb, StringRef& reply, bool& error) +{ + TemperatureSensor ** const spp = GetSensor(heater); + if (spp == nullptr || *spp == nullptr) + { + reply.printf("heater %d is not configured", heater); + error = true; + return false; + } + + return (*spp)->Configure(mcode, heater, gb, reply, error); +} + +// Get a pointer to the temperature sensor entry, or nullptr if the heater number is bad +TemperatureSensor **Heat::GetSensor(size_t heater) +{ + if (heater < Heaters) + { + return &heaterSensors[heater]; + } + if (heater >= FirstVirtualHeater && heater < FirstVirtualHeater + ARRAY_SIZE(virtualHeaterSensors)) + { + return &virtualHeaterSensors[heater - FirstVirtualHeater]; + } + return nullptr; +} + +// Get a pointer to the temperature sensor entry, or nullptr if the heater number is bad (const version of above) +TemperatureSensor * const *Heat::GetSensor(size_t heater) const +{ + if (heater < Heaters) + { + return &heaterSensors[heater]; + } + if (heater >= FirstVirtualHeater && heater < FirstVirtualHeater + ARRAY_SIZE(virtualHeaterSensors)) + { + return &virtualHeaterSensors[heater - FirstVirtualHeater]; + } + return nullptr; +} + +// Get the name of a heater, or nullptr if it hasn't been named +const char *Heat::GetHeaterName(size_t heater) const +{ + const TemperatureSensor * const * const spp = GetSensor(heater); + return (spp == nullptr || *spp == nullptr) ? nullptr : (*spp)->GetHeaterName(); +} + +// Get the temperature of a real or virtual heater +float Heat::GetTemperature(size_t heater, TemperatureError& err) +{ + TemperatureSensor ** const spp = GetSensor(heater); + if (spp == nullptr) + { + err = TemperatureError::unknownHeater; + return BAD_ERROR_TEMPERATURE; + } + + if (*spp == nullptr) + { + err = TemperatureError::unknownChannel; + return BAD_ERROR_TEMPERATURE; + } + + float t; + err = (*spp)->GetTemperature(t); + if (err != TemperatureError::success) + { + t = BAD_ERROR_TEMPERATURE; + } + return t; +} + // End diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index 2c487344..3ebb5c6d 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -29,6 +29,9 @@ Licence: GPL #include "Pid.h" #include "MessageType.h" +class TemperatureSensor; +class GCodeBuffer; + class Heat { public: @@ -45,16 +48,16 @@ public: void AllowColdExtrude(bool b); // Allow or deny cold extrusion int8_t GetBedHeater() const // Get hot bed heater number - post(-1 <= result; result < HEATERS); + post(-1 <= result; result < Heaters); void SetBedHeater(int8_t heater) // Set hot bed heater number - pre(-1 <= heater; heater < HEATERS); + pre(-1 <= heater; heater < Heaters); int8_t GetChamberHeater() const // Get chamber heater number - post(-1 <= result; result < HEATERS); + post(-1 <= result; result < Heaters); void SetChamberHeater(int8_t heater) // Set chamber heater number - pre(-1 <= heater; heater < HEATERS); + pre(-1 <= heater; heater < Heaters); void SetActiveTemperature(int8_t heater, float t); float GetActiveTemperature(int8_t heater) const; @@ -74,48 +77,60 @@ public: void Diagnostics(MessageType mtype); // Output useful information float GetAveragePWM(size_t heater) const // Return the running average PWM to the heater as a fraction in [0, 1]. - pre(heater < HEATERS); + pre(heater < Heaters); bool UseSlowPwm(int8_t heater) const; // Queried by the Platform class uint32_t GetLastSampleTime(size_t heater) const - pre(heater < HEATERS); + pre(heater < Heaters); void StartAutoTune(size_t heater, float temperature, float maxPwm, StringRef& reply) // Auto tune a PID - pre(heater < HEATERS); + pre(heater < Heaters); bool IsTuning(size_t heater) const // Return true if the specified heater is auto tuning - pre(heater < HEATERS); + pre(heater < Heaters); void GetAutoTuneStatus(StringRef& reply) const; // Get the status of the current or last auto tune const FopDt& GetHeaterModel(size_t heater) const // Get the process model for the specified heater - pre(heater < HEATERS); + pre(heater < Heaters); bool SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, bool usePid) // Set the heater process model - pre(heater < HEATERS); + pre(heater < Heaters); void GetHeaterProtection(size_t heater, float& maxTempExcursion, float& maxFaultTime) const - pre(heater < HEATERS); + pre(heater < Heaters); void SetHeaterProtection(size_t heater, float maxTempExcursion, float maxFaultTime) - pre(heater < HEATERS); + pre(heater < Heaters); bool IsHeaterEnabled(size_t heater) const // Is this heater enabled? - pre(heater < HEATERS); + pre(heater < Heaters); float GetHighestTemperatureLimit() const; // Get the highest temperature limit of any heater void SetM301PidParameters(size_t heater, const M301PidParameters& params) - pre(heater < HEATERS); + pre(heater < Heaters); bool WriteModelParameters(FileStore *f) const; // Write heater model parameters to file returning true if no error + int GetHeaterChannel(size_t heater) const; // Return the channel used by a particular heater, or -1 if not configured + bool SetHeaterChannel(size_t heater, int channel); // Set the channel used by a heater, returning true if bad heater or channel number + bool ConfigureHeaterSensor(size_t heater, unsigned int mcode, GCodeBuffer& gb, StringRef& reply, bool& error); // Configure the temperature sensor for a channel + const char *GetHeaterName(size_t heater) const; // Get the name of a heater, or nullptr if it hasn't been named + + float GetTemperature(size_t heater, TemperatureError& err); // Result is in degrees Celsius + private: - Heat(const Heat&); // private copy constructor to prevent copying + Heat(const Heat&); // Private copy constructor to prevent copying + TemperatureSensor **GetSensor(size_t heater); // Get a pointer to the temperature sensor entry + TemperatureSensor * const *GetSensor(size_t heater) const; // Get a pointer to the temperature sensor entry Platform& platform; // The instance of the RepRap hardware class - PID* pids[HEATERS]; // A PID controller for each heater + + PID* pids[Heaters]; // A PID controller for each heater + TemperatureSensor *heaterSensors[Heaters]; // The sensor used by the real heaters + TemperatureSensor *virtualHeaterSensors[MaxVirtualHeaters]; // Sensors for virtual heaters uint32_t lastTime; // The last time our Spin() was called float longWait; // Long time for things that happen occasionally diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp index 88ca87ec..145ff014 100644 --- a/src/Heating/Pid.cpp +++ b/src/Heating/Pid.cpp @@ -83,7 +83,7 @@ bool PID::SetModel(float gain, float tc, float td, float maxPwm, bool usePid) if (rslt) { #if !defined(DUET_NG) && !defined(__RADDS__) && !defined(__ALLIGATOR__) - if (heater == HEATERS - 1) + if (heater == Heaters - 1) { // The last heater on the Duet 0.8.5 + DueX4 shares its pin with Fan1 platform.EnableSharedFan(!model.IsEnabled()); @@ -112,17 +112,10 @@ bool PID::SetModel(float gain, float tc, float td, float maxPwm, bool usePid) TemperatureError PID::ReadTemperature() { TemperatureError err = TemperatureError::success; // assume no error - temperature = platform.GetTemperature(heater, err); // in the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned - if (err == TemperatureError::success) + temperature = reprap.GetHeat().GetTemperature(heater, err); // in the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned + if (err == TemperatureError::success && temperature > temperatureLimit) { - if (temperature < BAD_LOW_TEMPERATURE) - { - err = TemperatureError::openCircuit; - } - else if (temperature > temperatureLimit) - { - err = TemperatureError::tooHigh; - } + err = TemperatureError::tooHigh; } return err; } diff --git a/src/Heating/Sensors/CpuTemperatureSensor.cpp b/src/Heating/Sensors/CpuTemperatureSensor.cpp new file mode 100644 index 00000000..85ff00ba --- /dev/null +++ b/src/Heating/Sensors/CpuTemperatureSensor.cpp @@ -0,0 +1,31 @@ +/* + * CpuTemperatureSensor.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#include "CpuTemperatureSensor.h" +#include "Platform.h" +#include "RepRap.h" + +#ifndef __RADDS__ + +CpuTemperatureSensor::CpuTemperatureSensor(unsigned int channel) : TemperatureSensor(channel, "microcontroller embedded temperature sensor") +{ +} + +void CpuTemperatureSensor::Init() +{ +} + +TemperatureError CpuTemperatureSensor::GetTemperature(float& t) +{ + float minT, maxT; + reprap.GetPlatform().GetMcuTemperatures(minT, t, maxT); + return TemperatureError::success; +} + +#endif + +// End diff --git a/src/Heating/Sensors/CpuTemperatureSensor.h b/src/Heating/Sensors/CpuTemperatureSensor.h new file mode 100644 index 00000000..7e23ac77 --- /dev/null +++ b/src/Heating/Sensors/CpuTemperatureSensor.h @@ -0,0 +1,25 @@ +/* + * CpuTemperatureSensor.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_SENSORS_CPUTEMPERATURESENSOR_H_ +#define SRC_HEATING_SENSORS_CPUTEMPERATURESENSOR_H_ + +#include "TemperatureSensor.h" + +#ifndef __RADDS__ + +class CpuTemperatureSensor : public TemperatureSensor +{ +public: + CpuTemperatureSensor(unsigned int channel); + void Init() override; + TemperatureError GetTemperature(float& t) override; +}; + +#endif + +#endif /* SRC_HEATING_SENSORS_CPUTEMPERATURESENSOR_H_ */ diff --git a/src/Heating/Sensors/CurrentLoopTemperatureSensor.cpp b/src/Heating/Sensors/CurrentLoopTemperatureSensor.cpp new file mode 100644 index 00000000..388fdbc8 --- /dev/null +++ b/src/Heating/Sensors/CurrentLoopTemperatureSensor.cpp @@ -0,0 +1,123 @@ +/* + * LinearAdcTemperatureSensor.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#include <Heating/Sensors/CurrentLoopTemperatureSensor.h> +#include "RepRap.h" +#include "Platform.h" +#include "GCodes/GCodeBuffer.h" + +const uint32_t MCP3204_Frequency = 1000000; // maximum for MCP3204 is 1MHz @ 2.7V, will be slightly higher at 3.3V + +// The MCP3204 samples input data on the rising edge and changes the output data on the rising edge. +const uint8_t MCP3204_SpiMode = SPI_MODE_0; + +// Define the minimum interval between readings +const uint32_t MinimumReadInterval = 100; // minimum interval between reads, in milliseconds + +CurrentLoopTemperatureSensor::CurrentLoopTemperatureSensor(unsigned int channel) + : SpiTemperatureSensor(channel, "Current Loop", channel - FirstLinearAdcChannel, MCP3204_SpiMode, MCP3204_Frequency), + tempAt4mA(DefaultTempAt4mA), tempAt20mA(DefaultTempAt20mA) +{ + CalcDerivedParameters(); +} + +// Initialise the linear ADC +void CurrentLoopTemperatureSensor::Init() +{ + InitSpi(); + + for (unsigned int i = 0; i < 3; ++i) // try 3 times + { + TryGetLinearAdcTemperature(); + if (lastResult == TemperatureError::success) + { + break; + } + delay(MinimumReadInterval); + } + + lastReadingTime = millis(); + + if (lastResult != TemperatureError::success) + { + reprap.GetPlatform().MessageF(GENERIC_MESSAGE, "Error: failed to initialise daughter board ADC: %s\n", TemperatureErrorString(lastResult)); + } +} + +// Configure this temperature sensor +bool CurrentLoopTemperatureSensor::Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error) +{ + if (mCode == 305) + { + bool seen = false; + gb.TryGetFValue('L', tempAt4mA, seen); + gb.TryGetFValue('H', tempAt20mA, seen); + TryConfigureHeaterName(gb, seen); + + if (seen) + { + CalcDerivedParameters(); + } + else if (!gb.Seen('X')) + { + CopyBasicHeaterDetails(heater, reply); + reply.catf(", temperature range %.1f to %.1fC", tempAt4mA, tempAt20mA); + } + } + return false; +} + +TemperatureError CurrentLoopTemperatureSensor::GetTemperature(float& t) +{ + if (!inInterrupt() && millis() - lastReadingTime >= MinimumReadInterval) + { + TryGetLinearAdcTemperature(); + } + + t = lastTemperature; + return lastResult; +} + +void CurrentLoopTemperatureSensor::CalcDerivedParameters() +{ + minLinearAdcTemp = tempAt4mA - 0.25 * (tempAt20mA - tempAt4mA); + linearAdcDegCPerCount = (tempAt20mA - minLinearAdcTemp) / 4096.0; +} + +// Try to get a temperature reading from the linear ADC by doing an SPI transaction +void CurrentLoopTemperatureSensor::TryGetLinearAdcTemperature() +{ + // The MCP3204 waits for a high input input bit before it does anything. Call this clock 1. + // The next input bit it high for single-ended operation, low for differential. This is clock 2. + // The next 3 input bits are the channel selection bits. These are clocks 3..5. + // Clock 6 produces a null bit on its trailing edge, which is read by the processor on clock 7. + // Clocks 7..18 produce data bits B11..B0 on their trailing edges, which are read by the MCU on the leading edges of clocks 8-19. + // If we supply further clocks, then clocks 18..29 are the same data but LSB first, omitting bit 0. + // Clocks 30 onwards will be zeros. + // So we need to use at least 19 clocks. We round this up to 24 clocks, and we check that the extra 5 bits we receive are the 5 least significant data bits in reverse order. + + static const uint8_t adcData[] = { 0xC0, 0x00, 0x00 }; // start bit, single ended, channel 0 + uint32_t rawVal; + lastResult = DoSpiTransaction(adcData, 3, rawVal); + //debugPrintf("ADC data %u\n", rawVal); + + if (lastResult == TemperatureError::success) + { + const uint32_t adcVal1 = (rawVal >> 5) & ((1 << 13) - 1); + const uint32_t adcVal2 = ((rawVal & 1) << 5) | ((rawVal & 2) << 3) | ((rawVal & 4) << 1) | ((rawVal & 8) >> 1) | ((rawVal & 16) >> 3) | ((rawVal & 32) >> 5); + if (adcVal1 >= 4096 || adcVal2 != (adcVal1 & ((1 << 6) - 1))) + { + lastResult = TemperatureError::badResponse; + } + else + { + lastTemperature = minLinearAdcTemp + (linearAdcDegCPerCount * (float)adcVal1); + } + } +} + +// End diff --git a/src/Heating/Sensors/CurrentLoopTemperatureSensor.h b/src/Heating/Sensors/CurrentLoopTemperatureSensor.h new file mode 100644 index 00000000..92815f2e --- /dev/null +++ b/src/Heating/Sensors/CurrentLoopTemperatureSensor.h @@ -0,0 +1,35 @@ +/* + * LinearAdcTemperatureSensor.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_LINEARADCTEMPERATURESENSOR_H_ +#define SRC_HEATING_LINEARADCTEMPERATURESENSOR_H_ + +#include "SpiTemperatureSensor.h" + +class CurrentLoopTemperatureSensor : public SpiTemperatureSensor +{ +public: + CurrentLoopTemperatureSensor(unsigned int channel); + bool Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error) override; + void Init() override; + TemperatureError GetTemperature(float& t) override; + +private: + void TryGetLinearAdcTemperature(); + void CalcDerivedParameters(); + + // Configurable parameters + float tempAt4mA, tempAt20mA; + + // Derived parameters + float minLinearAdcTemp, linearAdcDegCPerCount; + + static constexpr float DefaultTempAt4mA = 385.0; + static constexpr float DefaultTempAt20mA = 1600.0; +}; + +#endif /* SRC_HEATING_LINEARADCTEMPERATURESENSOR_H_ */ diff --git a/src/Heating/Sensors/RtdSensor31865.cpp b/src/Heating/Sensors/RtdSensor31865.cpp new file mode 100644 index 00000000..35c4d2f7 --- /dev/null +++ b/src/Heating/Sensors/RtdSensor31865.cpp @@ -0,0 +1,207 @@ +/* + * RtdSensor31865.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#include "RtdSensor31865.h" +#include "RepRap.h" +#include "Platform.h" +#include "Core.h" + +const uint32_t MAX31865_Frequency = 4000000; // maximum for MAX31865 is also 5MHz + +// SPI modes: +// If the inactive state of SCL is LOW (CPOL = 0) (in the case of the MAX31865, this is sampled on the falling edge of CS): +// The MAX31865 changes data after the rising edge of CLK, and samples input data on the falling edge. +// This requires NCPHA = 0. +const uint8_t MAX31865_SpiMode = SPI_MODE_1; + +// Define the minimum interval between readings. The MAX31865 needs 62.5ms in 50Hz filter mode. +const uint32_t MinimumReadInterval = 100; // minimum interval between reads, in milliseconds + +// Table of temperature vs. MAX31865 result for PT100 thermistor, from the MAX31865 datasheet +struct TempTableEntry +{ + int16_t temperature; + uint16_t adcReading; +}; + +static const TempTableEntry tempTable[] = +{ + {-30, 7227}, + {-20, 7550}, + {-10, 7871}, + {0, 8192}, + {10, 8512}, + {20, 8830}, + {30, 9148}, + {40, 9465}, + {50, 9781}, + {60, 10096}, + {70, 10410}, + {80, 10723}, + {90, 11035}, + {100, 11346}, + {110, 11657}, + {120, 11966}, + {130, 12274}, + {140, 12582}, + {150, 12888}, + {160, 13194}, + {170, 13498}, + {180, 13802}, + {190, 14104}, + {200, 14406}, + {225, 15156}, + {250, 15901}, + {275, 16639}, + {300, 17371}, + {325, 18098}, + {350, 18818}, + {375, 19533}, + {400, 20242}, + {425, 20945}, + {450, 21642}, + {475, 22333}, + {500, 23018}, + {525, 23697}, + {550, 24370} +}; + +const size_t NumTempTableEntries = sizeof(tempTable)/sizeof(tempTable[0]); + +RtdSensor31865::RtdSensor31865(unsigned int channel) + : SpiTemperatureSensor(channel, "PT100 (MAX31865)", channel - FirstRtdChannel, MAX31865_SpiMode, MAX31865_Frequency) +{ +} + +// Perform the actual hardware initialization for attaching and using this device on the SPI hardware bus. +void RtdSensor31865::Init() +{ + InitSpi(); + + TemperatureError rslt; + for (unsigned int i = 0; i < 3; ++i) // try 3 times + { + rslt = TryInitRtd(); + if (rslt == TemperatureError::success) + { + break; + } + delay(MinimumReadInterval); + } + + lastReadingTime = millis(); + lastResult = rslt; + lastTemperature = 0.0; + + if (rslt != TemperatureError::success) + { + reprap.GetPlatform().MessageF(GENERIC_MESSAGE, "Error: failed to initialise RTD: %s\n", TemperatureErrorString(rslt)); + } +} + +// Try to initialise the RTD +TemperatureError RtdSensor31865::TryInitRtd() const +{ + // Note that to get the MAX31865 to do continuous conversions, we need to set the bias bit as well as the continuous-conversion bit + static const uint8_t modeData[2] = { 0x80, 0xC3 }; // write register 0, bias on, auto conversion, clear errors, 50Hz + uint32_t rawVal; + TemperatureError sts = DoSpiTransaction(modeData, 2, rawVal); + + if (sts == TemperatureError::success) + { + static const uint8_t readData[2] = { 0x00, 0x00 }; // read register 0 + sts = DoSpiTransaction(readData, 2, rawVal); + } + + //debugPrintf("Status %d data %04x\n", (int)sts, rawVal); + return (sts == TemperatureError::success && (uint8_t)rawVal != 0xC1) + ? TemperatureError::badResponse + : sts; +} + +TemperatureError RtdSensor31865::GetTemperature(float& t) +{ + if (inInterrupt() || millis() - lastReadingTime < MinimumReadInterval) + { + t = lastTemperature; + } + else + { + static const uint8_t dataOut[4] = {0, 55, 55, 55}; // read registers 0 (control), 1 (MSB) and 2 (LSB) + uint32_t rawVal; + TemperatureError sts = DoSpiTransaction(dataOut, 4, rawVal); + + if (sts != TemperatureError::success) + { + lastResult = sts; + } + else + { + lastReadingTime = millis(); + if (((rawVal & 0x00C10000) != 0xC10000) +#if 0 + // We no longer check the error status bit, because it seems to be impossible to clear it once it has been set. + // Perhaps we would need to exit continuous reading mode to do so, and then re-enable it afterwards. But this would + // take to long. +#else + || (rawVal & 1) != 0 +#endif + ) + { + // Either the continuous conversion bit has got cleared, or the fault bit has been set + TryInitRtd(); + lastResult = TemperatureError::hardwareError; + } + else + { + uint16_t adcVal = (rawVal >> 1) & 0x7FFF; + + // Formally-verified binary search routine, adapted from one of the eCv examples + size_t low = 0u, high = NumTempTableEntries; + while (high > low) + keep(low <= high; high <= NumTempTableEntries) + keep(low == 0u || tempTable[low - 1u].adcReading < adcVal) + keep(high == NumTempTableEntries || adcVal <= tempTable[high].adcReading) + decrease(high - low) + { + size_t mid = (high - low)/2u + low; // get the mid point, avoiding arithmetic overflow + if (adcVal <= tempTable[mid].adcReading) + { + high = mid; + } + else + { + low = mid + 1u; + } + } + assert(low <= NumTempTableEntries); + assert(low == 0 || tempTable[low - 1] < adcVal); + assert(low == NumTempTableEntries || adcVal <= tempTable[low]); + + if (low == 0) // if off the bottom of the table + { + lastResult = TemperatureError::shortCircuit; + } + else if (low >= NumTempTableEntries) // if off the top of the table + { + lastResult = TemperatureError::openCircuit; + } + else + { + const float interpolationFraction = (float)(adcVal - tempTable[low - 1].adcReading)/(float)(tempTable[low].adcReading - tempTable[low - 1].adcReading); + t = lastTemperature = ((float)(tempTable[low].temperature - tempTable[low - 1].temperature) * interpolationFraction) + + (float)tempTable[low - 1].temperature; + //debugPrintf("raw %u low %u interp %f temp %f\n", adcVal, low, interpolationFraction, *t); + lastResult = TemperatureError::success; + } + } + } + } + return lastResult; +} + +// End diff --git a/src/Heating/Sensors/RtdSensor31865.h b/src/Heating/Sensors/RtdSensor31865.h new file mode 100644 index 00000000..0ff0a110 --- /dev/null +++ b/src/Heating/Sensors/RtdSensor31865.h @@ -0,0 +1,24 @@ +/* + * RtdSensor31865.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_RTDSENSOR31865_H_ +#define SRC_HEATING_RTDSENSOR31865_H_ + +#include "SpiTemperatureSensor.h" + +class RtdSensor31865 : public SpiTemperatureSensor +{ +public: + RtdSensor31865(unsigned int channel); + void Init() override; + TemperatureError GetTemperature(float& t) override; + +private: + TemperatureError TryInitRtd() const; +}; + +#endif /* SRC_HEATING_RTDSENSOR31865_H_ */ diff --git a/src/Heating/Sensors/SpiTemperatureSensor.cpp b/src/Heating/Sensors/SpiTemperatureSensor.cpp new file mode 100644 index 00000000..dbf6c5b7 --- /dev/null +++ b/src/Heating/Sensors/SpiTemperatureSensor.cpp @@ -0,0 +1,63 @@ +/* + * SpiTemperatureSensor.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#include "SpiTemperatureSensor.h" + +SpiTemperatureSensor::SpiTemperatureSensor(unsigned int channel, const char *name, unsigned int relativeChannel, uint8_t spiMode, uint32_t clockFrequency) + : TemperatureSensor(channel, name) +{ + device.csPin = SpiTempSensorCsPins[relativeChannel]; + device.spiMode = spiMode; + device.clockFrequency = clockFrequency; + lastTemperature = 0.0; + lastResult = TemperatureError::notInitialised; +} + +void SpiTemperatureSensor::InitSpi() +{ + sspi_master_init(&device, 8); + lastReadingTime = millis(); +} + +// Send and receive 1 to 4 bytes of data and return the result as a single 32-bit word +TemperatureError SpiTemperatureSensor::DoSpiTransaction(const uint8_t dataOut[], size_t nbytes, uint32_t& rslt) const +{ + if (!sspi_acquire()) + { + return TemperatureError::busBusy; + } + + sspi_master_setup_device(&device); + delayMicroseconds(1); + sspi_select_device(&device); + delayMicroseconds(1); + + uint8_t rawBytes[4]; + spi_status_t sts = sspi_transceive_packet(dataOut, rawBytes, nbytes); + + delayMicroseconds(1); + sspi_deselect_device(&device); + delayMicroseconds(1); + + sspi_release(); + + if (sts != SPI_OK) + { + return TemperatureError::timeout; + } + + rslt = rawBytes[0]; + for (size_t i = 1; i < nbytes; ++i) + { + rslt <<= 8; + rslt |= rawBytes[i]; + } + + return TemperatureError::success; +} + +// End diff --git a/src/Heating/Sensors/SpiTemperatureSensor.h b/src/Heating/Sensors/SpiTemperatureSensor.h new file mode 100644 index 00000000..81399cbf --- /dev/null +++ b/src/Heating/Sensors/SpiTemperatureSensor.h @@ -0,0 +1,27 @@ +/* + * SpiTemperatureSensor.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_SPITEMPERATURESENSOR_H_ +#define SRC_HEATING_SPITEMPERATURESENSOR_H_ + +#include "TemperatureSensor.h" +#include "SharedSpi.h" // for sspi_device + +class SpiTemperatureSensor : public TemperatureSensor +{ +protected: + SpiTemperatureSensor(unsigned int channel, const char *name, unsigned int relativeChannel, uint8_t spiMode, uint32_t clockFrequency); + void InitSpi(); + TemperatureError DoSpiTransaction(const uint8_t dataOut[], size_t nbytes, uint32_t& rslt) const; + + sspi_device device; + uint32_t lastReadingTime; + float lastTemperature; + TemperatureError lastResult; +}; + +#endif /* SRC_HEATING_SPITEMPERATURESENSOR_H_ */ diff --git a/src/Heating/Sensors/TemperatureSensor.cpp b/src/Heating/Sensors/TemperatureSensor.cpp new file mode 100644 index 00000000..9fa8637e --- /dev/null +++ b/src/Heating/Sensors/TemperatureSensor.cpp @@ -0,0 +1,120 @@ +#include <Heating/Sensors/CurrentLoopTemperatureSensor.h> +#include "TemperatureSensor.h" +#include "Thermistor.h" +#include "ThermocoupleSensor31855.h" +#include "RtdSensor31865.h" +#include "GCodes/GCodeBuffer.h" + +#ifndef __RADDS__ +#include "CpuTemperatureSensor.h" +#endif + +#ifdef DUET_NG +#include "TmcDriverTemperatureSensor.h" +#endif + +// Constructor +TemperatureSensor::TemperatureSensor(unsigned int chan, const char *t) : sensorChannel(chan), sensorType(t), heaterName(nullptr) {} + +// Virtual destructor +TemperatureSensor::~TemperatureSensor() +{ + delete heaterName; +} + +// Set the name - normally called only once +void TemperatureSensor::SetHeaterName(const char *newName) +{ + // Change the heater name in a thread-safe manner + const char *oldName = heaterName; + heaterName = nullptr; + delete oldName; + + if (newName != nullptr) + { + char *temp = new char[strlen(newName)]; + strcpy(temp, newName); + heaterName = temp; + } +} + +// Default implementation of Configure, for sensors that have no configurable parameters +bool TemperatureSensor::Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error) +{ + bool seen = false; + if (mCode == 305) + { + TryConfigureHeaterName(gb, seen); + if (!seen && !gb.Seen('X')) + { + // No parameters provided, so report the current configuration + CopyBasicHeaterDetails(heater, reply); + } + } + return seen; +} + +void TemperatureSensor::CopyBasicHeaterDetails(unsigned int heater, StringRef& reply) const +{ + reply.printf("Heater %u", heater); + if (heaterName != nullptr) + { + reply.catf(" (%s)", heaterName); + } + reply.catf(" uses %s sensor channel %u", sensorType, sensorChannel); +} + +// Configure then heater name, if it is provided +void TemperatureSensor::TryConfigureHeaterName(GCodeBuffer& gb, bool& seen) +{ + char buf[MaxHeaterNameLength + 1]; + bool localSeen = false; + gb.TryGetQuotedString('H', buf, ARRAY_SIZE(buf), localSeen); + if (localSeen) + { + SetHeaterName(buf); + seen = true; + } +} + +// Factory method +TemperatureSensor *TemperatureSensor::Create(unsigned int channel) +{ + TemperatureSensor *ts = nullptr; + if (channel < Heaters) + { + ts = new Thermistor(channel); + } + else if (FirstThermocoupleChannel <= channel && channel < FirstThermocoupleChannel + MaxSpiTempSensors) + { + ts = new ThermocoupleSensor31855(channel); + } + else if (FirstRtdChannel <= channel && channel < FirstRtdChannel + MaxSpiTempSensors) + { + ts = new RtdSensor31865(channel); + } + else if (FirstLinearAdcChannel <= channel && channel < FirstLinearAdcChannel + MaxSpiTempSensors) + { + ts = new CurrentLoopTemperatureSensor(channel); + } +#ifndef __RADDS__ + else if (channel == CpuTemperatureSenseChannel) + { + ts = new CpuTemperatureSensor(channel); + } +#endif +#ifdef DUET_NG + else if (channel >= FirstTmcDriversSenseChannel && channel < FirstTmcDriversSenseChannel + 2) + { + ts = new TmcDriverTemperatureSensor(channel); + } +#endif + + if (ts != nullptr) + { + ts->Init(); + } + return ts; +} + +// End diff --git a/src/Heating/Sensors/TemperatureSensor.h b/src/Heating/Sensors/TemperatureSensor.h new file mode 100644 index 00000000..26aec74e --- /dev/null +++ b/src/Heating/Sensors/TemperatureSensor.h @@ -0,0 +1,55 @@ +#ifndef TEMPERATURESENSOR_H +#define TEMPERATURESENSOR_H + +#include "RepRapFirmware.h" +#include "Heating/TemperatureError.h" // for result codes + +class GCodeBuffer; + +class TemperatureSensor +{ +public: + TemperatureSensor(unsigned int chan, const char *type); + + // Configure the sensor from M305 parameters. + // If we find any parameters, process them and return true. If an error occurs while processing them, set 'error' to true and write an error message to 'reply. + // if we find no relevant parameters, report the current parameters to 'reply' and return 'false'. + virtual bool Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error); + + // Initialise or re-initialise the temperature sensor + virtual void Init() = 0; + + // Try to get a temperature reading + virtual TemperatureError GetTemperature(float& t) = 0; + + // Return the channel number + unsigned int GetSensorChannel() const { return sensorChannel; } + + // Return the sensor type + const char *GetSensorType() const { return sensorType; } + + // Configure then heater name, if it is provided + void TryConfigureHeaterName(GCodeBuffer& gb, bool& seen); + + // Virtual destructor + virtual ~TemperatureSensor(); + + // Set the name - normally called only once + void SetHeaterName(const char *newName); + + // Get the name. Returns nullptr if no name has been assigned. + const char *GetHeaterName() const { return heaterName; } + + // Factory method + static TemperatureSensor *Create(unsigned int channel); + +protected: + void CopyBasicHeaterDetails(unsigned int heater, StringRef& reply) const; + +private: + const unsigned int sensorChannel; + const char * const sensorType; + const char *heaterName; +}; + +#endif // TEMPERATURESENSOR_H diff --git a/src/Heating/Sensors/Thermistor.cpp b/src/Heating/Sensors/Thermistor.cpp new file mode 100644 index 00000000..83e967df --- /dev/null +++ b/src/Heating/Sensors/Thermistor.cpp @@ -0,0 +1,139 @@ +/* + * Thermistor.cpp + * + * Created on: 10 Nov 2016 + * Author: David + */ + +#include "Thermistor.h" +#include "Platform.h" +#include "RepRap.h" +#include "GCodes/GCodeBuffer.h" + +// The Steinhart-Hart equation for thermistor resistance is: +// 1/T = A + B ln(R) + C [ln(R)]^3 +// +// The simplified (beta) equation assumes C=0 and is: +// 1/T = A + (1/Beta) ln(R) +// +// The parameters that can be configured in RRF are R25 (the resistance at 25C), Beta, and optionally C. + +// Create an instance with default values +Thermistor::Thermistor(unsigned int channel) + : TemperatureSensor(channel - FirstThermistorChannel, "Thermistor"), adcLowOffset(0), adcHighOffset(0) +{ + r25 = (channel == FirstThermistorChannel) ? BED_R25 : EXT_R25; + beta = (channel == FirstThermistorChannel) ? BED_BETA : EXT_BETA; + shC = (channel == FirstThermistorChannel) ? BED_SHC : EXT_SHC; + seriesR = THERMISTOR_SERIES_RS; + CalcDerivedParameters(); +} + +void Thermistor::Init() +{ + reprap.GetPlatform().GetThermistorFilter(GetSensorChannel() - FirstThermistorChannel).Init((1 << AdcBits) - 1); +} + +// Configure the temperature sensor +bool Thermistor::Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error) +{ + bool seen = false; + if (mCode == 305) + { + // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first. + + gb.TryGetFValue('T', r25, seen); + gb.TryGetFValue('B', beta, seen); + gb.TryGetFValue('C', shC, seen); + gb.TryGetFValue('R', seriesR, seen); + if (seen) + { + CalcDerivedParameters(); + } + + if (gb.Seen('L')) + { + adcLowOffset = (int8_t)constrain<int>(gb.GetIValue(), -100, 100); + seen = true; + } + if (gb.Seen('H')) + { + adcHighOffset = (int8_t)constrain<int>(gb.GetIValue(), -100, 100); + seen = true; + } + + TryConfigureHeaterName(gb, seen); + + if (!seen && !gb.Seen('X')) + { + CopyBasicHeaterDetails(heater, reply); + reply.catf(", T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d", + r25, beta, shC, seriesR, adcLowOffset, adcHighOffset); + } + } + + return seen; +} + +// Get the temperature +TemperatureError Thermistor::GetTemperature(float& t) +{ + const volatile ThermistorAveragingFilter& filter = reprap.GetPlatform().GetThermistorFilter(GetSensorChannel() - FirstThermistorChannel); + if (filter.IsValid()) + { + const int32_t averagedReading = filter.GetSum()/(ThermistorAverageReadings >> Thermistor::AdcOversampleBits); + const float temp = CalcTemperature(averagedReading); + + if (temp < MinimumConnectedTemperature) + { + // thermistor is disconnected + t = ABS_ZERO; + return TemperatureError::openCircuit; + } + + t = temp; + return TemperatureError::success; + } + + // Filter is not ready yet + t = BAD_ERROR_TEMPERATURE; + return TemperatureError::busBusy; +} + +// Calculate temperature from an ADC reading in the range 0..1 +float Thermistor::CalcTemperature(int32_t adcReading) const +{ + const float denom = (float)(AdcRange + (int)adcHighOffset - adcReading) - 0.5; + if (denom <= 0.0) + { + return ABS_ZERO; + } + const float resistance = seriesR * ((float)(adcReading - (int)adcLowOffset) + 0.5)/denom; + const float logResistance = log(resistance); + const float recipT = shA + shB * logResistance + shC * logResistance * logResistance * logResistance; + return (recipT > 0.0) ? (1.0/recipT) + ABS_ZERO : BAD_ERROR_TEMPERATURE; +} + +// Calculate expected ADC reading at a particular temperature, rounded down as the ADC does +int32_t Thermistor::CalcAdcReading(float temperature) const +{ + const double bDFiv3c = shB/(3.0 * shC); + const double halfY = (shA - 1.0/(temperature - ABS_ZERO))/(2.0 * shC); + const double x = sqrt((bDFiv3c * bDFiv3c * bDFiv3c) + (halfY * halfY)); + const double oneThird = 1.0/3.0; + const float resistance = exp(pow(x - halfY, oneThird) - pow(x + halfY, oneThird)); + const float fraction = resistance/(resistance + seriesR); + const int32_t actualAdcRange = AdcRange + (int)adcHighOffset - (int)adcLowOffset; + const int32_t val = (int32_t)(fraction * (float)actualAdcRange) + (int)adcLowOffset; + return constrain<int>(val, 0, AdcRange - 1); +} + +// Calculate shA and shB from the other parameters +void Thermistor::CalcDerivedParameters() +{ + shB = 1.0/beta; + const double lnR25 = log(r25); + shA = 1.0/(25.0 - ABS_ZERO) - shB * lnR25 - shC * lnR25 * lnR25 * lnR25; +} + +// End diff --git a/src/Heating/Thermistor.h b/src/Heating/Sensors/Thermistor.h index eabc45f9..1c002c34 100644 --- a/src/Heating/Thermistor.h +++ b/src/Heating/Sensors/Thermistor.h @@ -8,7 +8,7 @@ #ifndef SRC_HEATING_THERMISTOR_H_ #define SRC_HEATING_THERMISTOR_H_ -#include "RepRapFirmware.h" +#include "TemperatureSensor.h" // The Steinhart-Hart equation for thermistor resistance is: // 1/T = A + B ln(R) + C [ln(R)]^3 @@ -18,10 +18,15 @@ // // The parameters that can be configured in RRF are R25 (the resistance at 25C), Beta, and optionally C. -class Thermistor +class Thermistor : public TemperatureSensor { public: - Thermistor(); // create an instance with default values + Thermistor(unsigned int channel); // create an instance with default values + bool Configure(unsigned int mCode, unsigned int heater, GCodeBuffer& gb, StringRef& reply, bool& error) override; // configure the sensor from M305 parameters + void Init() override; + TemperatureError GetTemperature(float& t) override; + +private: float CalcTemperature(int32_t adcReading) const; // calculate temperature from an ADC reading in the range 0..1 int32_t CalcAdcReading(float temperature) const; // calculate expected ADC reading at a particular temperature @@ -32,14 +37,12 @@ public: int8_t GetLowOffset() const { return adcLowOffset; } int8_t GetHighOffset() const { return adcHighOffset; } - void SetParameters(float p_r25, float p_beta, float p_shC, float p_seriesR); // initialise the main parameters void SetLowOffset(int8_t p_offset) { adcLowOffset = p_offset; } void SetHighOffset(int8_t p_offset) { adcHighOffset = p_offset; } // For the theory behind ADC oversampling, see http://www.atmel.com/Images/doc8003.pdf static const unsigned int AdcOversampleBits = 1 ; // we use 1-bit oversampling -private: void CalcDerivedParameters(); // calculate shA and shB // The following are configurable parameters diff --git a/src/Heating/Sensors/ThermocoupleSensor31855.cpp b/src/Heating/Sensors/ThermocoupleSensor31855.cpp new file mode 100644 index 00000000..10d6f2bc --- /dev/null +++ b/src/Heating/Sensors/ThermocoupleSensor31855.cpp @@ -0,0 +1,156 @@ +/* + * ThermocoupleSensor31855.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +// MAX31855 thermocouple chip +// +// The MAX31855 continuously samples a Type K thermocouple. When the MAX31855 +// is selected via its chip select (CS) pin, it unconditionally writes a 32 bit +// sequence onto the bus. This sequence is designed such that we need only +// interpret the high 16 bits in order to ascertain the temperature reading and +// whether there a fault condition exists. The low 16 bits provide the +// MAX31855's cold junction temperature (and thus the board temperature) as +// well as finer detail on any existing fault condition. +// +// The temperature read from the chip is a signed, two's-complement integer. +// As it is a 14 bit value (in units of one-quarter degrees Celsius), we +// convert it to a proper signed 16 bit value by adding two high bits. The +// high bits added should both be zero if the value is positive (highest bit +// of the 14 bit value is zero), or both be one if the value is negative +// (highest bit of the 14 bit value is one). +// +// Note Bene: there's a Arduino Due sketch floating about the internet which +// gets this wrong and for negative temperatures generates incorrect values. +// E.g, -2047C for what should be -1C; -1798C for what should be -250C. The +// values of -1C and -250C are shown as examples in Table 4 of the datasheet +// for the MAX21855.) The incorrect Arduino Due sketch appears in, and may be +// from, the book Arduino Sketches: Tools and Techniques for Programming +// Wizardry, James A. Langbridge, January 12, 2015, John Wiley & Sons. + +// Bits -- Interpretation +// ----- ----------------------------------------------------------------- +// 31:18 -- 14 bit, signed thermocouple temperature data. Units of 0.25 C +// 17 -- Reserved +// 16 -- Fault indicator (1 if fault detected; 0 otherwise) +// 15:04 -- 12 bit, signed cold junction temperature data. Units of 0.0625 C +// 03 -- Reserved +// 02 -- SCV fault; reads 1 if the thermocouple is shorted to Vcc +// 01 -- SCG fault; reads 1 if the thermocouple is shorted to ground +// 00 -- OC fault; reads 1 if the thermocouple is not connected (open) + +// For purposes of setting bit transfer widths and timings, we need to use a +// Peripheral Channel Select (PCS). Use channel #3 as it is unlikely to be +// used by anything else as the Arduino Due leaves pin 78 unconnected. +// +#include "ThermocoupleSensor31855.h" +#include "RepRap.h" +#include "Platform.h" +#include "Core.h" + +const uint32_t MAX31855_Frequency = 4000000; // maximum for MAX31855 is 5MHz + +// SPI modes: +// If the inactive state of SCL is LOW (CPOL = 0) (in the case of the MAX31865, this is sampled on the falling edge of CS): +// The MAX31855 sets up the first data bit after the falling edge of CLK, and changes the data on each falling clock edge. +// So the SAM needs to sample data on the rising clock edge. This requires NCPHA = 1. +const uint8_t MAX31855_SpiMode = SPI_MODE_0; + +// Define the minimum interval between readings +const uint32_t MinimumReadInterval = 100; // minimum interval between reads, in milliseconds + +ThermocoupleSensor31855::ThermocoupleSensor31855(unsigned int channel) + : SpiTemperatureSensor(channel, "Thermocouple (MAX31855)", channel - FirstThermocoupleChannel, MAX31855_SpiMode, MAX31855_Frequency) +{ +} + +// Perform the actual hardware initialization for attaching and using this device on the SPI hardware bus. +void ThermocoupleSensor31855::Init() +{ + InitSpi(); + lastReadingTime = millis(); +} + +TemperatureError ThermocoupleSensor31855::GetTemperature(float& t) +{ + if (inInterrupt() || millis() - lastReadingTime < MinimumReadInterval) + { + t = lastTemperature; + } + else + { + uint32_t rawVal; + TemperatureError sts = DoSpiTransaction(nullptr, 4, rawVal); + if (sts != TemperatureError::success) + { + lastResult = sts; + } + else + { + lastReadingTime = millis(); + + if ((rawVal & 0x00020008) != 0) + { + // These two bits should always read 0. Likely the entire read was 0xFF 0xFF which is not uncommon when first powering up + lastResult = TemperatureError::ioError; + } + else if ((rawVal & 0x00010007) != 0) // check the fault bits + { + // Check for three more types of bad reads as we set the response code: + // 1. A read in which the fault indicator bit (16) is set but the fault reason bits (0:2) are all clear; + // 2. A read in which the fault indicator bit (16) is clear, but one or more of the fault reason bits (0:2) are set; and, + // 3. A read in which more than one of the fault reason bits (0:1) are set. + if ((rawVal & 0x00010000) == 0) + { + // One or more fault reason bits are set but the fault indicator bit is clear + lastResult = TemperatureError::ioError; + } + else + { + // At this point we are assured that bit 16 (fault indicator) is set and that at least one of the fault reason bits (0:2) are set. + // We now need to ensure that only one fault reason bit is set. + uint8_t nbits = 0; + if (rawVal & 0x01) + { + // Open Circuit + ++nbits; + lastResult = TemperatureError::openCircuit; + } + if (rawVal & 0x02) + { + // Short to ground; + ++nbits; + lastResult = TemperatureError::shortToGround; + } + if (rawVal && 0x04) + { + // Short to Vcc + ++nbits; + lastResult = TemperatureError::shortToVcc; + } + + if (nbits != 1) + { + // Fault indicator was set but a fault reason was not set (nbits == 0) or too many fault reason bits were set (nbits > 1). + // Assume that a communication error with the MAX31855 has occurred. + lastResult = TemperatureError::ioError; + } + } + } + else + { + rawVal >>= 18; // shift the 14-bit temperature data to the bottom of the word + rawVal |= (0 - (rawVal & 0x2000)); // sign-extend the sign bit + + // And convert to from units of 1/4C to 1C + t = lastTemperature = (float)(0.25 * (float)(int32_t)rawVal); + lastResult = TemperatureError::success; + } + } + } + return lastResult; +} + +// End diff --git a/src/Heating/Sensors/ThermocoupleSensor31855.h b/src/Heating/Sensors/ThermocoupleSensor31855.h new file mode 100644 index 00000000..0b877f62 --- /dev/null +++ b/src/Heating/Sensors/ThermocoupleSensor31855.h @@ -0,0 +1,21 @@ +/* + * ThermocoupleSensor31855.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_THERMOCOUPLESENSOR31855_H_ +#define SRC_HEATING_THERMOCOUPLESENSOR31855_H_ + +#include "SpiTemperatureSensor.h" + +class ThermocoupleSensor31855 : public SpiTemperatureSensor +{ +public: + ThermocoupleSensor31855(unsigned int channel); + void Init() override; + TemperatureError GetTemperature(float& t) override; +}; + +#endif /* SRC_HEATING_THERMOCOUPLESENSOR31855_H_ */ diff --git a/src/Heating/Sensors/TmcDriverTemperatureSensor.cpp b/src/Heating/Sensors/TmcDriverTemperatureSensor.cpp new file mode 100644 index 00000000..fe2ccbf9 --- /dev/null +++ b/src/Heating/Sensors/TmcDriverTemperatureSensor.cpp @@ -0,0 +1,30 @@ +/* + * TmcDriverTemperatureSensor.cpp + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#include "TmcDriverTemperatureSensor.h" +#include "Platform.h" +#include "RepRap.h" + +#ifdef DUET_NG + +TmcDriverTemperatureSensor::TmcDriverTemperatureSensor(unsigned int channel) : TemperatureSensor(channel, "TMC2660 temperature warnings") +{ +} + +void TmcDriverTemperatureSensor::Init() +{ +} + +TemperatureError TmcDriverTemperatureSensor::GetTemperature(float& t) +{ + t = reprap.GetPlatform().GetTmcDriversTemperature(GetSensorChannel() - FirstTmcDriversSenseChannel); + return TemperatureError::success; +} + +#endif + +// End diff --git a/src/Heating/Sensors/TmcDriverTemperatureSensor.h b/src/Heating/Sensors/TmcDriverTemperatureSensor.h new file mode 100644 index 00000000..be18dd5c --- /dev/null +++ b/src/Heating/Sensors/TmcDriverTemperatureSensor.h @@ -0,0 +1,25 @@ +/* + * TmcDriverTemperatureSensor.h + * + * Created on: 8 Jun 2017 + * Author: David + */ + +#ifndef SRC_HEATING_SENSORS_TMCDRIVERTEMPERATURESENSOR_H_ +#define SRC_HEATING_SENSORS_TMCDRIVERTEMPERATURESENSOR_H_ + +#include "TemperatureSensor.h" + +#ifdef DUET_NG + +class TmcDriverTemperatureSensor : public TemperatureSensor +{ +public: + TmcDriverTemperatureSensor(unsigned int channel); + void Init() override; + TemperatureError GetTemperature(float& t) override; +}; + +#endif + +#endif /* SRC_HEATING_SENSORS_TMCDRIVERTEMPERATURESENSOR_H_ */ diff --git a/src/Heating/TemperatureError.cpp b/src/Heating/TemperatureError.cpp index 294c37bd..f50d1c0c 100644 --- a/src/Heating/TemperatureError.cpp +++ b/src/Heating/TemperatureError.cpp @@ -23,6 +23,8 @@ const char* TemperatureErrorString(TemperatureError err) case TemperatureError::busBusy: return "sensor bus busy"; case TemperatureError::badResponse: return "bad response from sensor"; case TemperatureError::unknownChannel: return "unknown temperature sensor channel"; + case TemperatureError::notInitialised: return "sensor not initialised"; + case TemperatureError::unknownHeater: return "unknown heater"; default: return "unknown temperature sense error"; } } diff --git a/src/Heating/TemperatureError.h b/src/Heating/TemperatureError.h index c9f3a09a..24ae9a41 100644 --- a/src/Heating/TemperatureError.h +++ b/src/Heating/TemperatureError.h @@ -24,7 +24,9 @@ enum class TemperatureError : uint8_t hardwareError, busBusy, badResponse, - unknownChannel + unknownChannel, + notInitialised, + unknownHeater }; const char* TemperatureErrorString(TemperatureError err); diff --git a/src/Heating/TemperatureSensor.cpp b/src/Heating/TemperatureSensor.cpp deleted file mode 100644 index 74a9c399..00000000 --- a/src/Heating/TemperatureSensor.cpp +++ /dev/null @@ -1,449 +0,0 @@ -#include "TemperatureSensor.h" -#include "Platform.h" -#include "RepRap.h" - -// MAX31855 thermocouple chip -// -// The MAX31855 continuously samples a Type K thermocouple. When the MAX31855 -// is selected via its chip select (CS) pin, it unconditionally writes a 32 bit -// sequence onto the bus. This sequence is designed such that we need only -// interpret the high 16 bits in order to ascertain the temperature reading and -// whether there a fault condition exists. The low 16 bits provide the -// MAX31855's cold junction temperature (and thus the board temperature) as -// well as finer detail on any existing fault condition. -// -// The temperature read from the chip is a signed, two's-complement integer. -// As it is a 14 bit value (in units of one-quarter degrees Celsius), we -// convert it to a proper signed 16 bit value by adding two high bits. The -// high bits added should both be zero if the value is positive (highest bit -// of the 14 bit value is zero), or both be one if the value is negative -// (highest bit of the 14 bit value is one). -// -// Note Bene: there's a Arduino Due sketch floating about the internet which -// gets this wrong and for negative temperatures generates incorrect values. -// E.g, -2047C for what should be -1C; -1798C for what should be -250C. The -// values of -1C and -250C are shown as examples in Table 4 of the datasheet -// for the MAX21855.) The incorrect Arduino Due sketch appears in, and may be -// from, the book Arduino Sketches: Tools and Techniques for Programming -// Wizardry, James A. Langbridge, January 12, 2015, John Wiley & Sons. - -// Bits -- Interpretation -// ----- ----------------------------------------------------------------- -// 31:18 -- 14 bit, signed thermocouple temperature data. Units of 0.25 C -// 17 -- Reserved -// 16 -- Fault indicator (1 if fault detected; 0 otherwise) -// 15:04 -- 12 bit, signed cold junction temperature data. Units of 0.0625 C -// 03 -- Reserved -// 02 -- SCV fault; reads 1 if the thermocouple is shorted to Vcc -// 01 -- SCG fault; reads 1 if the thermocouple is shorted to ground -// 00 -- OC fault; reads 1 if the thermocouple is not connected (open) - -// For purposes of setting bit transfer widths and timings, we need to use a -// Peripheral Channel Select (PCS). Use channel #3 as it is unlikely to be -// used by anything else as the Arduino Due leaves pin 78 unconnected. -// -// No warranty given or implied, use at your own risk. -// dan.newman@mtbaldy.us -// GPL v3 - -const uint32_t MAX31855_Frequency = 4000000; // maximum for MAX31855 is 5MHz -const uint32_t MAX31865_Frequency = 4000000; // maximum for MAX31865 is also 5MHz -const uint32_t MCP3204_Frequency = 1000000; // maximum for MCP3204 is 1MHz @ 2.7V, will be slightly higher at 3.3V - -// SPI modes: -// If the inactive state of SCL is LOW (CPOL = 0) (in the case of the MAX31865, this is sampled on the falling edge of CS): -// The MAX31855 sets up the first data bit after the falling edge of CLK, and changes the data on each falling clock edge. -// So the SAM needs to sample data on the rising clock edge. This requires NCPHA = 1. -// The MAX31865 changes data after the rising edge of CLK, and samples input data on the falling edge. -// This requires NCPHA = 0. -// The MCP3204 samples input data on the rising edge and changes the output data on the rising edge. - -const uint8_t MAX31855_SpiMode = SPI_MODE_0; -const uint8_t MAX31865_SpiMode = SPI_MODE_1; -const uint8_t MCP3204_SpiMode = SPI_MODE_0; - -// Define the minimum interval between readings. The MAX31865 needs 62.5ms in 50Hz filter mode. -const uint32_t MinimumReadInterval = 100; // minimum interval between reads, in milliseconds - -// Table of temperature vs. MAX31865 result for PT100 thermistor, from the MAX31865 datasheet -struct TempTableEntry -{ - int16_t temperature; - uint16_t adcReading; -}; - -static const TempTableEntry tempTable[] = -{ - {-30, 7227}, - {-20, 7550}, - {-10, 7871}, - {0, 8192}, - {10, 8512}, - {20, 8830}, - {30, 9148}, - {40, 9465}, - {50, 9781}, - {60, 10096}, - {70, 10410}, - {80, 10723}, - {90, 11035}, - {100, 11346}, - {110, 11657}, - {120, 11966}, - {130, 12274}, - {140, 12582}, - {150, 12888}, - {160, 13194}, - {170, 13498}, - {180, 13802}, - {190, 14104}, - {200, 14406}, - {225, 15156}, - {250, 15901}, - {275, 16639}, - {300, 17371}, - {325, 18098}, - {350, 18818}, - {375, 19533}, - {400, 20242}, - {425, 20945}, - {450, 21642}, - {475, 22333}, - {500, 23018}, - {525, 23697}, - {550, 24370} -}; - -const size_t NumTempTableEntries = sizeof(tempTable)/sizeof(tempTable[0]); - -// Perform the actual hardware initialization for attaching and using this device on the SPI hardware bus. -void TemperatureSensor::InitThermocouple(uint8_t cs) -{ - device.csPin = cs; - device.spiMode = MAX31855_SpiMode; - device.clockFrequency = MAX31855_Frequency; - sspi_master_init(&device, 8); - - lastReadingTime = millis(); - lastResult = TemperatureError::success; - lastTemperature = 0.0; -} - -// Perform the actual hardware initialization for attaching and using this device on the SPI hardware bus. -void TemperatureSensor::InitRtd(uint8_t cs) -{ - device.csPin = cs; - device.spiMode = MAX31865_SpiMode; - device.clockFrequency = MAX31865_Frequency; - sspi_master_init(&device, 8); - - TemperatureError rslt; - for (unsigned int i = 0; i < 3; ++i) // try 3 times - { - rslt = TryInitRtd(); - if (rslt == TemperatureError::success) - { - break; - } - delay(MinimumReadInterval); - } - - lastReadingTime = millis(); - lastResult = rslt; - lastTemperature = 0.0; - - if (rslt != TemperatureError::success) - { - reprap.GetPlatform().MessageF(GENERIC_MESSAGE, "Error: failed to initialise RTD: %s\n", TemperatureErrorString(rslt)); - } -} - -// Try to initialise the RTD -TemperatureError TemperatureSensor::TryInitRtd() const -{ - // Note that to get the MAX31865 to do continuous conversions, we need to set the bias bit as well as the continuous-conversion bit - static const uint8_t modeData[2] = { 0x80, 0xC3 }; // write register 0, bias on, auto conversion, clear errors, 50Hz - uint32_t rawVal; - TemperatureError sts = DoSpiTransaction(modeData, 2, rawVal); - - if (sts == TemperatureError::success) - { - static const uint8_t readData[2] = { 0x00, 0x00 }; // read register 0 - sts = DoSpiTransaction(readData, 2, rawVal); - } - - //debugPrintf("Status %d data %04x\n", (int)sts, rawVal); - return (sts == TemperatureError::success && (uint8_t)rawVal != 0xC1) - ? TemperatureError::badResponse - : sts; -} - -// Initialise the linear ADC -void TemperatureSensor::InitLinearAdc(uint8_t cs) -{ - device.csPin = cs; - device.spiMode = MCP3204_SpiMode; - device.clockFrequency = MCP3204_Frequency; - sspi_master_init(&device, 8); - - for (unsigned int i = 0; i < 3; ++i) // try 3 times - { - TryGetLinearAdcTemperature(); - if (lastResult == TemperatureError::success) - { - break; - } - delay(MinimumReadInterval); - } - - lastReadingTime = millis(); - - if (lastResult != TemperatureError::success) - { - reprap.GetPlatform().MessageF(GENERIC_MESSAGE, "Error: failed to initialise daughter board ADC: %s\n", TemperatureErrorString(lastResult)); - } - -} - -TemperatureError TemperatureSensor::GetThermocoupleTemperature(float& t) -{ - if (inInterrupt() || millis() - lastReadingTime < MinimumReadInterval) - { - t = lastTemperature; - } - else - { - uint32_t rawVal; - TemperatureError sts = DoSpiTransaction(nullptr, 4, rawVal); - if (sts != TemperatureError::success) - { - lastResult = sts; - } - else - { - lastReadingTime = millis(); - - if ((rawVal & 0x00020008) != 0) - { - // These two bits should always read 0. Likely the entire read was 0xFF 0xFF which is not uncommon when first powering up - lastResult = TemperatureError::ioError; - } - else if ((rawVal & 0x00010007) != 0) // check the fault bits - { - // Check for three more types of bad reads as we set the response code: - // 1. A read in which the fault indicator bit (16) is set but the fault reason bits (0:2) are all clear; - // 2. A read in which the fault indicator bit (16) is clear, but one or more of the fault reason bits (0:2) are set; and, - // 3. A read in which more than one of the fault reason bits (0:1) are set. - if ((rawVal & 0x00010000) == 0) - { - // One or more fault reason bits are set but the fault indicator bit is clear - lastResult = TemperatureError::ioError; - } - else - { - // At this point we are assured that bit 16 (fault indicator) is set and that at least one of the fault reason bits (0:2) are set. - // We now need to ensure that only one fault reason bit is set. - uint8_t nbits = 0; - if (rawVal & 0x01) - { - // Open Circuit - ++nbits; - lastResult = TemperatureError::openCircuit; - } - if (rawVal & 0x02) - { - // Short to ground; - ++nbits; - lastResult = TemperatureError::shortToGround; - } - if (rawVal && 0x04) - { - // Short to Vcc - ++nbits; - lastResult = TemperatureError::shortToVcc; - } - - if (nbits != 1) - { - // Fault indicator was set but a fault reason was not set (nbits == 0) or too many fault reason bits were set (nbits > 1). - // Assume that a communication error with the MAX31855 has occurred. - lastResult = TemperatureError::ioError; - } - } - } - else - { - rawVal >>= 18; // shift the 14-bit temperature data to the bottom of the word - rawVal |= (0 - (rawVal & 0x2000)); // sign-extend the sign bit - - // And convert to from units of 1/4C to 1C - t = lastTemperature = (float)(0.25 * (float)(int32_t)rawVal); - lastResult = TemperatureError::success; - } - } - } - return lastResult; -} - -TemperatureError TemperatureSensor::GetRtdTemperature(float& t) -{ - if (inInterrupt() || millis() - lastReadingTime < MinimumReadInterval) - { - t = lastTemperature; - } - else - { - static const uint8_t dataOut[4] = {0, 55, 55, 55}; // read registers 0 (control), 1 (MSB) and 2 (LSB) - uint32_t rawVal; - TemperatureError sts = DoSpiTransaction(dataOut, 4, rawVal); - - if (sts != TemperatureError::success) - { - lastResult = sts; - } - else - { - lastReadingTime = millis(); - if (((rawVal & 0x00C10000) != 0xC10000) -#if 0 - // We no longer check the error status bit, because it seems to be impossible to clear it once it has been set. - // Perhaps we would need to exit continuous reading mode to do so, and then re-enable it afterwards. But this would - // take to long. -#else - || (rawVal & 1) != 0 -#endif - ) - { - // Either the continuous conversion bit has got cleared, or the fault bit has been set - TryInitRtd(); - lastResult = TemperatureError::hardwareError; - } - else - { - uint16_t adcVal = (rawVal >> 1) & 0x7FFF; - - // Formally-verified binary search routine, adapted from one of the eCv examples - size_t low = 0u, high = NumTempTableEntries; - while (high > low) - keep(low <= high; high <= NumTempTableEntries) - keep(low == 0u || tempTable[low - 1u].adcReading < adcVal) - keep(high == NumTempTableEntries || adcVal <= tempTable[high].adcReading) - decrease(high - low) - { - size_t mid = (high - low)/2u + low; // get the mid point, avoiding arithmetic overflow - if (adcVal <= tempTable[mid].adcReading) - { - high = mid; - } - else - { - low = mid + 1u; - } - } - assert(low <= NumTempTableEntries); - assert(low == 0 || tempTable[low - 1] < adcVal); - assert(low == NumTempTableEntries || adcVal <= tempTable[low]); - - if (low == 0) // if off the bottom of the table - { - lastResult = TemperatureError::shortCircuit; - } - else if (low >= NumTempTableEntries) // if off the top of the table - { - lastResult = TemperatureError::openCircuit; - } - else - { - const float interpolationFraction = (float)(adcVal - tempTable[low - 1].adcReading)/(float)(tempTable[low].adcReading - tempTable[low - 1].adcReading); - t = lastTemperature = ((float)(tempTable[low].temperature - tempTable[low - 1].temperature) * interpolationFraction) - + (float)tempTable[low - 1].temperature; - //debugPrintf("raw %u low %u interp %f temp %f\n", adcVal, low, interpolationFraction, *t); - lastResult = TemperatureError::success; - } - } - } - } - return lastResult; -} - -TemperatureError TemperatureSensor::GetLinearAdcTemperature(float& t) -{ - if (!inInterrupt() && millis() - lastReadingTime >= MinimumReadInterval) - { - TryGetLinearAdcTemperature(); - } - - t = lastTemperature; - return lastResult; -} - -// Try to get a temperature reading from the linear ADC by doing an SPI transaction -void TemperatureSensor::TryGetLinearAdcTemperature() -{ - // The MCP3204 waits for a high input input bit before it does anything. Call this clock 1. - // The next input bit it high for single-ended operation, low for differential. This is clock 2. - // The next 3 input bits are the channel selection bits. These are clocks 3..5. - // Clock 6 produces a null bit on its trailing edge, which is read by the processor on clock 7. - // Clocks 7..18 produce data bits B11..B0 on their trailing edges, which are read by the MCU on the leading edges of clocks 8-19. - // If we supply further clocks, then clocks 18..29 are the same data but LSB first, omitting bit 0. - // Clocks 30 onwards will be zeros. - // So we need to use at least 19 clocks. We round this up to 24 clocks, and we check that the extra 5 bits we receive are the 5 least significant data bits in reverse order. - - static const uint8_t adcData[] = { 0xC0, 0x00, 0x00 }; // start bit, single ended, channel 0 - uint32_t rawVal; - lastResult = DoSpiTransaction(adcData, 3, rawVal); - //debugPrintf("ADC data %u\n", rawVal); - - if (lastResult == TemperatureError::success) - { - const uint32_t adcVal1 = (rawVal >> 5) & ((1 << 13) - 1); - const uint32_t adcVal2 = ((rawVal & 1) << 5) | ((rawVal & 2) << 3) | ((rawVal & 4) << 1) | ((rawVal & 8) >> 1) | ((rawVal & 16) >> 3) | ((rawVal & 32) >> 5); - if (adcVal1 >= 4096 || adcVal2 != (adcVal1 & ((1 << 6) - 1))) - { - lastResult = TemperatureError::badResponse; - } - else - { - lastTemperature = MinLinearAdcTemp + (LinearAdcDegCPerCount * (float)adcVal1); - } - } -} - -// Send and receive 1 to 4 bytes of data and return the result as a single 32-bit word -TemperatureError TemperatureSensor::DoSpiTransaction(const uint8_t dataOut[], size_t nbytes, uint32_t& rslt) const -{ - if (!sspi_acquire()) - { - return TemperatureError::busBusy; - } - - sspi_master_setup_device(&device); - delayMicroseconds(1); - sspi_select_device(&device); - delayMicroseconds(1); - - uint8_t rawBytes[4]; - spi_status_t sts = sspi_transceive_packet(dataOut, rawBytes, nbytes); - - delayMicroseconds(1); - sspi_deselect_device(&device); - delayMicroseconds(1); - - sspi_release(); - - if (sts != SPI_OK) - { - return TemperatureError::timeout; - } - - rslt = rawBytes[0]; - for (size_t i = 1; i < nbytes; ++i) - { - rslt <<= 8; - rslt |= rawBytes[i]; - } - - return TemperatureError::success; -} - -// End diff --git a/src/Heating/TemperatureSensor.h b/src/Heating/TemperatureSensor.h deleted file mode 100644 index d54338df..00000000 --- a/src/Heating/TemperatureSensor.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TEMPERATURESENSOR_H -#define TEMPERATURESENSOR_H - -#include "RepRapFirmware.h" -#include "TemperatureError.h" // for result codes -#include "SharedSpi.h" // for sspi_device - -class TemperatureSensor -{ -public: - TemperatureSensor() {} - void InitThermocouple(uint8_t cs); - void InitRtd(uint8_t cs); - void InitLinearAdc(uint8_t cs); - TemperatureError GetThermocoupleTemperature(float& t); - TemperatureError GetRtdTemperature(float& t); - TemperatureError GetLinearAdcTemperature(float& t); - -private: - TemperatureError DoSpiTransaction(const uint8_t dataOut[], size_t nbytes, uint32_t& rslt) const; - TemperatureError TryInitRtd() const; - void TryGetLinearAdcTemperature(); - - sspi_device device; - uint32_t lastReadingTime; - float lastTemperature; - TemperatureError lastResult; - - static constexpr float MinLinearAdcTemp = 385.0 - (1600.0 - 385.0) * (4.0/16.0); - static constexpr float LinearAdcDegCPerCount = (1600.0 - 385.0)/3200.0; -}; - -#endif // TEMPERATURESENSOR_H diff --git a/src/Heating/Thermistor.cpp b/src/Heating/Thermistor.cpp deleted file mode 100644 index d8357573..00000000 --- a/src/Heating/Thermistor.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Thermistor.cpp - * - * Created on: 10 Nov 2016 - * Author: David - */ - -#include "Thermistor.h" - -// The Steinhart-Hart equation for thermistor resistance is: -// 1/T = A + B ln(R) + C [ln(R)]^3 -// -// The simplified (beta) equation assumes C=0 and is: -// 1/T = A + (1/Beta) ln(R) -// -// The parameters that can be configured in RRF are R25 (the resistance at 25C), Beta, and optionally C. - -// Create an instance with default values -Thermistor::Thermistor() : adcLowOffset(0), adcHighOffset(0) -{ - SetParameters(EXT_R25, EXT_BETA, EXT_SHC, THERMISTOR_SERIES_RS); -} - -// Initialise the instance -void Thermistor::SetParameters(float p_r25, float p_beta, float p_shC, float p_seriesR) -{ - r25 = p_r25; - beta = p_beta; - shC = p_shC; - seriesR = p_seriesR; - CalcDerivedParameters(); -} - -// Calculate temperature from an ADC reading in the range 0..1 -float Thermistor::CalcTemperature(int32_t adcReading) const -{ - const float denom = (float)(AdcRange + (int)adcHighOffset - adcReading) - 0.5; - if (denom <= 0.0) - { - return ABS_ZERO; - } - const float resistance = seriesR * ((float)(adcReading - (int)adcLowOffset) + 0.5)/denom; - const float logResistance = log(resistance); - const float recipT = shA + shB * logResistance + shC * logResistance * logResistance * logResistance; - return (recipT > 0.0) ? (1.0/recipT) + ABS_ZERO : BAD_ERROR_TEMPERATURE; -} - -// Calculate expected ADC reading at a particular temperature, rounded down as the ADC does -int32_t Thermistor::CalcAdcReading(float temperature) const -{ - const double bDFiv3c = shB/(3.0 * shC); - const double halfY = (shA - 1.0/(temperature - ABS_ZERO))/(2.0 * shC); - const double x = sqrt((bDFiv3c * bDFiv3c * bDFiv3c) + (halfY * halfY)); - const double oneThird = 1.0/3.0; - const float resistance = exp(pow(x - halfY, oneThird) - pow(x + halfY, oneThird)); - const float fraction = resistance/(resistance + seriesR); - const int32_t actualAdcRange = AdcRange + (int)adcHighOffset - (int)adcLowOffset; - const int32_t val = (int32_t)(fraction * (float)actualAdcRange) + (int)adcLowOffset; - return constrain<int>(val, 0, AdcRange - 1); -} - -// Calculate shA and shB from the other parameters -void Thermistor::CalcDerivedParameters() -{ - shB = 1.0/beta; - const double lnR25 = log(r25); - shA = 1.0/(25.0 - ABS_ZERO) - shB * lnR25 - shC * lnR25 * lnR25 * lnR25; -} - -// End |