Code:
/* Teensy 4.1 MAX31865 SPI RTD interface test code
* Code demonstrates the use of the Teensy SPI1 serial interface
*
* S Williams, 7/11/20
*
* Disclaimer:
*
* This is demo code with no warranty and is not intended for final use as is, rather as a basis for modification. Any user is responsible for assessment of suitability for any
* purpose and for modifying the code to ensure compatibilty and function. Beware that use with inappropriate hardware configurations, such as when incorrect hardware port pins are
* grounded, can cause permanent hardware damage.
*
* Hardware:
*
* Teensyduino 4.1 ARM processor board, note 3.3V I/O
* MAX 31865 PTD100 interface (3.3V Vin, 430R ref) with 3 wire PTD ("max1")
* Connections on Teensy 4.1 are to SPI1 pins MOSI1 26, MISO1 39, SCK1 27. Data pins also used: CS 32, RDY 28 (these two can be any data pin)
* The default Teensy SPI port was not used during testing, note that the on board LED is connected to the SPI SCK output if modifying for this port
*
* The Adafruit Callender Van Dusen conversion code in function tempconversionCVD() was downloaded from: https://github.com/adafruit/Adafruit_MAX31865
* and was written by Limor Fried/Ladyada for Adafruit Industries (suppliers of MAX31865 interface boards!)
*
*/
#include <Arduino.h>
#include <SPI.h>
#define DEBUG 1
#define DELAY 2000 // The amount of time (in milliseconds) for default delays
#define LED 13 // LED is on pin 13 Teensy board
// SPI setup:
#define MOSI1 26 // Define the hardware SPI pins in use (must be available on hardware)
#define MISO1 39
#define SCK1 27
#define CSMAX1 32 // Not using a hardware CS pin, this can be any digital IO capable pin
#define RDYMAX1 28 // Pin can be polled for MAX conversion data ready
// MAX 31867 setup:
#define RREF 430 // RTD reference resistor value used for the PT100 sensor
#define RNOMINAL 100 // Nominal 0C resistance for PT 100 RTD
#define RTD_A 3.9083e-3 // Callender V D coefficiants A/B for temp conversion
#define RTD_B -5.775e-7
#define HIGH_THRESH_MSB 0x87 // High fault threshold is 0x87B6, low fault 0x196C, configuration values are ADC RTD output in 16 bit format
#define HIGH_THRESH_LSB 0xB6 // Fault will be flagged if RTD resistance > 212.05R, equivalent to approx 300C or if < 39.72R, -150C
#define LOW_THRESH_MSB 0x19
#define LOW_THRESH_LSB 0x6C
#define CONFIG_WRITE_ADDR 0x80
#define CONFIG_READ_ADDR 0x00
#define RTDLSB 0x02 // RTD read data address LSB
#define RTDMSB 0x01 // RTD read data address MSB
#define FAULT_ADDR 0x07 // Fault register
#define RTDFAULTBIT 0x01 // Fault bit is bit 0 of rtd LSB
#define CONFIG3WIRE 0b00110001 // Config register for MAX31865 set up with bias current off(D7) initially, 3 wire(D4),1 shot conversion
#define CONFIG24WIRE 0b00100001 // Config setup for 2/4 wire sensors.Base configs do not clear faults, random initial data on read fault reg.
#define FAULTCLEAR 0b00000010 // Bit D1 of config clears faults when set, autoclear
#define BIAS_ON 0b10000000 // Mask to set D7 of config register, turns on RTD bias current
#define CONVERSION_ON 0b00100000 // Mask to set config D5, starts a one shot conversion
SPISettings max1(5000000,MSBFIRST,SPI_MODE3); // Specifies SPI interface params for the MAX31865 RTD interface chip max1, maximum chip clock rate is 5 MHz
// Global variables:
float temp = 0;
uint8_t maxconfigdata = CONFIG3WIRE;
uint16_t ctr = 0;
uint16_t rtd = 0;
uint8_t fault = 0;
uint8_t data = 0;
uint8_t configread = 0;
uint8_t configerrorflag = 0;
uint8_t faultregister =0;
uint8_t faultflag = 0;
uint8_t readbuffer[8]; // Buffer used for multibyte register reads, could be up to 8 registers
void setup()
{
// Setup LED and SPI:
pinMode(LED, OUTPUT);
pinMode(CSMAX1, OUTPUT); // Slave select pin for MAX 31865 set as output
digitalWrite(CSMAX1, HIGH); // CS pin high = MAX1 inactive
SPI1.setMOSI(MOSI1); // Alternate hardware SPI pins set here, need to do if use SPI1 rather than default SPI port (testing was on Teensy 4.1)
SPI1.setMISO(MISO1);
SPI1.setSCK(SCK1);
SPI1.begin(); // Enable SPI port, SPI1, second of the hardware SPI ports being used here
delay(10); // Delay after setup in case any timing issue with Max chip response
//Initialise the MAX31865 registers:
SPI1.beginTransaction(max1); // SPI communication is transactional using max1 settings
SPI1WriteByte(CONFIG_WRITE_ADDR,maxconfigdata); // Write initial config data to the Max 13865 config register via port SPI1
configread=SPI1ReadByte(CONFIG_READ_ADDR); // Now read the same register to confirm we have SPI communication
SPI1.endTransaction(); // Don't set config fault clear bit pre read test here, autoclear causes error
configerrorflag = (maxconfigdata == configread) ? 0 : 1; // 0 means the configuration register write succeeded
if (configerrorflag)
{
// Handle error and generate a system error code, could indicate errors with onboard LED flashes?
}
else
{
SPI1.beginTransaction(max1);
SPI1WriteByte(0x83,HIGH_THRESH_MSB); // Write configuration data to the 4 fault threshold registers 0x83-86
SPI1WriteByte(0x84,HIGH_THRESH_LSB); // Could code more elegantly using data buffer and multibyte write if preferred
SPI1WriteByte(0x85,LOW_THRESH_MSB);
SPI1WriteByte(0x86,LOW_THRESH_LSB);
SPI1.endTransaction();
}
flashLED(); // Board LED indicates that setup() code ran
} //end setup
void loop()
{
ctr ++;
//int fault = thermo.readFault();
// Read the raw ADC data, returns int value:
setbias(1); // Sets RTD bias current on ready for 1 shot conversion
delay(10); // Allow for bias circuit time constant, ideally replace delay to allow processing, use timer instead
clearfault(); // Clear the fault register, note initialisation with random data
startconversion(); // Start one shot conversion ADD FAULT REGISTER result handling. Dont read value in if fault, reset rtd to zero!
delay(70); // Delay should be replaced to allow ongoing loop processing, use timer or Max RDY flag to allow read completion
readrtd(); // Readrtd() will return rtd data via global readbuffer[], element [0]=lsb, [1]=msb, [3]=fault register contents
setbias(0); // Bias off
faultflag = readbuffer[0] & RTDFAULTBIT; // Get the fault bit, D0 of LSB, 1 is fault, 0 is ok
rtd = 0;
if (faultflag==1)
{
faultregister=readbuffer[2]; // *** ADD FAULT HANDLING TO CODE ***
}
else
{
rtd = readbuffer[1]; // High byte of rtd value bitshifted into D16-9 of the 16 bit rtd integer
rtd <<= 8;
rtd |= readbuffer[0];
rtd >>= 1; // Shift 1 right, gets rid of fault bit leaving the 15 bit ADC value (RTD resistance = ADC/32767*RREF)
}
temp = tempconversionCVD(rtd, RNOMINAL, RREF); // Call Adafruit code for CVD temp conversion of 15 bit ADC value
if (DEBUG)
{
debugoutput(); // Outputs debug data including registers to serial monitor, nb delays loop, repeated output
}
delay (DELAY); // *** Test code delay, replace with timing code for reads ***
} //end loop
// Functions:
void flashLED (void)
{
digitalWrite(LED, HIGH); // turn the LED on
delay(100); // one LED flash
digitalWrite(LED, LOW); // turn the LED off
return;
}
void debugoutput (void)
{
Serial.print("Loop counter: "); Serial.println (ctr); // Serial output of debug data, nb delays loop, DEBUG=TRUE to enable
Serial.print("Max 31867 Config data: 0x"); Serial.println(configread,HEX);
Serial.print("Max 31867 Config error flag: "); Serial.println(configerrorflag);
Serial.print("Max 31867 RTD Fault flag: "); Serial.println(faultflag);
Serial.print("Max 31867 Fault Register: 0x"); Serial.println(fault,HEX);
Serial.print("Fault threshold hi MSB: 0x"); Serial.println (SPI1ReadByte(0x03),HEX); // Non-transactional reads used during debug, beware interrupt effects
Serial.print("Fault threshold hi LSB: 0x"); Serial.println (SPI1ReadByte(0x04),HEX);
Serial.print("Fault threshold low MSB: 0x"); Serial.println (SPI1ReadByte(0x05),HEX);
Serial.print("Fault threshold low LSB: 0x"); Serial.println (SPI1ReadByte(0x06),HEX);
Serial.print("RTD value: "); Serial.println(rtd);
Serial.print("Temp degrees C: "); Serial.println(temp);
/*
Serial.print("Fault code: "); Serial.println(fault);
*/
flashLED();
}
void SPI1WriteByte (uint8_t address, uint8_t writebyte)
{
digitalWrite(CSMAX1, LOW); // CS pin low = Activates MAX1 interface on SPI1
SPI1.transfer(address); // Send the address of register to be written
SPI1.transfer(writebyte); // Send the data to be written
digitalWrite(CSMAX1, HIGH); // Must toggle the CS pin high to terminate write before sending a further address
return;
}
uint8_t SPI1ReadByte(uint8_t address)
{
uint8_t returnbyte = 0;
digitalWrite(CSMAX1, LOW); // Take CS pin low to activate MAX1 interface
SPI1.transfer(address); // Send the address of register to be read
returnbyte = SPI1.transfer(0xFF); // Dummy FF write to clock out data
digitalWrite(CSMAX1, HIGH); // CS pin high = MAX1 inactive, further address write may follow if CS taken is active low again
return (returnbyte);
}
void clearfault (void)
{
SPI1.beginTransaction(max1);
uint8_t data=SPI1ReadByte(CONFIG_READ_ADDR); // First read the config register for the read modify write operation
SPI1WriteByte(CONFIG_WRITE_ADDR,(data |= FAULTCLEAR)); // Set bit 2 config register to clear fault register, bit autoclears
SPI1.endTransaction();
return;
}
void startconversion (void)
{
SPI1.beginTransaction(max1); // Starts one shot conversion, note can take approx 65 ms
uint8_t data=SPI1ReadByte(CONFIG_READ_ADDR); // Read modify write of config register
SPI1WriteByte(CONFIG_WRITE_ADDR,(data |= CONVERSION_ON)); // Set bit 5 of config to start one shot conversion, bit will clear automatically
SPI1.endTransaction();
return;
}
void readrtd (void)
{
SPI1.beginTransaction(max1); // Read both ADC RTD registers, must have allowed enough time for a conversion or read data RDY flag
readbuffer[0]=SPI1ReadByte(RTDLSB); // Return is via the global buffer, 3 bytes total
readbuffer[1]=SPI1ReadByte(RTDMSB);
readbuffer[2]=SPI1ReadByte(FAULT_ADDR); // Also return the contents of fault register
SPI1WriteByte(CONFIG_WRITE_ADDR,maxconfigdata); // Restore max config as initialised, turns off bias current
SPI1.endTransaction();
return;
}
void setbias(uint8_t bias)
{
SPI1.beginTransaction(max1);
uint8_t data=SPI1ReadByte(CONFIG_READ_ADDR); // First read the config register for the read modify write operation
if (bias)
{
SPI1WriteByte(CONFIG_WRITE_ADDR,(data |= BIAS_ON)); // Set bit 7 config register to to turn on RTD bias current
}
else
{
SPI1WriteByte(CONFIG_WRITE_ADDR,(data &= (~BIAS_ON))); // Unset bit 7 for bias current off
}
SPI1.endTransaction();
return;
}
float tempconversionCVD(uint16_t rtdval, float RTDnominal, float refResistor)
{
/*
* Callender Van Dusen temperature conversion of 15 bit ADC resistance ratio data
* The Adafruit conversion code in this function was downloaded from: https://github.com/adafruit/Adafruit_MAX31865
* Written by Limor Fried/Ladyada for Adafruit Industries (suppliers of MAX31865 interface boards!)
*/
float Z1, Z2, Z3, Z4, Rt, temp;
Rt = rtdval;
Rt /= 32768;
Rt *= refResistor;
// Serial.print("\nResistance: "); Serial.println(Rt, 8);
Z1 = -RTD_A;
Z2 = RTD_A * RTD_A - (4 * RTD_B);
Z3 = (4 * RTD_B) / RTDnominal;
Z4 = 2 * RTD_B;
temp = Z2 + (Z3 * Rt);
temp = (sqrt(temp) + Z1) / Z4;
if (temp >= 0)
return temp;
// ugh.
Rt /= RTDnominal;
Rt *= 100; // normalize to 100 ohm
float rpoly = Rt;
temp = -242.02;
temp += 2.2228 * rpoly;
rpoly *= Rt; // square
temp += 2.5859e-3 * rpoly;
rpoly *= Rt; // ^3
temp -= 4.8260e-6 * rpoly;
rpoly *= Rt; // ^4
temp -= 2.8183e-8 * rpoly;
rpoly *= Rt; // ^5
temp += 1.5243e-10 * rpoly;
return temp;
}