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/Libraries | |
parent | 3236714418b7c83a388de3a81c4326cb3814a59c (diff) |
Added support for PT100 and other RTDs
Added support for RTDs using the MAX31865 interface chip
Diffstat (limited to 'src/Libraries')
-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 |
4 files changed, 411 insertions, 241 deletions
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 |