I'm using a task manager adapted from the one found in this post by Alan
http://bleaklow.com/2010/07/20/a_very_simple_arduino_task_manager.html.
After looking into the issue with millis()/micros() overflow, many posts seem to disregard this as being a problem.
Example:
http://forum.arduino.cc/index.php?topic=122413.15
The gist of the task manager is a "now" variable being continuously updated with millis() and passed on to a "Tasks" function call that checks if the task is scheduled to run. The check is as follows:
...
uint32_t runTime;
...
bool TimedTask::canRun(uint32_t now) {
return now >= runTime;
}
The overflow issue is never really addressed here.
Consider a task scheduled to run, near the end of the "unsigned long" reach.
now = 2^32-10; runTime = 2^32-12; // now >= runTime == true
now = 2^32-10; runTime = 2^32-2; // now >= runTime == false
now = 2^32-10; runTime = 1; // now >= runTime == true -- INCORRECT -- this should NOT trigger the task to run, and may trigger the task to run very very many times more than intended
now = 10; runTime = 2^32-12; // now >= runTime == false -- INCORRECT -- if runTime is not incremented externally, the task will not run for a very very long time
There are thus two problems with this.
- A task may run when it's not supposed to, potentially near-infinite number of times.
- A task which should run regularly may not run for close to 49 days.
The general approach, "subtract old time from new time and compare with interval", does not translate directly to this situation. However we can adapt it to suit our needs.
now = 2^32-10; runTime = 2^32-12; // now-runTime == 2 -- direct comparison >= 0 WILL work here
now = 2^32-10; runTime = 2^32-2; // now-runTime == 2^32-8 -- direct comparison >= 0 will NOT work here
now = 2^32-10; runTime = 1; // now-runTime == 2^32-11 -- direct comparison >= 0 will NOT work here
now = 10; runTime = 2^32-12; // now-runTime == 22 -- direct comparison >= 0 WILL work here
What we see from this comparison is that the two scenarios which should execute (first and last), are very close to the positive side of the variable space, and the two scenarios which should NOT execute (second and third), are just "below" the maximum.
The simplest way then is to cast the result of now-runTime to a signed long, for which the very high values will end up on the negative end of the scale. In essence by casting the result we say that half the spectrum is in the past, and half is in the future.
What we may also want is to establish a limit for when a task is considered "in the past" and should be executed, and a task which is "in the future". In my opinion, the future should hold the majority of the scheduled time, seeing as if a task was supposed to have run for even one day (~2% of the total millis() space), but was unable to due to other tasks taking up all processing power, then we have a greater issue with starved tasks.
Simplest solution - divide the total time in half by casting the result as a signed long.
now = 2^32-10; runTime = 2^32-12; // (long)(now-runTime) == 2 -- direct comparison >= 0 WILL work here
now = 2^32-10; runTime = 2^32-2; // (long)(now-runTime) == -8 -- direct comparison >= 0 WILL work here
now = 2^32-10; runTime = 1; // (long)(now-runTime) == -11 -- direct comparison >= 0 WILL work here
now = 10; runTime = 2^32-12; // (long)(now-runTime) == 22 -- direct comparison >= 0 WILL work here
If we want to establish this "future" limit we do not need to cast the result, we only need to define what we consider "the future".
"Most time in the future" solution.
const uint32_t future = ((uint32_t)-1)/100*20; // 858993440
...
bool ... canRun(uint32_t now) {
return now-runTime+future >= future;
}
This will make sure that, regardless of whether you are using millis() or micros(), 80% of your schedulable time will be in the future, and only 20% is in the past.