501 lines
13 KiB
C++
501 lines
13 KiB
C++
/**
|
|
* Marlin 3D Printer Firmware
|
|
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|
*
|
|
* Based on Sprinter and grbl.
|
|
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Based on Based on Adafruit MAX31865 library:
|
|
*
|
|
* This is a library for the Adafruit PT100/P1000 RTD Sensor w/MAX31865
|
|
* Designed specifically to work with the Adafruit RTD Sensor
|
|
* https://www.adafruit.com/products/3328
|
|
*
|
|
* This sensor uses SPI to communicate, 4 pins are required to interface.
|
|
*
|
|
* Adafruit invests time and resources providing this open source code,
|
|
* please support Adafruit and open-source hardware by purchasing
|
|
* products from Adafruit!
|
|
*
|
|
* Written by Limor Fried/Ladyada for Adafruit Industries.
|
|
*
|
|
* Modifications by JoAnn Manges (@GadgetAngel)
|
|
* Copyright (c) 2020, JoAnn Manges
|
|
* All rights reserved.
|
|
*/
|
|
|
|
// Useful for RTD debugging.
|
|
//#define MAX31865_DEBUG
|
|
//#define MAX31865_DEBUG_SPI
|
|
|
|
#include "../inc/MarlinConfig.h"
|
|
|
|
#if HAS_MAX31865 && !USE_ADAFRUIT_MAX31865
|
|
|
|
//#include <SoftwareSPI.h> // TODO: switch to SPIclass/SoftSPI
|
|
#include "MAX31865.h"
|
|
|
|
// The maximum speed the MAX31865 can do is 5 MHz
|
|
SPISettings MAX31865::spiConfig = SPISettings(
|
|
#if defined(TARGET_LPC1768)
|
|
SPI_QUARTER_SPEED
|
|
#elif defined(ARDUINO_ARCH_STM32)
|
|
SPI_CLOCK_DIV4
|
|
#else
|
|
500000
|
|
#endif
|
|
, MSBFIRST
|
|
, SPI_MODE_1 // CPOL0 CPHA1
|
|
);
|
|
|
|
#ifndef LARGE_PINMAP
|
|
|
|
/**
|
|
* Create the interface object using software (bitbang) SPI for PIN values
|
|
* less than or equal to 127.
|
|
*
|
|
* @param spi_cs the SPI CS pin to use
|
|
* @param spi_mosi the SPI MOSI pin to use
|
|
* @param spi_miso the SPI MISO pin to use
|
|
* @param spi_clk the SPI clock pin to use
|
|
*/
|
|
MAX31865::MAX31865(int8_t spi_cs, int8_t spi_mosi, int8_t spi_miso, int8_t spi_clk) {
|
|
_cs = spi_cs;
|
|
_mosi = spi_mosi;
|
|
_miso = spi_miso;
|
|
_sclk = spi_clk;
|
|
}
|
|
|
|
/**
|
|
* Create the interface object using hardware SPI for PIN for PIN values less
|
|
* than or equal to 127.
|
|
*
|
|
* @param spi_cs the SPI CS pin to use along with the default SPI device
|
|
*/
|
|
MAX31865::MAX31865(int8_t spi_cs) {
|
|
_cs = spi_cs;
|
|
_sclk = _miso = _mosi = -1;
|
|
}
|
|
|
|
#else
|
|
|
|
/**
|
|
* Create the interface object using software (bitbang) SPI for PIN values
|
|
* which are larger than 127. If you have PIN values less than or equal to
|
|
* 127 use the other call for SW SPI.
|
|
*
|
|
* @param spi_cs the SPI CS pin to use
|
|
* @param spi_mosi the SPI MOSI pin to use
|
|
* @param spi_miso the SPI MISO pin to use
|
|
* @param spi_clk the SPI clock pin to use
|
|
* @param pin_mapping set to 1 for positive pin values
|
|
*/
|
|
MAX31865::MAX31865(uint32_t spi_cs, uint32_t spi_mosi,
|
|
uint32_t spi_miso, uint32_t spi_clk,
|
|
uint8_t pin_mapping) {
|
|
_cs = spi_cs;
|
|
_mosi = spi_mosi;
|
|
_miso = spi_miso;
|
|
_sclk = spi_clk;
|
|
}
|
|
|
|
/**
|
|
* Create the interface object using hardware SPI for PIN values which are
|
|
* larger than 127. If you have PIN values less than or equal to 127 use
|
|
* the other call for HW SPI.
|
|
*
|
|
* @param spi_cs the SPI CS pin to use along with the default SPI device
|
|
* @param pin_mapping set to 1 for positive pin values
|
|
*/
|
|
MAX31865::MAX31865(uint32_t spi_cs, uint8_t pin_mapping) {
|
|
_cs = spi_cs;
|
|
_sclk = _miso = _mosi = -1UL; //-1UL or 0xFFFFFFFF or 4294967295
|
|
}
|
|
|
|
#endif // LARGE_PINMAP
|
|
|
|
|
|
/**
|
|
*
|
|
* Instance & Class methods
|
|
*
|
|
*/
|
|
|
|
|
|
/**
|
|
* Initialize the SPI interface and set the number of RTD wires used
|
|
*
|
|
* @param wires The number of wires in enum format. Can be MAX31865_2WIRE, MAX31865_3WIRE, or MAX31865_4WIRE.
|
|
* @param zero The resistance of the RTD at 0 degC, in ohms.
|
|
* @param ref The resistance of the reference resistor, in ohms.
|
|
*/
|
|
void MAX31865::begin(max31865_numwires_t wires, float zero, float ref) {
|
|
Rzero = zero;
|
|
Rref = ref;
|
|
|
|
OUT_WRITE(_cs, HIGH);
|
|
|
|
if (_sclk != TERN(LARGE_PINMAP, -1UL, -1)) {
|
|
// Define pin modes for Software SPI
|
|
#ifdef MAX31865_DEBUG
|
|
SERIAL_ECHOLN("Initializing MAX31865 Software SPI");
|
|
#endif
|
|
|
|
OUT_WRITE(_sclk, LOW);
|
|
SET_OUTPUT(_mosi);
|
|
SET_INPUT(_miso);
|
|
}
|
|
else {
|
|
// Start and configure hardware SPI
|
|
#ifdef MAX31865_DEBUG
|
|
SERIAL_ECHOLN("Initializing MAX31865 Hardware SPI");
|
|
#endif
|
|
|
|
SPI.begin();
|
|
}
|
|
|
|
setWires(wires);
|
|
enableBias(false);
|
|
autoConvert(false);
|
|
clearFault();
|
|
|
|
#ifdef MAX31865_DEBUG_SPI
|
|
#ifndef LARGE_PINMAP
|
|
SERIAL_ECHOLNPGM(
|
|
"Regular begin call with _cs: ", _cs,
|
|
" _miso: ", _miso,
|
|
" _sclk: ", _sclk,
|
|
" _mosi: ", _mosi
|
|
);
|
|
#else
|
|
SERIAL_ECHOLNPGM(
|
|
"LARGE_PINMAP begin call with _cs: ", _cs,
|
|
" _miso: ", _miso,
|
|
" _sclk: ", _sclk,
|
|
" _mosi: ", _mosi
|
|
);
|
|
#endif // LARGE_PINMAP
|
|
|
|
SERIAL_ECHOLNPGM("config: ", readRegister8(MAX31856_CONFIG_REG));
|
|
SERIAL_EOL();
|
|
#endif // MAX31865_DEBUG_SPI
|
|
}
|
|
|
|
/**
|
|
* Read the raw 8-bit FAULTSTAT register
|
|
*
|
|
* @return The raw unsigned 8-bit FAULT status register
|
|
*/
|
|
uint8_t MAX31865::readFault() {
|
|
return readRegister8(MAX31856_FAULTSTAT_REG);
|
|
}
|
|
|
|
/**
|
|
* Clear all faults in FAULTSTAT.
|
|
*/
|
|
void MAX31865::clearFault() {
|
|
setConfig(MAX31856_CONFIG_FAULTSTAT, 1);
|
|
}
|
|
|
|
/**
|
|
* Whether we want to have continuous conversions (50/60 Hz)
|
|
*
|
|
* @param b If true, auto conversion is enabled
|
|
*/
|
|
void MAX31865::autoConvert(bool b) {
|
|
setConfig(MAX31856_CONFIG_MODEAUTO, b);
|
|
}
|
|
|
|
/**
|
|
* Whether we want filter out 50Hz noise or 60Hz noise
|
|
*
|
|
* @param b If true, 50Hz noise is filtered, else 60Hz(default)
|
|
*/
|
|
void MAX31865::enable50HzFilter(bool b) {
|
|
setConfig(MAX31856_CONFIG_FILT50HZ, b);
|
|
}
|
|
|
|
/**
|
|
* Enable the bias voltage on the RTD sensor
|
|
*
|
|
* @param b If true bias is enabled, else disabled
|
|
*/
|
|
void MAX31865::enableBias(bool b) {
|
|
setConfig(MAX31856_CONFIG_BIAS, b);
|
|
|
|
// From the datasheet:
|
|
// Note that if VBIAS is off (to reduce supply current between conversions), any filter
|
|
// capacitors at the RTDIN inputs need to charge before an accurate conversion can be
|
|
// performed. Therefore, enable VBIAS and wait at least 10.5 time constants of the input
|
|
// RC network plus an additional 1ms before initiating the conversion.
|
|
if (b)
|
|
DELAY_US(11500); //11.5ms
|
|
}
|
|
|
|
/**
|
|
* Start a one-shot temperature reading.
|
|
*/
|
|
void MAX31865::oneShot() {
|
|
setConfig(MAX31856_CONFIG_1SHOT, 1);
|
|
|
|
// From the datasheet:
|
|
// Note that a single conversion requires approximately 52ms in 60Hz filter
|
|
// mode or 62.5ms in 50Hz filter mode to complete. 1-Shot is a self-clearing bit.
|
|
// TODO: switch this out depending on the filter mode.
|
|
DELAY_US(65000); // 65ms
|
|
}
|
|
|
|
/**
|
|
* How many wires we have in our RTD setup, can be MAX31865_2WIRE,
|
|
* MAX31865_3WIRE, or MAX31865_4WIRE
|
|
*
|
|
* @param wires The number of wires in enum format
|
|
*/
|
|
void MAX31865::setWires(max31865_numwires_t wires) {
|
|
uint8_t t = readRegister8(MAX31856_CONFIG_REG);
|
|
if (wires == MAX31865_3WIRE)
|
|
t |= MAX31856_CONFIG_3WIRE;
|
|
else // 2 or 4 wire
|
|
t &= ~MAX31856_CONFIG_3WIRE;
|
|
writeRegister8(MAX31856_CONFIG_REG, t);
|
|
}
|
|
|
|
/**
|
|
* Read the raw 16-bit value from the RTD_REG in one shot mode. This will include
|
|
* the fault bit, D0.
|
|
*
|
|
* @return The raw unsigned 16-bit register value with ERROR bit attached, NOT temperature!
|
|
*/
|
|
uint16_t MAX31865::readRaw() {
|
|
clearFault();
|
|
enableBias(true);
|
|
|
|
oneShot();
|
|
uint16_t rtd = readRegister16(MAX31856_RTDMSB_REG);
|
|
|
|
#ifdef MAX31865_DEBUG
|
|
SERIAL_ECHOLNPGM("RTD MSB:", (rtd >> 8), " RTD LSB:", (rtd & 0x00FF));
|
|
#endif
|
|
|
|
// Disable the bias to lower power dissipation between reads.
|
|
// If the ref resistor heats up, the temperature reading will be skewed.
|
|
enableBias(false);
|
|
|
|
return rtd;
|
|
}
|
|
|
|
/**
|
|
* Calculate and return the resistance value of the connected RTD.
|
|
*
|
|
* @param refResistor The value of the matching reference resistor, usually 430 or 4300
|
|
* @return The raw RTD resistance value, NOT temperature!
|
|
*/
|
|
float MAX31865::readResistance() {
|
|
// Strip the error bit (D0) and convert to a float ratio.
|
|
// less precise method: (readRaw() * Rref) >> 16
|
|
return (((readRaw() >> 1) / 32768.0f) * Rref);
|
|
}
|
|
|
|
/**
|
|
* Read the RTD and pass it to temperature(float) for calculation.
|
|
*
|
|
* @return Temperature in C
|
|
*/
|
|
float MAX31865::temperature() {
|
|
return temperature(readResistance());
|
|
}
|
|
|
|
/**
|
|
* Given the 15-bit ADC value, calculate the resistance and pass it to temperature(float) for calculation.
|
|
*
|
|
* @return Temperature in C
|
|
*/
|
|
float MAX31865::temperature(uint16_t adcVal) {
|
|
return temperature(((adcVal) / 32768.0f) * Rref);
|
|
}
|
|
|
|
/**
|
|
* Calculate the temperature in C from the RTD resistance.
|
|
* Uses the technique outlined in this PDF:
|
|
* http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf
|
|
*
|
|
* @param Rrtd the resistance value in ohms
|
|
* @return the temperature in degC
|
|
*/
|
|
float MAX31865::temperature(float Rrtd) {
|
|
float temp = (RTD_Z1 + sqrt(RTD_Z2 + (RTD_Z3 * Rrtd))) / RTD_Z4;
|
|
|
|
// From the PDF...
|
|
//
|
|
// The previous equation is valid only for temperatures of 0°C and above.
|
|
// The equation for RRTD(t) that defines negative temperature behavior is a
|
|
// fourth-order polynomial (after expanding the third term) and is quite
|
|
// impractical to solve for a single expression of temperature as a function
|
|
// of resistance.
|
|
//
|
|
if (temp < 0) {
|
|
Rrtd = (Rrtd / Rzero) * 100; // normalize to 100 ohm
|
|
float rpoly = Rrtd;
|
|
|
|
temp = -242.02 + (2.2228 * rpoly);
|
|
rpoly *= Rrtd; // square
|
|
temp += 2.5859e-3 * rpoly;
|
|
rpoly *= Rrtd; // ^3
|
|
temp -= 4.8260e-6 * rpoly;
|
|
rpoly *= Rrtd; // ^4
|
|
temp -= 2.8183e-8 * rpoly;
|
|
rpoly *= Rrtd; // ^5
|
|
temp += 1.5243e-10 * rpoly;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
//
|
|
// private:
|
|
//
|
|
|
|
|
|
/**
|
|
* Set a value in the configuration register.
|
|
*
|
|
* @param config 8-bit value for the config item
|
|
* @param enable whether to enable or disable the value
|
|
*/
|
|
void MAX31865::setConfig(uint8_t config, bool enable) {
|
|
uint8_t t = readRegister8(MAX31856_CONFIG_REG);
|
|
if (enable)
|
|
t |= config;
|
|
else
|
|
t &= ~config; // disable
|
|
writeRegister8(MAX31856_CONFIG_REG, t);
|
|
}
|
|
|
|
/**
|
|
* Read a single byte from the specified register address.
|
|
*
|
|
* @param addr the register address
|
|
* @return the register contents
|
|
*/
|
|
uint8_t MAX31865::readRegister8(uint8_t addr) {
|
|
uint8_t ret = 0;
|
|
readRegisterN(addr, &ret, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Read two bytes: 1 from the specified register address, and 1 from the next address.
|
|
*
|
|
* @param addr the first register address
|
|
* @return both register contents as a single 16-bit int
|
|
*/
|
|
uint16_t MAX31865::readRegister16(uint8_t addr) {
|
|
uint8_t buffer[2] = {0, 0};
|
|
readRegisterN(addr, buffer, 2);
|
|
|
|
uint16_t ret = buffer[0];
|
|
ret <<= 8;
|
|
ret |= buffer[1];
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Read +n+ bytes from a specified address into +buffer+. Set D7 to 0 to specify a read.
|
|
*
|
|
* @param addr the first register address
|
|
* @param buffer storage for the read bytes
|
|
* @param n the number of bytes to read
|
|
*/
|
|
void MAX31865::readRegisterN(uint8_t addr, uint8_t buffer[], uint8_t n) {
|
|
addr &= 0x7F; // make sure top bit is not set
|
|
if (_sclk == TERN(LARGE_PINMAP, -1UL, -1))
|
|
SPI.beginTransaction(spiConfig);
|
|
else
|
|
WRITE(_sclk, LOW);
|
|
|
|
WRITE(_cs, LOW);
|
|
spixfer(addr);
|
|
|
|
while (n--) {
|
|
buffer[0] = spixfer(0xFF);
|
|
#ifdef MAX31865_DEBUG_SPI
|
|
SERIAL_ECHOLNPGM("buffer read ", n, " data: ", buffer[0]);
|
|
#endif
|
|
buffer++;
|
|
}
|
|
|
|
if (_sclk == TERN(LARGE_PINMAP, -1UL, -1))
|
|
SPI.endTransaction();
|
|
|
|
WRITE(_cs, HIGH);
|
|
}
|
|
|
|
/**
|
|
* Write an 8-bit value to a register. Set D7 to 1 to specify a write.
|
|
*
|
|
* @param addr the address to write to
|
|
* @param data the data to write
|
|
*/
|
|
void MAX31865::writeRegister8(uint8_t addr, uint8_t data) {
|
|
if (_sclk == TERN(LARGE_PINMAP, -1UL, -1))
|
|
SPI.beginTransaction(spiConfig);
|
|
else
|
|
WRITE(_sclk, LOW);
|
|
|
|
WRITE(_cs, LOW);
|
|
|
|
spixfer(addr | 0x80); // make sure top bit is set
|
|
spixfer(data);
|
|
|
|
if (_sclk == TERN(LARGE_PINMAP, -1UL, -1))
|
|
SPI.endTransaction();
|
|
|
|
WRITE(_cs, HIGH);
|
|
}
|
|
|
|
/**
|
|
* Transfer SPI data +x+ and read the response. From the datasheet...
|
|
* Input data (SDI) is latched on the internal strobe edge and output data (SDO) is
|
|
* shifted out on the shift edge. There is one clock for each bit transferred.
|
|
* Address and data bits are transferred in groups of eight, MSB first.
|
|
*
|
|
* @param x an 8-bit chunk of data to write
|
|
* @return the 8-bit response
|
|
*/
|
|
uint8_t MAX31865::spixfer(uint8_t x) {
|
|
if (_sclk == TERN(LARGE_PINMAP, -1UL, -1))
|
|
return SPI.transfer(x);
|
|
|
|
uint8_t reply = 0;
|
|
for (int i = 7; i >= 0; i--) {
|
|
reply <<= 1;
|
|
WRITE(_sclk, HIGH);
|
|
WRITE(_mosi, x & (1 << i));
|
|
WRITE(_sclk, LOW);
|
|
if (READ(_miso))
|
|
reply |= 1;
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
#endif // HAS_MAX31865 && !USE_ADAFRUIT_MAX31865
|