Several things at the same time follow up

Hi all,

got here from thread entitled the same(ish).

I have a functioning RFID reader that I use for immobilising / alarm for classic cars, the one issue I was having was that as or when the alarm was triggered, it was a little pot luck as to the timing when I passed the RFID card over the reader as to if it would turn the alarm off.

As you may have guessed I was using delay()'s which are causing the issue, so this led me onto the thread in question and my further thoughts, as this program will be running continuously for long periods millis() will overspill and return to 0, this will trigger negative values in the time checks every 49 days (ish)

So was thinking that I could simply reset millis(), but I cannot find anything that referred to doing that, I have simplified the original code for my novice head before re-writing my original code, and was looking at checking if millis() is about to overspill and resetting it before it does.

Is this the correct approach, or do I ignore this issue and let it overspill / reset or will either approach create another issue ?

Many thanks in advance

Jon

// SeveralThingsAtTheSameTime.ino

// An expansion of the BlinkWithoutDelay concept to illustrate how a script
//  can appear to do several things at the same time

// this sketch has been adapted from the above written by Robin2

//========================================

// ----------LIBRARIES--------------



// --------CONSTANTS (won't change)---------------

const int OutputPin1 =  13;      // the pin numbers for the outputs
const int OutputPin2 = 12;
const int OutputPin3 = 11;
const int OutputPin4 = 10;

const int buttonPin = 7; // the pin number for the button


const int Delay1 = 500; // number of millisecs between blinks  Delay 1
const int Delay2 = 2500;  //Delay 2
const int Delay3 = 4500;    //Delay 3
const int Delay4 = 500; // number of millisecs that Outputs are on - all three use this  (Delay 4)
const int Delay5 = 300; // number of millisecs between button readings   (Delay 5)


//------------ VARIABLES (will change)---------------------

byte State1 = LOW;             // used to set pins 
byte State2 = LOW;           //   LOW = off
byte State3 = LOW;
byte buttonLed_State = LOW;

unsigned long timeNow = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousTime1 = 0;   // will store last time the Output was updated
unsigned long previousTime2 = 0;
unsigned long previousTime3 = 0;

unsigned long previousButtonMillis = 0; // time when button press last checked


//========================================

void setup() {

  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running
  
      // set the Led pins as output:
  pinMode(OutputPin1, OUTPUT);
  pinMode(OutputPin2, OUTPUT);
  pinMode(OutputPin3, OUTPUT);
  pinMode(OutputPin4, OUTPUT);
  
      // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);
  
  }

//========================================

void loop() {

      // Notice that none of the action happens in loop() apart from reading millis()
      //   it just calls the functions that have the action code

  timeNow = millis();   // capture the latest value of millis()
                              //   this is equivalent to noting the time from a clock
                              //   use the same time for all LED flashes to keep them synchronized

  if timeNow >=  (4294967295 - 5000)  {   // max 4294967295
    Delay(5000); // wait 5000mS millis will go to 0 this should avoid overspill of previousTime(s)as long as this delay is greater than any of the Delay(s)
    previousTime1 = 0;   // resets previousTime(s)
    previousTime2 = 0;
    previousTime3 = 0;
    // there will be a slight error depending when timeNow ACTUALLY reaches this point but this ensure there is no negative value created in time tests in the functions
  }
  
  readButton();               // call all the functions that do the work
  UpdateState1();
  UpdateState2();
  UpdateState3();
  SwitchStates();
 

}

//========================================

void UpdateState1() {

  if (State1 == LOW) {
          // if the Output is off, we must wait for the (delay)interval to expire before turning it on
    if (timeNow - previousTime1 >= Delay1) {   //Delay1 = 500mS
          // if time is up, change the state to HIGH
          // say we check every 100mS then this is checked 5 times before State1 changes timeNow would have been 100 then 200 - 300 - 400 -500
       State1 = HIGH;
          // and save the time when we made the change
       previousTime1 += Delay1;   // say we check every 100mS on the 5th loop state switches to high and previousTime1 them becomes 0 + 500 next time the state changes it will become 1000 (500 +500)
          // NOTE: The previous line could alternatively be
          //              previousTime1 = timeNow     if this is used and it takes 1mS to get this far then the new previousTime1 would be (501mS this is the latency ?) timeNow resets to 0 after it exceeds 4.29 billion -- making their range from 0 to 4,294,967,295 (2^32 - 1).
          //        which is the style used in the BlinkWithoutDelay example sketch
          //        Adding on the interval is a better way to ensure that succesive periods are identical

    }
  }
  else {  // i.e. if State1 is HIGH
  
          // if the Led is on, we must wait for the ON duration (Delay4) to expire before turning it off
    if (timeNow - previousTime1 >= Delay4) {
          // time is up, so change the state to LOW
       State1 = LOW;
          // and save the time when we made the change
       previousTime1 += Delay4;   
        // NOTE: The previous line could alternatively be
          //              previousTime1 = timeNow
    } 
  }
}

//========================================

void UpdateState2() {

  if (State2 == LOW) {
    if (timeNow - previousTime2 >= Delay2) {
       State2 = HIGH;
       previousTime2 += Delay2;
    }
  }
  else {
    if (timeNow - previousTime2 >= Delay4) {
       State2 = LOW;
       previousTime2 += Delay4;
    } 
  }    
}

//========================================

void UpdateState3() {

  if (State3 == LOW) {
    if (timeNow - previousTime3 >= Delay3) {
       State3 = HIGH;
       previousTime3 += Delay3;
    }
  }
  else {
    if (timeNow - previousTime3 >= Delay4) {
       State3 = LOW;
       previousTime3 += Delay4;
    }
  }    
}

//========================================

void SwitchStates() {
      // this is the code that actually switches the LEDs on and off

  digitalWrite(OutputPin1, State1);
  digitalWrite(OutputPin2, State2);
  digitalWrite(OutputPin3, State3);
  digitalWrite(OutputPin4, buttonLed_State);
}

//========================================

void readButton() {

      // this only reads the button state after the button interval has elapsed
      //  this avoids multiple flashes if the button bounces
      // every time the button is pressed it changes buttonLed_State causing the Led to go on or off
      // Notice that there is no need to synchronize this use of millis() with the flashing Leds
  
  if (millis() - previousButtonMillis >= Delay5) {

    if (digitalRead(buttonPin) == LOW) {
      buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH 
                                           //   and to HIGH if it was LOW
      previousButtonMillis += Delay5;
    }
  }

}



//========================================END

The only way to reset millis is to reset /restart the controller. That's not needed.
Millis is an unsigned long. Just use it the proper way the wrap around will not make trouble.

Start with the Blink Without Delay: https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay.

It is preferred to set the previousMillis to the currentMillis (inside the if-statement). That is safer for a few rare situations.
Only when you need the timing to stay in sync with the time, then you can add the interval value.

This is good.
Note that a delay in the sketch will also delay the interval. That is safe and often no problem.

if (currentMillis - previousMillis >= interval)
{
  previousMillis = currentMillis;
  ...
}

This is only for special situations to sync with the real time. For example when making a clock and the interval is every second. Don't use it if you don't have to.

if (currentMillis - previousMillis >= interval)
{
  previousMillis += interval;
  ...
}

Don't calculate a millis value in the future and there is no need to make previousMillis zero.

If you want to have fun with millis(), then you can try a few examples that I made.

So basically use blinkwithoutdelay method if time keeping is not critical.

And ignore that fact that millis() with reset itself to 0 on overspill ?

many thanks, often easy to overthink a problem that is not there in the first place and then create other issues down the line.

:grin:

So basically use blinkwithoutdelay method if time keeping is not critical.
And ignore that fact that millis() with reset itself to 0 on overspill ?

BlinkWithoutDelay doesn't ignore millis() overflow. It handles it correctly.

So basically use blinkwithoutdelay method if time keeping is not critical.

Not time keeping. Time synchronization.

this will trigger negative values in the time checks every 49 days (ish)

If you use unsigned variables, negative values don't exist :wink:

aarg:
BlinkWithoutDelay doesn't ignore millis() overflow. It handles it correctly.

Not time keeping. Time synchronization.

I was referring to myself ignoring overflow not the code. But thanks for feedback.
Not sure why time sync is different to time keeping in my simple explanation, is that hair splitting or is there a fundamental difference, just for my clarity if I was to use similar code somewhere else.
thanks again guys for the pointers
Jon

Oh and Duh ! thanks sterretje the "unsigned" kinda gave that away, stupid question now you pointed that out, gave me a smile for my stupidity :slight_smile:

Proof:

unsigned long previousMillis;
unsigned long currentMillis;

void setup()
{
  Serial.begin( 9600);
  Serial.println();
  Serial.println( "Proof that rollover is no problem with BlinkWithoutDelay sketch");

  // normal situation.
  currentMillis = 0x00000010;
  previousMillis = 0x00000006;
  showResult();
  
  // simulate that a rollover just happened.
  currentMillis = 0x00000005;
  previousMillis = 0xFFFFFFFB;
  showResult();
}

void loop()
{
}

void showResult()
{
  // The next line with the subtraction is where the magic happens:
  unsigned long result = currentMillis - previousMillis;
  
  Serial.print( "result = ");
  Serial.print( currentMillis);
  Serial.print( " - ");
  Serial.print( previousMillis);
  Serial.print( " = ");
  Serial.print( result);
  Serial.println();
}

The previousMillis is a timestamp. The millis() value is the same or higher than previousMillis.
During a rollover, the millis() has rolled over from 0xFFFFFFFF to 0x00000000. It “could” have continued if there were 33 bits, but there are only 32 bits.
The calculation with unsigned long will give a good result, with or without the 33th bit. The 33th bit drops out of the calculation because it just isn’t there.

Koepel:
Proof:

unsigned long previousMillis;

unsigned long currentMillis;

void setup()
{
 Serial.begin( 9600);
 Serial.println();
 Serial.println( "Proof that rollover is no problem with BlinkWithoutDelay sketch");

// normal situation.
 currentMillis = 0x00000010;
 previousMillis = 0x00000006;
 showResult();
 
 // simulate that a rollover just happened.
 currentMillis = 0x00000005;
 previousMillis = 0xFFFFFFFB;
 showResult();
}

void loop()
{
}

void showResult()
{
 // The next line with the subtraction is where the magic happens:
 unsigned long result = currentMillis - previousMillis;
 
 Serial.print( "result = ");
 Serial.print( currentMillis);
 Serial.print( " - ");
 Serial.print( previousMillis);
 Serial.print( " = ");
 Serial.print( result);
 Serial.println();
}




The previousMillis is a timestamp. The millis() value is the same or higher than previousMillis.
During a rollover, the millis() has rolled over from 0xFFFFFFFF to 0x00000000. It "could" have continued if there were 33 bits, but there are only 32 bits.
The calculation with unsigned long will give a good result, with or without the 33th bit. The 33th bit drops out of the calculation because it just isn't there.

Thanks, I did the quick mental maths and figured this out.
Updated the code as below which I think covers the points mentioned.

// SeveralThingsAtTheSameTime.ino

// An expansion of the BlinkWithoutDelay concept to illustrate how a script
//  can appear to do several things at the same time

// this sketch has been adapted from the above written by Robin2

//========================================

// ----------LIBRARIES--------------



// --------CONSTANTS (won't change)---------------

const int OutputPin1 =  13;      // the pin numbers for the outputs
const int OutputPin2 = 12;
const int OutputPin3 = 11;
const int OutputPin4 = 10;

const int buttonPin = 7; // the pin number for the button


const int Delay1 = 500; // number of millisecs between blinks  Delay 1
const int Delay2 = 2500;  //Delay 2
const int Delay3 = 4500;    //Delay 3
const int Delay4 = 500; // number of millisecs that Outputs are on - all three use this  (Delay 4)
const int Delay5 = 300; // number of millisecs between button readings to avoid button bounce reading (Delay 5)


//------------ VARIABLES (will change)---------------------

byte State1 = LOW;             // used to set pins 
byte State2 = LOW;           //   LOW = off
byte State3 = LOW;
byte buttonLed_State = LOW;

unsigned long currentTime = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousTime1 = 0;   // will store last time the Output was updated
unsigned long previousTime2 = 0;
unsigned long previousTime3 = 0;

unsigned long previousButtonMillis = 0; // time when button press last checked


//========================================

void setup() {

  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running
  
      // set the Led pins as output:
  pinMode(OutputPin1, OUTPUT);
  pinMode(OutputPin2, OUTPUT);
  pinMode(OutputPin3, OUTPUT);
  pinMode(OutputPin4, OUTPUT);
  
      // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);
  
  }

//========================================

void loop() {

      // Notice that none of the action happens in loop() apart from reading millis()
      //   it just calls the functions that have the action code

  currentTime = millis();   // capture the latest value of millis()
                              //   this is equivalent to noting the time from a clock
                              //   use the same time for all LED flashes to keep them synchronized

 
  readButton();               // call all the functions that do the work
  UpdateState1();
  UpdateState2();
  UpdateState3();
  SwitchStates();
 }

//========================================

void UpdateState1() {

  if (State1 == LOW) {
          // if the Output is off, we must wait for the (delay)interval to expire before turning it on
    if (currentTime - previousTime1 >= Delay1) {   //Delay1 = 500mS
          // if time is up, change the state to HIGH
          // say we check every 100mS then this is checked 5 times before State1 changes currentTime would have been 100 then 200 - 300 - 400 -500
       State1 = HIGH;
          // and save the time when we made the change
       previousTime1 = currentTime;   // say we check every 100mS on the 5th loop state switches to high and previousTime1 them becomes 0 + 500 next time the state changes it will become 1000 (500 +500)
          
    }
  }
  else {  // i.e. if State1 is HIGH
  
          // if the Led is on, we must wait for the ON duration (Delay4) to expire before turning it off
    if (currentTime - previousTime1 >= Delay4) {
          // time is up, so change the state to LOW
       State1 = LOW;
          // and save the time when we made the change
       previousTime1 = currentTime;   
        
    } 
  }
}

//========================================

void UpdateState2() {

  if (State2 == LOW) {
    if (currentTime - previousTime2 >= Delay2) {
       State2 = HIGH;
       previousTime2 = currentTime;
    }
  }
  else {
    if (currentTime - previousTime2 >= Delay4) {
       State2 = LOW;
       previousTime2 = currentTime;
    } 
  }    
}

//========================================

void UpdateState3() {

  if (State3 == LOW) {
    if (currentTime - previousTime3 >= Delay3) {
       State3 = HIGH;
       previousTime3 = currentTime;
    }
  }
  else {
    if (currentTime - previousTime3 >= Delay4) {
       State3 = LOW;
       previousTime3 = currentTime;
    }
  }    
}

//========================================

void SwitchStates() {
      // this is the code that actually switches the LEDs on and off

  digitalWrite(OutputPin1, State1);
  digitalWrite(OutputPin2, State2);
  digitalWrite(OutputPin3, State3);
  digitalWrite(OutputPin4, buttonLed_State);
}

//========================================

void readButton() {

      // this only reads the button state after the button interval has elapsed
      //  this avoids multiple flashes if the button bounces
      // every time the button is pressed it changes buttonLed_State causing the Led to go on or off
      // Notice that there is no need to synchronize this use of millis() with the flashing Leds
  
  if (currentTime - previousButtonMillis >= Delay5) {  //button bounce time delay

    if (digitalRead(buttonPin) == LOW) {
      buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH 
                                           //   and to HIGH if it was LOW
      previousButtonMillis = currentTime;
    }
  }

}

//========================================END

jontf:
Not sure why time sync is different to time keeping in my simple explanation, is that hair splitting

To an extent it is hair splitting.

When you use code like this

if (millis() - previousMillis >= interval)

it is possible that the IF statement actually triggers at interval + 1 and every time that happens those +1 errors will be carried forward when using previousMillis = millis()

For most projects that tiny error does not matter.

If it does matter then the style

previousMillis += interval

corrects for that.

However if you are are using short intervals you can get weird results when the program starts if for the initial calculations previousMillis += interval is still less than millis(). On one occasion it took me several hours to spot the problem.

...R