Stay busy, but also sleep a lot Mar 2017
One of the examples in the multi.fs code contains this little gem:
: sleep ( -- ) [ $BF30 h, ] inline ; \ WFI Opcode, enters sleep mode task: lowpower-task : lowpower& ( -- ) lowpower-task activate begin eint? if \ Only enter sleep mode if interrupts have been enabled dint up-alone? if ( ." Sleep " ) sleep then eint then pause again ;
It’s a task which gets called periodically, like every task in the cooperative multi-tasker, and then checks if it’s the only enabled task, i.e. not set to idle. If so, it puts the ARM µC in “sleep” mode, a simple trick which halts the processor until the next interrupt. In other words, when there is nothing to do: pause until the next interrupt instead of frantically twiddling thumbs!
Sleep mode cuts power consumption roughly in half, so it’s not a huge win, but the interesting aspect is that it does not affect normal operation of the code: applications can take advantage of this without change. All they need to do is put tasks into idle mode when… idling!
For ultra low-power nodes, we’ll need to take this a lot further. We need to put the µC into “stop” (or even “standby”) mode, where all the main clocks are stopped. This has far more impact on the application: when clocks are stopped, the application loses all sense of time - not so great when you want to do a few things periodically.
Can we have an architecture whereby the application continues to think in terms of timers, periodic actions, and callbacks, yet also make the µC go into these really low-power modes whenever there is nothing to do?
This is where the timer task presented in the previous article could come into play. What if we were to extend it a bit as follows:
- for short timers (one-shot and periodic), nothing changes
- when the next timer is known to fire more than 10 ms into the future, we enter stop mode for that amount of time instead of just idling
The key benefit of managing all timers in a single task, is that it becomes the sole place in the application which needs to track the passage of time. That means it could figure out exactly when the next callback needs to be triggered.
We’ll probably need to take care of some other details to make this work well:
- all tasks should be set to idle when there is no work for them
- hardware µC interrupts will need to wake up the task that will handle them
Note that when the µC is in stop mode, some of its interrupt capabilities are in fact disabled. The UART for example, is likely to be comatose, so interrupts won’t even be generated. We could work around this by setting up a falling edge interrupt on the RX pin, to wake up on incoming data - even if that means losing the first character(s), as the µC springs back to life.
Here is an example, again from the
multi.fs package, which illustrates how the
command prompt task can easily be put to sleep:
0 variable seconds task: timetask : time& ( -- ) timetask background begin key? if boot-task wake then 1 seconds +! seconds @ . cr stop again ; time& lowpower& tasks : tick ( -- ) timetask wake ; ' tick irq-systick ! 16000000 $E000E014 ! \ How many ticks between interrupts ? 7 $E000E010 ! \ Enable the systick interrupt. stop \ Idle the boot task
It’s a fairly complex bit of code, but here’s the essence of what it does:
- define a new task called
- the code for this task increments
seconds, then stops itself, forever
- the SysTick handler is set to run once a second, and wake up the task
Two lines in this code are very special in this context:
key? if boot-task wake then
stop \ Idle the boot task
Looking at that last one: entering
stop on the command line puts the
command-processor in Mecrisp Forth to sleep. By doing this, we’ve disabled the
prompt and we’ve lost control!
However, the other task still running is
timetask, and it’s being woken up
every second by the
SysTick interruot handler. Since
timetask checks for new
key?, it can bring the command prompt back when fresh input needs
attention. Now we’re back in business!
This is by no means the only way to deal with the command prompt in low-power scenarios, but it illustrates that there are ways to have our cake and eat it too: an application which can enter low-power modes, yet retain the ability to listen to the serial port and jump back into interactve mode when needed.
So far, these considerations are preliminary and not exhaustively tested. But hey, you gotta start somewhere when trying to come up with a foundation for ultra low-power nodes, eh?