diff options
author | David Crocker <dcrocker@eschertech.com> | 2016-04-24 13:44:32 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2016-04-24 13:44:32 +0300 |
commit | 1a0ce16128371645ff8fca34630e791f884e330e (patch) | |
tree | c403c2f508f45025e4bd74294ee6cc50da0e9242 /src | |
parent | 3236714418b7c83a388de3a81c4326cb3814a59c (diff) |
Added support for PT100 and other RTDs
Added support for RTDs using the MAX31865 interface chip
Diffstat (limited to 'src')
-rw-r--r-- | src/Configuration.h | 13 | ||||
-rw-r--r-- | src/Duet/Pins_duet.h | 15 | ||||
-rw-r--r-- | src/ExternalDrivers.cpp | 57 | ||||
-rw-r--r-- | src/GCodeBuffer.cpp | 8 | ||||
-rw-r--r-- | src/GCodes.cpp | 87 | ||||
-rw-r--r-- | src/Heat.cpp | 127 | ||||
-rw-r--r-- | src/Heat.h | 99 | ||||
-rw-r--r-- | src/Libraries/MAX31855/MAX31855.cpp | 211 | ||||
-rw-r--r-- | src/Libraries/MAX31855/MAX31855.h | 30 | ||||
-rw-r--r-- | src/Libraries/TemperatureSensor/TemperatureSensor.cpp | 384 | ||||
-rw-r--r-- | src/Libraries/TemperatureSensor/TemperatureSensor.h | 27 | ||||
-rw-r--r-- | src/Platform.cpp | 92 | ||||
-rw-r--r-- | src/Platform.h | 48 | ||||
-rw-r--r-- | src/Reprap.cpp | 22 | ||||
-rw-r--r-- | src/TemperatureError.cpp | 44 | ||||
-rw-r--r-- | src/TemperatureError.h | 35 |
16 files changed, 791 insertions, 508 deletions
diff --git a/src/Configuration.h b/src/Configuration.h index 25e2abab..68721557 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -30,17 +30,13 @@ Licence: GPL #endif #ifndef DATE -# define DATE "2016-04-21" +# define DATE "2016-04-24" #endif #define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" #define FLASH_SAVE_ENABLED (1) -// If enabled, the following control the use of the optional ExternalDrivers module -//#define EXTERNAL_DRIVERS (1) -//#define FIRST_EXTERNAL_DRIVE (5) - // Other firmware that we might switch to be compatible with. enum Compatibility @@ -87,9 +83,12 @@ const float TIME_TO_HOT = 150.0; // Seconds const uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported const float BAD_LOW_TEMPERATURE = -10.0; // Celsius -const float DEFAULT_TEMPERATURE_LIMIT = 300.0; // Celsius +const float DEFAULT_TEMPERATURE_LIMIT = 260.0; // Celsius const float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on -const float BAD_ERROR_TEMPERATURE = 2000.0; // must exceed DEFAULT_TEMPERATURE_LIMIT +const float BAD_ERROR_TEMPERATURE = 2000.0; // Must exceed any reasonable 5temperature limit including DEFAULT_TEMPERATURE_LIMIT + +const unsigned int FirstThermocoupleChannel = 100; // Temperature sensor channels 100.. are thermocouples +const unsigned int FirstRtdChannel = 200; // Temperature sensor channels 200... are RTDs // PWM frequencies diff --git a/src/Duet/Pins_duet.h b/src/Duet/Pins_duet.h index 8eefabd4..b6f21512 100644 --- a/src/Duet/Pins_duet.h +++ b/src/Duet/Pins_duet.h @@ -17,6 +17,10 @@ const size_t DRIVES = 9; // The number of drives in the machine, including X, Y, and Z plus extruder drives #define DRIVES_(a,b,c,d,e,f,g,h,i) { a,b,c,d,e,f,g,h,i } +// If enabled, the following control the use of the optional ExternalDrivers module +//#define EXTERNAL_DRIVERS (1) +//#define FIRST_EXTERNAL_DRIVE (5) + const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the heated bed even if there isn't one #define HEATERS_(a,b,c,d,e,f,g) { a,b,c,d,e,f,g } @@ -66,23 +70,22 @@ const float EXT_BETA = 4138.0; // Thermistor series resistor value in Ohms const float THERMISTOR_SERIES_RS = 1000.0; -// Number of MAX31855 chips to support -const size_t MAX31855_START_CHANNEL = 100; +// Number of SPI temperature sensors to support #if SUPPORT_ROLAND // chrishamm's pin assignments -const size_t MAX31855_DEVICES = 2; +const size_t MaxSpiTempSensors = 2; // Digital pins the 31855s have their select lines tied to -const Pin MAX31855_CS_PINS[MAX31855_DEVICES] = { 77, 87 }; +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 77, 87 }; #else -const size_t MAX31855_DEVICES = 4; +const size_t MaxSpiTempSensors = 4; // Digital pins the 31855s have their select lines tied to -const Pin MAX31855_CS_PINS[MAX31855_DEVICES] = { 77, 87, 16, 17 }; +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 77, 87, 16, 17 }; #endif diff --git a/src/ExternalDrivers.cpp b/src/ExternalDrivers.cpp index 69d8656b..a8a6c92c 100644 --- a/src/ExternalDrivers.cpp +++ b/src/ExternalDrivers.cpp @@ -26,12 +26,12 @@ const size_t NumExternalDrivers = DRIVES - FIRST_EXTERNAL_DRIVE; // CLK 23 connect to ground 2 (GND // 5V_USB 5 +3.3V 3 (+3.3V) -// Connections between DuetNG 0.6 and TMC2660-EVAL board: +// Connections between DuetNG 0.6 and TMC2660-EVAL board (now using USART0): // Driver signal name Eval board pin Our signal name DuetNG 0.6 expansion connector pin # -// SDI 29 SPI1_MOSI 29 (SPI1_MOSI) -// SDO 28 SPI1_MISO 30 (SPI1_MISO) -// SCK 27 SPI1_SCLK 28 (SPI1_SCLK) +// SDI 29 SPI1_MOSI 13 (SPI0_MOSI) was 29 +// SDO 28 SPI1_MISO 14 (SPI0_MISO) was 30 +// SCK 27 SPI1_SCLK 12 (SPI0_SCLK) was 28 // /CS 24 /CS 24 (E2_EN) // GND 2,3,43,44 GND 2 (GND) // INT_STEP 19 E2_STEP 19 (E2_STEP) @@ -41,18 +41,32 @@ const size_t NumExternalDrivers = DRIVES - FIRST_EXTERNAL_DRIVE; // 5V_USB 5 +3.3V 3 (+3.3V) #ifdef DUET_NG +# if 1 +// Pin assignments for the first prototype, using USART0 SPI +const Pin DriversMosiPin = 27; // PB1 +const Pin DriversMisoPin = 26; // PB0 +const Pin DriversSclkPin = 30; // PB13 +# define USART_EXT_DRV USART0 +# define ID_USART_EXT_DRV ID_USART0 +# else +// Pin assignments for the second prototype, using USART1 SPI const Pin DriversMosiPin = 22; // PA13 const Pin DriversMisoPin = 21; // PA22 const Pin DriversSclkPin = 23; // PA23 +# define USART_EXT_DRV USART1 +# define ID_USART_EXT_DRV ID_USART1 +# endif const Pin DriverSelectPins[NumExternalDrivers] = {87, 88, 89, 90}; #else const Pin DriversMosiPin = 16; // PA13 const Pin DriversMisoPin = 17; // PA12 const Pin DriversSclkPin = 54; // PA16 const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ }; +# define USART_EXT_DRV USART1 +# define ID_USART_EXT_DRV ID_USART1 #endif -const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock +const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock for now // TMC2660 registers const uint32_t TMC_REG_DRVCTRL = 0; @@ -176,25 +190,25 @@ const uint32_t defaultSmartEnReg = // Send an SPI control string. The drivers need 20 bits. We send and receive 24 because the USART only supports 5 to 9 bit transfers. uint32_t SpiSendWord(uint32_t pin, uint32_t dataOut) { - USART1->US_CR = US_CR_RSTRX | US_CR_RSTTX; // reset transmitter and receiver + USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX; // reset transmitter and receiver digitalWrite(pin, LOW); // set CS low delayMicroseconds(1); // allow some CS low setup time - USART1->US_CR = US_CR_RXEN | US_CR_TXEN; // enable transmitter and receiver + USART_EXT_DRV->US_CR = US_CR_RXEN | US_CR_TXEN; // enable transmitter and receiver uint32_t dataIn = 0; for (int i = 0; i < 3; ++i) { - USART1->US_THR = (dataOut >> 16) & 0x000000FFu; + USART_EXT_DRV->US_THR = (dataOut >> 16) & 0x000000FFu; dataOut <<= 8; dataIn <<= 8; - for (int j = 0; j < 10000 && (USART1->US_CSR & (US_CSR_RXRDY | US_CSR_TXRDY)) != (US_CSR_RXRDY | US_CSR_TXRDY); ++j) + for (int j = 0; j < 10000 && (USART_EXT_DRV->US_CSR & (US_CSR_RXRDY | US_CSR_TXRDY)) != (US_CSR_RXRDY | US_CSR_TXRDY); ++j) { // nothing } - dataIn |= USART1->US_RHR & 0x000000FF; + dataIn |= USART_EXT_DRV->US_RHR & 0x000000FF; } delayMicroseconds(1); digitalWrite(pin, HIGH); // set CS high again - USART1->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS; // reset and disable transmitter and receiver + USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS; // reset and disable transmitter and receiver delayMicroseconds(1); // ensure it stays high for long enough before the next write return (dataIn >> 4) & 0x000FFFFF; } @@ -315,10 +329,6 @@ namespace ExternalDrivers void Init() { // Set up the SPI pins -#ifndef DUET_NG - // PinS AD0 and AD7 may have already be set up as an ADC pin by the Arduino core, so undo that here or we won't get a clock output - ADC->ADC_CHDR = (1 << 7); -#endif #ifdef DUET_NG // The pins are already set up for SPI in the pins table @@ -326,6 +336,9 @@ namespace ExternalDrivers ConfigurePin(GetPinDescription(DriversMisoPin)); ConfigurePin(GetPinDescription(DriversSclkPin)); #else + // PinS AD0 and AD7 may have already be set up as an ADC pin by the Arduino core, so undo that here or we won't get a clock output + ADC->ADC_CHDR = (1 << 7); + const PinDescription& pin2 = GetPinDescription(DriversMosiPin); pio_configure(pin2.pPort, PIO_PERIPH_A, pin2.ulPin, PIO_DEFAULT); const PinDescription& pin3 = GetPinDescription(DriversMisoPin); @@ -335,7 +348,7 @@ namespace ExternalDrivers #endif // Enable the clock to UART1 - pmc_enable_periph_clk(ID_USART1); + pmc_enable_periph_clk(ID_USART_EXT_DRV); // Set up the CS pins and set them all high // When this becomes the standard code, we must set up the STEP and DIR pins here too. @@ -345,17 +358,17 @@ namespace ExternalDrivers pinMode(DriverSelectPins[drive], OUTPUT); } - // Set USART1 in SPI mode, with data changing on the falling edge of the clock and captured on the rising edge - USART1->US_IDR = ~0u; - USART1->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS; - USART1->US_MR = US_MR_USART_MODE_SPI_MASTER + // Set USART_EXT_DRV in SPI mode, with data changing on the falling edge of the clock and captured on the rising edge + USART_EXT_DRV->US_IDR = ~0u; + USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS; + USART_EXT_DRV->US_MR = US_MR_USART_MODE_SPI_MASTER | US_MR_USCLKS_MCK | US_MR_CHRL_8_BIT | US_MR_CHMODE_NORMAL | US_MR_CPOL | US_MR_CLKO; - USART1->US_BRGR = VARIANT_MCK/DriversSpiClockFrequency; // 1MHz SPI clock - USART1->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS | US_CR_RSTSTA; + USART_EXT_DRV->US_BRGR = VARIANT_MCK/DriversSpiClockFrequency; // 1MHz SPI clock + USART_EXT_DRV->US_CR = US_CR_RSTRX | US_CR_RSTTX | US_CR_RXDIS | US_CR_TXDIS | US_CR_RSTSTA; // We need a few microseconds of delay here foe the USART to sort itself out, // otherwise the processor generates two short reset pulses on its own NRST pin, and resets itself. diff --git a/src/GCodeBuffer.cpp b/src/GCodeBuffer.cpp index 1beaaa0e..724b779b 100644 --- a/src/GCodeBuffer.cpp +++ b/src/GCodeBuffer.cpp @@ -319,6 +319,7 @@ const char* GCodeBuffer::GetString() // preference use GetString() which requires the string to have // been preceded by a tag letter. +// If no string was provided, it produces an error message if the string was not optional, and returns nullptr. const char* GCodeBuffer::GetUnprecedentedString(bool optional) { readPointer = 0; @@ -330,12 +331,11 @@ const char* GCodeBuffer::GetUnprecedentedString(bool optional) if (gcodeBuffer[readPointer] == 0) { readPointer = -1; - if (optional) + if (!optional) { - return nullptr; + platform->Message(GENERIC_MESSAGE, "Error: GCodes: String expected but not seen.\n"); } - platform->Message(GENERIC_MESSAGE, "Error: GCodes: String expected but not seen.\n"); - return gcodeBuffer; // Good idea? + return nullptr; } const char* result = &gcodeBuffer[readPointer + 1]; diff --git a/src/GCodes.cpp b/src/GCodes.cpp index b6140b28..e1a04c24 100644 --- a/src/GCodes.cpp +++ b/src/GCodes.cpp @@ -2296,7 +2296,8 @@ void GCodes::SetHeaterParameters(GCodeBuffer *gb, StringRef& reply) { int thermistor = gb->GetIValue(); if ( (0 <= thermistor && thermistor < HEATERS) - || ((int)MAX31855_START_CHANNEL <= thermistor && thermistor < (int)(MAX31855_START_CHANNEL + MAX31855_DEVICES)) + || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors)) + || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors)) ) { platform->SetThermistorNumber(heater, thermistor); @@ -2775,30 +2776,33 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) { const char* filename = gb->GetUnprecedentedString(); - QueueFileToPrint(filename); - if (fileToPrint.IsLive()) + if (filename != nullptr) { - reprap.GetPrintMonitor()->StartingPrint(filename); - if (platform->Emulating() == marlin && gb == serialGCode) + QueueFileToPrint(filename); + if (fileToPrint.IsLive()) { - reply.copy("File opened\nFile selected"); + reprap.GetPrintMonitor()->StartingPrint(filename); + if (platform->Emulating() == marlin && gb == serialGCode) + { + reply.copy("File opened\nFile selected"); + } + else + { + // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response + reply.printf("File %s selected for printing", filename); + } + + if (code == 32) + { + fileBeingPrinted.MoveFrom(fileToPrint); + reprap.GetPrintMonitor()->StartedPrint(); + } } else { - // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response - reply.printf("File %s selected for printing", filename); - } - - if (code == 32) - { - fileBeingPrinted.MoveFrom(fileToPrint); - reprap.GetPrintMonitor()->StartedPrint(); + reply.printf("Failed to open file %s", filename); } } - else - { - reply.printf("Failed to open file %s", filename); - } } break; @@ -2956,15 +2960,18 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) case 28: // Write to file { const char* str = gb->GetUnprecedentedString(); - bool ok = OpenFileToWrite(platform->GetGCodeDir(), str, gb); - if (ok) - { - reply.printf("Writing to file: %s", str); - } - else + if (str != nullptr) { - reply.printf("Can't open file %s for writing.", str); - error = true; + bool ok = OpenFileToWrite(platform->GetGCodeDir(), str, gb); + if (ok) + { + reply.printf("Writing to file: %s", str); + } + else + { + reply.printf("Can't open file %s for writing.", str); + error = true; + } } } break; @@ -2974,7 +2981,13 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) break; case 30: // Delete file - DeleteFile(gb->GetUnprecedentedString()); + { + const char *filename = gb->GetUnprecedentedString(); + if (filename != nullptr) + { + DeleteFile(filename); + } + } break; // For case 32, see case 24 @@ -3445,7 +3458,13 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) break; case 117: // Display message - reprap.SetMessage(gb->GetUnprecedentedString()); + { + const char *msg = gb->GetUnprecedentedString(); + if (msg != nullptr) + { + reprap.SetMessage(msg); + } + } break; case 119: @@ -4434,8 +4453,16 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) case 562: // Reset temperature fault - use with great caution if (gb->Seen('P')) { - int iValue = gb->GetIValue(); - reprap.GetHeat()->ResetFault(iValue); + int heater = gb->GetIValue(); + if (heater >= 0 && heater < HEATERS) + { + reprap.ClearTemperatureFault(heater); + } + else + { + reply.copy("Invalid heater number.\n"); + error = true; + } } break; diff --git a/src/Heat.cpp b/src/Heat.cpp index ec04e878..a33fcde5 100644 --- a/src/Heat.cpp +++ b/src/Heat.cpp @@ -110,6 +110,105 @@ bool Heat::HeaterAtSetTemperature(int8_t heater) const return (target < TEMPERATURE_LOW_SO_DONT_CARE) || (fabs(dt - target) <= TEMPERATURE_CLOSE_ENOUGH); } +Heat::HeaterStatus Heat::GetStatus(int8_t heater) const +{ + if (heater < 0 || heater >= HEATERS) + { + return HS_off; + } + + return (pids[heater]->FaultOccurred() ? HS_fault + : pids[heater]->SwitchedOff()) ? HS_off + : (pids[heater]->Active()) ? HS_active + : HS_standby; +} + +void Heat::SetActiveTemperature(int8_t heater, float t) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->SetActiveTemperature(t); + } +} + +float Heat::GetActiveTemperature(int8_t heater) const +{ + return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetActiveTemperature() : ABS_ZERO; +} + +void Heat::SetStandbyTemperature(int8_t heater, float t) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->SetStandbyTemperature(t); + } +} + +float Heat::GetStandbyTemperature(int8_t heater) const +{ + return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetStandbyTemperature() : ABS_ZERO; +} + +float Heat::GetTemperature(int8_t heater) const +{ + return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetTemperature() : ABS_ZERO; +} + +void Heat::Activate(int8_t heater) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->Activate(); + } +} + +void Heat::SwitchOff(int8_t heater) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->SwitchOff(); + } +} + +void Heat::SwitchOffAll() +{ + for (size_t heater = 0; heater < HEATERS; ++heater) + { + pids[heater]->SwitchOff(); + } +} + +void Heat::Standby(int8_t heater) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->Standby(); + } +} + +void Heat::ResetFault(int8_t heater) +{ + if (heater >= 0 && heater < HEATERS) + { + pids[heater]->ResetFault(); + } +} + +float Heat::GetAveragePWM(int8_t heater) const +{ + return pids[heater]->GetAveragePWM(); +} + +uint32_t Heat::GetLastSampleTime(int8_t heater) const +{ + return pids[heater]->GetLastSampleTime(); +} + +bool Heat::UseSlowPwm(int8_t heater) const +{ + return heater == bedHeater || heater == chamberHeater; +} + //****************************************************************************************************** PID::PID(Platform* p, int8_t h) : platform(p), heater(h) @@ -163,7 +262,7 @@ void PID::Spin() // Always know our temperature, regardless of whether we have been switched on or not - Platform::TempError err = Platform::TempError::errOk; // assume no error + 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 we're not switched on, or there's a fault, turn the power off and go home. @@ -180,18 +279,22 @@ void PID::Spin() // We are switched on. Check for faults. Temperature silly-low or silly-high mean open-circuit // or shorted thermistor respectively. - if (temperature < BAD_LOW_TEMPERATURE) + if (err == TemperatureError::success && temperature < BAD_LOW_TEMPERATURE) { - err = Platform::TempError::errOpen; + err = TemperatureError::openCircuit; } - else if (temperature > platform->GetTemperatureLimit()) + else if (err == TemperatureError::success && temperature > platform->GetTemperatureLimit()) { - err = Platform::TempError::errTooHigh; + err = TemperatureError::tooHigh; } - if (err != Platform::TempError::errOk) + if (err == TemperatureError::success) + { + badTemperatureCount = 0; + } + else { - if (platform->DoThermistorAdc(heater) || !(Platform::TempErrorPermanent(err))) + if (platform->IsThermistorChannel(heater) || !IsPermanentError(err)) { // Error may be a temporary error and may correct itself after a few additional reads badTemperatureCount++; @@ -208,18 +311,10 @@ void PID::Spin() { SetHeater(0.0); temperatureFault = true; - platform->MessageF(GENERIC_MESSAGE, "Temperature fault on heater %d%s%s, T = %.1f\n", - heater, - (err != Platform::TempError::errOk) ? ", " : "", - (err != Platform::TempError::errOk) ? Platform::TempErrorStr(err) : "", - temperature); + platform->MessageF(GENERIC_MESSAGE, "Error: Temperature fault on heater %d: %s\n", heater, TemperatureErrorString(err)); reprap.FlagTemperatureFault(heater); } } - else - { - badTemperatureCount = 0; - } // Now check how long it takes to warm up. If too long, maybe the thermistor is not in contact with the heater if (heatingUp && heater != reprap.GetHeat()->GetBedHeater()) // FIXME - also check bed warmup time? @@ -212,103 +212,4 @@ inline void Heat::SetChamberHeater(int8_t heater) chamberHeater = heater; } -inline Heat::HeaterStatus Heat::GetStatus(int8_t heater) const -{ - if (heater < 0 || heater >= HEATERS) - { - return HS_off; - } - - return (pids[heater]->FaultOccurred() ? HS_fault - : pids[heater]->SwitchedOff()) ? HS_off - : (pids[heater]->Active()) ? HS_active - : HS_standby; -} - -inline void Heat::SetActiveTemperature(int8_t heater, float t) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->SetActiveTemperature(t); - } -} - -inline float Heat::GetActiveTemperature(int8_t heater) const -{ - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetActiveTemperature() : ABS_ZERO; -} - -inline void Heat::SetStandbyTemperature(int8_t heater, float t) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->SetStandbyTemperature(t); - } -} - -inline float Heat::GetStandbyTemperature(int8_t heater) const -{ - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetStandbyTemperature() : ABS_ZERO; -} - -inline float Heat::GetTemperature(int8_t heater) const -{ - return (heater >= 0 && heater < HEATERS) ? pids[heater]->GetTemperature() : ABS_ZERO; -} - -inline void Heat::Activate(int8_t heater) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->Activate(); - } -} - -inline void Heat::SwitchOff(int8_t heater) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->SwitchOff(); - } -} - -inline void Heat::SwitchOffAll() -{ - for (size_t heater = 0; heater < HEATERS; ++heater) - { - pids[heater]->SwitchOff(); - } -} - -inline void Heat::Standby(int8_t heater) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->Standby(); - } -} - -inline void Heat::ResetFault(int8_t heater) -{ - if (heater >= 0 && heater < HEATERS) - { - pids[heater]->ResetFault(); - } -} - -inline float Heat::GetAveragePWM(int8_t heater) const -{ - return pids[heater]->GetAveragePWM(); -} - -inline uint32_t Heat::GetLastSampleTime(int8_t heater) const -{ - return pids[heater]->GetLastSampleTime(); -} - -inline bool Heat::UseSlowPwm(int8_t heater) const -{ - return heater == bedHeater || heater == chamberHeater; -} - #endif diff --git a/src/Libraries/MAX31855/MAX31855.cpp b/src/Libraries/MAX31855/MAX31855.cpp deleted file mode 100644 index 7ec6dbd1..00000000 --- a/src/Libraries/MAX31855/MAX31855.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "MAX31855.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 - -#define PERIPHERAL_CHANNEL_ID 3 -#define PERIPHERAL_CHANNEL_CS_PIN 78 // NPCS3 - -// 5.0 MHz Max clock frequency when clocking data out of a MAX31855 -#define MAX31855_MAX_FREQ 5000000u - -// Perform the actual hardware initialization for attaching and using this -// device on the SPI hardware bus. -void MAX31855::Init(uint8_t cs) -{ - device.csPin = cs; - pinMode(cs, OUTPUT); - digitalWrite(cs, HIGH); - -#ifdef DUET_NG - sspi_master_init(&device, 8); -#else - device.id = PERIPHERAL_CHANNEL_ID; // Peripheral channel - sspi_master_init(&device, 16); -#endif -} - -MAX31855_error MAX31855::getTemperature(float *t) const -{ - if (!sspi_acquire()) - { - return MAX31855_GSPI_BUSY; - } - - // Assume properly initialized - // Ensure that the configuration is as needed; another GSPI consumer - // may have changed the bus speed and/or timing delays. - sspi_master_setup_device(&device, SPI_MODE_0, MAX31855_MAX_FREQ); - - // Select the device; enable CS (set it LOW) - sspi_select_device(&device); - delayMicroseconds(1); // TODO shorten this (MAX31855 needs 100ns minimum) - - // Read in 32 bits - -#ifdef DUET_NG - uint8_t dataOut[4] = {0, 0, 0, 0}; - uint8_t rawBytes[4]; - spi_status_t sts = sspi_transceive_packet(dataOut, rawBytes, 4); - uint16_t raw[2]; - raw[0] = ((uint16_t)rawBytes[0] << 8) | (uint16_t)rawBytes[1]; - raw[1] = ((uint16_t)rawBytes[2] << 8) | (uint16_t)rawBytes[3]; -#else - uint16_t dataOut[2] = {0, 0}; - uint16_t raw[2]; - spi_status_t sts = sspi_transceive_packet16(dataOut, raw, 2); -#endif - - // Deselect the device; disable CS (set it HIGH) - sspi_deselect_device(&device); - - sspi_release(); - - if (sts != SPI_OK) - { - return MAX31855_ERR_TMO; - } - - if ((raw[0] & 0x02) || (raw[1] & 0x08)) - { - // These two bits should always read 0 - // Likely the entire read was 0xFF 0xFF which is not uncommon when first powering up - return MAX31855_ERR_IO; - } - - // The MAX31855 response is designed such that we can look at - // just the high 16 bits and know the temperature and whether - // an error occurred. The low 16 bits contain the cold junction - // temperature and finer detail on the nature of any error. - - // We also want to check for three more types of bad reads: - // - // 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. - // - // We will perform those three sanity checks as we set the error response code. - - if ((raw[0] & 0x01) || (raw[1] & 0x07)) - { - if (!(raw[0] & 0x01)) - { - // One or more fault reason bits are set but the fault indicator bit is clear? - return MAX31855_ERR_IO; - } - - // 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; - MAX31855_error err; - - if (raw[1] & 0x01) - { - // Open Circuit - ++nbits; - err = MAX31855_ERR_OC; - } - if (raw[1] & 0x02) - { - // Short to ground; - ++nbits; - err = MAX31855_ERR_SCG; - } - if (raw[1] && 0x04) - { - // Short to Vcc - ++nbits; - err = MAX31855_ERR_SCV; - } - - if (nbits == 1) - { - // Looks like a legitimate error response: bit 16 was set and only one of bits 0:2 was set - return err; - } - - // 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. - return MAX31855_ERR_IO; - } - - // Note, the reading is negative if raw[0] & 0x8000 is nonzero - - // Shift the data - raw[0] >>= 2; - int16_t temp = (int16_t)(raw[0] & 0x3fff); - - // Handle negative temperatures - if (raw[0] & 0x2000) - { - temp |= 0xc000; - } - - // And convert to from units of 1/4C to 1C - *t = (float)(0.25 * (float)temp); - - // Success! - return MAX31855_OK; -} - -const char* MAX31855::errorStr(MAX31855_error err) const -{ - switch (err) - { - default : return "unknown MAX31855 error"; - case MAX31855_OK : return "successful temperature read"; - case MAX31855_ERR_SCV : return "thermocouple is shorted to +Vcc"; - case MAX31855_ERR_SCG : return "thermocouple is shorted to ground"; - case MAX31855_ERR_OC : return "thermocouple is broken (open)"; - case MAX31855_ERR_TMO : return "error communicating with MAX31855; read timed out"; - case MAX31855_ERR_IO : return "error communicating with MAX31855; disconnected?"; - case MAX31855_GSPI_BUSY: return "general SPI bus is busy"; - } -} - -// End diff --git a/src/Libraries/MAX31855/MAX31855.h b/src/Libraries/MAX31855/MAX31855.h deleted file mode 100644 index a248a476..00000000 --- a/src/Libraries/MAX31855/MAX31855.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef MAX31855_H -#define MAX31855_H - -#include "Arduino.h" -#include "SharedSpi.h" // for gspi_device - -enum MAX31855_error -{ - MAX31855_OK = 0, // Success - MAX31855_ERR_SCV = 1, // Thermocouple is shorted to Vcc - MAX31855_ERR_SCG = 2, // Thermocouple is shorted to ground - MAX31855_ERR_OC = 3, // Thermocouple is open - MAX31855_ERR_TMO = 4, // Timeout waiting on I/O (bus busy) - MAX31855_ERR_IO = 5, // Chip not sending output? CS not hooked up? - MAX31855_GSPI_BUSY // General SPI bus is busy -}; - -class MAX31855 -{ -public: - MAX31855() {} - MAX31855_error getTemperature(float *temp) const; - void Init(uint8_t cs); - const char* errorStr(MAX31855_error err) const; - -private: - sspi_device device; -}; - -#endif //MAX31855_H diff --git a/src/Libraries/TemperatureSensor/TemperatureSensor.cpp b/src/Libraries/TemperatureSensor/TemperatureSensor.cpp new file mode 100644 index 00000000..266ddd33 --- /dev/null +++ b/src/Libraries/TemperatureSensor/TemperatureSensor.cpp @@ -0,0 +1,384 @@ +#include "TemperatureSensor.h" +#include "RepRapFirmware.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 + +#define PERIPHERAL_CHANNEL_ID 3 +#define PERIPHERAL_CHANNEL_CS_PIN 78 // NPCS3 + +const uint32_t MAX31855_Frequency = 4000000; // maximum for MAX31855 is 5MHz +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 MAX31855 sets up the first data bit after the falling edge of CS, 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 CS, and samples input data on the falling edge. +// This requires NCPHA = 0. + +const uint8_t MAX31855_SpiMode = SPI_MODE_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]); + +// 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; +#ifndef DUET_NG + device.id = PERIPHERAL_CHANNEL_ID; // Peripheral channel +#endif + 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; +#ifndef DUET_NG + device.id = PERIPHERAL_CHANNEL_ID; // Peripheral channel +#endif + 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; +} + +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 || table[low - 1] < adcVal) + //assert(low == NumTempTableEntries || adcVal <= table[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; +} + +// 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/Libraries/TemperatureSensor/TemperatureSensor.h b/src/Libraries/TemperatureSensor/TemperatureSensor.h new file mode 100644 index 00000000..37b64a3b --- /dev/null +++ b/src/Libraries/TemperatureSensor/TemperatureSensor.h @@ -0,0 +1,27 @@ +#ifndef TEMPERATURESENSOR_H +#define TEMPERATURESENSOR_H + +#include "TemperatureError.h" // for result codes +#include "Arduino.h" +#include "SharedSpi.h" // for sspi_device + +class TemperatureSensor +{ +public: + TemperatureSensor() {} + void InitThermocouple(uint8_t cs); + void InitRtd(uint8_t cs); + TemperatureError GetThermocoupleTemperature(float *temp); + TemperatureError GetRtdTemperature(float *temp); + +private: + TemperatureError DoSpiTransaction(const uint8_t dataOut[], size_t nbytes, uint32_t& rslt) const; + TemperatureError TryInitRtd() const; + + sspi_device device; + uint32_t lastReadingTime; + float lastTemperature; + TemperatureError lastResult; +}; + +#endif // TEMPERATURESENSOR_H diff --git a/src/Platform.cpp b/src/Platform.cpp index da45998d..e3ff82b9 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -228,12 +228,19 @@ void Platform::Init() ARRAY_INIT(tempSensePins, TEMP_SENSE_PINS); ARRAY_INIT(heatOnPins, HEAT_ON_PINS); - ARRAY_INIT(max31855CsPins, MAX31855_CS_PINS); + ARRAY_INIT(spiTempSenseCsPins, SpiTempSensorCsPins); configuredHeaters = (BED_HEATER >= 0) ? (1 << BED_HEATER) : 0; heatSampleTime = HEAT_SAMPLE_TIME; timeToHot = TIME_TO_HOT; + // Enable pullups on all the SPI CS pins. This is required if we are using more than one device on the SPI bus. + // Otherwise, when we try to initialise the first device, the other devices may respond as well because their CS lines are not high. + for (size_t i = 0; i < MaxSpiTempSensors; ++i) + { + setPullup(SpiTempSensorCsPins[i], true); + } + // Motors for (size_t drive = 0; drive < DRIVES; drive++) @@ -369,11 +376,17 @@ void Platform::SetThermistorNumber(size_t heater, size_t thermistor) { heaterTempChannels[heater] = thermistor; - // Initialize the associated MAX31855? - if (thermistor >= MAX31855_START_CHANNEL && thermistor < MAX31855_START_CHANNEL + MAX31855_DEVICES) + // Initialize the associated SPI temperature sensor? + if (thermistor >= FirstThermocoupleChannel && thermistor < FirstThermocoupleChannel + MaxSpiTempSensors) { - Max31855Devices[thermistor - MAX31855_START_CHANNEL].Init(max31855CsPins[thermistor - MAX31855_START_CHANNEL]); + SpiTempSensors[thermistor - FirstThermocoupleChannel].InitThermocouple(SpiTempSensorCsPins[thermistor - FirstThermocoupleChannel]); } + else if (thermistor >= FirstRtdChannel && thermistor < FirstRtdChannel + MaxSpiTempSensors) + { + SpiTempSensors[thermistor - FirstRtdChannel].InitRtd(spiTempSenseCsPins[thermistor - FirstRtdChannel]); + } + + reprap.GetHeat()->ResetFault(heater); } int Platform::GetThermistorNumber(size_t heater) const @@ -504,19 +517,21 @@ void Platform::GetZProbeAxes(bool (&axes)[AXES]) } } -float Platform::ZProbeStopHeight() const +float Platform::ZProbeStopHeight() { + const int8_t bedHeater = reprap.GetHeat()->GetBedHeater(); + float temperature = (bedHeater >= 0) ? GetTemperature(bedHeater) : 25.0; switch (nvData.zProbeType) { case 0: case 4: - return nvData.switchZProbeParameters.GetStopHeight(GetTemperature(0)); + return nvData.switchZProbeParameters.GetStopHeight(temperature); case 1: case 2: - return nvData.irZProbeParameters.GetStopHeight(GetTemperature(0)); + return nvData.irZProbeParameters.GetStopHeight(temperature); case 3: case 5: - return nvData.alternateZProbeParameters.GetStopHeight(GetTemperature(0)); + return nvData.alternateZProbeParameters.GetStopHeight(temperature); default: return 0; } @@ -1355,13 +1370,13 @@ void Platform::ClassReport(float &lastTime) // Result is in degrees celsius -float Platform::GetTemperature(size_t heater, TempError* err) const +float Platform::GetTemperature(size_t heater, TemperatureError* err) { // Note that at this point we're actually getting an averaged ADC read, not a "raw" temp. For thermistors, // we're getting an averaged voltage reading which we'll convert to a temperature. - if (DoThermistorAdc(heater)) + if (IsThermistorChannel(heater)) { - int rawTemp = GetRawTemperature(heater); + int rawTemp = GetRawThermistorTemperature(heater); // If the ADC reading is N then for an ideal ADC, the input voltage is at least N/(AD_RANGE + 1) and less than (N + 1)/(AD_RANGE + 1), times the analog reference. // So we add 0.5 to to the reading to get a better estimate of the input. @@ -1379,7 +1394,7 @@ float Platform::GetTemperature(size_t heater, TempError* err) const // thermistor is disconnected if (err) { - *err = TempError::errOpen; + *err = TemperatureError::openCircuit; } return ABS_ZERO; } @@ -1398,55 +1413,44 @@ float Platform::GetTemperature(size_t heater, TempError* err) const // thermistor short circuit, return a high temperature if (err) { - *err = TempError::errShort; + *err = TemperatureError::shortCircuit; } - return BAD_ERROR_TEMPERATURE; } } - else + else if (IsThermocoupleChannel(heater)) { // MAX31855 thermocouple chip float temp; - MAX31855_error res = Max31855Devices[heaterTempChannels[heater] - MAX31855_START_CHANNEL].getTemperature(&temp); - if (res == MAX31855_OK) + TemperatureError res = SpiTempSensors[heaterTempChannels[heater] - FirstThermocoupleChannel].GetThermocoupleTemperature(&temp); + if (res == TemperatureError::success) { return temp; } if (err) { - switch(res) - { - case MAX31855_OK : *err = TempError::errOk; break; // Success - case MAX31855_ERR_SCV : *err = TempError::errShortVcc; break; // Short to Vcc - case MAX31855_ERR_SCG : *err = TempError::errShortGnd; break; // Short to GND - case MAX31855_ERR_OC : *err = TempError::errOpen; break; // Open connection - case MAX31855_ERR_TMO : *err = TempError::errTimeout; break; // SPI comms timeout - case MAX31855_ERR_IO : - default : *err = TempError::errIO; break; // SPI comms not functioning - } + *err = res; } - return BAD_ERROR_TEMPERATURE; } -} - -/*static*/ const char* Platform::TempErrorStr(TempError err) -{ - switch(err) + else if (IsRtdChannel(heater)) { - default : return "Unknown temperature read error"; - case TempError::errOk : return "successful temperature read"; - case TempError::errShort : return "sensor circuit is shorted"; - case TempError::errShortVcc : return "sensor circuit is shorted to the voltage rail"; - case TempError::errShortGnd : return "sensor circuit is shorted to ground"; - case TempError::errOpen : return "sensor circuit is open/disconnected"; - case TempError::errTooHigh: return "temperature above safety limit"; - case TempError::errTimeout : return "communication error whilst reading sensor; read took too long"; - case TempError::errIO: return "communication error whilst reading sensor; check sensor connections"; + // MAX31865 RTD chip + float temp; + TemperatureError res = SpiTempSensors[heaterTempChannels[heater] - FirstRtdChannel].GetRtdTemperature(&temp); + if (res == TemperatureError::success) + { + return temp; + } + if (err) + { + *err = res; + } } + + return BAD_ERROR_TEMPERATURE; } // See if we need to turn on the hot end fan -bool Platform::AnyHeaterHot(uint16_t heaters, float t) const +bool Platform::AnyHeaterHot(uint16_t heaters, float t) { // Check tool heaters first for (size_t h = 0; h < reprap.GetToolHeatersInUse(); ++h) @@ -2539,7 +2543,7 @@ void Platform::Tick() case 1: // last conversion started was a thermistor case 3: { - if (DoThermistorAdc(currentHeater)) + if (IsThermistorChannel(currentHeater)) { ThermistorAveragingFilter& currentFilter = const_cast<ThermistorAveragingFilter&>(thermistorFilters[currentHeater]); currentFilter.ProcessReading(AnalogInReadChannel(thermistorAdcChannels[heaterTempChannels[currentHeater]])); diff --git a/src/Platform.h b/src/Platform.h index 328b5c21..d097a2f8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -36,9 +36,11 @@ Licence: GPL // Language-specific includes +#include <Libraries/TemperatureSensor/TemperatureSensor.h> #include <cctype> #include <cstring> #include <malloc.h> +#include <TemperatureError.h> #include <cstdlib> #include <climits> @@ -47,7 +49,6 @@ Licence: GPL #include "Arduino.h" #include "OutputMemory.h" #include "ff.h" -#include "MAX31855.h" #include "MCP4461.h" #include "MassStorage.h" #include "FileStore.h" @@ -417,9 +418,6 @@ public: // Enumeration to describe the status of a drive enum class DriveStatus : uint8_t { disabled, idle, enabled }; - // Error results generated by GetTemperature() - enum class TempError : uint8_t { errOk, errShort, errShortVcc, errShortGnd, errOpen, errTooHigh, errTimeout, errIO }; - Platform(); //------------------------------------------------------------------------------------------------------------- @@ -537,7 +535,7 @@ public: // Z probe - float ZProbeStopHeight() const; + float ZProbeStopHeight(); float GetZProbeDiveHeight() const; float GetZProbeTravelSpeed() const; int ZProbe() const; @@ -560,7 +558,7 @@ public: // Heat and temperature - float GetTemperature(size_t heater, TempError* err = nullptr) const; // Result is in degrees Celsius + float GetTemperature(size_t heater, TemperatureError* err = nullptr); // Result is in degrees Celsius void SetHeater(size_t heater, float power); // power is a fraction in [0,1] float HeatSampleTime() const; void SetHeatSampleTime(float st); @@ -570,11 +568,11 @@ public: void SetTimeToHot(float t); void SetThermistorNumber(size_t heater, size_t thermistor); int GetThermistorNumber(size_t heater) const; - bool DoThermistorAdc(uint8_t heater) const; + bool IsThermistorChannel(uint8_t heater) const; + bool IsThermocoupleChannel(uint8_t heater) const; + bool IsRtdChannel(uint8_t heater) const; void SetTemperatureLimit(float t); float GetTemperatureLimit() const { return temperatureLimit; } - static const char* TempErrorStr(TempError err); - static bool TempErrorPermanent(TempError err); void UpdateConfiguredHeaters(); // Fans @@ -758,14 +756,14 @@ private: // Heaters - bed is assumed to be the first - int GetRawTemperature(size_t heater) const; + int GetRawThermistorTemperature(size_t heater) const; void SetHeaterPwm(size_t heater, uint8_t pwm); - bool AnyHeaterHot(uint16_t heaters, float t) const; // called to see if we need to turn on the hot end fan + bool AnyHeaterHot(uint16_t heaters, float t); // called to see if we need to turn on the hot end fan Pin tempSensePins[HEATERS]; Pin heatOnPins[HEATERS]; - MAX31855 Max31855Devices[MAX31855_DEVICES]; - Pin max31855CsPins[MAX31855_DEVICES]; + TemperatureSensor SpiTempSensors[MaxSpiTempSensors]; + Pin spiTempSenseCsPins[MaxSpiTempSensors]; uint32_t configuredHeaters; // Bitmask of all heaters in use float heatSampleTime; float timeToHot; @@ -816,7 +814,7 @@ private: // // PinToAdcChannel(tempSensePins[tc]) // - // if (100 <= tc < 100 + (MAX31855_DEVICES - 1)) then + // if (100 <= tc < 100 + (MaxSpiTempSensors - 1)) then // The temperature channel is a thermocouple attached to a MAX31855 chip // The MAX31855 object corresponding to the specific MAX31855 chip is // @@ -824,14 +822,14 @@ private: // // Note that the MAX31855 objects, although statically declared, are not // initialized until configured via a "M305 Pn X10m" command with 0 <= n < HEATERS - // and 0 <= m < MAX31855_DEVICES. + // and 0 <= m < MaxSpiTempSensors. // // NOTE BENE: When a M305 command is processed, the onus is on the gcode processor, // GCodes.cpp, to range check the value of the X parameter. Code consuming the results // of the M305 command (e.g., SetThermistorNumber() and array lookups assume range // checking has already been performed. - uint8_t heaterTempChannels[HEATERS]; + unsigned int heaterTempChannels[HEATERS]; AnalogChannelNumber thermistorAdcChannels[HEATERS]; AnalogChannelNumber zProbeAdcChannel; uint32_t thermistorOverheatSums[HEATERS]; @@ -1146,7 +1144,7 @@ inline void Platform::ExtrudeOff() // Drive the RepRap machine - Heat and temperature -inline int Platform::GetRawTemperature(size_t heater) const +inline int Platform::GetRawThermistorTemperature(size_t heater) const { return (heater < HEATERS) ? thermistorFilters[heater].GetSum()/(THERMISTOR_AVERAGE_READINGS >> AD_OVERSAMPLE_BITS) @@ -1173,17 +1171,21 @@ inline void Platform::SetTimeToHot(float t) timeToHot = t; } -inline bool Platform::DoThermistorAdc(uint8_t heater) const +inline bool Platform::IsThermistorChannel(uint8_t heater) const { return heaterTempChannels[heater] < HEATERS; } -// Indicate if a temp sensor error is a "permanent" error: whether it is an -// error condition which will not rectify over time and so the heater should -// just be shut off immediately. -inline bool Platform::TempErrorPermanent(TempError err) +inline bool Platform::IsThermocoupleChannel(uint8_t heater) const +{ + return heaterTempChannels[heater] >= FirstThermocoupleChannel + && heaterTempChannels[heater] - FirstThermocoupleChannel < MaxSpiTempSensors; +} + +inline bool Platform::IsRtdChannel(uint8_t heater) const { - return (err != TempError::errTimeout) && (err != TempError::errIO) && (err != TempError::errOk); + return heaterTempChannels[heater] >= FirstRtdChannel + && heaterTempChannels[heater] - FirstRtdChannel < MaxSpiTempSensors; } inline const uint8_t* Platform::IPAddress() const diff --git a/src/Reprap.cpp b/src/Reprap.cpp index 117d2cc9..acae8f9c 100644 --- a/src/Reprap.cpp +++ b/src/Reprap.cpp @@ -497,7 +497,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) char ch = GetStatusCharacter(); response->printf("{\"status\":\"%c\",\"coords\":{", ch); - /* Coordinates */ + // Coordinates { float liveCoordinates[DRIVES + 1]; #if SUPPORT_ROLAND @@ -594,7 +594,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) } } - /* Parameters */ + // Parameters { // ATX power response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0); @@ -1121,20 +1121,10 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) response->catf(",\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); // Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false. - if (type != 0) - { - response->catf(",\"homed\":[%d,%d,%d]", - (gCodes->GetAxisIsHomed(0)) ? 1 : 0, - (gCodes->GetAxisIsHomed(1)) ? 1 : 0, - (gCodes->GetAxisIsHomed(2)) ? 1 : 0); - } - else - { - response->catf(",\"hx\":%d,\"hy\":%d,\"hz\":%d", - (gCodes->GetAxisIsHomed(0)) ? 1 : 0, - (gCodes->GetAxisIsHomed(1)) ? 1 : 0, - (gCodes->GetAxisIsHomed(2)) ? 1 : 0); - } + response->catf(",\"homed\":[%d,%d,%d]", + (gCodes->GetAxisIsHomed(0)) ? 1 : 0, + (gCodes->GetAxisIsHomed(1)) ? 1 : 0, + (gCodes->GetAxisIsHomed(2)) ? 1 : 0); if (printMonitor->IsPrinting()) { diff --git a/src/TemperatureError.cpp b/src/TemperatureError.cpp new file mode 100644 index 00000000..182d0755 --- /dev/null +++ b/src/TemperatureError.cpp @@ -0,0 +1,44 @@ +/* + * TemperatureError.cpp + * + * Created on: 21 Apr 2016 + * Author: David + */ + +#include "TemperatureError.h" + +const char* TemperatureErrorString(TemperatureError err) +{ + switch(err) + { + case TemperatureError::success: return "success"; + case TemperatureError::shortCircuit: return "short-circuit in sensor"; + case TemperatureError::shortToVcc: return "short to Vcc"; + case TemperatureError::shortToGround: return "short to ground"; + case TemperatureError::openCircuit: return "open circuit"; + case TemperatureError::tooHigh: return "temperature above limit"; + case TemperatureError::timeout: return "timeout"; + case TemperatureError::ioError: return "I/O error"; + case TemperatureError::hardwareError: return "hardware error"; + case TemperatureError::busBusy: return "bus busy"; + case TemperatureError::badResponse: return "bad response"; + default: return "unknown temperature sense error"; + } +} + +// Indicate if a temp sensor error is a "permanent" error: whether it is an error condition which will not rectify over time +// and so the heater should just be shut off immediately. +bool IsPermanentError(TemperatureError err) +{ + switch (err) + { + case TemperatureError::success: + case TemperatureError::busBusy: + case TemperatureError::ioError: + return false; + default: + return true; + } +} + +// End diff --git a/src/TemperatureError.h b/src/TemperatureError.h new file mode 100644 index 00000000..47caf489 --- /dev/null +++ b/src/TemperatureError.h @@ -0,0 +1,35 @@ +/* + * TemperatureError.h + * + * Created on: 21 Apr 2016 + * Author: David + */ + +#ifndef TEMPERATUREERROR_H_ +#define TEMPERATUREERROR_H_ + +#include <cstdint> + +// Result codes returned by temperature sensor drivers +enum class TemperatureError : uint8_t +{ + success, + shortCircuit, + shortToVcc, + shortToGround, + openCircuit, + tooHigh, + timeout, + ioError, + hardwareError, + busBusy, + badResponse +}; + +const char* TemperatureErrorString(TemperatureError err); + +// Indicate if a temp sensor error is a "permanent" error: whether it is an error condition which will not rectify over time +// and so the heater should just be shut off immediately. +bool IsPermanentError(TemperatureError err); + +#endif /* TEMPERATUREERROR_H_ */ |