MAX31865 and Adafruit RTD library on Teensy 4.1 problem

SteveW

Active member
Hi all, new here, I'm very much an amateur though have some past PIC C programming experience, I wanted to try out the Teensy as it seems ideal for my car project. I'm replacing the 80s retro looking dash thermometer in a Porsche 968 with something that looks the part but the hope is to develop the project over time to control dash display of other CAN sourced data, I plan to add a CAN bus to the car and gather a lot more data in time... Anyway I started out with the thermometer part of the project as an intro to the Arduino/Teensy world.

I attached a TM 1637 4x 7 segment board to the Teensy on pins 2-3 via a level shifter, all went well, I coded for temperature display from a float value, all worked fine. Perhaps being overconfident I then soldered an Adafruit clone MAX31865 board into the prototype. I cannot get any reading from the 31865 sadly. Ive extracted the code relating to the Max read and run it standalone, its pasted below, so no interaction with the TM 1367 library. The Adafruit library rtd() ADC read function returns zero as does fault(), cant even get an error code. Ive looked at much of the relevant library code though I admit I get lost in it a bit, I checked the pin config parameters seem to be being passed correctly though.

I'm sure many will say I should have breadboarded this, oh well. I went for software SPI on pins 29-32, the connections are shown in my photo. I've listed them in the attached test code. Connections seem good, no shorts, continuity from breakout to the Teensy pins is as expected and documented here. 3.3v breakout supply and good ground as expected. The 3 wire PT100 RTD is connected as shown, approx 108R accross the centre 2 screw terminals and 0.6 R across the yellow/blue wires as expected. The RTD resistance increases as I warm it so it seems good. The breakout has the correct RREF, 430R. The code below loops fine, counting/flashing the LED but outputting 0 for rtd and fault values (there is an invalid calculated temp) via the serial terminal. I've run the code with/without #includes, no different, guess the Adafruit code sorts those. Ive tried setting the CS pin 29 as output/low in my code, again no different.

Just wonder if anyone has had success with the Adafruit library and software SPI on the Teensy 4.1? I guess I could rewire so that Im using hardware SPI, I was intending to save that channel for other devices perhaps. I should really be able to make the current configuration work? Next step might be to play with a cheap logic analyser on the SPI bus I think, though I wonder if dead communications due to non-working code is more likely the problem. Im surprised none of the library functions returns an error really. I cannot 100 pc rule out a dead breakout board I suppose, but Ive done nothing bad to it that I know about. Any advice appreciated!

Steve


Boardpic.jpg

Code:
/*   Hardware:
 *   
 *   Teensyduino 4.1 ARM processor board, note 3.3V I/O 
 *
 *   MAX 31865 PTD100 interface (3.3V Vin, 430R ref), 3 wire PTD, connected pins 29 CS, 30 SDI, 31 SDO, 32 CLK 
 *   
 *   
 */


//#include <Arduino.h>
//#include <Wire.h>
//#include <SPI.h>
#include <Adafruit_MAX31865.h>

// 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

Adafruit_MAX31865 thermo = Adafruit_MAX31865(29,30,31,32);        //Set up thermometer object, defining interface pins for MAX RTD interface



// The amount of time (in milliseconds) for default delays
#define DELAY   2000


int led = 13;                     // Pin 13 has the LED on Teensy
float temp = 0;
int ctr = 0;
int rtd = 0;


void setup()
{
 
  pinMode(led, OUTPUT); 
  //pinMode(29, OUTPUT);           //Slave select pin MAX 31865 is 29 
  //digitalWrite(29, LOW);
  flashLED();
  thermo.begin(MAX31865_3WIRE);           // Set up MAX interface for the 3 wire RTD in use
}

  
void loop()
{
  ctr ++;
  
  int fault = thermo.readFault();

  temp = thermo.temperature(RNOMINAL, RREF);                 // Read temp, returns float value
  
  rtd = thermo.readRTD();                                    // Read the raw ADC data, returns int value

  Serial.print("Reading no: "); Serial.println (ctr);
  Serial.print("Fault code: "); Serial.println(fault);
  Serial.print("RTD value: "); Serial.println(rtd);
  Serial.print("Temp: "); Serial.println(temp);
  
  flashLED();
}

void flashLED (void)
{
  digitalWrite(led, HIGH);         // turn the LED on 
  delay(100);                      // one LED flash
  digitalWrite(led, LOW);          // turn the LED off
  delay (DELAY);
}
 
Hi Steve,
I'm using those boards but with hardware SPI. Are you sure that board will work with a 3.3V supply? I'm using 5V. Have you checked what the voltage is at the 3.3V output on the breakout board?
All the best,
Alan
 
Hi Steve,
I'm using those boards but with hardware SPI. Are you sure that board will work with a 3.3V supply? I'm using 5V. Have you checked what the voltage is at the 3.3V output on the breakout board?
All the best,
Alan

Thanks for the input Alan. Maybe I should have gone for hardware SPI, it is a prototype after all... I thought that the Adafruit style breakout could be fed with 3.3 to 5v optional at Vin. I checked the breakouts 3.3v out pin and it’s reading 3.3v so the on board regulator seems to be outputting correctly. If I don’t get anywhere I might get another teensy and play with hardware spi on a breadboard. Just frustrating that what perhaps should work does not,

Steve
 
OK so inspired by both Alan's comment and the not-fun nature of debugging code using the Adafruit MAX31865 library I decided to switch my design to hardware SPI and to dump the library, reducing code complexity. I coded my own Max routines so dependency is now only upon the SPI library. I wanted to use port SPI1 for geographical convenience and to avoid a clash with the on board LED. For a noob to the Teensy 4.1 the use of port SPI1 vs default SPI is a little thinly documented online, so I wondered if my working test code might help others finding this thread. I commented the code to try to make it helpful, there's a lot of MAX18365 info in there, not all of which was obvious to me from the datasheet. The code goes as far as obtaining a float temperature calculated via the Callender Van Dusen method - thanks for that bit Adafruit. I intend to develop it onwards and probably use lookup table temp conversion in final use. Obviously it can be made a lot more elegant and tighter, with removal of the delay()s (polling, interrupts for timing). I just wanted to get a basic working version first! Anyway, hope some Teensy 4.1/SPI1/Max31865 code is useful to someone:

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;
}
 
Similar issue?

I am not sure if this is possibly related...

I was busy yesterday putting together a teensy 3.6 with 6 of these adafruit sensors. I need to measure inlet an outlet temperatures on two heat exchangers and an TXV valve. It finally worked but it was a pain because it took way too long to complete. I thought I had mis-wired things... then I finally got it to work.
the interesting part...

Using the below method... further calls to the sensor come back as zero with no error return code:
Adafruit_MAX31865 thermo1 = Adafruit_MAX31865(2);

Setting it up this way the call to the rtd then returns a proper value:
Adafruit_MAX31865 thermo1 = Adafruit_MAX31865(2, 11, 12, 13);

I stumbled into this by using pin 30 with the first method... which worked and then tried the second method and that worked as well.
Using pin 2 with the first method did NOT work and led me to think my wiring was faulty... but does work with the second instantiation.

My board is now running 6 of these sensors (yes I have 6 CS lines running about) all with pt1000 rtd's and off the teensy while plugged into usb.
6x31865 boards running off the 3.3v supply of the teensy (~48ma?).

I don't have time to stop and dig into this... just offering the observation.

Scott
 
I am not sure if this is possibly related...

I was busy yesterday putting together a teensy 3.6 with 6 of these adafruit sensors. I need to measure inlet an outlet temperatures on two heat exchangers and an TXV valve. It finally worked but it was a pain because it took way too long to complete. I thought I had mis-wired things... then I finally got it to work.
the interesting part...

Using the below method... further calls to the sensor come back as zero with no error return code:
Adafruit_MAX31865 thermo1 = Adafruit_MAX31865(2);

Setting it up this way the call to the rtd then returns a proper value:
Adafruit_MAX31865 thermo1 = Adafruit_MAX31865(2, 11, 12, 13);

I stumbled into this by using pin 30 with the first method... which worked and then tried the second method and that worked as well.
Using pin 2 with the first method did NOT work and led me to think my wiring was faulty... but does work with the second instantiation.

My board is now running 6 of these sensors (yes I have 6 CS lines running about) all with pt1000 rtd's and off the teensy while plugged into usb.
6x31865 boards running off the 3.3v supply of the teensy (~48ma?).

I don't have time to stop and dig into this... just offering the observation.

Scott

Interesting that the Adafruit library seems to be fussy about which pins are used. As said I dumped the library in my code, it’s almost as easy to just code it using SPI calls,

Steve
 
Steve, thanks for this thread! I was also struggling to use my Teensy 4.1 with Adafruit's MAX31865. I had no luck with Adafruit's library and software SPI - I also got zero values for the RTD in serial monitor. My logic analyzer showed the chip select pin triggering but otherwise nothing else on the SPI pins.

Like you, I'd already committed to software SPI for my project, so I modified your hardware SPI code to bit-bang the software SPI connection. Your code and comments were super helpful, I would not have been able to figure this out without.

Here's my bit-banged version. I had to add delayMicroseconds(1) in a few places to slow down the SPI clock and CS signals - the Teensy is too fast for the MAX31865 even using this clunky SPI method.
Code:
// MAX 31865 Software SPI
//Attempt to make communication with Teensy 4.1 work - existing MAX31865 library does not work with software SPI.
//Code is by S. Williams and adapted by Graham Jessup to use bit-banged SPI rather than hardware SPI.

//SW SPI Bit-banging from https://circuitdigest.com/article/introduction-to-bit-banging-spi-communication-in-arduino-via-bit-banging
//PJRC Thread: https://forum.pjrc.com/threads/64101-MAX31865-and-Adafruit-RTD-library-on-Teensy-4-1-problem

/*   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>

#define DEBUG 1
#define DELAY   500                                        // The amount of time (in milliseconds) for default delays

// SPI setup:
#define RTD_MOSI 24                                            // Define the software SPI pins in use (must be available on hardware)
#define RTD_MISO 5
#define RTD_CLK 6
#define RTD_CS_1 7                                          

// MAX 31865 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


// Global variables:
byte sendData = 64;   // Value to be sent
byte slaveData = 0;  // for storing the  value sent by the slave



float temp = 0;
float tempF = 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

//Bit-banging variables

void setup() {
  // Setup SPI:

  pinMode(RTD_MISO, INPUT);
  pinMode(RTD_MOSI, OUTPUT);
  pinMode(RTD_CLK, OUTPUT);
  pinMode(RTD_CS_1, OUTPUT);
  digitalWrite(RTD_CS_1, HIGH);
  digitalWrite(RTD_CLK, HIGH);
  delay(10);                                      // Delay after setup in case any timing issue with Max chip response



}

byte swSPIWrite(byte _address, byte _send)  // This function transmits the address byte followed by the data byte, and reads response.
// Requires an address byte and a data byte to match MAX6675 SPI scheme bit bang function source: https://circuitdigest.com/article/introduction-to-bit-banging-spi-communication-in-arduino-via-bit-banging
{
  byte _receive = 0;
  digitalWrite(RTD_CS_1, LOW); //Set chip select low

  for (int i = 7; i >= 0; i--) // 8 bits in a byte - for loop transmits _address byte white reading MISO into _receive byte - _receive is immediately overwritten by next byte
  {
    digitalWrite(RTD_MOSI, bitRead(_address, i));    // Set MOSI
    digitalWrite(RTD_CLK, LOW);                  // SCK high
    delayMicroseconds(1);
    bitWrite(_receive, i, digitalRead(RTD_MISO)); // Capture MISO

    digitalWrite(RTD_CLK, HIGH);                   // SCK low
    delayMicroseconds(1);
  }

  for (int i = 7; i >= 0; i--) // 8 bits in a byte - for loop transmits _send byte white reading MISO into _receive byte
  {
    digitalWrite(RTD_MOSI, bitRead(_send, i));    // Set MOSI
    digitalWrite(RTD_CLK, LOW);                  // SCK high
    delayMicroseconds(1);
    bitWrite(_receive, i, digitalRead(RTD_MISO)); // Capture MISO
    digitalWrite(RTD_CLK, HIGH);                   // SCK low
    delayMicroseconds(1);
  }
  digitalWrite(RTD_CS_1, HIGH); //Set chip select high to end transmission
  digitalWrite(RTD_MOSI, HIGH);//set MOSI back to high
  delayMicroseconds(1);
  return _receive;        // Return the received data
}

byte swSPIRead(byte _address)  // This function transmits the address byte followed by the data byte, and reads response.
// Requires an address byte and a data byte to match MAX6675 SPI scheme bit bang function source: https://circuitdigest.com/article/introduction-to-bit-banging-spi-communication-in-arduino-via-bit-banging
{
  byte _receive = 0;
  byte _send = 0xFF; //blank send byte, MOSI send data doesn't matter for read operation
  digitalWrite(RTD_CS_1, LOW); //Set chip select low

  for (int i = 7; i >= 0; i--) // 8 bits in a byte - for loop transmits _address byte white reading MISO into _receive byte - _receive is immediately overwritten by next byte
  {
    digitalWrite(RTD_MOSI, bitRead(_address, i));    // Set MOSI
    digitalWrite(RTD_CLK, LOW);                  // SCK high
    delayMicroseconds(1);
    bitWrite(_receive, i, digitalRead(RTD_MISO)); // Capture MISO1
    digitalWrite(RTD_CLK, HIGH);                   // SCK low
    delayMicroseconds(1);
  }

  for (int i = 7; i >= 0; i--) // 8 bits in a byte - for loop transmits _send byte white reading MISO into _receive byte
  {
    digitalWrite(RTD_MOSI, bitRead(_send, i));    // Set MOSI
    digitalWrite(RTD_CLK, LOW);                  // SCK high
    delayMicroseconds(1);
    bitWrite(_receive, i, digitalRead(RTD_MISO)); // Capture MISO
    digitalWrite(RTD_CLK, HIGH);                   // SCK low
    delayMicroseconds(1);
  }
  digitalWrite(RTD_CS_1, HIGH); //Set chip select high to end transmission
  digitalWrite(RTD_MOSI, HIGH);//set MOSI back to high
  delayMicroseconds(1);
  return _receive;        // Return the received data

}

void clearFault (void) //Clears fault registers
{

  uint8_t data = swSPIRead(CONFIG_READ_ADDR);                        // First read the config register for the read modify write operation
  swSPIWrite(CONFIG_WRITE_ADDR, (data |= FAULTCLEAR));               // Set bit 2 config register to clear fault register, bit autoclears

  return;
}

void startConversion (void)
{
  // Starts one shot conversion, note can take approx 65 ms
  uint8_t data = swSPIRead(CONFIG_READ_ADDR);                         // Read modify write of config register
  swSPIWrite(CONFIG_WRITE_ADDR, (data |= CONVERSION_ON));             // Set bit 5 of config to start one shot conversion, bit will clear automatically

  return;
}

void readRtd (void)
{
  // Read both ADC RTD registers, must have allowed enough time for a conversion or read data RDY flag
  readbuffer[0] = swSPIRead(RTDLSB);                                  // Return is via the global buffer, 3 bytes total
  readbuffer[1] = swSPIRead(RTDMSB);
  readbuffer[2] = swSPIRead(FAULT_ADDR);                              // Also return the contents of fault register
  swSPIWrite(CONFIG_WRITE_ADDR, maxconfigdata);                       // Restore max config as initialised, turns off bias current

  return;
}

void setBias(uint8_t bias)
{

  uint8_t data = swSPIRead(CONFIG_READ_ADDR);                          // First read the config register for the read modify write operation
  if (bias)
  {
    swSPIWrite(CONFIG_WRITE_ADDR, (data |= BIAS_ON));                // Set bit 7 config register to to turn on RTD bias current
  }
  else
  {
    swSPIWrite(CONFIG_WRITE_ADDR, (data &= (~BIAS_ON)));             // Unset bit 7 for bias current off
  }

  return;
}


float tempConversionCVD(uint16_t rtdval, float RTDnominal, float refResistor) //Adafruit temp conversion code, see note below

{
  /*
     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;
}



void loop() {
  // 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
  delayMicroseconds(1);
  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
  delayMicroseconds(1);
  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
  tempF = (temp*1.8)+32;
  Serial.print("temp = "); Serial.println(tempF);                      // Prints temperature to Serial Monitor
  Serial.print("RTD value: "); Serial.println(rtd);                    // Prints RTD value to Serial Monitor
  
  delay (DELAY);                                                       // *** Test code delay, replace with timing code for reads  ***

}
 
Steve, thanks for this thread! I was also struggling to use my Teensy 4.1 with Adafruit's MAX31865. I had no luck with Adafruit's library and software SPI - I also got zero values for the RTD in serial monitor. My logic analyzer showed the chip select pin triggering but otherwise nothing else on the SPI pins.

Like you, I'd already committed to software SPI for my project, so I modified your hardware SPI code to bit-bang the software SPI connection. Your code and comments were super helpful, I would not have been able to figure this out without.

Here's my bit-banged version. I had to add delayMicroseconds(1) in a few places to slow down the SPI clock and CS signals - the Teensy is too fast for the MAX31865 even using this clunky SPI method.
Code:
// MAX 31865 Software SPI
//Attempt to make communication with Teensy 4.1 work - existing MAX31865 library does not work with software SPI.
//Code is by S. Williams and adapted by Graham Jessup to use bit-banged SPI rather than hardware SPI.

//SW SPI Bit-banging from https://circuitdigest.com/article/introduction-to-bit-banging-spi-communication-in-arduino-via-bit-banging
//PJRC Thread: https://forum.pjrc.com/threads/64101-MAX31865-and-Adafruit-RTD-library-on-Teensy-4-1-

[/QUOTE]

Glad the 31865 code was useful! Also that you have added a software spi version to the thread. Hope all the code here is useful for others. Using hardware SPI I found everything worked without delays but I can see it’s different if communicating in real time as it were. I never got round to attaching a scope or analyser with the adafruit code, decided life was too short at the time, interesting comments! 

Steve
 
Joining this thread 3 years late but if anyone happens to be struggling with this issue; adafruit has updated their library and you can now specify which HW SPI to use

Code:
Adafruit_MAX31865 thermo = Adafruit_MAX31865(10, &SPI1);

You just have to use the library after the commits from the end of June 2022
https://github.com/adafruit/Adafruit_MAX31865/commits/master

Great to have the thread updated! Funny to revisit my old code example after some time. I think I eventually managed some nicer non-blocking code. Useful to read as I plan to write some PIC code to interface to the MAX31865 sometime soon. I’m still not a massive fan of avoidable libraries, such chips are not that hard to interface to really

Steve
 
Back
Top