Beginner questions about delay() and millis()

Hi everybody,
I own an Arduino Uno since one week. I am writing some sketches, but I found (what I think is) a couple of strange behaviors.

  1. at the end of the loop function() I put these lines:
while (millis() - lastMillis < 60*1000) delay(1000);
lastMillis = millis();

lastMillis is initialized in the init() function (of course with the value of millis()), the code should wait for 60 seconds, but instead waits only for some seconds and then the loop restarts, I changed the code with:

while ((millis() - lastMillis) < 60000) delay(1000);
lastMillis = millis();

this… unexpectedly… works… isn’t it 60*1000 the same of 60000 ???

  1. before using millis() I used delay(60*1000); again, the code waits for a few seconds, instead of 1 minute

am I missing something?

isn't it 60*1000 the same of 60000 ???

No, for the Arduino it isn't.

For the Arduino, the native "int" is sixteen bit, allowing the representation of numbers between -32768 and +32767. 60 is representable, and 1000 is representable, but 60 * 1000 is not, because it is greater than 32767.

madmage:
this… unexpectedly… works… isn’t it 60*1000 the same of 60000 ???

And the solution is to qualify the literal, like this:

while (millis() - lastMillis < 60000UL) delay(1000);

The UL bit says “unsigned long” which will then hold a bigger number.

I have to say I find the combination of testing millis () and then using delay () rather confusing. Why do that? Why not just work out how many milliseconds you want to delay and delay for that long? Or, just loop without using delay, eg.

while (millis() - lastMillis < 60000UL)  
  { }  // do nothing except wait

Ok I understood. I am too familiar with 32 and 64bit computers, so 60000 does not ring any alarm while I code. This means that when I write 60000, the compiler knows that it cannot put it in an int and thus choose a long, right? On the other hand, when I write 60 * 1000, they are both ints and their result is supposed to fit in an int (which of course doesn't), right? The solution could be to write 60UL * 1000UL, or just 60UL * 1000, right?

The reason why I delay in the loop is, again, because I coded a lot with PC... a non-yielding loop like "while (something()) ;" is EVIL in the multi-process/multi-threaded PC world. Of course, there is no other process and no other thread in Arduino :-)

But you can write a function and attach it to a timer's overflow interrupt. So it can work as if there were 2 threads.

If you work a little bit more, you can fix your function to do something at one time and something else at another by using a counter. So it would work as if there were multiple threads.

madmage: This means that when I write 60000, the compiler knows that it cannot put it in an int and thus choose a long, right? On the other hand, when I write 60 * 1000, they are both ints and their result is supposed to fit in an int (which of course doesn't), right?

This appears to be the behavior. You might like to see how this code works as a quick exercise:

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  
  Serial.println("---------");
  
  Serial.print("60000: ");
  Serial.println(60000);
  
  Serial.print("60 * 1000: ");
  Serial.println(60*1000);

  Serial.print("60UL * 1000UL: ");
  Serial.println(60UL*1000UL);

  Serial.print("60000UL: ");
  Serial.println(60000UL);
  }

madmage: The reason why I delay in the loop is, again, because I coded a lot with PC... a non-yielding loop like "while (something()) ;" is EVIL in the multi-process/multi-threaded PC world.

Except that by putting a "delay()" into the loop, it (effectively) becomes non-yielding. Doing nothing inside the loop is better than blocking on a delay() call.

madmage: Ok I understood. I am too familiar with 32 and 64bit computers, so 60000 does not ring any alarm while I code. This means that when I write 60000, the compiler knows that it cannot put it in an int and thus choose a long, right? On the other hand, when I write 60 * 1000, they are both ints and their result is supposed to fit in an int (which of course doesn't), right?

That's pretty much it. Although there are (or seem to be) times when if you used 60000 it would be treated as an int and truncated (let's see, 60000 = 0xEA60 which looks like it would fit into two bytes as -5536). However if you tell the compiler that you are using a long, it will treat the literals as the right type. Consider:

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

  int a = 60000;
  long b = 60000;
  long c = 60 * 1000;
  long d = (long) 60 * 1000;
  long e = 60L * 1000;

  Serial.println (a, DEC);
  Serial.println (b, DEC);
  Serial.println (c, DEC);
  Serial.println (d, DEC);
  Serial.println (e, DEC);
}

void loop() {}

Output:

-5536    //  int a = 60000;
60000    // long b = 60000;
-5536    // long c = 60 * 1000;
60000    // long d = (long) 60 * 1000;
60000    // long e = 60L * 1000;

So it would appear that once the compiler has switched to "long mode" then it "works". The most interesting case (60 * 1000) comes out at -5536 probably because at the time of evaluating 60 * 1000 it thinks it is dealing with ints (ie. 2-byte ints) so it generates the code to multiply ints, not longs.

My last two examples (the typecast and the explicit type) effectively tell the compiler "we are using longs here".