Reseting timer while code is running

Hi community!

I have a question about how to reset a timer while code, depending on it is running. I'll give you an example. Please see the code below.

#include <Arduino.h>

// Pin definitions
const int ledPin1 = 4;  // GPIO 4
const int ledPin2 = 2;  // GPIO 2
const int ledPin17 = 17;  // GPIO 17


unsigned long previousMillis = 0;


void setup() {
  // Initialize LED pins as output
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin17, OUTPUT);

  // Code executed only once
  digitalWrite(ledPin1, LOW);
  digitalWrite(ledPin2, LOW);
  digitalWrite(ledPin17, LOW);
  delay(1000);

  digitalWrite(ledPin1, HIGH);
  digitalWrite(ledPin2, HIGH);
  digitalWrite(ledPin17, HIGH);
  delay(5000);

  // Turn off the main lights, blinking blue indicates it starts soon
  digitalWrite(ledPin1, LOW);
  digitalWrite(ledPin2, LOW);
  for (int i = 0; i < 1; i++) {   
    digitalWrite(ledPin17, LOW);
    delay(1000);
    digitalWrite(ledPin17, HIGH);
    delay(3000);
  }
  for (int i = 0; i < 3; i++) {    
    digitalWrite(ledPin17, LOW);
    delay(1000);
    digitalWrite(ledPin17, HIGH);
    delay(1000);
  }
  for (int i = 0; i < 10; i++) {     
    digitalWrite(ledPin17, LOW);
    delay(500);
    digitalWrite(ledPin17, HIGH);
    delay(500);
  }

  previousMillis = millis();  // Start the timer for the sequence
}

void loop() {

  unsigned long currentMillis = millis();

  // Calculate the time elapsed since the code started
  unsigned long elapsedMillis = currentMillis - previousMillis;

  // Turn on LedPin1 for 20 seconds, every after 50 seconds
  if (elapsedMillis >= 50000 && elapsedMillis <= 70000 ) {
    digitalWrite(ledPin1, HIGH);  // Turn on LedPin1
  } else if (elapsedMillis < 50000 || elapsedMillis > 70000)   {
    digitalWrite(ledPin1, LOW);   // Turn off LedPin1
  }
  
    // Turn on LedPin2 for 20 seconds after 110 seconds
  if (elapsedMillis >= 110000 && elapsedMillis <= 130000) {
    digitalWrite(ledPin2, HIGH);  // Turn on LedPin2
  } else if (elapsedMillis < 110000 || elapsedMillis > 130000) {
    digitalWrite(ledPin2, LOW);   // Turn off LedPin2
  }
  

  // Reset the timer after 120 seconds
   if (elapsedMillis >= 120000) {
    previousMillis = currentMillis;
  }
}

Basically, I have a separate ESP32-CAM that takes images every 60 seconds and I want to fix the lighting. On a separate ESP32-Dev I have installed two LEDs at separate GPIOs. One Led (LedPin1) should turn on 10 seconds before the camera takes an image and turn off 10 seconds after. The other LED (LedPin2) should do the same for the second image and the loop continues.

The first part of the code (in the Setup()) should only be executed once (this is so I have time to prepare the camera). After this part, the timer should start and 50 seconds after it enters the loop LedPin1 should be turned on.

This part of the code works. However, I get into problems when I have to restart the timer. If I restart it after 120 seconds so that LedPin1 is turned on again 10 seconds before the camera takes an image - the LedPin2 is turned off 10 seconds before it should (after 120 seconds instead of 130 seconds.

How can I restart the timer even if the code running is dependent on it?

Unrelated to this topic but I'm just curious, do you know any way that I can link my ESP32-Dev (that controls the light) with my ESP32-CAM (that takes images) so that I have one code for everything?

Best Regards!

According to Arduino documentation " Reconfiguration of the microcontroller’s timers may result in inaccurate millis() readings."

So why don't you do something like this.

// Reset the timer after 120 seconds
if (elapsedMillis + 120000 >= elapsedMillis ) {
    elapsedMillis = millis()  + 120000;
}

You can also do the same for each LED i.e. LedOneMillis = LedTwoMillis =

That won't compile.

Typo mistake :smile:

Interesting equation, the only time that can be false is when elapsedMillis is within 12000 of the rollover point.

2 Likes

Might be a bit easier with a state machine, but the basic sequence seems to be:

Start
delay for 10 seconds to get correct offset for timing
{  //repeat the following code indefinitely
    delay 40 seconds
    turn on LED1
    delay 20 seconds
    turn off LED1
    delay 40 seconds
    turn on LED2
    delay 20 seconds
    turn off LED2
}
1 Like

I replaced my if-statement with yours. It does allow for LedPin2 to work for 20 seconds (from the 110th second to the 130th), however, it stops the cycle.

Remember, I have a camera that takes images every 60 seconds. The lights should turn on 10 seconds before the image is taken and be turn off 10 seconds after. The lights alternate; so for the first image, Ledpin1 is turned on. For the second image, LedPin2 is turned on and so on.

Yes you are right @ david_2018, I probably should have tested it first before posting. This was what I meant.

  if (millis() >= elapsedMillis ) {
    elapsedMillis = millis()  + 10000;


  }

Can you post the changes you made in your code?

What I done was remove unsigned long elapsedMillis = currentMillis - previousMillis; from loop() and declared unsigned long elapsedMillis = 120000; above setup(). If you declare each interval the same then it should work.

There's no need to use elapsedMillis for every interval, create an interval for each time range and update them in each if statement.

Hi david_2018, is the state machine a built in function, I've never seen it before?

Hi Delta_G

I started using Arduino programming from last year but I initially started programming years ago using other different languages. Looking at quick a Google search it seems like a concept I have used for years without knowing that there is a name for it!

I want to rephrase this description to fit better to the concept on non-blocking timing

Start
store actual time as a reference-point in time
start comparing "how much time has passed by since reference-point in time has been stored
by calculating
actualTimeDIFFERENCE = (further counted up) actual time minus reference-point in time
check if this difference is equal or greater than 10 seconds
if greater than 10 seconds
in short "X seconds have passed by"

{ //repeat the following code indefinitely
after 40 seconds have passed by
turn on LED1
after 20 seconds have passed by
turn off LED1
after 40 seconds have passed by
turn on LED2
after 20 seconds have passed by
turn off LED2
}

You could see this as different modes of operation
mode 1: initial waiting for 10 seconds
mode 2: waiting for 40 seconds. If 40 seconds have passed by switch on LED1 and change to mode3

mode 3: waiting for 20 seconds. If 20 seconds have passed by switch OFF LED1 and change to mode4

mode 4: waiting for 40 seconds. If 40 seconds have passed by switch on LED2 and change to mode 5

mode 5: waiting for 20 seconds. If 20 seconds have passed by switch OFF LED2 and change to mode 2

highly sophisticated informatic scientists gave these modes the name "state"
and the complete code-structure with state1, state2.....
where always only that part of the code that belongs to a certain state is executed mutually exclusive is called a state-machine.

So there are two things to learn which will take some time

  1. non-blocking timing based on function millis()
  2. how state-machines work

tutorial for 1 non-blocking timing

tutorial for 2 state-machine

best regards Stefan

1 Like

Your states should have names that describe spot-on what the state is doing

my suggestion for the constant-names:
"sm_" indicating state-machine


const byte sm_initialWait          = 0;
const byte sm_waitForSwitchOnLED1  = 1;
const byte sm_waitForSwitchOffLED1 = 2;
const byte sm_waitForSwitchOnLED2  = 3;
const byte sm_waitForSwitchOffLED2 = 4;

byte myStateVar;

best regards Stefan

Hi StefanL38

This is how I would have written it

  if (millis() >= LedOneInterval ) {
    LedOneInterval = millis()  + 50000;
    LedOneMillis = millis()  + 20000;
    LEDOneStatus = 1;
    digitalWrite(ledPin1, HIGH);
  }
  
  if (millis() >= LedOneMillis && LEDOneStatus == 1) {
    LEDOneStatus = 0;
    digitalWrite(ledPin1, LOW);
  }

but I also like your method in post #14, I guess it would probably look cleaner to using that with a function to calculate the interval and have a basic if/Case statement

 if (sm_waitForSwitchOnLED1  == 1 ) {
    digitalWrite(ledPin1, HIGH);

  }

Thanks for letting me know @Delta_G
:+1:

in this way making the code-execution mutually exclusive requires additional variables.
The switch-case-break;-statement eliminates the additional flag-variables and reduces the if-conditions to a minimum.

You can see the switch.case-break-statement as a variant of

if ()
else if()
else if()
...

where the condition reduces down to a single value

switch (myStateVar) {
  
  case sm_initialWait: 
    // mutually exclusive execute code here         
    break; // IMMIDIATELY jump down to END-OF-SWITCH
  
  case sm_waitForSwitchOnLED1: // more compact way of coding if (myStateVar == sm_waitForSwitchOnLED1) 
    // mutually exclusive execute code here         
    break; // IMMIDIATELY jump down to END-OF-SWITCH
  
  case sm_waitForSwitchOffLED1:
    // mutually exclusive execute code here         
    break; // IMMIDIATELY jump down to END-OF-SWITCH
  
  case sm_waitForSwitchOnLED2:
    // mutually exclusive execute code here         
    break; // IMMIDIATELY jump down to END-OF-SWITCH
  
  case sm_waitForSwitchOffLED2:
    // mutually exclusive execute code here         
    break; // IMMIDIATELY jump down to END-OF-SWITCH
  
} // END-OF-SWITCH

take a look into the switch-case tutorial

1 Like

@Delta_G I understand what you mean when you say "ALWAYS calculate the elapsed time since something happened." i.e. it's better to be more cautious.

But if LedOneInterval is set to millis() + 50000, surely it couldn't set a number smaller than millis() ?

@StefanL38
Ok I see what you mean, calculate the interval then pass myStateVar into the case statement.

:+1:

millis() delivers an unsigned long
which counts up to 4.294.967.295

if you have the number
4.294.967.294
and do the calculation
4.294.967.294 + 2 the result will be zero
which is for sure smaller than
4.294.967.294

and millis() istelf when the value has reached
which counts up to 4.294.967.295
the next value will be zero

This is the roll-over of unsigned long

if you do the calculation of the difference exactly this way

if( millis() - StartOfPeriod >= interval)

the result will always be correct even with a rollover of millis()
because if you do this calculation with unsigned long
there are no negative values

remember unsigned long
going from 0....up to .....4.294.967.295
but never a negative number

best regards Stefan

2 Likes

@Delta_G

very good and still short explanation of what is going on