Your Opinion about this version of millis() as a replacement for delay()

Hi everybody,

I would like to read your opinions about this version of millis() (or should I write "BWD")

The use of delay() can be such a DELAY for newbees writing code that does not work they expect it.

Execute code only once every X seconds/minutes/hours is a task that comes along in a lot of cases. So I would call it a basic programming-technique.

delay(2000) is written down very quickly while something like

  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod ) {
     lastMillis = currentMillis;

etc. takes more time to write and it is something that has no selfexplaining name

recently I came across this version that reduces how much you have to write and offers a more selfexplaining name

//nbt nonblockingtimer using millis()
//this function checks if a timestamp containing milliseconds passed as first parameter 
// was made more than 
// X milliseconds in the past then second parameter "TimePeriod" specifies.
// if yes the function returns true and automatically updates the variable that keeps the timestamp
// if no just returns false

boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}
unsigned long MyTestTimer = 0;

void loop(){
  // check if the timestamp-value of variable "MyTestTimer" was made more 
  // than 1000 milliseconds in the past. 
  // behaviour: execute "DoThings" only once every 1000 milliseconds 
  if ( TimePeriodIsOver(MyTestTimer,1000) ) { 
    DoThings; 
  }

Of course any solution has its pro's and con's

So I would like to read from you your opinion.

My opinion is: if all those simple "Blink-LED", "read out sensor and Serial.print" examples would use this construction newbees would have to think over the code 10 minutes longer once at the beginning of their learning-curve and avoid a lot of help-less hours trying to make code work with delay() that really cannot work with delay()

best regards Stefan

++Karma; // For trying to solve and old problem that drives me mad!

Hi Stefan, I don't know. I think there's more than one way to do this and each person will have their own preference. I think the real problem is first off new folk are introduced to delay in the very first example and it works, it does what they expect and they can smile at actually having got a computer program to work. The trouble is that then sets their expectation that delay is OK to use, and at first it will be. In small, simple programs delay probably doesn't do any harm so people carry on using it. Later comes the question on here 'my code does not respond when I press a button and I don't know why', you know the kind of thing. Then they have to unlearn delay and learn to program properly with multi-tasking code, if only the first example were a simple, multitasking compatible example.

If you read my posts on here you will see I try to dissuade use of delay repeatedly, so the point where I suspect some other posters think I am obsessed by it; they are right!

Well done for trying, but this problem is not going to go away.

boolean TimePeriodIsOver(unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime < TimePeriod )
    return false;            // not expired
  expireTime = currentMillis; // set new expireTime
  return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
}

it is something that has no selfexplaining name

That is largely because you did not give the variables meaningful names

if (currentTime - startTime >= requiredInterval)
{
  //do something
  startTime = currentTime;
}

would be better but it all depends on what you want to do

boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod)

I am willing to bet that most beginners will forget to pass the expiry time by reference then be confused as to why they do not get the results they expect. If you can grasp the difference between pass by value and pass by reference then you are probably not going to find it difficult to understand the principle of using millis() for timing

A big problem in dealing with queries on a forum rather than interactively is that you get no immediate feedback such as furrowed brows, quizzical looks and hesitation at the keyboard that you do when training in person. To complicate matters further everyone has different experience and backgrounds, to say nothing of the problem of understanding something in what may not be their native language.

One size does not fit all and I am sure that some people will like and use your solution so thank you for posting it and opening this discussion, but nobody has yet found a perfect answer to the question "how do I use millis() for timing ?"

If you want the luxury of a "fit and forget" solution then use a library to do the timing.

StefanL38:
delay(2000) is written down very quickly while something like

  unsigned long currentMillis  = millis();

if ( currentMillis - expireTime >= TimePeriod ) {
    lastMillis = currentMillis;



etc. takes more time to write

The fact that it takes more time to write seems unimportant in itself.

I like this style because it hides nothing from the programmer whereas giving them a function such as TimePeriodIsOver() can mean that they just get stuck in a different “black box” (rather than in delay() ). And I think a newbie will find the code within your function harder to follow if they discover a situation where it does not exactly meet their need. A fairly common requirement is where lastMillis = currentMills should NOT be part of the block - it needs to be updated elsewhere in the program.

But this is very much a matter of preference and people are different.

…R

there's the distinction between easy to understand and efficiency. Of course, easy to understand is desirable for newbies. Unfortunately it can be bad practice in a professional environment.

in the chapter on "Program Development" in The Unix Programming Environment, kernighan and pike build a simple interpretive language as a way to demonstrate how to develop and test a less than trivial programs. They start with a very simple approach that is easy to understand and in stages use more sophisticated programming techniques to significantly expand its functionality.

they demonstrate the development of prototype designs that are partially discarded to help the developer build an understanding of the problem in order to understand how to implement a more efficient design; stepping stones that are discarded.

i think TimePeriodIsOver() is inefficient and a poor way to develop code if it is used many times in a program. why repeatedly call millis()?

one of the first features described by Doug Comer in XINU (Xinu is not Unix) is the use of linked lists and a single timer interrupt to manage an unlimited # of timed events in the OS. I've used this approach at work on DSP projects without on OS. Such an approach could be used on an Arduino or esp32 if there were many asynchronous (or periodic) timed events occurring.

of course, there is no one right way. The "good enough" approach depends on the type of program as well as the type of programmer

i've been disappointed by professional colleagues who are satisfied with making it work instead of making it maintainable, and have had to pay the price of rewriting their code to fix bugs or enhance features.

BWD ??

It's a noble goal, but unfortunately I don't think it will help much.

Newbs will just faff around with function TimePeriodIsOver, without understanding it, and mess up your whole paradigm. Best put in a class library, where they cannot (so easily) access it and fiddle with it, or indeed the variables other than by the API provided. But then they are probably already timing libraries....

expireTime should probably be "lastExpireTime" or "lastRunTime".

Edit: The real problem is as PerryBebbington says. Newbs are introduced to delay, it works, and it is very simple to understand. It's not a great learning experience to discover the very first example given is not fit for purpose in a real world problem (especially if you've just spent many hours trying to make it so in your first solo coding adventure).

Execute code only once every X seconds/minutes/hours is a task that comes along in a lot of cases. So I would call it a basic programming-technique.

I totally agree on that. BwD ist THE example everyone has to understand and to "hide" the timekeeping in a library doesn't help so much. In the end this just means, beginners [u]have to[/u] go through the BwD.

I am willing to bet that most beginners will forget to pass the expiry time by reference then be confused as to why they do not get the results they expect.

agree.

Personally I prefer an BwD example which - wraps the todos in functions - keeps the loop as small as possible and just calls the different functions (whatever they do) - avoids globals

So based on the BwD in the IDE, something like:

const int ledPin =  LED_BUILTIN;                        // the number of the LED pin

void doBlink() {
  static unsigned long previousMillis = 0;              // will store last time LED was updated
  static bool ledState = false;
  const unsigned long interval = 1000UL;                // interval at which to blink (milliseconds)
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;                     // save the last time you blinked the LED
    if (ledState == LOW) {                              // if the LED is off turn it on and vice-versa:
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    digitalWrite(ledPin, ledState);                     // set the LED with the ledState of the variable
  }
}

void setup() {
  pinMode(ledPin, OUTPUT);                              // set the digital pin as output
}

void loop() {
  doBlink();
}

Yes, it might be necessary to explain, what's static but this a pattern he/she can reuse for every element he/she wants to add.

For me this is the first step which might lead to introduce OOP. But that might be the next question: Why are beginners afraid to use OOP in their sketch?

Hello everybody,

thank you very much for replying. Very interesting opinions and arguments. @gcjr

one of the first features described by Doug Comer in XINU (Xinu is not Unix) is the use of linked lists and a single timer interrupt to manage an unlimited # of timed events in the OS.

that sounds very interesting. can you post an example of how it is done. My first idea about it is setting up a very fast timer that counts up a lot of variables that are set back to zero in different intervals. So you could check for T100msec == 0 T10msec == 0 etc. but I have no idea if this come close to what you mentioned.

@noiasca: I like this idea put it inside the function itself Maybe with a suffix that gives a headsup that the execution of the main part of the funcion will happen only in intervals

it's unfortunate for newbies who plan on becoming professionals to learn bad programming practices so early that will influence them their entire career. There are tons of professionally written code that is difficult to maintain.

StefanL38: @gcjrthat sounds very interesting. can you post an example of how it is done.

if there are multiple timed events being waited on and knowing the time between events, code can just wait for the time to expire for the earliest event, perform that action and then start waiting the difference in time for the following event.

a linked list is used to keep track of events. Each element on the list includes the time from the previous element. A new item is inserted by tracing thru the list, accumulating the time between each element and finding the element that is after the desired time

knowing the accumulated time until the previous element, the accumulated time is subtracted from the time for the new element and it is inserted in the list. The new elements delta time is subtracted from the following element.

if two elements have the same time, the following element has a delta of zero.

since Arduino delays are in msec, the linked list concept can be used, keeping track of time in msec and the conventional approach of testing if millis() > tMsec can be used instead of a interrupt tic every msec.

of course this approach is a bit challenging, even for the experienced programmer. but it doesn't take much code when done properly and it efficiently supports an unlimited # of events.

I am more of a C guy than C++. One of the things I dislike about C++ is pass by reference which, at the point of the function call, does not make explicit the fact that you are doing.

So I wonder

UKHeliBob: ...

I am willing to bet that most beginners will forget to pass the expiry time by reference then be confused as to why they do not get the results they expect.

what exactly are beginners going to forget? There is no & operator involved in the call.

If you mean they will forget to write the function correctly to receive by reference, they aren't writing that function, probably cutting and pasting or retyping, so it will just end up in the pile of problems that are characterized by "=" meaning something different that "==".

But my first thought about this was that clever noobs would soon be proud to have wrapped any whizzy mechanisms as proposed here so that they could have a good old delay()-type thing that does what they want and "need", something that just hangs up for a while so we can see the LED go on and off.

I think I would leave the delay() for early use/misuse, counsel where and whenever we can about the real good idea of looking into BwoD as @PerryBebbington does.

[I feel bad for noobs that get told to check out BwoD and state machines when it is clear that those are, never mind how second nature they become, razor sharp ideas that look very intimidating on first exposure. Goodness knows how many tutorials have been written, they all start off meaning well but accelerate to about C/10 in a paragraph or two]

I don't think of anything as having to "unlearn". One thing that should, somehow, be made clear very soon is exactly what delay() does - eyes closed, fingers in the ears and a nose-clip for milliseconds, any number f which is, again as @PerryBebbington pout it recently an eternity.

I still use delay() on many occasions where it is what and all I need. My sketches tend to the literal which makes them easy to write, easy to get working and deployed and, naturally, totally not a good thing to try combining or extending as regards function.

a7

gcjr: it's unfortunate for newbies who plan on becoming professionals ....

I would be delighted with the idea that a newbie to Arduino programming would evolve him/herself into a professional career.

However I don't consider that the advice I give should be circumscribed to cater for that remote possibility. I suspect that very few of our newbies see an Arduino as the first step to a professional career. Most of them just want their hobby project to work.

Also, I believe any person planning for a professional career in any field should know enough to seek advice from sources that discuss the professional approach as well as the amateur approach. And if they don't know to do that then tough!

...R

StefanL38: expireTime = currentMillis; // set new expireTime

I like that you are thinking about it and that you value the opinions of others. I will suggest the above line be changed as follows to avoid any potential drift in time. This also frees you up to use millis() in-line with your condition, without a need for currentMillis, and without having back-to-back calls to millis()

expireTime += TimePeriod;

Also, consider the following class

class CycleTimer {
  public:
  CycleTimer(const unsigned long i) : CycleInterval(i) {}
  void synchronizeCycle() {
    cycleTimestamp = millis();
  }
  bool cycleComplete() {
    if (millis() - cycleTimestamp >= CycleInterval) {
      cycleTimestamp += CycleInterval;
      return true;
    }
    return false;
  }
  const unsigned long CycleInterval;
  unsigned long cycleTimestamp;
};

Here I have inserted it into the blink without delay example.

/*
  Blink without Delay

  Turns on and off a light emitting diode (LED) connected to a digital pin,
  without using the delay() function. This means that other code can run at the
  same time without being interrupted by the LED code.

  The circuit:
  - Use the onboard LED.
  - Note: Most Arduinos have an on-board LED you can control. On the UNO, MEGA
    and ZERO it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN
    is set to the correct LED pin independent of which board is used.
    If you want to know what pin the on-board LED is connected to on your
    Arduino model, check the Technical Specs of your board at:
    https://www.arduino.cc/en/Main/Products

  created 2005
  by David A. Mellis
  modified 8 Feb 2010
  by Paul Stoffregen
  modified 11 Nov 2013
  by Scott Fitzgerald
  modified 9 Jan 2017
  by Arturo Guadalupi

  This example code is in the public domain.

  http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
*/

// constants won't change. Used here to set a pin number:
const int ledPin =  LED_BUILTIN;// the number of the LED pin

// Variables will change:
int ledState = LOW;             // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
//unsigned long previousMillis = 0;        //now built into the class

// constants won't change:
const long interval = 1000;           // interval at which to blink (milliseconds)

class CycleTimer {
  public:
  CycleTimer(unsigned long i) : CycleInterval(i) {}
  void synchronizeCycle() {
    cycleTimestamp = millis();
  }
  bool cycleComplete() {
    if (millis() - cycleTimestamp >= CycleInterval) {
      cycleTimestamp += CycleInterval;
      return true;
    }
    return false;
  }
  const unsigned long CycleInterval;
  unsigned long cycleTimestamp;
} bwod(interval);

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
  bwod.synchronizeCycle();
}

void loop() {
  // here is where you'd put code that needs to be running all the time.

  // check to see if it's time to blink the LED
  if (bwod.cycleComplete()) {

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

If you want to synchronize different timings, you need to record the value of millis() once at the beginning of loop() and use it for each timing. This system can't do that, as presented.

aarg: If you want to synchronize different timings, you need to record the value of millis() once at the beginning of loop() and use it for each timing. This system can't do that, as presented.

I am not sure what system you are referring to, but in my class and example, I do synchronize to millis() at the end of setup.

Robin2: Also, I believe any person planning for a professional career in any field should know enough to seek advice from sources that discuss the professional approach as well as the amateur approach. And if they don't know to do that then tough!

i certainly wasn't aware of any EE's or CSE's when I started playing with electronic circuits in junior high and high school. And I didn't think I would have a career as an embedded firmware developer when I entered college.

but i believe it's a good idea to expose newbies and even some professionals to well written code even if they don't fully understand it. I think it would help them to know they can do better.

All I know is that looking at ugly, hacky code makes my brain bleed. This forum is good for several hemorrhages a day.

Perehama: I will suggest the above line be changed as follows to avoid any potential drift in time. This also frees you up to use millis() in-line with your condition, without a need for currentMillis, and without having back-to-back calls to millis()

expireTime += TimePeriod;

That does prevent drift in time but for many many cases the small error is irrelevant.

On the other hand I got badly bitten by that with one of my projects in which the first usage of that line happened quite some time after the Arduino started and the increment period was very short. For example, if the increment is 100 millisecs and the code is not used for 10 seconds after the Arduino starts then there will be 100 almost instantaneous repeats before expireTime catches up with millis(). It took me almost a day to spot the error, because, of course, it got mixed up with other stuff.

Consequently my current approach is to use expireTime = currentMillis unless I especially need the greater precision.

...R

gcjr: but i believe it's a good idea to expose newbies and even some professionals to well written code even if they don't fully understand it.

I wonder if I would call it well-written if it is not easily understood?

There seem to me two requirements for code {A} it should do the job properly and {B} it should be easily understood so it is easy to maintain.

A more philosophical point is whether it is helpful to present a student with techniques that are not immediately necessary but which appear complex to the student. Add to that the fact that this is not a formal educational curriculum. My approach is to limit the introduction of new ideas to those that are essential to sort out the problem. I may also mention that there are other features that the student could usefully study. For example learning about arrays may be necessary to solve a problem, but learning about structs may not, even though they may be the approach of a more experienced programmer.

The other side of this, of course, is for the experienced programmer to realise that what he finds as easy as breathing may be very hard work for a newbie.

...R

Robin2: That does prevent drift in time but for many many cases the small error is irrelevant.

On the other hand I got badly bitten by that with one of my projects in which the first usage of that line happened quite some time after the Arduino started and the increment period was very short. For example, if the increment is 100 millisecs and the code is not used for 10 seconds after the Arduino starts then there will be 100 almost instantaneous repeats before expireTime catches up with millis(). It took me almost a day to spot the error, because, of course, it got mixed up with other stuff.

Consequently my current approach is to use expireTime = currentMillis unless I especially need the greater precision.

...R

I too have been bitten by this. I think it only takes one bite to never forget. I agree that this is only if the precision is needed.