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.

Meet the timed 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 blink”).

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:

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:

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.

Weblog © Jean-Claude Wippler. Generated by Hugo.