gabriel mellace
gabriel mellace

Reputation: 229

Arduino millis function taking longer than expected

I'm trying to make my Arduino sketch sleep 1 seconds on every execution of the loop function.

The methods involved on the loop may vary their execution time, and that's why I implemented millis.

I'm doing the following:

unsigned long ejecucionExcedida = 0;

int calcularExceso(int tiempo) {
    if (tiempo>1000) {
        ejecucionExcedida = ejecucionExcedida + (tiempo-1000);
        // TO DO agregar alarma si el exceso se incrementa mucho
        if(ejecucionExcedida > 20000) {
            alertas(9);
        }
        // Listo las alertas :D
        return 1000;
    }
    else {
        if(ejecucionExcedida == 0) {
            return tiempo;
        }
        else {
            if (ejecucionExcedida + tiempo < 1000) {
                ejecucionExcedida = 0;
                return ejecucionExcedida + tiempo;
            }
            else {
                int exceso = ejecucionExcedida + tiempo - 1000;
                ejecucionExcedida = exceso;
                return 1000;
            }
        }
    }
}

void loop() {
    unsigned long comienzo = millis();

    // A couple of methods

    unsigned long final = millis();

    delay(calcularExceso(final-comienzo));
}

It is expected that the sketch will delay exactly one seconds on each execution, but I have timed it with a clock and it's taking longer than a second per execution.

Upvotes: 2

Views: 2501

Answers (2)

Udo Klein
Udo Klein

Reputation: 6902

The loop function in combination with your code is causing the issue.

void loop() {
// B 

    unsigned long comienzo = millis();

    // a couple of methods

    unsigned long final = millis();
// C
    delay(calcularExceso(final-comienzo));
// A
}

You do not account for the time used from A to B. You also do not account for time used between the measurements and the delay (C).

The biggest cause is A to B. If you look in arduino/hardware/arduino/cores/arduino you will find main.cpp. Once you look into this file it becomes clear why this takes to long.

#include <Arduino.h>

int main(void)
{
        init();

#if defined(USBCON)
        USB.attach();
#endif

        setup();

        for (;;) {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

It actually does more than just "nothing".

I suggest to switch to the following approach to compensate for this

void loop() {
    static unsigned long start = millis();

    // a couple of methods

    while (millis() - start < 1000) {
        // busy wating
    }
    // do NOT read again as this would cummulate the drift
    // instead add just one second to start
    start += 1000;
}

Unlike your code this declares the variable start as static. This implies that start will be initialized from millis() during the first pass of loop(). After each pass of loop() its value will be retained for the next pass of loop(). During subsequent passes it will not be initialized anymore. Thus in the first pass start might have any coincidental value, for example 42. It follows that the final while would wait till millis() reaches 1042 which totals in 1s runtime. Then start will be incremented by 1000. So in the second pass it will be 1042 and the final while will wait till millis() reaches 2042. In the third pass start will then be 2042 and the final while will wait till millis() reaches 3042 and so on. As you can see the end of the final while will be always spaced 1000ms apart. It follows that the start of loop() will be spaced 1000 ms (on average) except for some jitter that may be introduced by the processing of serialEventRun().

If you are still experiencing massive drift after this change, then there is something in your code that blocks interrupts for to long. As the Arduino's time is kept with interrupts you must not block them for to long. Unfortunately there are functions that may block interrupts as a side effect. You would have to remove pieces of your code to find out which parts are causing this issue. Usually it is a good idea to add some LED and switch its state once per pass.

Having said that in the end you will still experience some drift depending on your Arduino model. Older models have crystal clocks which drift some 10ppm (some seconds per day). Newer models often have crystal resonators (cheaper) that may drift several 1000ppm (some seconds per hour). I analyzed this in an article on Arduino crystal deviation

Upvotes: 5

Eric
Eric

Reputation: 19873

Your code takes time to execute but you do not account for that

Upvotes: 0

Related Questions