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 enabled task (i.e. not in idle mode). 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 example, but here’s the essence of what this code does:
- create a new task called
timetask
- 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
input using 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?