Millis() assistance required

Hi,

I'm trying to implement this code, to turn on a relay for a certain portion of a time window, the relay is active LOW:

 if (temp < lowerTemp){  //Heat up constantly

    digitalWrite(HeaterPin,LOW);
    delay(500);
   }else  if (temp < lowerTemp2){//slow down heating to prevent overshooting
    digitalWrite(HeaterPin,LOW);
    delay(125);
    digitalWrite(HeaterPin,HIGH);
    delay(375);
  }else if(temp<setTemp){
    digitalWrite(HeaterPin,LOW);
    delay(50);
    digitalWrite(HeaterPin,HIGH);
    delay(450);
   }

With millis() instead of delay, as i have other tasks going on.

I have been reading tutorials and examples on millis(), i have the severthingsatthesametime example open during all of this, and i expect i'm making a basic error.

This is what i currently have but it not behaving as expected, Serial.print reports back the correct interval but the relay just remains LOW all the time (or HIGH) if i reverse the action, can't decide which way round but i think the orientation below is right):

if (temp < (Setpoint - 35)) {          //full power for cold start
    heaterInterval = (WindowSize - 0);
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
     
      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;
    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }

  }
  else  if (temp < (Setpoint - 15)) {         
    heaterInterval = (WindowSize - 375);          //reduce power to gently reach SP
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {

      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;

    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }
    //delay(500);
  }

  else if (temp < Setpoint) {          
    heaterInterval = (WindowSize - 450); //Reduce power further to stabilise on setpoint
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {

      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;

    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }

  }

Essentially this checks the temperature and then turns on a heater (via a relay) for a given portion of a 500ms window, the portion that it is on reduces as we approach the setpoint.
I have had this code working in the original form (poached from someone else) but i would like a none-blocking version.

post full code (a way to remember if you need to switch on or off the relay in a given temperature bracket is probably needed)

Apologies, was trying to save the effort of trimming out a load of commented code, shouldn't be so lazy when asking for help :slight_smile:

// which analog pin to connect
#define THERMISTORPIN A0
// resistance at 25 degrees C
#define THERMISTORNOMINAL 100000
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3950
// the value of the 'other' resistor
#define SERIESRESISTOR 100000
#define HeaterPin 6
#define PumpPin 7
#define ButtonPin 8
unsigned long startTime;
int oldState;
long shotStart;
unsigned long previousSerialMillis;
unsigned long currentPumpMillis;
unsigned long previousPumpMillis;
unsigned long previousButtonMillis = 0;
unsigned long currentHeaterMillis;
unsigned long previousHeaterMillis = 0;
unsigned long currentMillis;
const int buttonInterval = 1000;
const int pumpInterval = 500;
int heaterInterval;

int PID_no;
byte PumpState = LOW;
unsigned long PumpPurge = 8000;
String Power;

int samples[NUMSAMPLES];

// include the library code:
#include <LiquidCrystal.h>
#include <math.h>


// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//Define Variables we'll be connecting to
double Setpoint = 90, Input, Output;
float temp;


int WindowSize = 500;
unsigned long windowStartTime;


void setup(void) {
  Serial.begin(9600);

  lcd.begin(16, 2);  // set up the LCD's number of columns and rows:
  analogReference(EXTERNAL);
  pinMode(HeaterPin, OUTPUT);
  pinMode(PumpPin, OUTPUT);
  pinMode(ButtonPin, INPUT_PULLUP); //no button as yet
  digitalWrite(ButtonPin, HIGH);
  digitalWrite(HeaterPin, HIGH); //heater off
  digitalWrite(PumpPin, HIGH); //pump off


  windowStartTime = millis();

}


void Get_Temp()
{

  uint8_t i;
  float average;

  // take N samples in a row, with a slight delay
  for (i = 0; i < NUMSAMPLES; i++) {
    samples[i] = analogRead(THERMISTORPIN);
    delay(10);
  }

  // average all the samples out
  average = 0;
  for (i = 0; i < NUMSAMPLES; i++) {
    average += samples[i];
  }
  average /= NUMSAMPLES;

  // convert the value to resistance
  average = 1023 / average - 1;
  average = SERIESRESISTOR / average;

  //Calculates temp C from NTC resistance readings gather above

  temp = average / THERMISTORNOMINAL;     // (R/Ro)
  temp = log(temp);                  // ln(R/Ro)
  temp /= BCOEFFICIENT;                   // 1/B * ln(R/Ro)
  temp += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
  temp = 1.0 / temp;                 // Invert
  temp -= 273.15;                         // convert to C
}

void loop(void) {


  Get_Temp();

  currentMillis = millis();

  lcd.setCursor(0, 0);
  lcd.print("Heating Water...");



  if (temp < (Setpoint - 35)) {          //full power for cold start
    heaterInterval = (WindowSize - 0);
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;
      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;

    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }

  }
  else  if (temp < (Setpoint - 15)) {         //reduce power to gently reach SP
    heaterInterval = (WindowSize - 375);
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;
      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;

    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }

  }
  else if (temp < Setpoint) {          //close to Setpoint
    heaterInterval = (WindowSize - 450); //reduce power even futher to stabilse on setpoint
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;

      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;
 
    }
    else
    {
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;
    }

  }

  if (millis() - previousSerialMillis >= 1000)
  {
    unsigned long currentTime = (millis() / 1000);

    Serial.print(currentTime);
    Serial.print(" ");
/*
    Serial.print("Power ");
    Serial.print(Power);
    Serial.print(" ");
    Serial.print(Output);
    Serial.print(" ");
    Serial.print(Kp1);
    Serial.print(" ");
    Serial.print(Ki1);
    Serial.print(" ");
    Serial.print(Kd1);
    Serial.print(" ");
    */
    Serial.println(temp);
    //    Serial.println(" *C");
    lcd.setCursor(0, 1);
    lcd.print("Temp "); lcd.print(temp); lcd.print(" "); lcd.print((char)223); lcd.print("C");
    Serial.print(" ");

    previousSerialMillis += 1000;
  }
}

Here's an example that just does one of your cases. You should be able to extrapolate to solve the others too.

void loop()
{
  unsigned long currentMillis = millis();
  static bool Heating = false;
  static unsigned long HeatingStartTime = 0;
  if (temp < (Setpoint - 35))            //full power for cold start
  {
    if (Heating)
    {
      if (currentMillis - HeatingStartTime > heaterInterval)
      {
        Heating = false;
        digitalWrite(HeaterPin, HIGH);
      }
    }
    else
    {
      Heating = true;
      HeatingStartTime = currentMillis;
      heaterInterval = WindowSize;
      digitalWrite(HeaterPin, LOW);
    }
  }
}

Because of your else clauses. Those always execute if your timer hasn't fired.

Probably something like...

if (temp < lowerTemp){  //Heat up constantly

  heaterOn = true;
  previousHeaterMillis = currentMillis;
  heaterInterval = 0;

}else  if (temp < lowerTemp2){//slow down heating to prevent overshooting
      
  if (currentMillis - previousHeaterMillis >= heaterInterval)
  {
    heaterOn = !heaterOn;
    heaterInterval = heaterOn ? 125 : 375;
    previousHeaterMillis += heaterInterval;
  }

} else if(temp < setTemp) {

  if (currentMillis - previousHeaterMillis >= heaterInterval)
  {
    heaterOn = !heaterOn;
    heaterInterval = heaterOn ? 50 : 450;
    previousHeaterMillis += heaterInterval;
  }

}

digitalWrite(HeaterPin, !heaterOn);

Note; code untested.

This is not right

   previousHeaterMillis = currentMillis;
      digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;

Only use one of the first or third lines, not both. My strong preference is for the first (simpler) style unless it is very important to avoid the occasional timing error of 1 millisec.

And I think this is not correct either, for a different reason

 else
    {
      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;
    }

The ELSE clause should NOT update the timer because it would have the effect of preventing the timer from ever expiring

Also keep in mind that this ELSE clause will probably be called hundreds of times for every occasion that the IF clause is satisfied.

That's probably enough for now.

The demo Several Things at a Time illustrates the use of millis() to manage timing without blocking. It may help with understanding the technique.

Have a look at Using millis() for timing. A beginners guide if you need more explanation.

...R

Edit ... because I left out the word not - sorry for any confusion

Thanks guys, i will read and digest these this evening.

To wildbill and Robin, i appreciated your help on the PID thread but i just wasnt able to get the parameters right, i found someone doing a similar project who went with this much simpler heating regime, i tested and tweaked ansd was very happy with the result, minimal overshoot and subsequently stable temperatures, recovery time from an espresso shot is not super-quick but i think quick enough for day-to-day use given most uses will be for a single drink.

Robin2:
This is not right

    previousHeaterMillis = currentMillis;

digitalWrite(HeaterPin, HIGH);
      previousHeaterMillis += heaterInterval;



Only use one of the first or third lines, not both. My strong preference is for the first (simpler) style unless it is very important to avoid the occasional timing error of 1 millisec.

And I think this is correct either, for a different reason


else
    {
      digitalWrite(HeaterPin, LOW);
      previousHeaterMillis += heaterInterval;
    }



The ELSE clause should NOT update the timer because it would have the effect of preventing the timer from ever expiring

Also keep in mind that this ELSE clause will probably be called hundreds of times for every occasion that the IF clause is satisfied.

That's probably enough for now.

The demo [Several Things at a Time](http://forum.arduino.cc/index.php?topic=223286.0) illustrates the use of millis() to manage timing without blocking. It may help with understanding the technique.

Have a look at [Using millis() for timing. A beginners guide](http://forum.arduino.cc/index.php?topic=503368.0) if you need more explanation.

...R

Ok i have modified the code as per your suggestion, i'm now getting some action on the relay, however it seems to be acting the reverse of what i need, and i don;t think it is the digitalWrite actions that are wrong. I have modified the setpoint for testing as i've burnt my thermal fuses so can't actually heat the block right now >:(

It appears that in Stage 1 heating the relay is on more than off, i.e. it is on and blips off briefly once per 500ms window, instead of on exclusively (500ms out of every 500ms window)
In Stage 2 heating it turns on and off rapidly (should be on for 125ms in every 500ms window)
and in Stage 3 heating it is completely off, instead of blipping on for 50ms in every 500ms window.

Hopefully you can see how it is incorrect, let me know if i need to rephrase anything?

Current sketch:

// Sketch to control the temperature of a thermoblock for the production of small quatities of water 
// for espresso shots.
// Non-PID version
// Three stage heating process implemented


// which analog pin to connect
#define THERMISTORPIN A0
// resistance at 25 degrees C
#define THERMISTORNOMINAL 100000
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3950
// the value of the 'other' resistor
#define SERIESRESISTOR 100000
#define HeaterPin 6
#define PumpPin 7
#define ButtonPin 8
unsigned long startTime;
int oldState;
long shotStart;
unsigned long previousSerialMillis;
unsigned long currentPumpMillis;
unsigned long previousPumpMillis;
unsigned long previousButtonMillis = 0;
unsigned long currentHeaterMillis;
unsigned long previousHeaterMillis = 0;
unsigned long currentMillis;
const int buttonInterval = 1000;
const int pumpInterval = 500;
int heaterInterval;

byte PumpState = LOW;
unsigned long PumpPurge = 8000;
String Power;

int samples[NUMSAMPLES];

// include the library code:
#include <LiquidCrystal.h>
#include <math.h>


// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//Define Variables we'll be connecting to
double Setpoint = 27, Input, Output;
float temp;


int WindowSize = 500;
unsigned long windowStartTime;


void setup(void) {
  Serial.begin(9600);

  lcd.begin(16, 2);  // set up the LCD's number of columns and rows:
  analogReference(EXTERNAL);
  pinMode(HeaterPin, OUTPUT);
  pinMode(PumpPin, OUTPUT);
  pinMode(ButtonPin, INPUT_PULLUP); //no button as yet
  digitalWrite(ButtonPin, HIGH);
  digitalWrite(HeaterPin, HIGH); //heater off
  digitalWrite(PumpPin, HIGH); //pump off
  windowStartTime = millis();
}


void Get_Temp()
{

  uint8_t i;
  float average;

  // take N samples in a row, with a slight delay
  for (i = 0; i < NUMSAMPLES; i++) {
    samples[i] = analogRead(THERMISTORPIN);
    delay(10);
  }

  // average all the samples out
  average = 0;
  for (i = 0; i < NUMSAMPLES; i++) {
    average += samples[i];
  }
  average /= NUMSAMPLES;

  // convert the value to resistance
  average = 1023 / average - 1;
  average = SERIESRESISTOR / average;

  //Calculates temp C from NTC resistance readings gather above

  temp = average / THERMISTORNOMINAL;     // (R/Ro)
  temp = log(temp);                  // ln(R/Ro)
  temp /= BCOEFFICIENT;                   // 1/B * ln(R/Ro)
  temp += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
  temp = 1.0 / temp;                 // Invert
  temp -= 273.15;                         // convert to C
}

void loop(void) {

  Get_Temp();
  currentMillis = millis();

  lcd.setCursor(0, 0);
  lcd.print("Heating Water...");



  if (temp < (Setpoint - 35)) {          //full power for cold start
    heaterInterval = (WindowSize - 0);
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;
      digitalWrite(HeaterPin, HIGH);
      Serial.print(WindowSize);
      Serial.print(" ");
      Serial.print(heaterInterval);
      Serial.println(" ");
    }
    else
    {
      digitalWrite(HeaterPin, LOW);
    }

  }
  else  if (temp < (Setpoint - 15)) {         //reduce power to gently reach SP
    heaterInterval = (WindowSize - 375);
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;
      digitalWrite(HeaterPin, HIGH);
      Serial.print(WindowSize);
      Serial.print(" ");
      Serial.print(heaterInterval);
      Serial.println(" ");
    }
    else
    {
      digitalWrite(HeaterPin, LOW);
    }

  }
  else if (temp < Setpoint) {          //close to Setpoint
    heaterInterval = (WindowSize - 450); //reduce power even futher to stabilse on setpoint
    if (currentMillis - previousHeaterMillis >= heaterInterval)
    {
      previousHeaterMillis = currentMillis;
      Serial.print(WindowSize);
      Serial.print(" ");
      Serial.println(heaterInterval);
      digitalWrite(HeaterPin, HIGH);

    }
    else
    {
      digitalWrite(HeaterPin, LOW);
    }
  }

  if (millis() - previousSerialMillis >= 1000)
  {
    unsigned long currentTime = (millis() / 1000);
    Serial.print(currentTime);
    Serial.print(" ");
    Serial.println(temp);
    //    Serial.println(" *C");
    lcd.setCursor(0, 1);
    lcd.print("Temp "); lcd.print(temp); lcd.print(" "); lcd.print((char)223); lcd.print("C");
    Serial.print(" ");

    previousSerialMillis += 1000;
  }
}

Have a re-read of my post number 4. You can’t have these else clauses in there...

else
    {
      digitalWrite(HeaterPin, LOW);
    }

The code will execute most times through the loop. Only when the timer “pops“ will it go in the TRUE part of the if statement, turn the heater pin HIGH, but then on the very next cycle through loop (less than 1 ms later) it will go in the ELSE part and turn it LOW again.

You need to ONLY change the state of the pin when the timer pops (inside the TRUE). If you change it anywhere else it won’t be staying in the state you want it for the amount of amount of time you want it to stay there. That should be obvious if you think about it...

pcbbc:
Have a re-read of my post number 4. You can’t have these else clauses in there...

else

{
      digitalWrite(HeaterPin, LOW);
    }



The code will execute most times through the loop. Only when the timer “pops“ will it go in the TRUE part of the if statement, turn the heater pin HIGH, but then on the very next cycle through loop (less than 1 ms later) it will go in the ELSE part and turn it LOW again.

You need to ONLY change the state of the pin when the timer pops (inside the TRUE). If you change it anywhere else it won’t be staying in the state you want it for the amount of amount of time you want it to stay there. That should be obvious if you think about it...

Please excuse my confusion over the logic, the way i feel like it reads currently is this:

if (time since last loop is greater than 'heater on interval')
then turn off (i.e. it's already been on for it's full pulse)

otherwise
turn on

Am i wrong?

You are right at the time it executes the if.
However, after it has executed the code IN that if, it adjusts previousHeaterMillis for the next timeout.
Then, next time through the loop, it is no longer true, so the code immediately goes in the else next time through (since the required number of milliseconds haven’t yet passed).

Your code is sitting in loop() which is a constantly checking...

if (certain number of milliseconds have passed since last time I did something)
{
do something
record time I last did something
}
else
{
you don’t want to do ANYTHING here
otherwise any code you place here will be done continuously while waiting for time to pass
}

May be you will understand the logic better if there is a clear state machine that makes decisions on what to do.

it also helps to create small functions to make code easy to read

Your heater can be in different stages:

  • not activated --> OFF
  • activated --> in that case it's either on or off based on setPoint and cycle.

basically the core of the state machine is:

if the heater is activated and on, then I check if it's time to turn it off
else
if the heater is activated and off, then I check if it's time to turn it on

That could look something like this (typed here, so may be some typos)

const uint8_t heaterPin = 7;
const int setPoint = 60; // °C

const uint32_t heaterPeriod = 500;
uint32_t heaterPeriodOn = 0;
uint32_t heaterPeriodOff = heaterPeriod - heaterPeriodOn;


enum t_heaterState : uint8_t {NO_CHANGE, HEATER_STOP, HEATER_START, HEATER_IN_USE_ON, HEATER_IN_USE_OFF} heaterState = HEATER_STOP;

int getTemperature()
{
  return random(-10, 120); // put real code there!
}

void adjustHeatingTime()
{
  int currentTemperature = getTemperature();
  if (currentTemperature < (setPoint - 35)) heaterPeriodOn = 500;       //full power for cold start, 500 LOW & 0 HIGH
  else if (currentTemperature < (setPoint - 15)) heaterPeriodOn = 125;  // start reducing, 125 LOW & 375 HIGH
  else if (currentTemperature < setPoint) heaterPeriodOn = 50;          // slow heating, 50 LOW & 450 HIGH
  else heaterPeriodOn = 0;                                              //above setPoint, turn off.  0 LOW & 500 HIGH
  heaterPeriodOff = heaterPeriod - heaterPeriodOn;
}

void heaterOn()
{
  digitalWrite(heaterPin, LOW);
  heaterState = HEATER_IN_USE_ON;
}

void heaterOff()
{
  digitalWrite(heaterPin, HIGH);
  heaterState = HEATER_IN_USE_OFF;
}

void heaterStop()
{
  digitalWrite(heaterPin, HIGH);
  heaterState = HEATER_STOP;
}

void updateHeater(t_heaterState newState = NO_CHANGE)
{
  static uint32_t chrono;

  switch (newState) {
    case HEATER_START:
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOn != 0) heaterOn();
      else heaterOff();
      break;

    case HEATER_STOP:
      heaterStop();
      break;

    default: break;
  }

  if (heaterState == HEATER_IN_USE_ON) {
    if (millis() - chrono > heaterPeriodOn) {
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOff != 0) heaterOff();
    }
  } else if (heaterState == HEATER_IN_USE_OFF) {
    if (millis() - chrono > heaterPeriodOff) {
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOn != 0) heaterOn();
    }
  }
}

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

  pinMode(heaterPin, OUTPUT);
  heaterStop();

  // force start the heater
  updateHeater(HEATER_START); // call with HEATER_STOP to turn it off
}

void loop()
{
  updateHeater();
  // you can do other stuff here as long as it's short

}

Thank you very much for writing out that code for me, i will try to assimilate it.

In terms of the loop function where you say other things can go but must be short, is that to keep response time to changes in temperature, down?

I'm hoping to use a LCD and keypad to allow a menu and setting changes to be made by the user and there will be a case where i need a pump to run for 30s with the heater on as well. Is this likely to be too much to handle?

pcbbc:
You are right at the time it executes the if.
However, after it has executed the code IN that if, it adjusts previousHeaterMillis for the next timeout.
Then, next time through the loop, it is no longer true, so the code immediately goes in the else next time through (since the required number of milliseconds haven’t yet passed).

Your code is sitting in loop() which is a constantly checking...

if (certain number of milliseconds have passed since last time I did something)
{
do something
record time I last did something
}
else
{
you don’t want to do ANYTHING here
otherwise any code you place here will be done continuously while waiting for time to pass
}

Thanks for helping me to understand!

greenbeast:
In terms of the loop function where you say other things can go but must be short, is that to keep response time to changes in temperature, down?

That - but far more importantly the need to turn the heater on and off with your 125/375ms and 50/450ms duty cycles.

The code is constantly checking "is it time to turn the heater on/off yet?". If your code is off doing something else, for example a while loop waiting for something to happen, or worse a delay() statement, then the code won't be doing that checking any more.

Let's say the heater needs to be turned off in 5ms, but your code to do the 30s pump run is implemented with a delay(30000), instead of using millis() like it should. It's going to be at least 29995ms late in checking if it is time to turn the heater off. So the heater will stay on for nearly 30s longer than you want.

I'm hoping to use a LCD and keypad to allow a menu and setting changes to be made by the user and there will be a case where i need a pump to run for 30s with the heater on as well. Is this likely to be too much to handle?

These will all be fine, as long as the code implement them is not looping or using delay(). Just use millis() for everything. However you really need to "get your head around" event driven programming (as per state machine logic) instead of simple linear programming (do this, then do this, then do this).

greenbeast:
In terms of the loop function where you say other things can go but must be short, is that to keep response time to changes in temperature, down?

I'm hoping to use a LCD and keypad to allow a menu and setting changes to be made by the user and there will be a case where i need a pump to run for 30s with the heater on as well. Is this likely to be too much to handle?

--> As @pcbbc explained :slight_smile:

updateHeater(); is where we check if it's time to do something with the heater, so if you are slow calling it then the timings for ON/OFF are not going to be well respected. if you do other stuff, even within the ms range, you won't bee too far off - just don't prevent the loop from looping for hundreds of ms or worse.

if you need to do some other stuff that might be a bit long, that's also fine to call updateHeater(); elsewhere in the code too. that will help with granularity of the checks.

Ah yes, i totally get the need to do away with delay, hence this thread.

For completeness, here is the working code, incorporating your suggestions. It appears to be working correctly and gives be a better foundation to build on.

const uint8_t heaterPin = 6;
const int setPoint = 90; // °C

const uint32_t heaterPeriod = 500;
uint32_t heaterPeriodOn = 0;
uint32_t heaterPeriodOff = heaterPeriod - heaterPeriodOn;

// which analog pin to connect
#define THERMISTORPIN A0
// resistance at 25 degrees C
#define THERMISTORNOMINAL 100000
// temp. for nominal resistance (almost always 25 C)
#define TEMPERATURENOMINAL 25
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 5
// The beta coefficient of the thermistor (usually 3000-4000)
#define BCOEFFICIENT 3950
// the value of the 'other' resistor
#define SERIESRESISTOR 100000

#define PumpPin 7
#define ButtonPin 8

int samples[NUMSAMPLES];
float temp;
float currentTemperature;
enum t_heaterState : uint8_t {NO_CHANGE, HEATER_STOP, HEATER_START, HEATER_IN_USE_ON, HEATER_IN_USE_OFF} heaterState = HEATER_STOP;
unsigned long previousSerialMillis;
float getTemp()
{

  uint8_t i;
  float average;

  // take N samples in a row, with a slight delay
  for (i = 0; i < NUMSAMPLES; i++) {
    samples[i] = analogRead(THERMISTORPIN);
    delay(10);
  }

  // average all the samples out
  average = 0;
  for (i = 0; i < NUMSAMPLES; i++) {
    average += samples[i];
  }
  average /= NUMSAMPLES;

  // convert the value to resistance
  average = 1023 / average - 1;
  average = SERIESRESISTOR / average;

  //Calculates temp C from NTC resistance readings gather above

  temp = average / THERMISTORNOMINAL;     // (R/Ro)
  temp = log(temp);                  // ln(R/Ro)
  temp /= BCOEFFICIENT;                   // 1/B * ln(R/Ro)
  temp += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
  temp = 1.0 / temp;                 // Invert
  temp -= 273.15;                         // convert to C
  return temp;
}


void adjustHeatingTime()
{
  currentTemperature = getTemp();
  if (currentTemperature < (setPoint - 35)) heaterPeriodOn = 500;       //full power for cold start, 500 LOW & 0 HIGH
  else if (currentTemperature < (setPoint - 15)) heaterPeriodOn = 125;  // start reducing, 125 LOW & 375 HIGH
  else if (currentTemperature < setPoint) heaterPeriodOn = 50;          // slow heating, 50 LOW & 450 HIGH
  else heaterPeriodOn = 0;                                              //above setPoint, turn off.  0 LOW & 500 HIGH
  heaterPeriodOff = heaterPeriod - heaterPeriodOn;
}

void heaterOn()
{
  digitalWrite(heaterPin, LOW);
  heaterState = HEATER_IN_USE_ON;
}

void heaterOff()
{
  digitalWrite(heaterPin, HIGH);
  heaterState = HEATER_IN_USE_OFF;
}

void heaterStop()
{
  digitalWrite(heaterPin, HIGH);
  heaterState = HEATER_STOP;
}

void updateHeater(t_heaterState newState = NO_CHANGE)
{
  static uint32_t chrono;

  switch (newState) {
    case HEATER_START:
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOn != 0) heaterOn();
      else heaterOff();
      break;

    case HEATER_STOP:
      heaterStop();
      break;

    default: break;
  }

  if (heaterState == HEATER_IN_USE_ON) {
    if (millis() - chrono > heaterPeriodOn) {
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOff != 0) heaterOff();
    }
  } else if (heaterState == HEATER_IN_USE_OFF) {
    if (millis() - chrono > heaterPeriodOff) {
      adjustHeatingTime();
      chrono = millis();
      if (heaterPeriodOn != 0) heaterOn();
    }
  }
}

void setup() {
  Serial.begin(9600);

  pinMode(heaterPin, OUTPUT);
  heaterStop();

  // force start the heater
  updateHeater(HEATER_START); // call with HEATER_STOP to turn it off
}

void loop()
{
  updateHeater();
  // you can do other stuff here as long as it's short


    if (millis() - previousSerialMillis >= 1000)
  {
    unsigned long currentTime = (millis() / 1000);
    Serial.print(currentTime);
    Serial.print(" ");
    Serial.print(temp);
    //    Serial.println(" *C");

    Serial.print(" ");
    Serial.print(heaterPeriodOn);
    Serial.println("");
    previousSerialMillis += 1000;
  }

}

Good, glad it helped. State machines makes a good base for readable code

Note that if you are connected to your PC you don't need to slow down things by a low baud rate  Serial.begin(9600);--> use 115200 or more.

J-M-L:
Note that if you are connected to your PC you don't need to slow down things by a low baud rate  Serial.begin(9600);--> use 115200 or more.

Hmm ok, whenever i selected anything other than 9600 in the IDE it won't upload.

I'v started looking at the menu code i;m going to implement, i notice it has button debounces based on delay(100), i'm guessing this is going to interfere with my heater timing?

You don’t need 100ms to debounce a button, usually 15 is good enough with most buttons but that’s large enough to impact your timing ( note that will only happen only the user is changing settings, so not all the time => maybe that ok to skip a beat ?)

Easy solution is to add a cap near your button so that you debounce in hardware and don’t have to deal with it in software or implement a software debounce using millis that is non blocking (or just ignore and don’t re-trigger if the last trigger was less than say 50ms ago)