First of all: thanks to everyone who commented on the recent posts, both online and by direct email. Fantastic feedback, lots of ideas, and several valuable corrections.
In yesterday’s post, I agonized about how hard it is to track time in some sort of reasonably continuous way when most of it is spent in comatose state. Fortunately, a Room Node won’t need that awareness very much. I just wanted to show how things get complex in terms of watchdogs running-but-not-yet-expired, and multiple interrupt sources.
For the next version of the rooms.pde sketch, my plan of attack is to simply ignore the issue. The watchdog will be used for guaranteed wake-up, while PIR motion detection and radio reception interrupts will simply cause the millis() clock to lose time, occasionally.
For ACKs, I’m going to start simple and wait for up to 2 milliseconds in idle mode, before resuming lower-power options again. One interesting suggestion was to slow down the clock while doing so (through the system clock pre-scaler), but that has to be done with care because a slower-running ATmega will also respond more slowly to RF12 driver interrupts – and there is not that much slack there.
Here’s the updated Scheduler class, now included in the Ports library:
Only a minor extension on the API side: by using pollWaiting() instead of poll(), you can tell the system to enter low-power mode until the next scheduled task. If another interrupt pre-empts this wait cycle, then it’ll break out of power-down mode and go through the loop and re-enter the low-power state the next time around.
The other change I’m currently testing is that a few Scheduler functions can be called from interrupt code, so this provides a way for interrupt code to safely schedule, resume, or cancel a task “via the main thread”.
This is the tiny change needed to make the schedule.pde demo energy efficient:
However, because it now uses the watchdog, you also need to add the following boilerplate to the sketch:
ISR(WDT_vect) { Sleepy::watchdogEvent(); }
The demo now draws 25 µA when I remove the LEDs.
So here’s the deal: if you can manage to write all your code is such a way that it never calls delay() – or delayMicroseconds() with large values – and instead uses this Scheduler-with-tasks approach, then you’ll get a fairly low power usage almost for free… i.e. without having to do anything else! (just don’t forget to turn the radio on and off as needed)
The code has been checked into subversion, and is also available as ZIP archive – see the Ports info.
Update – more changes checked in to better handle watchdog vs other interrupts.