Reputation: 229
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
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