MAX31865 Library Removing Delay & Changing to Non-Blocking Method

Hi guys,

I am using the MAX31865 library for the PT100 temp sensor, however it uses delays which are blocking other parts of my sketch. I have tried to modify it to use millis but it does not work. (produces wrong temp reading)

Would be great if someone could point me in the right direction for replacing the delays with a non-blocking method.

Thanks a lot!

The code in the library

uint16_t Adafruit_MAX31865::readRTD(void) {
  clearFault();
  enableBias(true);
  delay(10);
  uint8_t t = readRegister8(MAX31856_CONFIG_REG);
  t |= MAX31856_CONFIG_1SHOT;
  writeRegister8(MAX31856_CONFIG_REG, t);
  delay(65);

  uint16_t rtd = readRegister16(MAX31856_RTDMSB_REG);

  // remove fault
  rtd >>= 1;

  return rtd;
}

My non working attempt!

int period1 = 10;
int period2 = 65;

unsigned long time_now = 0;


uint16_t Adafruit_MAX31865::readRTD(void) {
  clearFault();
  enableBias(true);

  if(millis() > time_now + period1){
        time_now = millis();

          //delay(10);
          uint8_t t = readRegister8(MAX31856_CONFIG_REG);
          t |= MAX31856_CONFIG_1SHOT;
          writeRegister8(MAX31856_CONFIG_REG, t);


          if(millis() > time_now + period2){
            time_now = millis();

            //delay(65);
            uint16_t rtd = readRegister16(MAX31856_RTDMSB_REG);

            // remove fault
            rtd >>= 1;

            return rtd;       
    }     
  }
}

the delays are probably needed by the underlying hardware, I suppose they are somewhere in the spec.

if you want to have a fast call, that is an async readRTD(), then since there are 2 delays you need to split the function into 3 parts (3 functions) that you would call from your main loop once enough ms have elapsed. You can do that right in the library if you modify a bit the signature of the function so that it lets you know if it got the data

here is a simple example. the function getValBlocking() would be your existing readRTD() method with the delay(10) and delay(65). if you call it, it's blocking.

the function getValAsync() returns true when the value has been made available (and updates its parameter). You can call it repetitively until the data is available. It's not blocking, so you can do something else whilst waiting for the data (in this example I'm counting with the tick variable)

unsigned long tick = 0;

uint16_t getValBlocking()
{

  Serial.println("some initial code");
  delay(10);
  Serial.println("some middle code");
  delay(65);
  Serial.println("some final code");
  uint16_t val = random(101);
  return val;
}

bool getValAsync(uint16_t& val)
{
  enum t_state : byte {STATE1, STATE2, STATE3};
  static t_state state = STATE1;
  static uint32_t chrono = 0;
  bool valueAvailable = false;
  switch (state) {
    case STATE1:
      Serial.println("some initial code");
      chrono = millis();
      state = STATE2;
      break;
    case STATE2:
      if (millis() - chrono >= 10) {
        Serial.println("some middle code");
        chrono = millis();
        state = STATE3;
      }
      break;
    case STATE3:
      if (millis() - chrono >= 65) {
        Serial.println("some final code");
        val = random(101);
        valueAvailable = true; // signal computation is done
        state = STATE1;
      }
      break;
  }
  return valueAvailable;
}

void setup() {
  Serial.begin(115200);
}

void loop()
{
  uint16_t myResult;
  if (getValAsync(myResult)) {
    Serial.print("Result is = "); Serial.println(myResult);
    while (true); // stop here
  }
  Serial.println(tick++); // you can do something else whilst the value is being computed
}

Hey JML,

Thanks so much for taking the time to help me! I have been studying the code example you provided and I understand how it works now.

However I am struggling a bit to integrate it into the library. I had a few attempts but they are not working, could you give me a final clue to get it working?

Thanks a lot!

Jack

bool getValAsync(uint16_t& val)
{
  enum t_state : byte {STATE1, STATE2, STATE3};
  static t_state state = STATE1;
  static uint32_t chrono = 0;
  bool valueAvailable = false;
  switch (state) {
    case STATE1:

      clearFault();     // first section of code
      enableBias(true);

      chrono = millis();
      state = STATE2;
      break;
    case STATE2:
      if (millis() - chrono >= 10) {


      uint8_t t = readRegister8(MAX31856_CONFIG_REG); // second section of code
      t |= MAX31856_CONFIG_1SHOT;
      writeRegister8(MAX31856_CONFIG_REG, t);

        chrono = millis();
        state = STATE3;
      }
      break;
    case STATE3:
      if (millis() - chrono >= 65) {


        uint16_t rtd = readRegister16(MAX31856_RTDMSB_REG);
        // remove fault
        rtd >>= 1;
        return rtd;

        val = random(101);
        valueAvailable = true; // signal computation is done
        state = STATE1;
      }
      break;
  }
  return valueAvailable;
}





uint16_t Adafruit_MAX31865::readRTD(void) {
  uint16_t myResult;
  if (getValAsync(myResult)) {
    // Serial.print("Result is = "); Serial.println(myResult);
    while (true); // stop here
  }
}

you are not too far but cannot have a return in the middle of the code or you break the state machine.

an async Method could look like this

bool Adafruit_MAX31865::readRTDAsync(uint16_t& rtd) {
  enum t_state : byte {STATE1, STATE2, STATE3};
  static t_state state = STATE1;
  static uint32_t chrono = 0;
  bool valueAvailable = false;
  
  switch (state) {

    case STATE1:
      clearFault();
      enableBias(true);
      chrono = millis();
      state = STATE2;
      break;

    case STATE2:
      if (millis() - chrono >= 10) {
        uint8_t t = readRegister8(MAX31856_CONFIG_REG);
        t |= MAX31856_CONFIG_1SHOT;
        writeRegister8(MAX31856_CONFIG_REG, t);
        chrono = millis();
        state = STATE3;
      }
      break;

    case STATE3:
      if (millis() - chrono >= 65) {
        rtd = readRegister16(MAX31856_RTDMSB_REG); // EDITED FOR CORRECTNESS (SEE @countrypaul POST #11)
        rtd >>= 1;        // remove fault
        state = STATE1;   // get ready for next time
        valueAvailable = true; // signal computation is done
      }
      break;
  }
  return valueAvailable;
}

Note the & in readRTDAsync(uint16_t[color=red]&[/color] rtd). This is passing by reference, meaning that the method will modify the actual memory position of the parameter.

you would call it this way in your loop/code

uint16_t rtd;
if (myMax31865.readRTDAsync(rtd)) {
  // the value was ready and we got it in rtd now
  ...
} else {
  // value was not ready do something else
}

This means the call is non blocking so that you can proceed with the other things you need to handle but you have to come back to this readRTDAsync() call often enough or you won't be able to meet the exact timing requirement (I don't know if it's bad if you come back 11ms after instead of 10ms for example. You would need to read the hardware spec for that).

Thanks a lot for your reply, however I am more confused now haha. I get how this would work in a normal sketch but making this work in the library is proving to be challenging!

I saw this info on github from a guy who removed the delays from the same lib, apparently using millis. esp8266 with max31865 · Issue #2 · olewolf/arduino-max31865 · GitHub

Is it possible to make the changes just to the library or will I need to integrate code into my actual program to make it work?

Thanks again!

Jack

you can integrate the function I provided above straight in the library. add that function to the .cpp (next to the implementation of the readRTD method)

in the .h you need to define a public readRTDAsync() method next to the readRTD method.

 bool readRTDAsync(uint16_t& rtd);

so that this method will be known to all instances.

Epic we are nearly there, thanks for sticking with me. I am defiantly not that experienced just yet haha.

So I added the line to the .h file and the function to the .cpp leaving the current code there with the delays. I understand that we are just calling this new function without the delay. Is that right?

Using this code in my actual program sketch to call the new function. However I get an error saying

uint16_t rtd;
if (myMax31865.readRTDAsync(rtd)) {
  // the value was ready and we got it in rtd now
  ...
} else {
  // value was not ready do something else
}

error: 'myMax31865' was not declared in this scope

&

error: expected primary-expression before '...' token

Should I be using this code in my main sketch or have I confused things there? :o

well myMax31865 is meant to be your instance of your Adafruit_MAX31865 class :slight_smile:

how were you calling it in your code ?

that is if you've done

#include <Adafruit_MAX31865.h>
Adafruit_MAX31865 thermo = Adafruit_MAX31865(10, 11, 12, 13);

then thermo is your instance name so you would write

 if (thermo.readRTDAsync(rtd)) {
...

Yep I'm in so deep I forgot the basics haha!

So I am getting the RTD value now all good, thanks so much!

But... I just realised I actually need the temp in degC, for my PID loop of temp control. That calculation is done in the lib using the RTD value, so I would think to just replace the value that we got from the code you provided, but reality does not seam that easy haha. At this point my brain is smoking... :sweat_smile:

Temp calc in the lib

float Adafruit_MAX31865::temperature(float RTDnominal, float refResistor) {
  float Z1, Z2, Z3, Z4, Rt, temp;

  Rt = readRTD();
  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;
}

add a new method to the library to calculate the temperature based on an RTD you have acquired asynchronously, ie you just add Rt as a parameter and you don't acquire it in the code. basically almost the same code as the other one minus the line [s] Rt = readRTD();[/s]

 float Adafruit_MAX31865::temperatureAsync(float Rt, float RTDnominal, float refResistor) {
  float Z1, Z2, Z3, Z4, temp;

  Rt /= 32768;
  Rt *= refResistor;

  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;
}

then you just call this method once you have read the RTD

 uint16_t rtd;
if (myMax31865.readRTDAsync(rtd)) {
  // the value was ready and we got it in rtd now
  float temp = myMax31865. temperatureAsync(rtd, ...); 
  ...
} else {
  // value was not ready do something else
}

you of course need to add the method to the public part of the .h as well.

nice, that makes sense. So I added that function and added the line in the .h file -

float temperatureAsync(float Rt, float RTDnominal, float refResistor);

I thought that was it but now I get an error compiling -

error: no matching function for call to 'Adafruit_MAX31865::temperatureAsync(uint16_t&)'
   float temp1 = thermo1. temperatureAsync(rtd);

Also maybe a stupid question but what are the ... parts, is that just where I should add something haha?

Cheers, Jack

J-M-L:
you are not too far but cannot have a return in the middle of the code or you break the state machine.

an async Method could look like this

bool Adafruit_MAX31865::readRTDAsync(uint16_t& rtd) {

enum t_state : byte {STATE1, STATE2, STATE3};
  static t_state state = STATE1;
  static uint32_t chrono = 0;
  bool valueAvailable = false;
 
  switch (state) {

case STATE1:
      clearFault();
      enableBias(true);
      chrono = millis();
      state = STATE2;
      break;

case STATE2:
      if (millis() - chrono >= 10) {
        uint8_t t = readRegister8(MAX31856_CONFIG_REG);
        t |= MAX31856_CONFIG_1SHOT;
        writeRegister8(MAX31856_CONFIG_REG, t);
        chrono = millis();
        state = STATE3;
      }
      break;

case STATE3:
      if (millis() - chrono >= 65) {
        uint16_t rtd = readRegister16(MAX31856_RTDMSB_REG);
        rtd >>= 1;        // remove fault
        state = STATE1;  // get ready for next time
        valueAvailable = true; // signal computation is done
      }
      break;
  }
  return valueAvailable;
}

In STATE3 what is the point of calling ReadRegister16? It appears to return a value to rtd which is then shifted and then discarded since rtd is declared local within the scope of the case statement.

countrypaul:
In STATE3 what is the point of calling ReadRegister16? It appears to return a value to rtd which is then shifted and then discarded since rtd is declared local within the scope of the case statement.

Gee - You are totally right - my bad.. (I'm writing this from my iPad so never tested)
there should be NO uint16_t in front of that rtd, we want to use the parameter passed by reference!

Good catch. +1 karma!

so corrected code is:

bool Adafruit_MAX31865::readRTDAsync(uint16_t& rtd) {
  enum t_state : byte {STATE1, STATE2, STATE3};
  static t_state state = STATE1;
  static uint32_t chrono = 0;
  bool valueAvailable = false;
  
  switch (state) {

    case STATE1:
      clearFault();
      enableBias(true);
      chrono = millis();
      state = STATE2;
      break;

    case STATE2:
      if (millis() - chrono >= 10) {
        uint8_t t = readRegister8(MAX31856_CONFIG_REG);
        t |= MAX31856_CONFIG_1SHOT;
        writeRegister8(MAX31856_CONFIG_REG, t);
        chrono = millis();
        state = STATE3;
      }
      break;

    case STATE3:
      if (millis() - chrono >= 65) {
        rtd = readRegister16(MAX31856_RTDMSB_REG);
        rtd >>= 1;        // remove fault
        state = STATE1;   // get ready for next time
        valueAvailable = true; // signal computation is done
      }
      break;
  }
  return valueAvailable;
}

okay great, nice spot, I have updated that code in the lib file.

I am pretty close now, could you help me out with the last 2 questions?

Thanks a lot!

what are the ... parts, is that just where I should add something haha?

you can't call just   float temp1 = thermo1. temperatureAsync(rtd);
the function requires 3 parameters:

float temperatureAsync(float Rt, [color=red]float RTDnominal, float refResistor[/color]);

what would you have used when calling the standard temperature() function? you need the same 2 extra parameters

Yep obviously!

Its working great, thank you so so much for your help!

I really appreciate you taking the time, now I can go back to my programming comfort zone!

have fun! hope you learnt something about state machines and async programming along the way :slight_smile:

Hi, jackdavies, can You, please, publish Your modifications? Im just looking for async reading of MAX31865, standard Adafruit library reading is 150ms and thats too long for my code. Thanks.

Hey ddano007,

Sure thing, I have attached my modified lib files, please see the messages above for how to use it.

Just replace the files in the existing lib folder with these.

Thanks to JML for the help.

Hope that helps, Jack

Adafruit_MAX31865.cpp (13.6 KB)

Adafruit_MAX31865.h (2.71 KB)

Hi, Jack,
it works. Instead of 150ms it`s 90 - 180us, great thanks to both You and JML !!!
Daniel