This was originally a lesson in the Designing Embedded Software for Change course. Version 2 resulted in a refactoring of the module. The original lesson is preserved in the Field Atlas for those who want to reference it.
While a large amount of bare metal embedded software is still being developed, the vast majority of projects we’ve worked on have involved an operating system (typically an RTOS), such as FreeRTOS, ThreadX (now Azure RTOS), or Zephyr. Software will often directly use the OS APIs and types throughout the program, which results in tight coupling to the OS.
While these RTOSes provide similar capabilities, each one is different. Migrating from one RTOS is more difficult than a simple find-and-replace operation, since the functionality, configuration options, error conditions, parameters, and API style often differ between RTOSes.
Coupling to an OS still has a significant impact on your software system’s ability to respond to change:
- If a processor change occurs, you may find that the RTOS you are using does not support the new processor. Then you must switch to a new OS (changing all OS API/type references) or go through the effort of porting the OS to the new platform.
- You may also find that the vendor’s SDK is based on a different OS (e.g., Zephyr), in which case, you may be forced to switch OSs for practical reasons.
- When a module directly uses OS API calls, you can only reuse it with another OS after a porting effort.
- Code that depends on an RTOS is much more difficult to test independently and to test off-hardware, since the OS calls need to be mocked. Testing may be difficult to do, so teams often leave OS-dependent code untested (or only tested on the target).
Directly using OS API calls and types in your software results in tight coupling to the OS. Eliminating this source of coupling makes software resilient to changes in the underlying OS, improves its portability and reusability, and positions us to write automated tests off-target.
References
-
Processor dependencies make embedded software difficult to change
-
Prototyping and Design for Change: Lightweight Architectural Strategies
-
5 Tips for Developing an RTOS Application Software Architecture by Jacob Beningo
One problem that I often see in RTOS based software architectures is that developers select their RTOS and then build their entire software architecture around it. While at first glance this doesn’t seem like a bad idea, it can become quite a headache to maintain or port that code to other applications where a different RTOS may be used. Think about it, there is no RTOS interface standard that RTOS developers follow (even though Arm does have CMSIS-RTOSv2). Instead there are about 100 different RTOSes each with their own API. Switching RTOSes requires every RTOS API call to be found and changed which is probably time consuming.
A better way to design the architecture is to make the application agnostic to the RTOS. Most RTOSes provide similar functionality anyways, so the putting the RTOS behind an Operating System Abstraction Layer (OSAL) allows the specific RTOS selected to be deferred until much later, decreases application RTOS dependence and improves the architectures flexibility.
-
Building Software for Portability by Greg Blackham (1988)
Different operating systems provide different services to an application. This is a veritable Pandora’s box of problems for which there are no easy solutions.
Each operating system has a distinct set of possible error conditions that can be encountered by an application. Many of these extend across all environments. For example, every operating system returns a “file not found” error when an “open” system call fails because the file does not exist. Each environment may also have its own unique set of messages. On multiuser or networking systems, an “open” might also fail because the user does not have rights to access the file, or because the file is in use by another user. Error-handling code must be flexible to handle the various exceptions appropriately.
