Millis() instead of delay and loop() instead of for-loop

I think the concepts are intimately connected. There's a lot of tricky stuff hidden in the details of the BWOD sketch, and it's difficult to see the necessary differences between the blocking and non-blocking methods.

Rather than the setup()-loop() separation between the two systems, it might be more parallel if one started with slightly more complicated base example. Perhaps this one with a fast, controlled count, a pause, and repeat:

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

void loop() {
  const unsigned long quickInterval = 100; //
  const unsigned long slowInterval = 1000;

  for (int i = 0 ; i < 10 ; ++i) {
      Serial.print(i);
      Serial.print(" ");
      delay(quickInterval);
  }
  Serial.println("-- blocking");
  delay(slowInterval);
}

And with the non-blocking code shown in parallel in the same sketch:

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

void loop() {
  const bool doBlocking = true; // choose between blocking or non-blocking code

  const unsigned long quickInterval = 100; //
  const unsigned long slowInterval = 1000;

  if (doBlocking == true) { // use delay() and for(;;)
    for (int i = 0 ; i < 10 ; ++i) {
      Serial.print(i);
      Serial.print(" ");
      delay(quickInterval);
    }
    Serial.println("-- blocking");
    delay(slowInterval);

  }  else { // unblocked loop() by:
    // * replacing delays with millis() edge-detectors/state machines
    // * replacing for(;;) loop with a counter state machine

    // state variables to persist values past the end of loop()'s scope
    static int i = 0; // digit to print
    static unsigned long lastDigitMs = -quickInterval; // backdate by -quickInterval for immediate operation
    static unsigned long lastLineMs = 0; // state variable for edge-detecting slow delay

    unsigned long now = millis(); // remember the loop time

    // edge detecting state machine {i,now-las}
    if (i < 10 && now - lastDigitMs >= quickInterval) { // edge-detect interval change
      lastDigitMs = now; // measure digit delay from last digit print
      Serial.print(i);
      Serial.print(" ");
      ++i;
      lastLineMs = now; // measure line ending delay from last print time
    }
    // edge-detecting state machine
    if (i >= 10  && now - lastLineMs >= slowInterval) {
      Serial.println( "-- nonblocking");
      i = 0; // reset the count
    }
  }
}

I think I like the comparison between the two schemes in the same script.

The results of these two schemes aren't quite identical, since the EOL message comes before the delay in the blocking case, and after the pause in the non-blocking case. An additional i==10 case could fix that

    if( i == 10) {
       Serial.println( "-- nonblocking");
       ++i;
    }
    if (i > 10  && now - lastLineMs >= slowInterval) {
    ...

...but this difference shows that it is trickier to explicitly handle the interacting state variables and their transitions than it is to depend on the Instruction Pointer to keep track of program state.

It isn't a simple task to make the switch from blocking code to non-blocking code.

Edit: This is a nice thread on transforming a for(;;) to use state variables with loop()

Hello @ec2021,

I'm a beginner and I'm working on my hobby project that uses millis() for timing. When I started to get more different functions that needed individual calls for millis() I read UKHeliBob's thread again for clues, and I saw this thread at the top. Seems I'm in luck :slight_smile:

At the moment I'm using 2 different ways to compare millis(), so the variables don't get mixed up. Ofcourse I could use currentMillis_1 and currentMillis_2 but that's beside the point.

At first I was using the following, copied from UKHeliBob's example.

unsigned long startMillis;  
unsigned long currentMillis;
const unsigned long period = 1000;  
const byte ledPin = 13;    

void setup()
{
  Serial.begin(115200);  
  pinMode(ledPin, OUTPUT);

  startMillis = millis();  

void loop()
{
  currentMillis = millis();  
  if (currentMillis - startMillis >= period)  
  {
    digitalWrite(ledPin, !digitalRead(ledPin));  

    startMillis = currentMillis;  
  }
}

.

Afterwards, from your code I've seen I can use:

unsigned long readTime;  
const unsigned long period = 1000;  
const byte ledPin = 13;    

void setup()
{
  Serial.begin(115200);  
  pinMode(ledPin, OUTPUT);

  readTime = 0;  
}
void loop()
{
  if (millis() - readTime >= period) 
  {    
    digitalWrite(ledPin, !digitalRead(ledPin));  

    readTime = millis();  
  }
}

The code I've seen you use is a little more compact and uses millis() directly, so there is 1 less constant long filled with a value to be compared. Does this make the code more efficient?
(I don't want to say "better" because I'm sure they both work equally well).
And does this make any difference when millis() rolls over?

My second question is about the firstTime = true flag.
My project involves taking sensor measurements and operating relays based on the readings. I intend to use the firstTime flag as a way to delay the switching of the relay based on 2 measurements instead of just one.

My thoughts are to rename it secondRead and set it to false in setup. Then adjust the code with a nested if condition, like so:

unsigned long readTime;  
const unsigned long period = 1000;  
const byte ledPin = 13; 
bool seconRead = false;   

void setup()
{
  Serial.begin(115200);  
  pinMode(ledPin, OUTPUT);

  readTime = 0;  
}
void loop()
{
  if (millis() - readTime >= period) 
  {         
     if ( secondRead == true) { 
     digitalWrite(ledPin, !digitalRead(ledPin));  
     secondRead = false;
     }
    readTime = millis();
    secondRead = true;
  }
}

But this way the flag will always be true, and I'm not sure how to reset it correctly :frowning:

It makes no difference when millis() rolls over. The reasons for saving the value of millis() to a variable are that it allows you to give the value a meaningful name, it ensures consistency of timing within the sketch and removes the need for multiple calls to millis() which itself takes time and uses resources

2 Likes

Hi @arneko,

if I am correct there are four issues in your post:

  1. @UKHeliBob's use of "currentmillis" makes sure that the time spend inside the if-clause is taken care of. It might be only a small difference (like in the example) but that depends...

  2. I introudced the firstTime flag so that the if-clause is always entered when it is reached the first time, independend of the time it takes to come to it.

  3. In your sketch using "secondRead" there is small typo "bool seconRead = false;" should read "secondRead".

  4. To set secondRead to true and false every second time you can use an else-clause:

unsigned long readTime;  
const unsigned long period = 1000;  
const byte ledPin = 13; 
bool secondRead = false;   

void setup()
{
  Serial.begin(115200);  
  pinMode(ledPin, OUTPUT);

  readTime = 0;  
}
void loop()
{
  if (millis() - readTime >= period) 
  {         
     if ( secondRead == true) { 
       digitalWrite(ledPin, !digitalRead(ledPin));  
       secondRead = false;
     } else {
       secondRead = true;
  };
    readTime = millis();
  }
}

or this

unsigned long readTime = 0;
const unsigned long period = 1000;
const byte ledPin = 13;
bool secondRead = false;

void setup()
{
 Serial.begin(115200);
 pinMode(ledPin, OUTPUT);
}
void loop()
{
 if (millis() - readTime >= period)
 {
   if ( secondRead == true) {
     digitalWrite(ledPin, !digitalRead(ledPin));
   }
   secondRead = !secondRead; // inverts the value of secondRead 
   readTime = millis();
 }
}

P.S.:If you like to check it out in an Arduino simulator feel free to look here

1 Like

Thank you @UKHeliBob for clearing that up. I think I might use something like functionnameMillis in the future.

And thank you @ec2021 for your answer. I started to edit my own post 6 or 7 times, one of which was with an else addition but I kept secondguessing myself and aborting.

I've not yet seen this way of using != to flip a boolean. I feel like I've learned something very usefull :slight_smile:

Just to make more clear why I introduced the "firstTime" flag:

In the usual case

  if (millis() - readTime >= period) {
    doSomething();
  }

where readTime is zero, doSomething() will be executed the first time when millis() >= period. Therefore after restart of the Arduino doSomething() will be executed earliest after a time of "period" milliseconds. You can test this by setting period to e.g. 5000 msec and see that it takes 5 s after start for the first call of doSomething() .

I think I understood it correctly. The flag makes sure that the if (x) condtion includes something that is always true (at the start or after a reset) by using the or operator || , and "discarding" it after.

Be aware that there is a difference between

b = !a;

and

b != a;

The first statement inverts the value of a and applies the result to b.
The second compares the values of b and a and returns true if both are different and false if b equals a!

1 Like

That's ok.

The very first execution of the if-clause is safely triggered by the flag , every further execution is triggered by the timing by the values of "period" and "readTime".

Be aware that there is a difference between

b = !a;

and

b != a;

I've got a long way to go :sweat_smile:

No worries :wink:

This may be one of several sources to assist:

https://cplusplus.com/doc/tutorial/

and to make it also more clear: "b != a" is used e.g. in if statements but the result can also be stored in a third variable:

  // Example I

  boolean a = false;
  boolean b = true;

  if (a != b) {
    // a does not equal b
  } else {
  // a and b are equal
 };

// Example II
boolean c = (a != b);
  if (c) {
    // a does not equal b
  } else {
  // a and b are equal
 };

You are talking about details without having said what the complete functionality shall be.

Highly depending on the complete functionality it might be that a different approach is better suited.

I came more and more to the conclusion that knowing how to debug by serial printing is the most important technique fordebugging and for learning.

I like to ask a question about the Millis function in order to understand it right. I know, that Timer0 operates with a period of 2.048 ms and creates an interrupt for the millis counter update each 1.024 ms. So I had to figure out what was going on, when it was not updated every 1 ms as should be expected. And by luck I saw this post:

https://ucexperiment.wordpress.com/2012/03/16/examination-of-the-arduino-millis-function/

The problem is solved by the fact, that the Millis counter now and then steps two instead of just one as I expected.

Let us assume, that I like to make some code that should run for about every 5 ms and I use Millis() for that. Then what actually will happen is, that it will run 6 or 7 times with about 5.12 ms time distance and then one time with 4.096 ms time distance. I guess that it will be in order most of the time. But if you presume that millis only will step one each time, you may be up for a surprise.

I am new to Arduino programming - so am I right or wrong here?

Nice question :wink:

To my understanding

  • the error adds up 24 µs with every 1 millisecond iteration until the fraction counter reaches or exceeds 125 (in case of a 16 MHz oszillator).
  • the correction is down about every 41.67 msec

So just before that correction the error my add up further (up to just around 1 msec).

If you need a better resolution you may use micros() instead.

See the information linked here (just scroll down to the very end of that page):

http://www.gammon.com.au/millis

Thanks for your answer and link. It seems that I did got this right then.

Yes, you are right, that you can have your code taking more than 1 ms, and then you have always got the problem, that the next call of Millis will increase the count by more than 1.

I guess that most of the code used, will have no problem with this. But in some rare situations, you may just wonder what is going on. If you want to have better accuracy for the timing of a loop, you may also be somewhat intelligent and detect when millis returns a value higher than normal, and wait one count more to do your code. So in my example you get about 5.12 ms period each time.

I have noticed, that some of the Arduino Nano sold got at 20 MHz clock. Does it mean that you should expect the interrupt for the millis counter will be every 0.8192 ms ?

This should mean that a code using millis for polling every 5 ms will be released every 4.9152 ms or 5.7344 ms.

I guess that the normal PWM frequency of 488 Hz need to be changed to 610 Hz - am I right?

For a deeper insight you may refer to

https://www.best-microcontroller-projects.com/arduino-millis.html

If you scroll down there is an explanation for different clocks ...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.