Since I've had success with developing an idea to preproduction prototype stage using Arduinos, I'm giving back to the community by spamming several subfora with things I've worked out through my dev process that I know people have asked about. ("I know people asked about" because I also asked, and found posts here where others have asked before me.) I'll be posting various things in various places, so hopefully I'll get the locations right, and if not, if a mod could make moves as needed that'd be lovely.
For this post, I'll throw in yet another tutorial on using millis() and micros() for timing.
Let's start with the raw definitions. These two functions return the number of milliseconds or microseconds that have elapsed since the program started running. Since they simply return a number, they don't block code execution, and that means they're wonderfully useful for situations where you have a loop running and need code inside that loop to fire off on a regular interval without hanging the loop in the process like a blocking function like delay() will.
The way we use these for timing is twofold: first, we take a snapshot of the current millis() or micros() value just before we enter a loop. Second, inside the loop we check to see what the difference is from the current millis()/micros value is versus the snapshot. By subtracting first value from current we get a value of how many milliseconds or microseconds have elapsed, and if enough time has passed, we replace the snapshot with a current value and wait for the difference to reach our desired interval value again. Rinse and repeat as needed.
Let's look at a real-world example from running code. We start by declaring a variable for the snapshot - it must be an unsigned long, as that's the return value for both millis() and micros().
unsigned long snapshot_time = 0;
Just before the looping code within which time-delayed code will run, we'll set the snapshot variable:
snapshot_time = millis();
Inside the loop we'll perform the following:
if (millis() < snapshot_time)
snapshot_time = 4294967295 - snapshot_time;
if (millis() > snapshot_time + 150)
{
snapshot_time = millis();
// Do work, son!
}
The first two lines are there to deal with the fact that millis() and micros() will wrap around to zero after a while. Since they're unsigned longs, the maximum value is 2^32 - 1, or 4294967295, so if millis()/micros() is less than the last snapshot taken, the millis()/micros() value has wrapped to zero and we have to subtract the last snapshot time from that limit to bring it back into range. If this isn't done, the time-delayed code may never fire again once the millis() or micros() value wraps to zero!
Next, we have the comparison that determines how long to wait before executing the code inside the "if" condition. In the example we're waiting for millis() to exceed the previous millis() reading plus 150 milliseconds, which means that the delay we're getting as a result is at least 150 milliseconds. I say it's "at least" because the timing granularity on slower Arduinos might not be adequate to be precise, and a long code loop takes longer to execute than a short one does, so never use an equals here, but instead always check for greater-than.
Once the current time is at least 150ms later than the previous time, we snapshot the millis() (or micros()) value again and run our delayed code. Once it finishes up, the loop continues and the code in the "if" is ignored until at least 150ms has elapsed since the previous, and so on and so forth.
Fun fact: An unsigned long containing milliseconds from boot will run to max and loop back to zero in about 49.7 days. Windows 95 had a bug in it that would hang the OS once its millisecond counter rolled over, because some of the OS components didn't understand how to deal with the rollover!
Pretty straightforward and easy to use once you get the hang of it! Have more ideas on how to time events in a non-blocking manner? Post 'em below!