Using millis() for unequal ON/OFF times in nested statements

Okay so I am working on a project with an LM34dz that will control a digital pin on an off that is attached
to a relay. I have three different temperatures where I want the relay to be ON for a certain amount and OFF for
a different amount of time. I have done this successfully with delay's but I am aware that this causes the arduino
to not process anything during these times. I know one solution is to use millis() and I have see code where you can
have independent on and off times from another post (listed below my conditions).

The temps and ON/OFF times are listed here
Temp>80°F relay on for .5mins off for 2.5mins
Temp>90°F relay on for 1min off for 2mins
Temp>95°F relay on for 2.5mins off for .5mins

Code that does unequal intervals from Using millis to blink - unequal intervals? - Programming Questions - Arduino Forum

unsigned long currentTime = millis();
  if (currentTime > nextTime) {
    if (digitalRead(PIN)) {
      digitalWrite(PIN, LOW);
      nextTime = currentTime + OFF_INTERVAL;
    } else {
      digitalWrite(PIN, HIGH);
      nextTime = currentTime + ON_INTERVAL;
    }
  }
}

My problem is I am using a series of if else if for the temperature conditional statements and have a variable that will store
the previous millis and I cant seems to either reset that variable or make independent previous millis varibles and have the code work. The code I currently have works some what but for whatever reason the current time gets too large to ever be
caught up to if the temp changes quickly

I would greatly appreciate any help as I feel this could be beneficial to others.

Here is my code so far sorry its quite a bit but repetitive in nature

//code to have relay turn ON and OFF at uneven intervals- in multiple conditional statements
const int threshold1 = 80;
const int threshold2 = 90;
const int threshold3 = 95;
const unsigned long ON_INTERVAL1=1.5*60*1000;
const unsigned long OFF_INTERVAL1=.5*60*1000;
const unsigned long ON_INTERVAL2=1*60*1000;
const unsigned long OFF_INTERVAL2=2*60*1000;
const unsigned long ON_INTERVAL3=.5*1000*60;
const unsigned long OFF_INTERVAL3=2.5*1000*60;
unsigned long nextTime=0;
int LEDpin_Relay = 13;


void setup() {
  Serial.begin(9600);
  pinMode(LEDpin_Relay, OUTPUT);
}

void loop() {
 
  int rawvalue = analogRead(A0); 

  //next we need to convert the reading to a voltage value
  //using the ReadAnalogVoltage example code we can do this

  float voltage = rawvalue * (5.0/1023.0);   //Here we scale the raw A/D value to a voltage
                                             //because we are using the 5v onboard power supply
                                             //Our sensor reads in millivolts so we will need to convert so we can see the change. The
                                             //LM34dz does 10mV per °F (deg symbol done with Alt+0176)

  float millivolts = voltage * 1000;        //here we convert volts to millivolts by multiplying by 1000
  float TempF = millivolts/10;              //because our sensor reads 1 degree farenheit/10mV we will
  //divide by 10 to scale our value 

  Serial.print (TempF); 


  if (TempF > threshold3){
    
    Serial.println ("   >95");    //simply print the temperature with a new line
    unsigned long currentTime = millis();
    if (currentTime > nextTime) {
      if (digitalRead(LEDpin_Relay)) {
        digitalWrite(LEDpin_Relay, LOW);
        nextTime = currentTime + OFF_INTERVAL1;
      } 
      else {
        digitalWrite(LEDpin_Relay, HIGH);
        nextTime = currentTime + ON_INTERVAL1;
      }
    }
  }

  else if ( TempF >= threshold2 && TempF <= threshold3) {
   
    Serial.println (">90");
    unsigned long currentTime = millis();
    if (currentTime > nextTime) {
      if (digitalRead(LEDpin_Relay)) {
        digitalWrite(LEDpin_Relay, LOW);
        nextTime = currentTime + OFF_INTERVAL2;
      } 
      else {
        digitalWrite(LEDpin_Relay, HIGH);
        nextTime = currentTime + ON_INTERVAL2;
      }
    }
  }

  else if (TempF >= threshold1 && TempF <= threshold2){
    Serial.println(">80"); 
    unsigned long currentTime = millis();
    if (currentTime > nextTime) {
      if (digitalRead(LEDpin_Relay)) {
        digitalWrite(LEDpin_Relay, LOW);
        nextTime = currentTime + OFF_INTERVAL3;
      } 
      else {
        digitalWrite(LEDpin_Relay, HIGH);
        nextTime = currentTime + ON_INTERVAL3;
      }
    }
  }

  else  if (TempF < threshold1) {

    Serial.println("<80");
    digitalWrite(LEDpin_Relay ,LOW);
  }
   delay(1000);
}

Suggest you define variables to hold the current on duration and off duration, then record whether the output is on or off and use that to compare the elapsed time against either the on interval or the off interval, something like this:

if(digitalRead(PIN) == HIGH)
{
    if(millis() - startTime >= onDuration)
    {
        startTime += onDuration;
        digitalWrite(PIN, LOW);
    }
}
else
{
    if(millis() - startTime >= offDuration)
    {
        startTime += offDuration;
        digitalWrite(PIN, HIGH);
    }
}

I'll leave it for you to update the current on and off durations depending on the temperature.

Peter, Thank you very much for the response! I have not used the += operator before and am reading up on it but still having trouble on how it works. I need to sit down with the code you have provided me with and test it on my setup. Would you recommend I make different starttime constants for each condition?

Thanks,
Steven

SteveTron:
but still having trouble on how it works.

Dirt simple:

startTime += offDuration;

is the same as

startTime = startTime + offDuration;

Would you recommend I make different starttime constants for each condition?

startTime isn't a constant, it's a variable.

SteveTron:
Would you recommend I make different starttime constants for each condition?

In your project only one starttime can be applicable at a time, so it makes sense to use a single variable to hold it. If you had a project which needed to time different things concurrently, you'd need to record the start time separately for each independent thing you needed to time.

Arrch thank you for clearing that up for me. I slipped up in saying startTime should be a constant. I was mainly asking whether or not I would need separate starTime's for each IF statement. I guess this post has also opened me up to timing things concurrently, which will be my next task after digesting the current matter.

quick question... If this method: (from Peters segment)

 if(millis() - startTime >= onDuration)
    {
        startTime += onDuration;
        digitalWrite(PIN, LOW);
    }

is more versatile. Why was this method: (from the code I found in a previous post)

if (currentTime > nextTime) {
    if (digitalRead(PIN)) {
      digitalWrite(PIN, LOW);
      nextTime = currentTime + OFF_INTERVAL;

Ever be used?

Here is my new code. It seems to do what I want it to right now. I have small time values for testing, but will ramp it up later when it goes to the application. In closing if there are anythings you guys would do to shape it up a bit, I would appreciate any input. P.S. I took the comments out just to make it easier on the eyes sense were all on the same page. Thanks again!

unsigned long onDuration1 = 1000;
unsigned long offDuration1 = 10000;
unsigned long onDuration2 = 10000;
unsigned long offDuration2 = 1000;
unsigned long onDuration3 = 5000;
unsigned long offDuration3 = 5000;
const int threshold1 = 80;
const int threshold2 = 90;
const int threshold3 = 95;

int PIN = 13;

unsigned long startTime = 0;


void setup() {

  pinMode(PIN, OUTPUT);

  Serial.begin(9600);

}


void loop() {

  int rawvalue = analogRead(A0);
  float voltage = rawvalue * (5.0/1023.0);
  float millivolts = voltage * 1000;
  float TempF = millivolts/10;
  Serial.print (TempF);


  if (TempF > threshold3){
    Serial.println ("   >95");

    if(digitalRead(PIN) == HIGH)
    {
      if(millis() - startTime >= onDuration1)
      {
        startTime += onDuration1;
        digitalWrite(PIN, LOW);
      }
    }
    else
    {
      if(millis() - startTime >= offDuration1)
      {
        startTime += offDuration1;
        digitalWrite(PIN, HIGH);
      }
    } 

  }


  else if ( TempF >= threshold2 && TempF <= threshold3) {

    Serial.println (">90");
    if(digitalRead(PIN) == HIGH)
    {
      if(millis() - startTime >= onDuration2)
      {
        startTime += onDuration2;
        digitalWrite(PIN, LOW);
      }
    }
    else
    {
      if(millis() - startTime >= offDuration2)
      {
        startTime += offDuration2;
        digitalWrite(PIN, HIGH);
      }
    } 


  }

  else if (TempF >= threshold1 && TempF <= threshold2){
    Serial.println(">80");
    if(digitalRead(PIN) == HIGH)
    {
      if(millis() - startTime >= onDuration3)
      {
        startTime += onDuration3;
        digitalWrite(PIN, LOW);
      }
    }
    else
    {
      if(millis() - startTime >= offDuration3)
      {
        startTime += offDuration3;
        digitalWrite(PIN, HIGH);
      }
    } 

  }

  else  if (TempF < threshold1) {

    Serial.println("<80");
    digitalWrite(PIN ,LOW);
  }
}

In closing if there are anythings you guys would do to shape it up a bit

Yup:

const int threshold1 = 80;
const int threshold2 = 90;
const int threshold3 = 95;
...

  int rawvalue = analogRead(A0);
  float voltage = rawvalue * (5.0/1023.0);
  float millivolts = voltage * 1000;
  float TempF = millivolts/10;
  Serial.print (TempF);


  if (TempF > threshold3 ){

Using basic algebra, we can get rid of the need to use floats (no hardware assistance for floating points on the Arduino, so it's emulated in software).
Start with:

  if (TempF > threshold3){

Simple replacement:

if (millivolts/10 > 95)

Multiply both sides by 10:

if (millivolts > 950)

Another replacement:

if (voltage * 1000 > 950)

Divide both sides by 1000

if (voltage > 0.950)

More replacement:

if (rawvalue * (5.0/1023.0) > 0.950)

Multiply both sides by 1023.0/5.0:

if (rawvalue > 194)

Now we can write it as:

const int threshold3 = 194;
...
int rawvalue = analogRead(A0);

if (rawvalue > 194)

Doing that with each threshold allows you to remove floating point calculations entirely from your code. You could even throw in a comment to make it clear what that 194 stands for:

code int threshold3 = 194; // 95 degrees fahrenheit

Abstractly, you're acknowledging that analogRead has 1,024 possible value that it can return, and that each value maps to a temperature. Using the above method is just going through and finding that 1 in 1,024 that represents the voltage threshold you are after. You're doing the algebraic legwork once, rather than forcing the Arduino to do it every time.

unsigned long onDuration1 = 1000;
unsigned long offDuration1 = 10000;
unsigned long onDuration2 = 10000;
unsigned long offDuration2 = 1000;
unsigned long onDuration3 = 5000;
unsigned long offDuration3 = 5000;
const int threshold1 = 80;
const int threshold2 = 90;
const int threshold3 = 95;

This looks prime for an array of structs.

 if(digitalRead(PIN) == HIGH)
    {
      if(millis() - startTime >= onDuration1)
      {
        startTime += onDuration1;
        digitalWrite(PIN, LOW);
      }
    }
    else
    {
      if(millis() - startTime >= offDuration1)
      {
        startTime += offDuration1;
        digitalWrite(PIN, HIGH);
      }
   }

This code is repeated two other times with the only difference being the on and off duration. You can use two more globals:

unsigned long currentOnDuration;
unsigned long currentOffDuration;

and break the code out a little bit:

if (TempF > threshold3)
{
   currentOnDuration = onDuration1;
   currentOffDuration = offDuration1;
}
else if ( etc....)

Then, if it's not two low, run the code with the globals:

if (TempF < threshold1) 
{
  ...
}
else
{
    if(digitalRead(PIN) == HIGH)
    {
      if(millis() - startTime >= currentOnDuration )
      {
        startTime += currentOnDuration ;
        digitalWrite(PIN, LOW);
      }
    }
    else
    {
      if(millis() - startTime >= currentOffDuration)
      {
        startTime += currentOffDuration;
        digitalWrite(PIN, HIGH);
      }
    } 
}
if (TempF > threshold3){
....
else if ( TempF >= threshold2 && TempF <= threshold3) {
....
else if (TempF >= threshold1 && TempF <= threshold2){
...
else  if (TempF < threshold1) {

The middle two selection statements have unnecessary conditions (their second ones). Realize that what you have right now can be symbolized by this:

if ( A )
else if ( B && !A )
else if ( C && !B )
else if ( !C )

If A is true, then you don't touch B && !A, therefore, in order to actuall check the conditions B && !A, A must be false, making the check for !A redundant.

This is a comment about style ...

I find I easily become confusd with the sort of extensive block of code you have where there are several IF statements with their attendant functional code.

I prefer to use the IF statements to set the value of a variable that records the state of the system. For example it might be called temperatureState and it might contain one of h, c, or m meaning hot, cold or medium.

Then I would have a function that switches on the heater depending on the value of temperatureState. It would use the usual millis() to manage the timing.

The value in temperatureState could also be used to select which timing interval is appropriate.

Rather than call the different things duration1, duration2 etc it would simplify the programming to store them in an array so that they can be referred to as duration[n] where n could be 1, 2 or 3 as required.

...R

SteveTron:
Why was this method: (from the code I found in a previous post)

if (currentTime > nextTime) {

Ever be used?

Ignorance or carelessness by the person who wrote it. The version quoted above doesn't handle timer rollover correctly so is not recommended for general use. In specific situations where you know timer rollover will never occur, the difference doesn't matter. However, it's better to get into the habit of using the approach that always works.

Wow that was fast! I have yet to incorporate what Arrch has suggested, mainly because I am a bit lost. My code is still looking like my last post except I have added in larger time intervals. After looking at my ledpin and timing it using a POT to hit my temp intervals. I have noticed my >95 interval works perfect my >90 seems to not work at all and my >80 will be off for the offduration first.

If i incorporate the global variable will this solve the problem?

Suppose that you had a sketch that blinked one led unevenly according to ON and OFF wait times and then added a sensor read section that changed those wait times depending on the read value and the blink changes in about 1 ms?

I just realized I had an unsigned long set to 1601000 instead of 1.0601000. That may fix the >90° loop.

SteveTron:
I just realized I had an unsigned long set to 1601000 instead of 1.0601000. That may fix the >90° loop.

1.0 is a float constant, not an unsigned long

You want 1UL * 60UL * 1000UL

GoForSmoke,

I thought putting UL a number forces to be an unsigned long, but if you declare it an unsigned long then that works too. Are you saying that unsigned long 1.0601000 becomes a float? Regardless why does unsigned long 1.0601000 work and not unsigned long 1601000. I'm guessing the 1 is looked at as logic no matter what.

Arrch,

so in my case

if (TempF > threshold3){
....
else if ( TempF >= threshold2 ) {
....
else if (TempF >= threshold1 ){
...
else  if (TempF < threshold1) {

Does the exact same thing?

Also the reason why I did all of those floats for the math was to show the next person
the math behind getting a raw A/D to a temp without simplifying (though I am sure there is a better way to show that as well). I intend on giving the code to some on that teaches an electronics class.

Lastly when you say

Then, if it's not two low, run the code with the globals:

Do you mean the temp?

thanks for your help this is an awesome place to learn!

SteveTron:
GoForSmoke,

I thought putting UL a number forces to be an unsigned long, but if you declare it an unsigned long then that works too. Are you saying that unsigned long 1.0601000 becomes a float? Regardless why does unsigned long 1.0601000 work and not unsigned long 1601000. I'm guessing the 1 is looked at as logic no matter what.

There is a difference between how the number is treated during calculation and storage.

Example:

int a = 5 / 10;

int b = 5.0 / 1.0;




Both a and b will **store** the same value, because 0.5 can't be stored in an int. How it's **calculated**, however, will be different:

5 / 10 is integer division and will result in 0.
5.0 / 10.0 is floating point division and will result in 0.5.

Arrch,

so in my case



if (TempF > threshold3){
....
else if ( TempF >= threshold2 ) {
....
else if (TempF >= threshold1 ){
...
else  if (TempF < threshold1) {




Does the exact same thing?

So long as threshold3 > threshold 2 > threshold 1. The last else if is also unnecessary, it could just be else. At that point, you know it's less than threshold1 because you just checked to see if it was greater than or equal to. Since it's not, it's less than.

Also the reason why I did all of those floats for the math was to show the next person
the math behind getting a raw A/D to a temp without simplifying (though I am sure there is a better way to show that as well). I intend on giving the code to some on that teaches an electronics class.

SteveTron:
Lastly when you say

Then, if it's not two low, run the code with the globals:

Do you mean the temp?

Correct. If the temp isn't too low. If the temp is below all the thresholds, the behaviors is different, so you have to check for that first.

SteveTron:
GoForSmoke,

I thought putting UL a number forces to be an unsigned long, but if you declare it an unsigned long then that works too. Are you saying that unsigned long 1.0601000 becomes a float? Regardless why does unsigned long 1.0601000 work and not unsigned long 1601000. I'm guessing the 1 is looked at as logic no matter what.

Nope, a logic 1 is 1. Defined in the language: ON == 1 HIGH == 1 TRUE == 1

OTOH any integer 0 is false and any integer non-0 is true, regardless of value which won't be changed.

There are people who will tell that may change. Someday people may visit nearby stars too.

It's because of how the compiler treats constants when you don't tell it what to do.

const unsigned long x =1*60*1000; // makes an unsigned long out of int math that overflows then shifts
const unsigned long y =1.0*60.0*1000.0; // uses floats that works out this time but can bite you other times
const unsigned long z =1UL * 60UL * 1000UL; // unsigned long makes unsigned long that works

// ALWAYS take care with integers that you do not overflow the type
// you can crank up floats but you can lose accuracy doing it

// if you mix types in a calculation then cast the mixed type and take care about limits and precisions
// if you're EVER not sure then make a sketch like this to check. BE SURE or be clueless.

void setup( void )
{
  Serial.begin( 115200 );
  Serial.println( );
  Serial.println( x );
  Serial.println( y );
  Serial.println( z );
}

void loop( void )
{
}