I don't really understand how the millis work

Hi guys!
I have a project where (before the game starts) there is a light wave. I have 11 LEDs next to each other. I put them into an array so I can go through them and turn ON or OFF. I had delay between the HIGH or LOW states. In the code there are delay comments, there were the delays and now I want to replace the delays with the millis logical if statements. (I go through the LEDs and turn them on or off but had delay between, so same backwards with an other for loop) . I understand why this code doesn't work and tried a lot of things but none of those worked and I don't know the solution. Can anyone help me a little bit?

//makes the animation light wave before strating and checks the buttons
//set the led pins
int led1 = 22;
int led2 = 24;
int led3 = 28;
int led4 = 32;
int led5 = 36;
int led6 = 40;
int led7 = 44;
int led8 = 48;
int led9 = 52;
int led_correct = 12;
int led_incorrect = 13;
int Led_Array[] = {led1, led2, led3, led4, led5, led6, led7, led8, led9, led_correct, led_incorrect};
unsigned long previousTime = millis();
long bef_game_delay = 500;

void bef_sta_ani() {
  unsigned long currentTime = millis();
//go through all the LEDs from 1 to the last
  for (byte i = 0; i < 11; i++) {
      if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;
        digitalWrite(Led_Array[i], HIGH);
      }
      if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;
        //delay(50);
        digitalWrite(Led_Array[i+1], HIGH);
        digitalWrite(Led_Array[i], LOW);
      }
      //delay(50);
    }
//go through all the LEDs from the last to 1
    for (byte j = 11; j > 2 ; j--) {
      if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;
        //delay(50);
        digitalWrite(Led_Array[j], LOW);
        digitalWrite(Led_Array[j-2], HIGH);
      }
      if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;
        //delay(50);
        digitalWrite(Led_Array[j-2], LOW);
        digitalWrite(Led_Array[j-3], HIGH);
      }
      //delay(50);
    }
} 

There's only one previousTime for all the LEDs, so after the first time it triggers and updates, all the rest of the LEDs in the loop won't trigger.

Try looking at the tricks in

1 Like

In a typical deployment, that code pattern will run many times, possibly thousands, before the test becomes true and a timed action is taken, and the timer reset.

Your for loop races past testing every step, probably not true at all for any, no chance to test again.

You have to lose the for loop and make your own function that

  • sees if it is time to (just like the above)

and if it is

  • handles the next LED, including bump along the timer

You'll need a variable to play the roll of the for loop index, incrementing with each timed LED and resetting to zero when you done all eleven or N.

A hand made loop, in a function you call very very frequently from the loop() as it, um, loops, most of the time that function will be doing nothing, as it won't be time to yet.

You kinda have to see this from a few perspectives, but once you Aha! it you'll wonder what the fuss was all about.

Like so many things it will be hard until it is easy.

HTH

a7

2 Likes

The following sketch demonstares the concurrent blinking of two LEDs at different rates using millis() function.

#define LED11 4   //2 sec interval
#define LED12 5   //1 sec interval

unsigned long LED11Millis = millis();
byte LED11State = LOW;
unsigned long LED12Millis = millis();
byte LED12State = LOW;

void setup()
{
  pinMode(LED11, OUTPUT);
  pinMode(LED12, OUTPUT);
  digitalWrite(LED11, LED11State);
  digitalWrite(LED12, LED12State);
}

void loop()
{
  if (millis() - LED11Millis >= 2000)
  {
    LED11State = !LED11State;
    digitalWrite(LED11, LED11State);
    LED11Millis = millis();
  }

  if (millis() - LED12Millis >= 1000)
  {
    LED12State = !LED12State;
    digitalWrite(LED12, LED12State);
    LED12Millis = millis();
  }
}
1 Like

Hi @botond500 ,

not sure what you really want to achieve, but feel free to have a look at this on Wokwi
https://wokwi.com/projects/391000332028236801

/*
  Forum: https://forum.arduino.cc/t/i-dont-really-understand-how-the-millis-work/1229674
  Wokwi: https://wokwi.com/projects/391000332028236801
*/

byte leds[] = {2, 3, 4, 5, 6, 7};
const int noOfLeds = sizeof(leds) / sizeof(leds[0]);

const unsigned long ledDelay = 100;
unsigned long lastChange = 0;
int actLed = 0;
int direction = 1;

void setup() {
  for (int i = 0; i < noOfLeds; i++) {
    pinMode(leds[i], OUTPUT);
  }
}

void loop() {
  ledWave();
}

void ledWave() {
  if (millis() - lastChange > ledDelay) {
    lastChange = millis();
    resetLeds();
    digitalWrite(leds[actLed], HIGH);
    actLed += direction;
    if (actLed == noOfLeds - 1 || actLed == 0) {
      direction = -direction;
    }
  }
}

void resetLeds() {
  for (int i = 0; i < noOfLeds; i++) {
    digitalWrite(leds[i], LOW);
  }
}

The main issues that your sketch has are (as already partly mentioned above):

  for (byte i = 0; i < 11; i++) {
      // This loop is usually far too fast for anything with millis() inside
     // Better use millis() as the "outside" and put the code that shall 
    //  be performed once and a while to its "inside"
    if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;
       // The values of these variables do never change inside the for-loop!!
      //  and when they change the statements inside this condition will only be
      // performed ONCE for i == 0 as previousTime will be set to currentTime
     // so all other values of i do not have any chance to get here
      }
    }

Hope that helps to improve your understanding ...
Good luck!
ec2021

1 Like

i thought you wanted all the LEDs off at the end of each cycle

const byte PinLed [] = { 22, 24, 28, 32, 36, 40, 44, 48, 52 };
enum { Off = LOW, On = HIGH };

const int  Nled      = sizeof(PinLed);
      int  ledIdx    = -1;
      int  ledIdxDir = 1;

const unsigned long bef_game_delay = 200;   // msec
      unsigned long previousTime;
      unsigned long currentTime;
      unsigned long msec0;

bool  waveEn = true;

// -----------------------------------------------------------------------------
bool
bef_sta_ani()
{
    if ((currentTime - previousTime) > bef_game_delay) {
        previousTime = currentTime;

        if (0 <= ledIdx)
            digitalWrite (PinLed [ledIdx], Off);

        ledIdx += ledIdxDir;
        Serial.println (ledIdx);

        if (0 > ledIdx)  {
            ledIdxDir = -ledIdxDir;
            return false;
        }

        if (Nled-1 == ledIdx)
            ledIdxDir = -ledIdxDir;

        digitalWrite (PinLed [ledIdx], On);
    }
    return true;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    currentTime = millis ();

    if (currentTime - msec0 >= 3000) {
        msec0  = currentTime;
        waveEn = true;
    }

    if (waveEn)
        waveEn = bef_sta_ani ();
}

void
setup ()
{
    Serial.begin (9600);

    for (int n = 0; n < Nled; n++)  {
        pinMode      (PinLed [n], OUTPUT);
        digitalWrite (PinLed [n], Off);
    }
}

The basic basic blink_without_delay example-code makes understanding non-blocking timing
harder than it must be for three reasons:

  1. the blink without delay-example does NOT mention that there is a fundamental difference between delay() and non-blocking timing.
    Without explicitly explaining this difference newcomers are in danger to see a delay()-similar-Thing
    And trying to see a delay()-similar thing will confuse to the maximum because there is NO similarity!

  2. a part of the variable-names is badly chosen.

  3. the demo-code distributes the variables to multiple places

There is a really fundamental difference between blocking timing with delay()
and
non-blocking timing based on function millis().

based on means: it is NOT a simple replacement of delay() with millis()

blocking timing with delay() works linear top down

non-blocking timing based on millis() works circular repeating
This video explains it pretty good

best regards Stefan

2 Likes

I don't really understand how the millis work

Lets take it out of context and bring it to simple numbers. We have this magic counter that increments once every second (time base) unless we deliberately stop it. To determine elapsed time we take a reading of that counter(timer/mills) and save it for later, 55. We now forget it and do our thing. When the event ends we again read the magic counter which now contains a bigger number, 65.

To get the elapsed time we subtract what it was in the beginning 55 from what we have now 65, the result is the time (counter ticks) that has passed since we started. For example we start and the counter is 55. When we finish it reads 65. Then 65 - 55 = 10 or ten seconds which is the the difference times the time base which was 1 second.

With this we can time events or whatever else we want to. In the Arduino world they use an unsigned 32 bit number, the largest standard number The Arduino would work with. They use an unsigned integer because once you have the most significant bit high it becomes a negative number if it is signed. if unsigned it just is another bit.

That bit is how computers determine a negative or positive number. You only get 1/2 the count positive but you also get 1/2 the count negative so it evens out and no sign to work with. We tell it to ignore the sign bit when specifying an unsigned integer, default is signed
.

1 Like

// keep time values unsigned!
// unsigned long millis is good to 49.71-some days intervals.
// if you time with unsigned long millis the interval is 65.535 seconds.

Huh?

1 Like

??

unsigned int maybe?

70 minutes for unsigned long micros(): https://www.arduino.cc/reference/en/language/functions/time/micros/ and for 16ms for delayMicroseconds(): https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/

1 Like

Perhaps a graphic will hlep.

You pick up how integer (not floating point or text) variables work.

Variable types long and unsigned long are 32-bits, 4 bytes long.
As type unsigned long it can count 0 to 4294967295.

The time formula End - Start = Elapsed depends on how unsigned math works. On a round 12 hour clock say we save Start as 9 and now it is 5. If I turn the hour hand 9 hours leftward, the subtract direction from 5 to 8, the elapsed time. The same code works across rollovers always as long as elapsed time fits in your time variables.

4294967295 millis... 4294967.295 seconds, 49.7102696181 days. You can sit a data collector out with batteries in an Altoid tin and get readings a month apart if that's your desire. It would need a crystal, not a drifty resonator (the Uno does) or an RTC but there used to be neat-logger projects here before.

A 16 bit type unsigned int can count from 0 to 65535.
As 65535 ms, the maximum wait-for time is 65.535 seconds. See?
16 bit variables on AVR chip use half the RAM as 32 bit variables..
and they subtract and compare twice as fast sooooo
If you don't need to wait > 1 minute, unsigned int timers are better.

For close timing where millis get < 20, the millis() function is +/- 1 ms by the way it counts. If it matters, use micros() instead of millis().
Unsigned long micros() rolls over every 71.some minutes, good for over an hour.
If you wanted to measure how long a pendulum blocks IR at the bottom of the swing to get the speed, time with micros!

1 Like

whoooops! yes, unsigned int (Arduino type word btw) ms!

1 Like

delayMicros() should only be used for very short delays but then

how close does it need to be?
A pin-test and count loops till exit or time-out takes ~ 1 usec per internal while loop.

1 Like

Presumably this is not the full code? There is a void for bef_sta_ani() but no void loop()
The usigned long currentTime = millis() should be in the top block of code not in the void()
And all of my timers are checking current millis() against the unsigned long variable.
i.e if((millis() - previousTime >= bef_game_delay)
and at the end of the if test there would be a statment resetting the previosTime to current millis().
i.e previousTime = millis();
I wouldn't be using currentTime at all.

As presneted I don't think the code would compile, and if it did it wouldn't do anything.
This should compille ok (but it might not do what you want it to?

int led1 = 22;
int led2 = 24;
int led3 = 28;
int led4 = 32;
int led5 = 36;
int led6 = 40;
int led7 = 44;
int led8 = 48;
int led9 = 52;
int Led_Array[] = {led1, led2, led3, led4, led5, led6, led7, led8, led9,};
unsigned Timer = millis();
long bef_game_delay = 500;

void loop()
{
//go through all the LEDs from 1 to the last and turn them all on
  for (byte i = 0; i < 8; i++) 
{
      if ((millis() - Timer) >= bef_game_delay) {
        digitalWrite(Led_Array[i], HIGH);
      }
//Then go back from the last one and turn them all off
Timer =millis();
  for (byte i = 0; i < 8; i--) 
      if ((millis() - Timer) >= bef_game_delay) {
        digitalWrite(Led_Array[i], LOW);
      }
    }


If the works you can go back in and add the 
        digitalWrite(Led_Array[j], LOW);
        digitalWrite(Led_Array[j-2], HIGH);  // should be i - 1
stuff which presumably is trying to achieve a floating LED that is off and all the rest saty on

I might be missing a } in the code - I don't know how to use the code formatter in this forum.

Copy all your formatted code.

Come here and use the / button in the message composition window toolbar and do like it sez:

type or paste code here

Post a complete sketch. You can't run that kind of timer pattern inside a loop over nine elements. That idiom expects to be executed very more frequently than the basic timing rate. delay() is what works in such a loop.

Of course I think post #3 goes into that in some detail.

Why? If this is just a one-off startup dance who cares?

a7

1. Once the uploading of a sketch in Arduino UNO is done, an unsigned 32-bit counter (called millsCounter, Fig-1) is automatically started from zero, which keeps accumulatig 1 ms time period in the background on interrupt basis. The millisCounter will rollover (back to zero) after counting 232 milliseconds.

millisCounter
Figure-1:

2. The content of the millisCounter (current elapsed time from the point of Arduino start) can be read on-the-fly (without disturbing the counting process of the millisCounter) at any time by executing the following codes; where, the variable presentMillis holds the current elapsed time/content of millisCounter.

unsigned long int presentMillis = millis();

3. In order to make a delay of 2000 ms without blocking the MCU (the MCU cannot do any other task), the millisCounter/millis() are helpfule to offer the following codes. Here, the MCU is checking if 2000 ms has elapsed on not at 1-ms interval.

unsigned long int presentMillis = millis();    //time *t1* is recorded
if(millis() - presentMillis  >= 2000)  //2000 ms has elapsed
{
     // do something like -- change the state of a LED1 (On to Off or Off to On)
}
else
{
     //2000 ms has not elapsed; do another thing like -- check the Fire Alarm
}

Sorry, maybe i was too passive-aggressive in pointing out a typo?
You referenced millis for both intervals.
Shouldn't the second example be micros?