Simply Embedded
It's all about changing the paradigm. Thinking simply that is. There's a trend towards complexity nowadays with the embodiment of desktop operating systems like Linux onto cheap $4 micros. While this brings a lot to the table, sometimes it's too much. And for things produced in volumes, too much usually translates to too expensive. And then when it comes to battery life, less is more. Since maintenance is always a large part of your software life cycle, complexity will multiply that cost.
If we go way back to where it all started with micros taking over from discrete circuits, there was an approach called bare metal which consisted of only your code. Back then there was a small smattering of RTOS’s that, much like their desktop brethren, were there to allow us to program much like we do on desktop computers - task centric and in full ownership of the computer with some software tools to help deal with all the other tasks trying to do the same thing. In a nutshell, the paradigm is to hang on to the computing resources until we are ready to use them or simply embodied in code as a forever loop:
while(1) doOneTask();
What are the costs of thinking in this manner? On the surface it seems natural as this is how we conduct our lives. We wait at the red light until it goes green and then we drive on. We wait in the elevator until the door opens. We sit on a bus until it gets to our destination. We wait on our pizza in the oven until it is cooked. We queue up at the coffee shop until a barista is ready to take our order. What do all these things have in common? Well they are easy to do but we must be there waiting tying up whatever resource we need.
What if we could operate differently. In fact, people are starting to operate differently. Most people have smart phones that gives them more power. Albeit sometimes it's just another cat picture or a friend's wild 7 second blip, but we are also using them to find better driving routes; preorder so that we can skip the ordering queue and pick up our drinks when we get to the coffee shop; get things done while we wait on other things to be ready. This is working smarter and changing the paradigm.
Let's jump back into the microcosm of the embedded world. With the forever
loop paradigm presented earlier, only one thing will be done by the micro. Sometimes this is just fine if you only need to do one thing. But most often we need to do other things too and this is where we either shoehorn everything into a big superloop:
while(1) doManyTask();
Again this might work for some things but there is no management of time spent in each of the many tasks. Nor is there any scheduling. Everything you need to do is always done. We haven't talked about efficiency yet but with small battery driven devices, this is a big issue and we rather be doing nothing instead of everything.
An alternative to the superloop is that we jump to the next level of complexity and get some magic in the guise of an RTOS to make it appear that we are able to run several forever loops simultaneously along with tools to juggle the multiple task spaces; coordinate data movement and manage resource sharing between them. There's lots of magic out there to choose from. Some of it is even free.
I've always questioned conventional thinking - looking to improve on the thoughts and ways of others to help move things forward. So my insight here is - what if we didn't program in loops? What if we just programmed simply to do things without tying up a processor's resources. What if we could do what we needed to - just when we needed to do it.
How could we do this? The simple answer is we abandon forever loops. These are unnecessary props that get in the way of what we need to do, take us down the wrong path and instead make it necessary for us to add even more parts such as a scheduler, semaphores, mutexes and multiple stacks to get things done. This is not necessary.
"The best thing we can be doing with our time is nothing. If we are doing nothing, then we have the time to do anything."
A good analogy is to look at hardware design. Hardware partitions things into multiple dedicated parallel circuits that run as needed. Hardware solutions handle complexity by using state machines. In hardware, nothing happens until an event occurs. This causes some action to be taken which is predicated on the context of previous events and actions.
Let us start with this and define a machine as the simplest thing which gets work done. The difference with software is that the machines are defined as code sitting in memory and only one can be run at a time. This has a tremendous advantage over hardware in terms of its circuit simplicity but it cannot run all the programs at the same time so we must introduce the notion of granularity. We ask the question of when do we run our machine and how do we share the resource of a microprocessor with multiple machines. Do we need to run all machines all the time or are there circumstances where only some of them need to be run. Clearly we need some flexibility of when to run these machines. The notion of a machine is the first step.
Lets go back to the coffee shop where we have a queue of people and instead have a queue of machines. The machine at the front of the queue is serviced and then it is out of the queue. The next machine in the queue will then get serviced. When the queue is empty, we do nothing.
Let us consider an alternate situation for a minute. Consider the scenario where the barista spends 5 seconds with each person in the queue before going to the next one. And all the people that ever wanted coffee were in that queue. Think of the complexity involved in keeping all those orders separate. Lets not do that.
Instead the barista services each machine in the queue one at a time, completing each transaction before moving to the next one. The transaction might either be completed or there will be a follow-on transaction at a later point when an item becomes ready.
Putting this in perspective, we have now moved from the task centric model where its all about the task, to the service model where machines are serviced and then let go. Lets code this:
while(machines()) runMachine();
This looks a lot like our previous loop - but we've gained something here. We have turned our loop into a flow. Each item that we have programmed as a machine can now be queued as needed. This goes even further - more importantly each machine will finish before another is run. Why is this important? It means that since a machine doesn't get switched out in the middle of what it is doing, we don't need to create operating spaces and then worry about all the possible states the machine might be in when it gets swapped out. Simply, it runs, it finishes and it is done. The machines are simply embedded.
In accompanying articles we will explore the power of this paradigm shift, introduce more complex machines and connect machines to generated code to create powerful platforms for running embedded software. As well we will examine how interrupts can be nicely integrated into this model.
Heresy, next thing you'll say is that the world is parallel, ;).
Nicely written! I like the coffee shop references ;) A quote I heard a while ago reminds me of parts of this post: "It is common to think the lights are red until they are green. You are waiting for the opportunity. But it is better to think the lights are green until they are red! Go until you are stopped!" Also, multitasking. We try to do it to much with smartphones, real people, and actions. We overload our brains and crash. Why not think of machines as the same. X while(1) doManyTask(); X
Glad to see you're still out there thinking, Rob.