Replacing delay with millis but it's nested inside a function already using millis

I am trying to make a multizone thermostat for a project and want to use millis to delay readings so that user interaction is not inhibited by delays.

The first instance of using millis to replace delay is fine but I'm confused about implementing it again inside the if function* to replace delay(10) because it seems like it's not actually going to enable the processor to do other stuff; it will just cycle pointlessly within that bit of code until it gets all the readings. I'm taking readings from thermistors and apparently the readings can be noisy so I should take averages. Maybe it would be acceptable to just reduce the delay from 10 to 1? Otherwise I don't know how I can get five readings with a small delay between them whilst still responding when a user presses a button.


  // at the thermistorReadInterval
  if (millis() - previousThermistorReadTime1 >= thermistorReadInterval1) {
    previousThermistorReadTime1 = millis();

    // take N samples in a row, with a slight delay
    for (i = 0; i < NUMSAMPLES; i++) {
      samplesFront[i] = analogRead(THERMISTORPINFRONT);
      samplesBack[i] = analogRead(THERMISTORPINBACK);
      samplesArms[i] = analogRead(THERMISTORPINARMS);
      samplesLegs[i] = analogRead(THERMISTORPINLEGS);
      delay(10);
    }
    Serial.println('Front');
    averageFrontTemp = samplesToTemp(samplesFront[NUMSAMPLES]);
    Serial.println('Back');
    averageBackTemp = samplesToTemp(samplesBack[NUMSAMPLES]);
    Serial.println('Arms');
    averageArmsTemp = samplesToTemp(samplesArms[NUMSAMPLES]);
    Serial.println('Legs');
    averageLegsTemp = samplesToTemp(samplesLegs[NUMSAMPLES]);

    // compare temperature readings to thermostat settings and switch MOSFETs accordingly
    if (averageFrontTemp < tempSettingFront) digitalWrite(HEATERMOSFETFRONT, HIGH);
    else digitalWrite(HEATERMOSFETFRONT, LOW);
    if (averageBackTemp < tempSettingBack) digitalWrite(HEATERMOSFETBACK, HIGH);
    else digitalWrite(HEATERMOSFETBACK, LOW);
    if (averageArmsTemp < tempSettingArms) digitalWrite(HEATERMOSFETARMS, HIGH);
    else digitalWrite(HEATERMOSFETARMS, LOW);
    if (averageLegsTemp < tempSettingLegs) digitalWrite(HEATERMOSFETLEGS, HIGH);
    else digitalWrite(HEATERMOSFETLEGS, LOW);
  }

There is more stuff that happens after the readings are taken.

*am I using the terminology right? The code in question is inside an if function, right?

Yes, but why do you even need a delay there?

Why are you passing the first element after the array to the "samplesToTemp()" function?!? That doesn't look at all right. It looks like you are filling an array with samples and then passing an element off the end of the array to a function that calculates an average somehow.

1 Like

Regarding the delay: I copied someone else's code and it had that delay in there between readings. I don't know how important it is really.

Regarding the array: I don't know what I'm doing. I was trying to send the whole array to a function.

I'm looking up how to do this properly. I'm a scriptkiddy at best. Thanks for the heads-up.

Since you asked, no. It would be called an if statement, and instead of inside you can also say part of the body of the if statement.

a7

1 Like

You cannot simply replace the delay by the millis technique. You have to rewrite the code. The for() loop needs to be transformed. To keep it simple, your top level code could be as follows.

Example not complete code (Click to reveal)
bool sampling = false;
bool average = false;

loop()
{
  timerTask();
  samplingTask();
  averageTask();
}

bool timerTask()
{
  const uint32_t INTERVAL = 1000;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  previousMillis = currentMillis;

  sampling = true;

  return true;
}

bool samplingTask()
{
  const uint32_t INTERVAL = 10;
  static uint32_t previousMillis = 0;
  static index = 0;

  if ( !sampling )
  {
    return;
  }

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  previousMillis = currentMillis;

  samples[index] = analogRead( SENSOR_PIN );
  index = ( index + 1 ) % SAMPLE_BUFFER_SIZE;
  
  if ( index == 0 )
  {
    sampling = false;
    average = true;
    return true;
  }

  return false;
}

void averageTask()
{
  if ( average )
  {
    ...
    average = false;
  }
}

Each task can be called as often as possible but they only work when the flags are set and the timing is correct otherwise they return. The timerTask starts the process. The samplingTask collects the samples and sets a flag when the buffer is filled. The averageTask calculates the average and finishes the sequence.

By using static variables inside the task functions we can reuse the variable name for the millis code.

You can add many more task like this without changing the overall timing as long as you have enough time. Most of the time the tasks will simply return doing nothing.

2 Likes

Thanks. I am trying now to rewrite my code. Could you please tell me whether this:

bool timerTask()
{
  const uint32_t INTERVAL = 1000;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  previousMillis = currentMillis;

  sampling = true;

  return true;
}

is the same as this:

bool timerTask()
{
  const uint32_t INTERVAL = 1000;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  else {
  previousMillis = currentMillis;

  sampling = true;

  return true;
  }
}

Yes, those will do the same thing.

Since you return form inside the first part, it doesn't matter how the second part gets executed, whether as an else part, or just the rest of the function.

This happens in this case, be careful how you extrapolate.

Best: learn these structures, and run you finger through them "playing computer".

a7

1 Like

Thanks. It makes perfect sense. I have indeed gone through it line by line and I fully understand it (after looking up a couple of things) except for one aspect: sometimes the script returns true or false and I can't see what is happening with that returned value. I understand the flag settings to true and false but, for example, line 25: return true; - what is that doing? Why not just return? Same for lines 42, 52 and 56 - they all return values but to where? As far as I can tell they might as well just say return; because the values true or false aren't going anywhere? Is that correct?

We'd have to see all the code to see where the call to the function comes from.

So look at where that function is called. It may do something with a returned value, but there's no requirement, it may just ignore it.

But the returned value may be useful in other contexts.

a7

The code is Klaus_K's example:

bool sampling = false;
bool average = false;

loop()
{
  timerTask();
  samplingTask();
  averageTask();
}

bool timerTask()
{
  const uint32_t INTERVAL = 1000;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  previousMillis = currentMillis;

  sampling = true;

  return true;
}

bool samplingTask()
{
  const uint32_t INTERVAL = 10;
  static uint32_t previousMillis = 0;
  static index = 0;

  if ( !sampling )
  {
    return;
  }

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis < INTERVAL )
  {
    return false;
  }
  previousMillis = currentMillis;

  samples[index] = analogRead( SENSOR_PIN );
  index = ( index + 1 ) % SAMPLE_BUFFER_SIZE;
  
  if ( index == 0 )
  {
    sampling = false;
    average = true;
    return true;
  }

  return false;
}

void averageTask()
{
  if ( average )
  {
    ...
    average = false;
  }
}

Well that is not a real program, he must be talking about this in general terms.

Do you have a link to this Klaus_K or do I live under a rock everyone knows who he is?

Never mind, I do live under a rock.

In any case, here he makes no use of the returned value, which is fine but does mean, as you figured out, that the function could easily be changed to return void, that is nothing, and just return;, with the signal it is developing being shared as it is now through the setting of a global flag.

I'm at a loss to see why a returned value would be useful. Maybe just because it's near time for more food input to the biological system that carries my brain around. :wink:

a7

1 Like

Thanks very much to you and everyone who responded. I'm still a scriptkiddy but now I feel more like a scriptkiddy+ :slight_smile:

As alto777 already confirmed they are the same thing. I did not use the else intentionally. The goal of this technique is to avoid nesting if and else statements. e.g., if() inside another if() and so on. It makes the code easier to follow. Compare the two simple examples:

Nested if() example

if ( headLight == working )
{
  if ( breakLight == working )
  {
    if ( turnSignal == working )
    {
      return carSafeToDrive;
    }
  }
}

return carNotSafeToDrive;

Inverted Logic example without nesting

if ( headLight != working )
{
  return carNotSafeToDrive;
}

if ( breakLight != working )
{
  return carNotSafeToDrive;
}

if ( turnSignal != working )
{
  return carNotSafeToDrive;
}

return carSafeToDrive;

Sometimes it can be useful to also return a value. It made you think about the code. :slight_smile: Well done.

Yes, well earned. :slight_smile:

1 Like

@Klaus_K well I recognize the distinctive and unique KK icon if not the name, sry.

Guess Iā€™m becoming more visual orientated in my dotage old age. :expressionless:

a7

1 Like