The millis() function is handy for timing things with the Particle Photon (and Electron, and Core). It executes very quickly and has a good resolution (milliseconds). From the manual:
Returns the number of milliseconds since the device began running the current program. This number will overflow (go back to zero), after approximately 49 days.
Note: The return value for millis is an unsigned long, errors may be generated if a programmer tries to do math with other datatypes such as ints.
Now in reality, most devices will reboot before 49 days. Some programs even force the device to reboot periodically, say once every few weeks; this could be used to clear memory fragmentation and other issues. But ideally, the device should be able to run forever without rebooting, so here are some handy tips for properly dealing with millis(). It’s easy once you know the trick!
Here’s a simple recipe that you see pretty often. It does not, however, handle rollover properly. When you get close to the limit, adding 1000 (in the example) wraps around to being a small number, so for a period of time the code in the if statement will get executed on every single loop. This would be bad if you were doing something like transmitting on a Particle Electron and were using up your data allowance!
unsigned long nextRun = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
// Don't do this!
if (millis() >= nextRun) {
nextRun = millis() + 1000;
Serial.printlnf("%lu", millis());
}
}
And this similar recipe, that doesn’t quite work either. It fails in the same way.
unsigned long lastRun = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
// Don't do this!
if (millis() >= (lastRun + 1000)) {
lastRun = millis();
Serial.printlnf("%lu", millis());
}
}
But this one, this one is good. It works exactly as you’d hope!
unsigned long lastTime = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
unsigned long now = millis();
if ((now - lastTime) >= 1000) {
lastTime = now;
Serial.printlnf("%lu", now);
}
}
If you’re new to this, and maybe even if you’re not, you’re probably wondering how that’s even possible. It looks more or less like the other ones. Why does this one work?
Here’s some code you could compile that shows how math works with unsigned long integers:
Serial.printlnf("0xffffffff=%lu", 0xfffffffflu); // 4294967295
Serial.printlnf("3000-1000=%lu", 3000 - 1000); // 2000
Serial.printlnf("4294967295 - 499=%lu", 4294967295lu - 499); // 4294966796
Serial.printlnf("500 - 4294966796=%lu", 500 - 4294966796lu); // 1000
The first one is the maximum unsigned long value, 0xffffffff (hexadecimal) that’s 4294967295 (decimal). Or 2^32 - 1 if you prefer.
The second one is just an obvious simple math calculation. So is the third one, though the numbers are bigger.
The last one, however, would normally result in a negative value if we were doing signed math (small - big = negative number).
500 - 4294966796=-4294966296 (signed math)
The thing is that when the C compiler stores a signed integer value it uses a method of representing a value called two’s complement. Every negative value has an unsigned equivalent, and, -4294966296 is stored in memory the same as 1000, so it works! Magic!
Also, as an added benefit, this recipe waits the wait interval after booting before running the code. This may be handy if you’re using the Electron and uploading data in the if statement. By waiting a period of time, if something goes horribly wrong and a bug causes your device to keep rebooting shortly after your code runs, you don’t have to worry about it uploading very frequently. It will always wait the minimum wait interval.
If you really want it to run immediately on boot, you can initialize lastTime this way:
unsigned long lastTime = 0 - 1000;