Sometimes, timers are easier Mar 2017
Multi-tasking is a great mechanism, but there is a drawback: each task needs its own stack. In the case of Forth, it’s even worse because each task needs both a return stack and a data stack.
In its current configuration, multi.fs is set to 64 elements on each stack, which translates to a whopping 512 bytes of RAM needed (plus about 20 bytes for the task descriptors) - per task!
On an embedded µC, with 8 .. 20 KB of RAM, this really isn’t very convenient.
Stacks are needed when you want to write each task as if it ruled the world, so to speak: run in loops, and leave state on the data and return stacks while going through the logic of the code.
But there is another ways to get a lot of independent work done: callbacks. And while forcing callbacks onto an application (as NodeJS does) is not a good idea, there are nevertheless many cases where just some callbacks can easily handle things.
The difference between callbacks and tasks comes down to not leaving state on the stack: in a callback world, something triggers your code, it does its thing, and then it returns. If it needs to do more, it can schedule additional callbacks, usually via some sort of timer mechanism. The key here is the word “return”: callbacks cannot leave stuff on either stack, they need to manage all state between invocations elsewhere, i.e. in variables and buffers.
module and it’s documentation,
both written and generously contributed by Thomas Lohmüller
(@tht on GitHub). With
timed, calling your code some
time in the future, either once or periodically, is a piece of cake.
Here is the multi-tasking demo from the previous article, now using a timer instead of a task:
include ../flib/any/timed.fs PA12 constant LED1 OMODE-PP LED1 io-mode! ; timed-init : blink ( -- ) LED1 iox! ; \ toggle LED1 ' blink 200 0 call-every \ set up a periodic callback
That’s all there is to it. By default, we have eight timer “slots”, and in the
above code, we’ve set up a periodic callback every 200 ms in slot zero
' blink” means “the address of
For a slightly larger example with 4 LEDs blinking at different rates, see ex/timers.fs.
But here’s the interesting bit of how this has been implemented:
timedpackage defines and starts a task (!) to run in the background
- so we can still stop all timers by entering
singletask, as with tasks
- timing will only be accurate if all other tasks play nice and call
In this demo there is not much difference evident, but when you have
a lot of activities and timeouts, the
timed module can help manage it
all. It can be configured to handle any number of timers (and it’s much more
lightweight than using task stacks: a timer uses only 16 bytes).
So what this design does, is simply to merge the two concepts: we’re still using
the cooperative multi-tasker to create the illusion of parallelism, but now
the simple one-shot and periodic callbacks are all contained within a single task,
avoiding the per-task memory and switching overhead. The
timed background task
merely keeps track of what to call back, and when, and offers three simple words
to manage these activities in whatever way you need:
call-aftersets up a one-shot call in a specified slot
call-everysets up a periodic call in a specified slot
call-nevercancels the callback associated with a specified slot
See the documentation page for examples and additional details.
With multi-tasking and timers, we now have some nice tools to deal with more complex tasks.