Some weeks ago, I wrote a blog about the way our definitions can get in the way of our creativity. Lately, we've been having a few conversations here at Microcontroller Central about the pros and cons of using an “operating system” in designs -- and I'd like to suggest that this is another example of letting our definitions get in the way of our designs.
We all tend to think of an “OS” as something that comes to us out of a box, like Windows or Linux or
Free RTOS. But that way of thinking carries a lot of baggage -- not least of which is the notion that an OS is going to burden our system with more resource- and time-overhead than we care to tolerate. The belief is that using an OS in a small system is wasteful and leads to sub-optimal system design.
But let me ask you to abandon that concept of an OS for a minute -- and think of it instead as the name we give to a software design paradigm. Instead of thinking “OS” in terms of a product, try thinking of it as the lowest layer of your software in a multi-layered software design approach, where that lowest layer is just a collection of routines in your code that provide services to your more application-oriented code.
Taking this view, I suspect that most of our microcontroller programs contain some elements of an “OS” of our own creation. Support routines for RS232 communications are one example, as are procedures for switching context from one application task to another. The "OS" is there -- it's just spread all over our code in a more or less hit-and-miss way. Spreading it out like this is OK, but it misses what may be an opportunity: to add the structure and discipline that goes with designing our software specifically to adopt a multi-layered software architecture.
By ignoring the possibility of creating layered code, we may indeed save a few resources, but we also pay a price. Lacking a formal structure, our programs may take longer to build, be harder to debug, be less reliable in the field, and will certainly be harder to maintain, if it ever comes to that. An unstructured approach may also make it more difficult to build libraries of useful service routines that can be adapted and reused over multiple projects. And in many ways, isn't a library like that what a packaged OS really is?
We sometimes need to think about an “OS” not as a packaged product, but as a collection of routines that provide services to our higher-level application tasks in an explicitly layered software architecture. And it's OK to talk about those low-level routines as “our OS,” even if we've created them ourselves!
There's good reason why our software systems have evolved to look more like the structure on the left than that on the right in the figure below.
MCU Code Structures
Structured code, with services (i.e., an OS) at the "bottom" (i.e., closest to the hardware), may have a code penalty, but organic code like that on the right can become a nightmare to maintain.
It's genuinely useful to design our software in a layered way, with OS routines “at the bottom,” rather than designing it (or just letting it grow organically) as a single, monolithic program. The more structured approach may cost us a few bytes of code, a few clock cycles in a task switch, and a few more bytes in our stack demand. But in the end, those things have to be traded off against a better, easier-to-understand design.