Embedded Software Architecture Design - Embedded Software Design - A Practical Approach To Architecture, Processes, and Coding Techniques
Embedded Software Architecture Design - Embedded Software Design - A Practical Approach To Architecture, Processes, and Coding Techniques
The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. Beningo, Embedded Software Design
https://doi.org/10.1007/978-1-4842-8279-3_2
Jacob Beningo1
(1) Linden, MI, USA
If I could describe my first few years as an embedded software engi-
neer, I would use the words “code first, architect second.” I could sim-
plify this statement further to “There is only code!”. Creating a software
architecture was honestly a foreign concept, and it was an industry-
wide issue, not just for me. When a task needed to be done, we would
jump into our IDE and start coding. For simple projects, this wasn’t an
issue. Everything that needed to be done could be kept straight in our
minds; however, as projects became more complex, the rework re-
quired to add features ballooned out of control.
I recall the first time I went against the wisdom of the time and decided to draw
out and design what I was doing before coding it up. It was like I had been coding in
the dark my whole life, and suddenly the clouds cleared, the sun shone, and I
understood! Architect first and code second became my mantra. Developing a
software architecture allowed me to think through what I would code, adjust how it
worked, and then code it once. Writing code went from constantly rewriting,
modifying, and debugging to designing and implementing. The time required
decreased by literally 10x! I had discovered what Ralf Speth had previously stated:
If you think good design is expensive, you should look at the cost of
bad design.
A map tells the reader the lay of the land. Landmarks, buildings, and
roads provide a general organization of an area and the relationship
between those objects. A software architecture does the same thing for
software; instead of physical objects and spatial relationships, we are
dealing with components, objects, and the relationships between them.
The software architecture tells a developer what they are building; it
doesn’t tell them how to make it!
Figure 2-1 Embedded software architectures are broken into two architectures: a hardware-indepen-
tom-up
First, designers will focus more on their product features and differ-
entiators, the secret sauce that will help sell their products. The more
focused I am on my product features, the more I can think through, pri-
oritize, and ensure that my design is going in the right direction. If I
save it for the end, I will likely “mess up” on my features and provide a
poor experience to the product users.
Second, modern development tools, which we will discuss later in the book, can
be used to model and simulate the application’s behavior long before the hardware
can. This allows us to put the application code to the test early. The earlier we can
iterate on our design and adjust before coding on the target, the less expensive
development will be. The change cost grows exponentially as a project progresses
through the various development stages, as seen in Figure 2-3.4 The sooner we can
catch issues and mistakes, the better it will be from a cost and time-to-market
standpoint.
Figure 2-3 The change cost grows exponentially as a project progresses through the various develop-
ment stages
Finally, by designing the top level first, simulating it, and getting
feedback, if things don’t go well, we can fail quickly. In business, noth-
ing is worse than investing in something that drags on, consumes the
budget, and then fails. Companies want projects that are going to fail to
fail quickly.5 If a product isn’t going to work, the sooner we know, the
better, and the faster we can move on to the next idea. Starting at the
top can help us validate our application with the target audience
sooner.
No matter what the end goals are for the software, there are two
characteristics that every architect needs to carefully manage to reach
their end goals: coupling and cohesion.
Architectural Coupling
There are several different types and causes for coupling to occur in
a software system. First, common coupling occurs when multiple mod-
ules have access to the same global variable(s). In this instance, code
can’t be easily ported to another system without bringing the global
variables along. In addition, the global variables become dangerous be-
cause they can be accessed by any module in the system. Easy access
encourages “quick and dirty” access to the variables from other mod-
ules which then increases the coupling even further. The modules have
a dependency on those globally shared variables.
Coupling is most easily seen when you try to port a feature from one
code base to another. I think we’ve all gone through the process of
grabbing a module, dropping it into our new code base, compiling it,
and then discovering a ton of compilation errors. Upon closer examina-
tion, there is a module dependency that was overlooked. So, we grab
that dependency, put it in the code, and recompile. More compilation
errors! Adding the new module quadrupled the number of errors! It
made things worse, not better. Weeks later, we finally decided it’s faster
to just start from scratch.
Architectural Cohesion
The coupling is only the first part of the story. Low module coupling
doesn’t guarantee that the architecture will exhibit good characteristics
and meet our goals. Architects ultimately want to have low coupling
and high cohesion. Cohesion refers to the degree to which the module
or class elements belong together.
In a microcontroller environment, a low cohesion example would
be lumping every microcontroller peripheral function into a single
module or class. The module would be significant and unwieldy.
Instead, a base class could be created that defines the common inter-
face for interacting with peripherals. Each peripheral could then inher-
it from that interface and implement the peripheral-specific functional-
ity. The result is a highly cohesive architecture, low coupling, and other
desirable characteristics like reusable, portable, scalable, and so forth.
Cohesion is really all about putting “things” together that belong to-
gether. Code that is highly cohesive is easy to follow because everything
needed is in one place. Developers don’t have to search and hunt
through the code base to find related code. For example, I often see de-
velopers using an RTOS (real-time operating system) spread their task
creation code throughout the application. The result is low cohesion.
Instead, I pull all my task creation code into a single module so that I
only have one place to go. The task creation code is highly cohesive,
and it’s easily ported and configured as well. We’ll look at an example
later in the Development and Coding Skills part of the book.
Over the years, several types of architectural patterns have found their
way into embedded software. It’s not uncommon for systems to have
several architectural designs depending on the system and its needs.
While there are many types of patterns in software engineering, the
most common and exciting for microcontroller-based systems include
unstructured monoliths, layered monoliths, event-driven architectures,
and microservices. Let’s examine each of these in detail and under-
stand the pros and cons of each.
Figure 2-4 Unstructured monolithic architectures are tightly coupled applications that exist in the ap-
plication layer
The most common example of swapping out a layer is when the ar-
chitecture will support multiple hardware platforms. For example, I’ve
worked with clients who wanted to use Microchip parts in one product,
NXP parts in another, and STM parts in another. The products were all
related and needed to run some standard application components.
Instead of writing each product application from scratch, we designed
the architecture so that the application resembled Figure 2-5. The driv-
ers were placed behind a standard hardware abstraction layer (HAL)
that made the application dependent on the HAL, not the underlying
hardware.
Figure 2-6 An example is modern layered monolithic architecture for an embedded system
Figure 2-6 has many benefits. First, note how we can exchange the
driver layer for working with nearly any hardware by using a hard-
ware abstraction layer. For example, a common HAL today can be
found in Arm’s CMSIS.8 The HAL again decouples the hardware drivers
from the above code, breaking the dependencies.
Next, notice how we don’t even allow the application code to depend
on an RTOS or OS. Instead, we use an operating system abstraction lay-
er (OSAL). If the team needs to change RTOSes, which does happen,
they can just integrate the new RTOS without having to change a bunch
of application code. I’ve encountered many teams that directly make
calls to their RTOS APIs, only later to decide they need to change
RTOSes. What a headache that creates! An example of OSAL can be
found in CMSIS-RTOS2.9
Next, the board support package exists outside the driver layer! At
first, this may seem counterintuitive. Shouldn’t hardware like sensors,
displays, and so forth be in the driver layer? I view the hardware and
driver layer as dedicated to only the microcontroller. Any sensors and
so on that are connected to the microcontroller should be communicat-
ed through the HAL. For example, a sensor might be on the I2C bus. The
sensor would depend on the I2C HAL, not the low-level hardware. The
abstraction dependency makes it easier for the BSP to be ported to oth-
er applications.
Suppose you want to go into more detail about this type of architec-
ture and learn how to design your hardware abstraction layer and driv-
ers. In that case, I’d recommend reading my other book Reusable
Firmware Development.
Event-Driven Architectures
Event-driven architectures make a lot of sense for real-time embedded applications
and applications concerned with energy consumption. In an event-driven
architecture, the system is generally in an idle state or low-power state unless an
event triggers an action to be performed. For example, a widget may be in a low-
power idle state until a button is clicked. Clicking the button triggers an event that
sends a message to a message processor, which then wakes up the system. Figure 2-7
shows what this might look like architecturally.
Figure 2-7 An example modern layered monolithic architecture for an embedded system
Figure 2-8 The event-driven architecture is scalable and portable. In this example, a sensor sampled
Figure 2-9 To improve performance, the event-driven architecture allows the button-pressed event to
circumvent the message processor and directly perform the desired action
Microservice Architectures
For embedded systems, microservices can go far beyond just the sys-
tem’s business logic. For example, a weather station might contain mi-
croservices for telemetry, sensor interfacing, motor control (to maxi-
mize sun illumination for battery charging), and a command service.
The sensor service would be responsible for directly interacting with sensors
such as temperature, humidity, motor position, etc. The service would then report this
information to the motor and telemetry services. The motor service would drive the
motor based on commands from the command service. Status information from the
motor service would be reported to the telemetry service. Finally, the command
service could command the motor and provide status updates to telemetry. The user
could interact with the system, whether connected via a browser, smart app, or
whatever. The example architecture can be seen in Figure 2-10.
Figure 2-10 An example weather station microservice architecture. Each service is independent and
At first glance, the microservice architecture may not seem very decoupled. This
is because we have not yet discussed what the microservice block looks like. A
microservice is made up of five pieces: the microservice, an incoming queue,
outbound messages, a log, and microservice status. Figure 2-11 shows each of these
pieces.
Figure 2-11 A microservice comprises five standard components, which are graphically represented
here
It turns out that there are four common application domains that every embedded
designer needs to consider. These include
Within the Cortex-M processor, it’s interesting to also note that there are also two
modes that code can be running in which affect the privileged mode: thread and
handler modes. Thread mode is used for normal code execution, and the code can be
in either the privileged or unprivileged mode. Handler mode is specifically for
executing exception handlers and only runs in privileged mode. A typical visual
summary for these modes can be found in Figure 2-12.
Figure 2-12 A summary of the privilege states and modes is on Arm Cortex-M microcontrollers11
Figure 2-13 The top-level diagram is now broken up into execution domains in a high-level attempt to
It’s interesting to note that Figure 2-13 doesn’t specify which tech-
nology should be used for isolation at this point, only that there are se-
cure processing environments (SPE) and nonsecure processing environ-
ments (NSPE). The solution could be to use a multicore processor with a
dedicated security core or a single-core solution like Arm TrustZone.
We will discuss designing secure applications in Chapter 3 and how de-
signers develop their security requirements.
Multiple cores naturally force developers to decide which components and tasks
run on which cores. For example, developers working with an ESP32, typically a
dual-core Tensilica Xtensa LX6 32-bit processor, will often break their application
up such that one core runs the Wi-Fi and Bluetooth stack. In contrast, the other core
runs the real-time application. This ensures that the application is not choked by the
Wi-Fi and Bluetooth stacks and can still achieve real-time requirements. An example
domain partition between cores can be seen in Figure 2-14.
Figure 2-14 Multicore microcontrollers break an application into multiple execution domains that con-
currently run on multiple cores. This is one example of execution domain decomposition
There are many potential use cases for multicore designers. We will
explore the use cases and how to design applications that use multiple
cores in Chapter 5.
Final Thoughts
The software architecture is the blueprint, the road map that is used by
developers to construct the designers’ vision for the embedded soft-
ware. Designers and developers must carefully decouple the hardware
from the application’s business logic. Embedded designers have multi-
ple architectures that are available to them today that can help them
avoid a giant ball of mud if they minimize coupling and maximize
cohesion.
Action Items
To put this chapter's concepts into action, here are a few activities the reader can
perform to start architecting their embedded software:
For a current or past project, what architecture did you use? What
were the benefits and disadvantages of using that architecture? Are
any of the architectures we discussed a better fit for your
applications?
Identify three changes you will make to your current architecture
to better fit your team's needs.
Have you considered the various application domains in your de-
signs? What areas have you overlooked, and how could these have
impacted the overall performance, security, and behavior of your
applications? Take some time to break down a new application into
its various application domains.
These are just a few ideas to go a little bit further. Carve out time in
your schedule each week to apply these action items. Even minor ad-
justments over a year can result in dramatic changes!
Footnotes
1 https://en.wikipedia.org/wiki/IEEE_1471. IEEE 1471 has been super-
seded by IEEE 42010. (I just like the IEEE 1471 definition).
2 In a Q&A session, I was once accused of being a Rust advocate because I men-
tioned it! As of this writing, I still haven’t written a single line of it, although it
sounds intriguing.
3 https://encyclopedia2.thefreedictionary.com/abstraction+layer
5 We’d like to think that businesses don’t fail, but 90% of businesses fail within
their first ten years of operation!
6 https://bit.ly/33R6psu
7 https://en.wikipedia.org/wiki/Monolithic_application
9 The use of an RTOS with an OSAL will often require an OSAL extension to ac-
cess unique RTOS features not supported in every RTOS.
10 www.guru99.com/microservices-tutorial.html
11 www.researchgate.net/figure/Armv7-M-operation-modes-and-privi-
leged-levels_fig1_339784921
12 https://cloud.google.com/tpu/docs/tpus